AWSにRails + Nginxな環境をTerraformで構築してみようと思います。
はじめに
本連載で一つずつインフラを構築していきます。
ドメインのhttps化したり、ECS Fargateを使用したコンテナオーケストレーションを用いてアプリケーションをデプロイします。
この記事ではNginxのみをECS Fargateで起動をします。
環境は以下です。
| OS | Cataline 10.15.6 |
| Terraform | 0.14.4 |
基本構文などこちらにまとめてますので、よかったらみてください!
AWS Terraform 基本コード まとめ
連載一覧
- terraform AWS環境構築 事前準備
- 【ネットワーク環境構築】terraform AWS環境構築 第1回
- 【ドメインhttps化・ACM(SSL)証明書発行】terraform AWS環境構築 第2回
- 【ロードバランサー構築】terraform AWS環境構築 第3回
- 【ECS Fargate(nginx)実行】terraform AWS環境構築 第4回 ←ここ
- 【RDS構築】terraform AWS環境構築 第5回
- 【Docker/ECR作成】terraform AWS環境構築 第6回
- 【ECS Fargate(rails + nginx)実行】terraform AWS環境構築 第7回
- 【CircleCIによるCI/CD】terraform AWS環境構築 番外
やること
以下の定義と作成をします。
- ECSクラスター:ドキュメント
- CloudWatchLogs:ドキュメント
- タスク定義:ドキュメント
- ターゲットグループ:ドキュメント
- ロードバランサーリスナルール:ドキュメント
- ECSサービス:ドキュメント
ECS Fargate(nginxのみ)の作成
ecs clusterモジュールの作成
ECSクラスターを作成するモジュールを作成します。
terraformフォルダ内にecs_clusterフォルダを作成しましょう。main.tfも作成しておきます。
ecs_clusterモジュールを使用できるよう./main.tfに以下を追記します。
./main.tf
module "ecs_cluster" {
source = "./ecs_cluster"
}
ecs nginxモジュール作成
Nginx用のECSを作成するモジュールを作成します。
terraformフォルダ内にecs_nginxフォルダを作成しましょう。main.tfも作成しておきます。
ecs_clusterモジュールを使用できるよう./main.tfに以下を追記します。
./main.tf
module "ecs_nginx" {
source = "./ecs_nginx"
}
ディレクトリ構成は以下のようにしています。
[terraform] $ tree . ├── ecs_cluster │ └── main.tf ├── ecs_nginx │ └── main.tf ├── acm │ ├── main.tf │ └── variable.tf ├── network │ ├── main.tf │ └── variable.tf ├── elb │ └──main.tf ├── env │ └── backend.config ├── main.tf ├── backend.tf ├── provider.tf ├── terraform.tfvars └── variable.tf
用意できたら、terraformの初期化を行いましょう。
[terraform] $ terraform init -backend-config=env/backend.config -upgrade
ECSクラスターの作成
ECSサービスを実行するクラスターの作成をします。
./ecs_cluster/main.tf に以下を追記します。
locals {
name = "rails-hello-nginx"
}
resource "aws_ecs_cluster" "ecs_cluster" {
name = local.name
}
ECSサービス作成の際、このクラスターを使用するのでアウトプット変数を定義しておきましょう。
output "cluster_name" {
value = aws_ecs_cluster.ecs_cluster.name
}
terraform plan, apply して作成されるか見てみましょう。

クラスターが作成されたのが確認できましたね。
CloudWatchLogsの作成
ECSのログ主力先を作成します。
./ecs_nginx/main.tf に以下を追記します。
resource "aws_cloudwatch_log_group" "ecs_log" {
name = "/ecs/example/${local.name}"
retention_in_days = 180
}
- retention_in_days:保存期間
terraform plan, apply して作成されるか見てみましょう。

