Terraformをチュートリアルレベルまで理解できたので、試しに何か環境を作成することにしました。
今回はWordPress環境をWeb3層構成(ELB, EC2, RDS)と、可用性を考慮したアーキテクチャ設計にて構築しました。
構成図
可用性のポイント
- EC2はオートスケーリングにより、各AZ毎に1台デプロイされます。
- WordPressの設定ファイルや画像等(/var/www/html)はEFSに配置し、複数のEC2で共有可能にしました。
- RDSはマルチAZにしました。
作成手順+ソースコード公開
Githubに作成手順やソースコードを公開しました。
git clone
してterraform
ディレクトリへ移動後にリソース作成願います。
今回あえてやったこと
- terraformコマンドの実行回数を減らすため、リソース毎にフォルダを分けておりません。
- tfstateファイルのバックエンドはローカル環境にしています。
ソースコード解説
ディレクトリ構造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| % tree
.
├── aws_ec2.tf
├── aws_efs.tf
├── aws_lb.tf
├── aws_rds.tf
├── aws_security_group_ec2.tf
├── aws_security_group_efs.tf
├── aws_security_group_lb.tf
├── aws_security_group_rds.tf
├── aws_vpc.tf
├── config.tf
├── output.tf
├── script.tpl
├── terraform.tfstate
├── terraform.tfstate.backup
├── wp_ec2 #SSH key pair
└── wp_ec2.pub #SSH key pair
|
aws_ec2.tf
AMIはAmazon Linux 2のパブリックイメージを使用しています。
EC2はELBのオートスケーリングにより起動テンプレートの内容で起動されます。
EFSをマウントするにはEFS作成完了後にIDを取得する必要があるので、Template File
を使用しています。
また、KeyPairは事前に作成した公開鍵を使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| ####################
# EC2 Launch Template
####################
resource "aws_launch_template" "ec2_launch" {
name = "ec2_launch"
image_id = "ami-0ce107ae7af2e92b5"
instance_type = "t2.small"
key_name = aws_key_pair.wp_ec2.id
vpc_security_group_ids = [
aws_security_group.ec2.id
]
//EFS作成後にユーザデータを実行させるためtemplate_file使用
user_data = base64encode(data.template_file.script.rendered)
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = "30"
volume_type = "gp2"
delete_on_termination = "true"
}
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "wp_dev_ec2"
}
}
tags = {
Name = "wp_dev_ec2_lt"
}
}
####################
# User data用 Template File
####################
data "template_file" "script" {
template = file("script.tpl")
vars = {
efs_id = aws_efs_file_system.efs.id
}
}
####################
# Key Pair
####################
resource "aws_key_pair" "wp_ec2" {
key_name = "wp_ec2"
public_key = file("./wp_ec2.pub")
}
|
aws_efs.tf
EFSを作成し、AZの1aと1cにマウントします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| ####################
# EFS
####################
resource "aws_efs_file_system" "efs" {
tags = {
Name = "wp-dev-efs"
}
}
####################
# EFS Mount Target 1a
####################
resource "aws_efs_mount_target" "efs_1a" {
file_system_id = aws_efs_file_system.efs.id
subnet_id = aws_subnet.sub_pub_1a.id
security_groups = [aws_security_group.efs.id]
}
####################
# EFS Mount Target 1c
####################
resource "aws_efs_mount_target" "efs_1c" {
file_system_id = aws_efs_file_system.efs.id
subnet_id = aws_subnet.sub_pub_1c.id
security_groups = [aws_security_group.efs.id]
}
|
aws_lb.tf
Webアクセスのフロント用にALBを作成します。
起動テンプレートをもとに、Auto ScalingでEC2を起動します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| ####################
# ELB
####################
resource "aws_lb" "elb" {
name = "wp-alb"
internal = false
load_balancer_type = "application"
security_groups = [
aws_security_group.elb.id
]
subnets = [
aws_subnet.sub_pub_1a.id,
aws_subnet.sub_pub_1c.id
]
}
####################
# ELB Listener
####################
resource "aws_lb_listener" "elb_http" {
load_balancer_arn = aws_lb.elb.arn
port = "80"
protocol = "HTTP"
default_action {
target_group_arn = aws_lb_target_group.ec2_http.arn
type = "forward"
}
}
####################
# ELB Target Group
####################
resource "aws_lb_target_group" "ec2_http" {
name = "wp-dev-alb-tg-http"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.vpc.id
health_check {
interval = 10
path = "/"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
healthy_threshold = 3
unhealthy_threshold = 3
}
stickiness {
cookie_duration = 1800
enabled = true
type = "lb_cookie"
}
}
####################
# Auto Scaling Group
####################
resource "aws_autoscaling_group" "ec2_ag" {
name = "wp_dev_ec2_ag"
max_size = 4
min_size = 2
health_check_grace_period = 300
health_check_type = "EC2"
desired_capacity = 2
vpc_zone_identifier = [
aws_subnet.sub_pub_1a.id,
aws_subnet.sub_pub_1c.id
]
launch_template {
id = aws_launch_template.ec2_launch.id
version = "$Latest"
}
target_group_arns = [aws_lb_target_group.ec2_http.arn]
}
|
aws_rds.tf
WordPressの動的コンテンツ作成用に、RDS for MySQLを作成します、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| ####################
# RDS DB Instance
####################
resource "aws_db_instance" "rds" {
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
name = "wpdb"
username = "dbadmin"
password = "SuperSecret"
parameter_group_name = aws_db_parameter_group.db_pg.name
option_group_name = aws_db_option_group.db_og.name
multi_az = true
db_subnet_group_name = aws_db_subnet_group.db_sub_gp.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = "7"
backup_window = "22:29-22:59"
//DB削除前にスナップショットを作成しない
skip_final_snapshot = true
//自動スケーリング上限
max_allocated_storage = 200
//接続エンドポイント
identifier = "wpdb"
tags = {
Name = "wp_dev_rds"
}
}
####################
# RDS DB Option Group
####################
resource "aws_db_option_group" "db_og" {
name = "wp-dev-db-og"
engine_name = "mysql"
major_engine_version = "5.7"
}
####################
# RDS DB Parameter Group
####################
resource "aws_db_parameter_group" "db_pg" {
name = "wp-dev-db-pg"
family = "mysql5.7"
}
|
aws_security_group_ec2.tf
EC2用のセキュリティグループです。
HTTP(80ポート)はELBからしか許可しません。
インバウンドルールを複数定義するためにaws_security_group_rule
を利用しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| ####################
# EC2 Security Group
####################
resource "aws_security_group" "ec2" {
name = "wp_dev_sg_ec2"
description = "wp_dev_sg_ec2"
vpc_id = aws_vpc.vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "wp_dev_sg_ec2"
}
}
//80番ポート(http)許可のインバウンドルール
resource "aws_security_group_rule" "inbound_http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
//elbセキュリティグループが紐づくリソースにアクセス許可
source_security_group_id = aws_security_group.elb.id
//ec2セキュリティグループに紐付け
security_group_id = aws_security_group.ec2.id
}
//22番ポート(ssh)許可のインバウンドルール
resource "aws_security_group_rule" "inbound_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [
"0.0.0.0/0"
]
//ec2セキュリティグループに紐付け
security_group_id = aws_security_group.ec2.id
}
|
aws_security_group_efs.tf
EFS用のセキュリティグループです。
EC2からのアクセスしか許可しません。
こちらはインバウンドルールが一つなので、ingress
で定義しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| ####################
# EFS Security Group
####################
resource "aws_security_group" "efs" {
name = "wp_dev_sg_efs"
description = "wp_dev_sg_efs"
vpc_id = aws_vpc.vpc.id
ingress {
from_port = 2049
to_port = 2049
protocol = "tcp"
//ec2セキュリティグループが紐づくリソースにアクセス許可
security_groups = [aws_security_group.ec2.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "wp_dev_sg_efs"
}
}
|
aws_security_group_lb.tf
ELB用のセキュリティグループです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| ####################
# ELB Security Group
####################
resource "aws_security_group" "elb" {
name = "wp_dev_sg_alb"
description = "wp_dev_sg_alb"
vpc_id = aws_vpc.vpc.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "wp_dev_sg_alb"
}
}
|
aws_security_group_rds.tf
RDS用のセキュリティグループです。
EC2からのアクセスしか許可しません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| ####################
# RDS Security Group
####################
resource "aws_security_group" "rds" {
name = "wp_dev_sg_rds"
description = "wp_dev_sg_rds"
vpc_id = aws_vpc.vpc.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
//ec2セキュリティグループが紐づくリソースにアクセス許可
security_groups = [aws_security_group.ec2.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "wp_dev_sg_rds"
}
}
|
aws_vpc.tf
VPC関連を作成します。
DBサブネットもこちらで定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
| ####################
# VPC
####################
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "wp_dev_vpc"
}
}
####################
# Subnet
####################
resource "aws_subnet" "sub_pub_1a" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.0.0/24"
//パブリック IPv4 アドレスの自動割り当て
map_public_ip_on_launch = true
availability_zone = "ap-northeast-1a"
tags = {
Name = "wp_dev_sub_pub_1a"
}
}
resource "aws_subnet" "sub_pub_1c" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.1.0/24"
//パブリック IPv4 アドレスの自動割り当て
map_public_ip_on_launch = true
availability_zone = "ap-northeast-1c"
tags = {
Name = "wp_dev_sub_pub_1c"
}
}
resource "aws_subnet" "sub_db_pri_1a" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.20.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "wp_dev_sub_db_pri_1a"
}
}
resource "aws_subnet" "sub_db_pri_1c" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.21.0/24"
availability_zone = "ap-northeast-1c"
tags = {
Name = "wp_dev_sub_db_pri_1c"
}
}
####################
# DB Subnet
####################
resource "aws_db_subnet_group" "db_sub_gp" {
name = "dbsubnet"
subnet_ids = [aws_subnet.sub_db_pri_1a.id, aws_subnet.sub_db_pri_1c.id]
tags = {
Name = "wp_dev_sub_gp"
}
}
####################
# Internet Gateway
####################
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "wp_dev_igw"
}
}
####################
# Route Table
####################
resource "aws_route_table" "pub_rt" {
vpc_id = aws_vpc.vpc.id
//インターネットゲートウェイ向けのルート追加
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "wp_dev_pub_rt"
}
}
resource "aws_route_table" "db_pri_rt" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "wp_dev_db_pri_rt"
}
}
####################
# Route Association
####################
resource "aws_route_table_association" "sub_pub_1a_rt_assocication" {
subnet_id = aws_subnet.sub_pub_1a.id
route_table_id = aws_route_table.pub_rt.id
}
resource "aws_route_table_association" "sub_pub_1c_rt_assocication" {
subnet_id = aws_subnet.sub_pub_1c.id
route_table_id = aws_route_table.pub_rt.id
}
resource "aws_route_table_association" "sub_db_pri_1a_rt_assocication" {
subnet_id = aws_subnet.sub_db_pri_1a.id
route_table_id = aws_route_table.db_pri_rt.id
}
resource "aws_route_table_association" "sub_db_pri_1c_rt_assocication" {
subnet_id = aws_subnet.sub_db_pri_1c.id
route_table_id = aws_route_table.db_pri_rt.id
}
|
config.tf
AWSリージョンや、Terraform周りのバージョンを固定します。
1
2
3
4
5
6
7
8
9
10
11
| provider "aws" {
//バージョンを固定
version = "3.14.1"
//東京リージョン
region = "ap-northeast-1"
}
terraform {
//バージョンを固定
required_version = "0.13.5"
}
|
output.tf
リソース作成後のアクセス確認や設定で使用する値をコンソールで表示します。
1
2
3
4
5
6
7
8
9
| //Webアクセス用にコンソール表示
output "elb_dns_name" {
value = aws_lb.elb.dns_name
}
//WordPress設定用にコンソール表示
output "rds_endpoint" {
value = aws_db_instance.rds.endpoint
}
|
script.tpl
EC2のユーザデータ用のスクリプトになります。
WordPress設定全般について記述しています。
WordPressは最新版をインストールしたかったのですが、
PHPバージョンと互換性がなかったため特定バージョンをインストールしています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| #!/bin/bash
yum update -y
#EFSマウント設定
yum install -y amazon-efs-utils
efs_id="${efs_id}"
echo "$efs_id:/ /var/www/html efs defaults,_netdev 0 0" >> /etc/fstab
#RDS for MySQL接続クライアントインストール
yum install -y php php-dom php-gd php-mysql
yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm -y
yum-config-manager --disable mysql80-community
yum-config-manager --enable mysql57-community
yum -y install mysql-community-client.x86_64
#/etc/fstabに記載がある全てのデバイスをマウント
mount -a
#WordPress初期化
#WordPress関連のフォルダが存在しなければ配置
if [ ! -e /var/www/html/wp-admin ]; then
cd /tmp
wget https://wordpress.org/wordpress-5.0.7.tar.gz
tar xzvf /tmp/wordpress-5.0.7.tar.gz --strip 1 -C /var/www/html
rm /tmp/wordpress-5.0.7.tar.gz
fi
#ファイルやディレクトリのユーザやグループを変更
chown -R apache:apache /var/www/html
#Apache自動起動を有効化
systemctl enable httpd
#Apache起動
systemctl start httpd
|
改善点
今回は最低限の可用性を考慮した設計なので、セキュリティやパフォーマンスを考慮するとまだまだ改善点があります。
- EC2をプライベートサブネットに配置しNatGateway導入する。
- EC2のセッション管理用にElastiCacheを構築する。
- EC2へのSSHアクセス用にSSMセッションマネージャを追加する。
- MySQLと互換性のあるAuroraを導入しパフォーマンスを向上させる。
- AWS WAFを導入しアプリケーション脆弱性リスクを軽減する。
- GuardDuty有効化し脅威検出可能にする。
- EFSのAWS Backup有効化する。
- ALBやVPC、ClouTrail等のログを有効化する。
- Fargateを導入し、EC2のIDS/IPSやウィルス検知を不要にする。
- etc…
参考
リンク
まとめ
実際に手を動かしてみると想定どおりにリソースが作成できなく色々とてこずりました。
特にEFSは何回やってもEC2へのマウントが失敗したので、解決までに時間がかかりました。
改善点を対応したら、また記事にしてみようと思います。