ecspresso
ecspresso はECSのタスク定義やサービスを管理するためのツール。
Terraform + ecspresso
ECSの構成
ECSを構成するコンポーネントとして以下がある。
- タスク定義
- クラスター
- サービス
- タスク
クラスターがサービスを持ち、サービスがタスクを持つ。
タスクはタスク定義から作成される。

Terraformとecspressoのそれぞれの役割
TerraformとecspressoでECSを構築する場合はTerraformでクラスターを作成する。
ecspressoはタスク定義、サービス、タスクを作成する。

TerraformでECSクラスターと必要なリソースを作成する
本記事ではnginxをコンテナで動かす最小限のECSクラスターを作成する。
初期設定
まずTerraformの初期設定。
今回はtfstateファイルをローカルに保存する。
S3などリモートに保存する場合は適宜backendを変更する。
terraform {
  required_version = ">= 0.12"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
  backend "local" {
    path = "example.tfstate"
  }
}
provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Project = "terraform-ecspresso-sample"
    }
  }
}
VPCとサブネットの作成
VPCとサブネットを作成する。
CIDRについて今回はこだわりがないので適当に大きめの範囲を指定する。
サブネットは2つのAZに作成する。
resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "terraform-ecspresso-sample"
  }
}
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "terraform-ecspresso-sample"
  }
}
resource "aws_subnet" "subnet_a" {
  vpc_id                  = aws_vpc.vpc.id
  availability_zone       = "ap-northeast-1a"
  cidr_block              = "10.0.0.0/20"
  map_public_ip_on_launch = true
  tags = {
    Name = "terraform-ecspresso-sample-a"
  }
}
resource "aws_subnet" "subnet_c" {
  vpc_id                  = aws_vpc.vpc.id
  availability_zone       = "ap-northeast-1c"
  cidr_block              = "10.0.16.0/20"
  map_public_ip_on_launch = true
  tags = {
    Name = "terraform-ecspresso-sample-c"
  }
}
resource "aws_route_table" "route_table" {
  vpc_id = aws_vpc.vpc.id
}
resource "aws_route" "route" {
  route_table_id         = aws_route_table.route_table.id
  gateway_id             = aws_internet_gateway.igw.id
  destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route_table_association" "route_table_association_a" {
  subnet_id      = aws_subnet.subnet_a.id
  route_table_id = aws_route_table.route_table.id
}
resource "aws_route_table_association" "route_table_association_c" {
  subnet_id      = aws_subnet.subnet_c.id
  route_table_id = aws_route_table.route_table.id
}
タスク実行ロールの作成
ECSタスク実行ロールを作成する。
今回はCloudWatch Logsへログを出力したい(エラーとなった場合に原因を探りたい)のでCloudWatch Logsへのアクセス権限を付与する。
data "aws_iam_policy_document" "ecs_task_execution" {
  statement {
    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    resources = ["*"]
  }
}
resource "aws_iam_policy" "ecs_task_execution_policy" {
  name   = "ecs_task_execution_policy"
  policy = data.aws_iam_policy_document.ecs_task_execution.json
}
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}
resource "aws_iam_role" "ecs_task_execution_role" {
  name               = "ecs_task_execution_role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "amazon_ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = aws_iam_policy.ecs_task_execution_policy.arn
}
CloudWatch Logsのロググループの作成
ECSタスクからログを出力するためのCloudWatch Logsのロググループを作成する。
resource "aws_cloudwatch_log_group" "nginx" {
  name = "nginx-cluster-log-group"
}
Security Groupの作成
ECSクラスターにセキュリティグループを作成する。
今回は練習のためのnginxサーバを立ち上げるだけでALBも無く証明書を用意するのも面倒なため80番ポートのみ許可する。
外部へのアクセスはすべて許可しているが必要な場合は適宜制限する。
resource "aws_security_group" "security_group" {
  name        = "nginx"
  description = "nginx"
  vpc_id      = aws_vpc.vpc.id
  tags = {
    Name = "terraform-ecspresso-sample"
  }
}
resource "aws_security_group_rule" "ingress_http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group.id
}
resource "aws_security_group_rule" "ingress_https" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group.id
}
resource "aws_security_group_rule" "egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.security_group.id
}
ECSクラスターの作成
ECSクラスターを作成する。
クラスターの中身やタスク定義はecspressoで管理するため、ここではクラスターのみとなる。
resource "aws_ecs_cluster" "cluster" {
  name = "nginx-cluster"
}
Terraformの実行
Terraformのapplyを実行する。
$ terraform init
$ terraform plan
$ terraform apply
ecspressoでタスク定義、サービス、タスクを作成する
ディレクトリ構成
今回はここまで作った.tfファイルの横にecspressoディレクトリを作成し、その中にecspressoの設定ファイルを配置する。
.
├── cloudwatch_logs.tf
├── ecs.tf
├── ecspresso                # ecspressoの設定ファイルを配置するディレクトリ
│   ├── ecs-service-def.json # 以降で作成
│   ├── ecs-task-def.json    # 以降で作成
│   └── ecspresso.yml        # 以降で作成
├── example.tfstate
├── execution_role.tf
├── provider.tf
├── security_group.tf
└── vpc.tf
ecspressoの設定ファイル(ecspresso.yml)
ecspresso.ymlを作成する。
region: ap-northeast-1
cluster: nginx
service: nginx
service_definition: ecs-service-def.json
task_definition: ecs-task-def.json
timeout: "10m0s"
plugins:
  - name: tfstate
    config:
      path: ../example.tfstate
