【ECS Fargate(nginx)実行】terraform AWS環境構築 第4回

AWS

AWSRails + Nginxな環境をTerraformで構築してみようと思います。

はじめに

本連載で一つずつインフラを構築していきます。

ドメインのhttps化したり、ECS Fargateを使用したコンテナオーケストレーションを用いてアプリケーションをデプロイします。

この記事ではNginxのみをECS Fargateで起動をします。

環境は以下です。

OS Cataline 10.15.6
Terraform 0.14.4

 

 

 

基本構文などこちらにまとめてますので、よかったらみてください!
AWS Terraform 基本コード まとめ

連載一覧

やること

以下の定義と作成をします。

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つの変数を受け取れるようにします

  • nameIAMロール名
  • policy:ポリシードキュメント
  • identifierIAMロールを紐づける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モジュールを呼び出して、ロールを作成しましょう。

AmazonECSTaskExecutionRolePolicyData 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.jsonData 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の構築をします。

参考サイト

ECS - Terraformで構築するAWS

コメント

タイトルとURLをコピーしました