ecspresso

ecspresso はECSのタスク定義やサービスを管理するためのツール。

Terraform + ecspresso

ECSの構成

ECSを構成するコンポーネントとして以下がある。

  • タスク定義
  • クラスター
  • サービス
  • タスク

クラスターがサービスを持ち、サービスがタスクを持つ。
タスクはタスク定義から作成される。

ECS

Terraformとecspressoのそれぞれの役割

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

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.ymltask_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.ymlservice_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のデフォルトページが表示される。

nginx

ECS execでコンテナに接続

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