ロググループに作成されたのが確認できましたね。
Nginxタスク定義の作成
Nginxを立ち上げるタスク定義を作成します。
コンテナの定義
まずは、起動するコンテナの定義をjsonファイルで用意します。
ecs_nginxフォルダにcontainer_definitions.json を作成します。
./ecs_nginx/container_definitions.json
[
{
"name": "nginx",
"image": "nginx:1.14",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "nginx",
"awslogs-group": "/ecs/example/rails-hello-nginx"
}
},
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
]
}
]
ECSタスク実行IAMロールの作成
次に、ECSタスクを実行するIAMロールを作成します。
iam roleモジュールの作成
IAMロールの作成は他のモジュールで対応します。
terraformフォルダ内にiam_roleフォルダを作成します。
以下の3つの変数を受け取れるようにします
- name:IAMロール名
- policy:ポリシードキュメント
- identifier:IAMロールを紐づけるAWSのサービス識別子
./iam_role/variable.tf
variable "name" {}
variable "policy_arn" {}
variable "identifier" {}
信頼ポリシーを定義しておきます。 ([var.identifier]サービスにロールを関連づける)
./iam_role/data.tf
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = [var.identifier]
}
}
}
IAMロールを定義して、信頼ポリシーを紐付けます。([var.name]ロールに紐付ける)
./iam_role/main.tf
resource "aws_iam_role" "this" {
name = var.name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
IAMロールとIAMポリシーを紐付けます。
./iam_role/main.tf
resource "aws_iam_role_policy_attachment" "this" {
role = aws_iam_role.this.name
policy_arn = var.policy_arn
}
定義したIAMロール他のモジュールで使用できるように、アウトプット変数を定義しておきましょう。
./iam_role/output.tf
output "iam_role_arn" {
value = aws_iam_role.this.arn
}
output "iam_role_name" {
value = aws_iam_role.this.name
}
iam_roleモジュールは完了です。
ロールの作成
iam_roleモジュールを呼び出して、ロールを作成しましょう。
AmazonECSTaskExecutionRolePolicyをData Sourceで取得しポリシードキュメントを定義します。
./ecs_nginx/data.tf
# AmazonECSTaskExecutionRolePolicy の参照
data "aws_iam_policy" "ecs_task_execution_role_policy" {
arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
./ecs_nginx/main.tf
module "ecs_task_execution_role" {
source = "../iam_role"
name = "ecs-task-execution"
identifier = "ecs-tasks.amazonaws.com"
policy_arn = data.aws_iam_policy.ecs_task_execution_role_policy.arn
}
terraform plan, apply して作成されるか見てみましょう。


IAMロール・ポリシーが作成できたのが確認できましたね。
タスク定義
container_definitions.jsonをData Sourceで読み込みます。
./ecs_nginx/data.tf に以下を追記します。
data "template_file" "container_definitions" {
template = file("./ecs_nginx/container_definitions.json")
}
./ecs_nginx/main.tf に以下を追記します。
locals {
name = "rails-hello-nginx"
}
resource "aws_ecs_task_definition" "task_definition" {
family = local.name
cpu = 256
memory = 512
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = data.template_file.container_definitions.rendered
execution_role_arn = module.ecs_task_execution_role.iam_role_arn
}
- family:タスク定義名
- cpu:cpuサイズ
- memory:メモリサイズ
- network_mode:ネットワークモード
- requires_compatibilities:起動モード
- container_definitions:コンテナ定義
- execution_role_arn:タスク実行ロール
terraform plan, apply して作成されるか見てみましょう。


タスク定義が作成されたのが確認できましたね。
ターゲットグループの作成
ロードバランサーがリクエストをフォワードする対象を定義します。
./ecs_nginx/main.tf に以下を追記します。
resource "aws_lb_target_group" "target_group" {
name = local.name
vpc_id = var.vpc_id
# ALBからECSタスクのコンテナへトラフィックを振り分ける設定
port = 80
protocol = "HTTP"
target_type = "ip"
health_check {
port = 80
path = "/"
}
}
- vpc_id:ターゲット対象のvpc id
- port:ポート番号
- protocol:プロトコル
- target_type:ターゲット対象のタイプ
- health_check:ヘルスチェック
- port:ポート番号
- path:チェックするパス
vpc_idが必要なので、ecs_nginxモジュールに追記します。
module "ecs_nginx" {
source = "./ecs_nginx"
# 追記
vpc_id = module.network.vpc_id
}
受け取れるように、variable.tfにも追記しておきます。
./ecs_nginx/variable.tf
variable "vpc_id" {}
terraform plan, apply して作成されるか見てみましょう。

ターゲットグループが作成されているのが確認できましたね。
ロードバランサーリスナルールの作成
ロードバランサーがリクエストを受け渡すルールを定義します。
./ecs_nginx/main.tf に以下を追記します。
resource "aws_lb_listener_rule" "http_rule" {
listener_arn = var.http_listener_arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group.id
}
condition {
path_pattern {
values = ["*"]
}
}
}
resource "aws_lb_listener_rule" "https_rule" {
listener_arn = var.https_listener_arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group.id
}
condition {
path_pattern {
values = ["*"]
}
}
}
- listener_arn:ルールを適用するリスナー
- type:ルーティング方式
- target_group_arn:ターゲットグループ
- condition:ルーティング条件
http_listener_arn, https_listener_arnが必要なので、ecs_nginxモジュールに追記します。
module "ecs_nginx" {
source = "./ecs_nginx"
vpc_id = module.network.vpc_id
# 追記
http_listener_arn = module.elb.http_listener_arn
https_listener_arn = module.elb.https_listener_arn
}
受け取れるように、variable.tfにも追記しておきます。
./ecs_nginx/variable.tf
variable "http_listener_arn" {}
variable "https_listener_arn" {}
terraform plan, apply して作成されるか見てみましょう。


