ようへいの日々精進XP

よかろうもん

terraform で Amazon ECS 環境を弄る

はじめに

改めて terraform を勉強したいと思ってドキュメントを見ていたら、扱えるリソースとして ECS もサポートしているようなのでひとまず試してみる。

www.terraform.io

尚、利用する terraform のバージョンは以下の通り。

% terraform -version
Terraform v0.6.1

改めて ECS とは

黒帯目指して

www.slideshare.net

上記の資料がとても詳しいのでウンチクは割愛。


terraform で ECS

terraform のバージョン

今回利用する terraform のバージョンは以下の通り。

% terraform -version
Terraform v0.6.1

terraform で扱える ECS のリソース

terraform で扱える ECS のリソースは以下の通り。

詳細については上記のドキュメントを参照して頂くとして、それぞれのサンプルを以下に掲載。

aws_ecs_cluster

ECS のクラスタは以下のように定義。

resource "aws_ecs_cluster" "kappa-cluster" {
  name = "kappa-cluster"
}

aws_ecs_task_definition

Task definition は以下の通りに定義。

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

registry.json は以下の通り。

[
  {
    "environment": [],
    "name": "registry",
    "image": "registry:2",
    "cpu": 10,
    "portMappings": [
      {
          "containerPort": 5000,
          "hostPort": 5000
      }
    ],
    "memory": 10,
    "command": [
    ],
    "essential": true
  }
]

Docker registry コンテナを起動する定義。

aws_ecs_service

サービスは以下のように定義。

resource "aws_ecs_service" "kappa-registry" {
  name = "kappa-registry"
  cluster = "${aws_ecs_cluster.kappa-cluster.id}"
  task_definition = "${aws_ecs_task_definition.docker_registry.arn}"
  desired_count = 1
}

ECS の中で Service についてなかなか理解が出来ていなかったが、先ほどの資料の 43 ページ目で一発理解出来た気がする。ざっくり、ECS で docker run するなら、Run TaskService を定義すると覚えておけばイイと勝手に解釈。

さらに

コンテナインスタンスが必要になったりするので、github にサンプルをアップ。

github.com

事前にアクセスキー、シークレットアクセスキー、SSH キー、VPC のサブネット、セキュリティグループ(1 つ)と以下のような IAM ロール(以下のサンプルでは your_iam_role_name という名前で定義する)を用意しておく。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "ecs:*",
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

ということでサンプルを動かしてみる

事前に用意した情報

パラメータ 備考
アクセスキー AK123456789123456789
シークレットアクセスキー xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SSH キー your_ssh_key_name コンテナインスタンスにログインする為に必要
VPC サブネット subnet-12345678 任意の VPC から一つのサブネットを選択
セキュリティグループ sg-12345678 任意の VPC で定義されているセキュリティグループを選択
IAM ロール your_iam_role_name

サンプルを少し修正

