読者です 読者をやめる 読者になる 読者になる

Carpe Diem

備忘録。https://github.com/jun06t

TerraformでECS環境の構築

概要

ECSというコンテナのクラスタ環境構築のサービスをTerraformで作成してみます。
簡単のため、以下の設定はこのコードには含んでいません。

  • EC2インスタンスのオートスケール用のアラーム設定なし
  • ECSのコンテナのオートスケールはなし(Terraform未対応)

完成形は以下です。

github.com

環境

  • Terraform v0.6.16

ポイント

  • VPC, Subnet, RouteTable, SecurityGroup, NATの用意
  • AutoScalingGroupの用意
  • ECS用のIAMを作る
  • ECSに最適化されたAMIを使用する
  • user_dataでECSクラスターに登録するための設定を作る
  • task definitionの定義
  • ECSの作成

VPC, Subnet, RouteTable, SecurityGroup, NATの用意

今まで通りなので割愛します。

AutoScalingGroupの用意

これも前述と同じなので割愛。ただしuser-dataには後述するものを使用します。

ECS用のIAMを作る

@riywoさんに「AWS Managed PolicyをRoleにattachしておいた方がよい」というご指摘を受けたので修正しています(修正コミット)。ご指摘ありがとうございます!

resource "aws_iam_role" "ecs_instance_role" {
    name = "ecs_instance_role"
    assume_role_policy = "${file("policies/ec2-assume-role.json")}"
}

resource "aws_iam_role" "ecs_service_role" {
    name = "ecs_service_role"
    assume_role_policy = "${file("policies/ecs-assume-role.json")}"
}

resource "aws_iam_policy_attachment" "ecs_instance_role_attach" {
    name = "ecs-instance-role-attach"
    roles = ["${aws_iam_role.ecs_instance_role.name}"]
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_iam_policy_attachment" "ecs_service_role_attach" {
    name = "ecs-service-role-attach"
    roles = ["${aws_iam_role.ecs_service_role.name}"]
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"
}

resource "aws_iam_instance_profile" "ecs" {
    name = "ecs-instance-profile"
    path = "/"
    roles = ["${aws_iam_role.ecs_instance_role.name}"]
}

assumeRoleを用意してAWS Managed Policyをattachしています。

ec2-assume-role.json

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}

ecs-assume-role.json

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ecs.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}

似ているようでPrincipalに指定しているAWSサービスは違うので注意してください。

ECSに最適化されたAMIを使用する

最新版は以下で更新されます。

Amazon ECS-optimized AMI - Amazon EC2 Container Service

今回は以下のようにvariables.tfに書きます。

variable "access_key" {}
variable "secret_key" {}
variable "region" {
    default = "ap-northeast-1"
}
variable "amis" {
    default = {
        ecs = "ami-2b08f44a"
        nat = "ami-27d6e626"
    }
}
variable "key_name" {
    default = "YOUR_SSH_KEY_PAIR_NAME"
}

user_dataでECSクラスターに登録するための設定を作る

ECS optimized なAMIではECS agentというデーモンが起動します。このデーモンは/etc/ecs/ecs.configというファイルの設定を読み込むので、ここにクラスタ名を記述することで、ASGで生成されたインスタンスがECSのクラスタに自動で登録されるようになります。クラスタ名をapi-clusterとすると、

#!/bin/bash
echo ECS_CLUSTER=api-cluster >> /etc/ecs/ecs.config

と記述します。

ちなみにDockerHubのPrivateRepoを使いたい場合は以下のように設定するとimageをpullできるようになります。

#!/bin/bash
cat << EOF >> /etc/ecs/ecs.config
ECS_CLUSTER=api-cluster
ECS_ENGINE_AUTH_TYPE=docker
ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"ログインユーザ名","password":"パスワード","email":"メールアドレス"}}
EOF

task definitionの定義

今回はシンプルにnginxのみ起動するので以下の通りにします。

[
  {
    "name": "nginx",
    "image": "nginx",
    "cpu": 1024,
    "memory": 200,
    "essential": true,
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ]
  }
]

使用できるパラメータは以下の通りです。

Task Definition Parameters - Amazon EC2 Container Service

ECSの作成

クラスタ名、TaskDefinition、IAMRole、RolePolicyなどを指定します。

resource "aws_ecs_cluster" "api_cluster" {
    name = "api-cluster"
}

resource "aws_ecs_service" "api" {
    name = "api-service"
    cluster = "${aws_ecs_cluster.api_cluster.id}"
    task_definition = "${aws_ecs_task_definition.api.arn}"
    desired_count = 1
    iam_role = "${aws_iam_role.ecs_service_role.arn}"
    depends_on = ["aws_iam_role_policy.ecs_service_role_policy"]
    deployment_minimum_healthy_percent = 50
    deployment_maximum_percent = 100
    load_balancer {
        elb_name = "${aws_elb.dev_elb.name}"
        container_name = "nginx"
        container_port = 80
    }
}

resource "aws_ecs_task_definition" "api" {
    family = "api"
    container_definitions = "${file("task-definitions/api.json")}"
}

各パラメータは以下の役割があります。

パラメータ 意味
family task definition名
desired_count 起動したいコンテナ数。ただしポートの競合などがあるとインスタンスが増えない限りコンテナも増えない
deployment_minimum_healthy_percent rolling update時に最低限保持する旧コンテナ数。desired_countに対する割合
deployment_maximum_percent rolling update時に新旧コンテナが混在して最大いくつまで起動するかの数。desired_countに対する割合

以上のポイントを押さえれば問題なく作成することが出来ます。お疲れ様でした。

ソース