http, httpsのリスナルールにターゲットグループへ転送するルールが追加されたのが確認できましたね。
ECSサービスの作成
最後に、ECSサービスを定義します。
ECSサービス用のセキュリティーグループを作成しておきましょう。
resource "aws_security_group" "ecs_security_group" {
name = "${local.name}-sg"
description = "security group of rails-hello-nginx ecs"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = local.name
}
}
resource "aws_security_group_rule" "http_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "https_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
(dynamic 以外の書き方をしました。。)
./ecs_nginx/main.tf に以下を追記します。
resource "aws_ecs_service" "ecs_service" {
name = "${local.name}-service"
launch_type = "FARGATE"
desired_count = "1"
cluster = var.cluster_name
task_definition = aws_ecs_task_definition.task_definition.arn
network_configuration {
security_groups = [aws_security_group.ecs_security_group.id]
subnets = var.public_subnet_ids
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.target_group.arn
container_name = "nginx"
container_port = 80
}
}
- name:サービス名
- launch_type:タスク定義の起動タイプ
- desired_count:起動するコンテナ数
- task_definition:タスク定義
- network_configuration:サービスのネットワーク設定
- security_groups:セキュリティーグループ
- subnets:サブネット
- assign_public_ip:パブリックIPを割り当てるかどうか
- load_balancer
- target_group_arn:ターゲットグループ
- container_name:コンテナ名
- container_port:コンテナポート
public_subnet_ids, cluster_nameが必要なので、ecs_nginxモジュールに追記します。
module "ecs_nginx" {
source = "./ecs_nginx"
vpc_id = module.network.vpc_id
http_listener_arn = module.elb.http_listener_arn
https_listener_arn = module.elb.https_listener_arn
# 追記
cluster_name = module.ecs_cluster.cluster_name
public_subnet_ids = module.network.public_subnet_ids
}
受け取れるように、variable.tfにも追記しておきます。
./ecs_nginx/variable.tf
variable "cluster_name" {}
variable "public_subnet_ids" {}
terraform plan, apply して作成されるか見てみましょう。

ECSサービスが実行できているのが確認できましたね。
それでは、サイトにアクセスしてみましょう。