% git diff oreno_tf_ecs_ecs.tf                                                                                                             [~/git/myrepo/oreno_docker-compose]
diff --git a/oreno_tf_ecs_ecs.tf b/oreno_tf_ecs_ecs.tf
index fedebd8..61c92cc 100644
--- a/oreno_tf_ecs_ecs.tf
+++ b/oreno_tf_ecs_ecs.tf
@@ -21,7 +21,7 @@ resource "aws_ecs_service" "kappa-sample1" {
   name = "kappa-sample1"
   cluster = "${aws_ecs_cluster.kappa-cluster.id}"
   task_definition = "${aws_ecs_task_definition.sample_app1.arn}"
-  desired_count = 0
+  desired_count = 1
 }
 
 resource "aws_ecs_service" "kappa-sample2" {
@@ -35,5 +35,5 @@ resource "aws_ecs_service" "kappa-registry" {
   name = "kappa-registry"
   cluster = "${aws_ecs_cluster.kappa-cluster.id}"
   task_definition = "${aws_ecs_task_definition.docker_registry.arn}"
-  desired_count = 0
+  desired_count = 1
 }

desired_count0 から 1 に増やすことで Task definition で定義されている Task の数を定義する。

% git diff oreno_tf_ecs_ec2.tf
diff --git a/oreno_tf_ecs_ec2.tf b/oreno_tf_ecs_ec2.tf
index 071e6b0..5e66e0a 100644
--- a/oreno_tf_ecs_ec2.tf
+++ b/oreno_tf_ecs_ec2.tf
@@ -2,7 +2,7 @@
 # Launch Container Instance
 #
 resource "aws_instance" "oreno_tf_ecs" {
-  count = 0
+  count = 1
   instance_type = "${var.instance_type}"
   ami = "${lookup(var.aws_amis, var.region)}"
   subnet_id = "${var.subnet}"

コンテナインスタンス1 台起動するので count = 0count = 1 に修正。

以下のように plan を実行

terraform plan \
-var 'access_key=AK123456789123456789' \
-var 'secret_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-var 'ssh_key_name=your_ssh_key_name' \
-var 'subnet=subnet-12345678' \
-var 'securiy_group=sg-12345678' \
-var 'iam_profile_name=your_iam_role_name'

以下のように出力される。

+ aws_ecs_cluster.kappa-cluster
    name: "" => "kappa-cluster"

+ aws_ecs_service.kappa-registry
    cluster:         "" => "${aws_ecs_cluster.kappa-cluster.id}"
    desired_count:   "" => "1"
    name:            "" => "kappa-registry"
    task_definition: "" => "${aws_ecs_task_definition.docker_registry.arn}"

+ aws_ecs_service.kappa-sample1
    cluster:         "" => "${aws_ecs_cluster.kappa-cluster.id}"
    desired_count:   "" => "1"
    name:            "" => "kappa-sample1"
    task_definition: "" => "${aws_ecs_task_definition.sample_app1.arn}"

+ aws_ecs_service.kappa-sample2
    cluster:         "" => "${aws_ecs_cluster.kappa-cluster.id}"
    desired_count:   "" => "0"
    name:            "" => "kappa-sample2"
    task_definition: "" => "${aws_ecs_task_definition.sample_app2.arn}"

+ aws_ecs_task_definition.docker_registry
    arn:                   "" => "<computed>"
    container_definitions: "" => "bd45398f7dc732c1ba3a004a8b9c9f1c118a8eb9"
    family:                "" => "docker_registry"
    revision:              "" => "<computed>"

+ aws_ecs_task_definition.sample_app1
    arn:                   "" => "<computed>"
    container_definitions: "" => "6d19f9f53f6179d307ff2563cec96da7ef466117"
    family:                "" => "sample_app1"
    revision:              "" => "<computed>"

+ aws_ecs_task_definition.sample_app2
    arn:                   "" => "<computed>"
    container_definitions: "" => "56d66c407c63da3519cbcd03a73ab551bb78b9cd"
    family:                "" => "sample_app2"
    revision:              "" => "<computed>"

+ aws_instance.oreno_tf_ecs
    ami:                               "" => "ami-50b01450"
    associate_public_ip_address:       "" => "1"
    availability_zone:                 "" => "<computed>"
    ebs_block_device.#:                "" => "<computed>"
    ephemeral_block_device.#:          "" => "<computed>"
    iam_instance_profile:              "" => "ecs_iam_role"
    instance_type:                     "" => "t2.micro"
    key_name:                          "" => "your_ssh_key_name"
    placement_group:                   "" => "<computed>"
    private_dns:                       "" => "<computed>"
    private_ip:                        "" => "<computed>"
    public_dns:                        "" => "<computed>"
    public_ip:                         "" => "<computed>"
    root_block_device.#:               "" => "<computed>"
    security_groups.#:                 "" => "<computed>"
    source_dest_check:                 "" => "1"
    subnet_id:                         "" => "subnet-12345678"
    tags.#:                            "" => "1"
    tags.Name:                         "" => "ecs-container-instance01"
    tenancy:                           "" => "<computed>"
    user_data:                         "" => "592ff0d81b045135aec95ba9b2adfdbfcf93ad62"
    vpc_security_group_ids.#:          "" => "1"
    vpc_security_group_ids.3722172370: "" => "sg-12345678"


Plan: 8 to add, 0 to change, 0 to destroy.

そして apply

terraform apply \
-var 'access_key=AK123456789123456789' \
-var 'secret_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-var 'ssh_key_name=your_ssh_key_name' \
-var 'subnet=subnet-12345678' \
-var 'securiy_group=sg-12345678' \
-var 'iam_profile_name=your_iam_role_name'

ヅラヅラとメッセージが流れて...

Apply complete! Resources: 8 added, 0 changed, 0 destroyed.

terraform show でも確認しておこう。

確認

コンテナインスタンスにログインして確認。

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                        NAMES
1db091f91203        centos:centos6                   "/bin/sh -c 'hostnam   20 seconds ago      Up 20 seconds       0.0.0.0:32769->4567/tcp      ecs-sample_app1-6-centos-c0eabebd9b8d90898301         
818edd2adf1e        redis:latest                     "/entrypoint.sh redi   34 seconds ago      Up 33 seconds       0.0.0.0:32768->6379/tcp      ecs-sample_app1-6-redis-fce9a3e7d4fbd9ea8b01          
ae12b982594b        registry:2                       "registry cmd/regist   47 seconds ago      Up 47 seconds       0.0.0.0:5000->5000/tcp       ecs-docker_registry-2-registry-eebb8da6f3c2a8d33f00   
e8c5b33fd767        amazon/amazon-ecs-agent:latest   "/agent"               2 minutes ago       Up 2 minutes        127.0.0.1:51678->51678/tcp   ecs-agent

ひと通り Task definition で定義した内容でコンテナが起動してるが、AWS CLI でも定義を確認しておく。

# クラスタ一覧確認
% aws ecs list-clusters
{
    "clusterArns": [
        "arn:aws:ecs:ap-northeast-1:123456789123:cluster/kappa-cluster"
    ]
}

# サービス一覧確認
% aws ecs list-services --cluster kappa-cluster
{
    "serviceArns": [
        "arn:aws:ecs:ap-northeast-1:123456789123:service/kappa-sample2", 
        "arn:aws:ecs:ap-northeast-1:123456789123:service/kappa-sample1", 
        "arn:aws:ecs:ap-northeast-1:123456789123:service/kappa-registry"
    ]
}

# コンテナインスタンス一覧
% aws ecs list-container-instances --cluster kappa-cluster
{
    "containerInstanceArns": [
        "arn:aws:ecs:ap-northeast-1:123456789123:container-instance/7b242f77-58e9-4c75-98d2-b5b878077dd8"
    ]
}

# タスク定義一覧
% aws ecs list-task-definitions                                                                                                            [~/git/myrepo/oreno_docker-compose]
{
    "taskDefinitionArns": [
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/docker_registry:1", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/docker_registry:2", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/sample_app1:5", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/sample_app1:6", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/sample_app2:6", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/sample_app2:7"
    ]
}

# タスク一覧
% aws ecs list-tasks --cluster kappa-cluster
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:123456789123:task/331e0a1d-a9ac-4651-b92b-35288dd9a657", 
        "arn:aws:ecs:ap-northeast-1:123456789123:task/ce8c54e2-9a52-446d-9718-9c81c3dfdcc1"
    ]
}

ということで...

気付いた点

  • Task definition の内容を update して apply してもコンテナが入れ替わらないっぽい

こちらの資料に記載されているように Task definition の内容を update(JSON の内容を修正)して terraform apply すると自動で古いコンテナから新しいコンテナに入れ替わってくれるのかなーと期待していたが、そのような動作にはならなかった。terraform 以外での挙動は試せていないので切り分けは不完全、引き続き要調査。(自分の認識不足も否めない)

  • Run Task に該当するリソースが無いのはナゼなんだろう

漠然とした疑問。

AWS CLI + JSON で弄るのと比較して...

Task definition は JSON で定義するのは変わらないので、どっちがイイとかは言えないが、terraform を利用することで得られるメリット(state ファイルによる状態管理等)を享受出来る点では terraform を利用する意味はあると感じた。