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の構築をします。
コメント