「Nginx」が表示されましたね!
まとめ
今回、作成したコードとディレクトリ構成は以下になります。
[terraform] $ tree . ├── ecs_cluster │ ├── main.tf │ └── output.tf ├── ecs_nginx │ ├── container_definitions.json │ ├── data.tf │ ├── main.tf │ └── variable.tf ├── iam_role │ ├── data.tf │ ├── main.tf │ ├── output.tf │ └── variable.tf ├── elb │ ├── data.tf │ ├── main.tf │ ├── output.tf │ └── variable.tf ├── acm │ ├── data.tf │ ├── main.tf │ ├── output.tf │ └── variable.tf ├── network │ ├── main.tf │ ├── output.tf │ └── variable.tf ├── env │ └── backend.config ├── main.tf ├── output.tf ├── backend.tf ├── provider.tf ├── terraform.tfvars └── variable.tf
ecs_clusterモジュール
./ecs_cluster/main.tf
locals {
name = "rails-hello-nginx"
}
resource "aws_ecs_cluster" "ecs_cluster" {
name = local.name
}
./ecs_cluster/output.tf
output "cluster_name" {
value = aws_ecs_cluster.ecs_cluster.name
}
ecs_nginxモジュール
./ecs_nginx/main.tf
locals {
name = "rails-hello-nginx"
}
resource "aws_cloudwatch_log_group" "ecs_log" {
name = "/ecs/example/${local.name}"
}
module "ecs_task_execution_role" {
source = "../iam_role"
name = "ecs-task-execution"
identifier = "ecs-tasks.amazonaws.com"
policy_arn = data.aws_iam_policy.ecs_task_execution_role_policy.arn
}
resource "aws_ecs_task_definition" "task_definition" {
family = local.name
cpu = 256
memory = 512
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = data.template_file.container_definitions.rendered
execution_role_arn = module.ecs_task_execution_role.iam_role_arn
}
resource "aws_lb_target_group" "target_group" {
name = local.name
vpc_id = var.vpc_id
# ALBからECSタスクのコンテナへトラフィックを振り分ける設定
port = 80
protocol = "HTTP"
target_type = "ip"
health_check {
port = 80
path = "/"
}
}
resource "aws_lb_listener_rule" "http_rule" {
listener_arn = var.http_listener_arn
# 受け取ったトラフィックをターゲットグループへ受け渡す
action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group.id
}
# ターゲットグループへ受け渡すトラフィックの条件
condition {
path_pattern {
values = ["*"]
}
}
}
resource "aws_lb_listener_rule" "https_rule" {
listener_arn = var.https_listener_arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group.id
}
condition {
path_pattern {
values = ["*"]
}
}
}
resource "aws_security_group" "ecs_security_group" {
name = "${local.name}-sg"
description = "security group of rails-hello-nginx ecs"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = local.name
}
}
resource "aws_security_group_rule" "http_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "https_ingress_rule" {
security_group_id = aws_security_group.ecs_security_group.id
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_ecs_service" "ecs_service" {
name = "${local.name}-service"
launch_type = "FARGATE"
desired_count = "1"
cluster = var.cluster_name
task_definition = aws_ecs_task_definition.task_definition.arn
network_configuration {
security_groups = [aws_security_group.ecs_security_group.id]
subnets = var.public_subnet_ids
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.target_group.arn
container_name = "nginx"
container_port = 80
}
}
./ecs_nginx/variable.tf
variable "vpc_id" {}
variable "http_listener_arn" {}
variable "https_listener_arn" {}
variable "cluster_name" {}
variable "public_subnet_ids" {}
./ecs_nginx/data.tf
data "template_file" "container_definitions" {
template = file("./ecs_nginx/container_definitions.json")
}
# AmazonECSTaskExecutionRolePolicy の参照
data "aws_iam_policy" "ecs_task_execution_role_policy" {
arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
./ecs_nginx/container_definitions.json
[
{
"name": "nginx",
"image": "nginx:1.14",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "nginx",
"awslogs-group": "/ecs/example/rails-hello-nginx"
}
},
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
]
}
]
iam_roleモジュール
./iam_role/main.tf
resource "aws_iam_role" "this" {
name = var.name
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "this" {
role = aws_iam_role.this.name
policy_arn = var.policy_arn
}
./iam_role/variable.tf
variable "name" {}
variable "policy_arn" {}
variable "identifier" {}
./iam_role/data.tf
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = [var.identifier]
}
}
}
./iam_role/output.tf
output "iam_role_arn" {
value = aws_iam_role.this.arn
}
output "iam_role_name" {
value = aws_iam_role.this.name
}
おわり
これで、ECS Fargate(Nginxのみ)が完了しました。お疲れさまでした!
何か疑問に思うことがあれば、何でもコメントしてください!
次回は、DBサーバのRDSの構築をします。

コメント