Terraformで作成したリソースを参照するためにtfstateプラグインを使用する。
パスはTerraformのbackendで指定したファイルを指定する。tfstateプラグインを使用すると.tfstateファイルの内容を参照し以下の書式でリソースの値を取得できる。
{{ tfstate `リソース名.プロパティ名` }}
S3などリモートに保存するbackend設定をしている場合はpathではなくurlで指定する。
タスク定義(ecs-task-def.json)
タスク定義の設定ファイルを作成する。
ファイル名はecspresso.ymlのtask_definitionへ指定した値で作成する。
{
  "family": "terraform-ecspresso-sample",
  "cpu": "256",
  "memory": "512",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "executionRoleArn": "{{ tfstate `aws_iam_role.ecs_task_execution_role.arn` }}",
  "containerDefinitions": [
    {
      "name": "nginx",
      "image": "public.ecr.aws/nginx/nginx:latest",
      "essential": true,
      "portMappings": [
        {
          "appProtocol": "",
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-region" : "ap-northeast-1",
          "awslogs-group": "{{ tfstate `aws_cloudwatch_log_group.nginx.name` }}",
          "awslogs-stream-prefix": "nginx-container-log-stream"
        }
      }
    }
  ]
}
サービス定義(ecs-service-def.json)
サービス定義の設定ファイルを作成する。
ファイル名はecspresso.ymlのservice_definitionへ指定した値で作成する。
{
  "deploymentConfiguration": {
    "deploymentCircuitBreaker": {
      "enable": true,
      "rollback": true
    },
    "maximumPercent": 200,
    "minimumHealthyPercent": 100
  },
  "deploymentController": {
    "type": "ECS"
  },
  "desiredCount": 1,
  "enableECSManagedTags": true,
  "enableExecuteCommand": false,
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "ENABLED",
      "securityGroups": [
        "{{ tfstate `aws_security_group.security_group.id` }}"
      ],
      "subnets": [
        "{{ tfstate `aws_subnet.subnet_a.id` }}",
        "{{ tfstate `aws_subnet.subnet_c.id` }}"
      ]
    }
  },
  "pendingCount": 0,
  "platformFamily": "Linux",
  "platformVersion": "LATEST",
  "propagateTags": "NONE",
  "runningCount": 0,
  "schedulingStrategy": "REPLICA",
  "tags": [
    {
      "key": "Project",
      "value": "terraform-ecspresso-sample"
    }
  ]
}
ecspressoの実行
ecspressoを実行してECSのタスク定義、サービス、タスクを作成する。
$ ecspresso deploy
確認
AWSのコンソールのECSの管理画面からクラスター、サービス、タスクを確認する。
タスクの画面のネットワーキングの欄にパブリックIPが表示されている。

ブラウザでこのIPにアクセスするとnginxのデフォルトページが表示される。

ECS execでコンテナに接続
【Terraform】ecspresso execコマンドでコンテナ内に接続する

