ようへいの日々精進XP

よかろうもん

terraform で Amazon ECS 環境を弄る(2)〜 docker-registry でプライベートリポジトリ環境を一気通貫で作る 〜

はじめに

ECS を弄っていたら docker-registry でプライベートなリポジトリが欲しいと思ったので terraform で作ってみた。

github.com

そもそも ECS には docker build するステップは無くてビルド済みのコンテナイメージをどうこうするようなサービスだと思うので、実際に ECS を運用しようとなるとコンテナイメージを public に晒したくない等の要望からプライベートリポジトリへのニーズはおのずと高まるのではないかと思ったりしている。

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

% terraform -version
Terraform v0.6.1

構成概要図

以下のような環境を terraform 一発で作成してみる。

f:id:inokara:20150725112612p:plain

terraform graph

terraform graphgraphviz を利用して構成を以下のようなイメージとして出力することが出来る。

% terraform graph | dot -Tpng > graph.png
% open graph.png

Mac の場合には Preview が起動して以下のような画像が表示される。

f:id:inokara:20150725135902p:plain

各リソースの関連付けが一発で確認出来るのが嬉しい。


デモ

事前に用意しておくこと

  • AWS access key
  • AWS secret access key
  • SSH key(for Container Instance)
  • VPC Subnet ID
  • Security Group
  • IAM role の名前
  • S3 Bucket の名前

コンテナインスタンスと ECS Service の tf ファイルを修正

% git diff
diff --git a/oreno_tf_ecs_ec2.tf b/oreno_tf_ecs_ec2.tf
index 6bc3662..d1a2efa 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}"
diff --git a/oreno_tf_ecs_ecs_service.tf b/oreno_tf_ecs_ecs_service.tf
index 4c369f6..556d06f 100644
--- a/oreno_tf_ecs_ecs_service.tf
+++ b/oreno_tf_ecs_ecs_service.tf
@@ -19,5 +19,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
 }

コンテナインスタンス、ECS Service(今回は docker-registry コンテナ)それぞれ 1 台、1 コンテナ起動するので上記のように修正する。

terraform 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'
-var 's3_bucket_name=your-s3-bucket-name'

terraform 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'
-var 's3_bucket_name=your-s3-bucket-name'

暫くすると環境が出来上がる...ので terraform showインスタンスの IP を確認して ssh でアクセスしてみる。

% ssh -i ~/.ssh/your_ssh_key_name ec2-user@${コンテナインスタンスの IP}

アクセス出来たら docker ps を叩いて実行されているコンテナを確認する。

$ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                        NAMES
6e1e4b8bddbf        registry:2.0                     "registry cmd/regist   52 seconds ago      Up 52 seconds       0.0.0.0:5000->5000/tcp       ecs-docker_registry-17-registry-dcb9b6ae99a881e16700   
88cd5d9bbdb8        amazon/amazon-ecs-agent:latest   "/agent"               2 minutes ago       Up 2 minutes        127.0.0.1:51678->51678/tcp   ecs-agent

コンテナインスタンス起動直後は ECS Agent コンテナのみが起動している状態になっているかもしれないが、これは ECS Service が task を実行中である可能性があるので暫く時間を置いてから改めて docker ps にて確認する。

また、AWS CLI では以下のように確認することが出来る。

# 起動中の task 一覧を取得
% aws ecs list-tasks --cluster kappa-cluster
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:123456789012:task/aa550cfc-9fe8-477b-a6d9-fc854ed5e41a"
    ]
}

# 起動している task の詳細を確認
% aws ecs describe-tasks --cluster kappa-cluster --tasks arn:aws:ecs:ap-northeast-1:123456789012:task/aa550cfc-9fe8-477b-a6d9-fc854ed5e41a
{
    "failures": [], 
    "tasks": [
        {
            "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/aa550cfc-9fe8-477b-a6d9-fc854ed5e41a", 
            "overrides": {
                "containerOverrides": [
                    {
                        "name": "registry"
                    }
                ]
            }, 
            "lastStatus": "RUNNING", 
            "containerInstanceArn": "arn:aws:ecs:ap-northeast-1:123456789012:container-instance/4fa5beda-a401-4275-9fe8-928574e22c24", 
            "clusterArn": "arn:aws:ecs:ap-northeast-1:123456789012:cluster/kappa-cluster", 
            "desiredStatus": "RUNNING", 
            "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/docker_registry:17", 
            "startedBy": "ecs-svc/9223370599073264585", 
            "containers": [
                {
                    "containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/a8ee35df-01d1-4ad8-a966-542f9509e3c9", 
                    "taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/aa550cfc-9fe8-477b-a6d9-fc854ed5e41a", 
                    "name": "registry", 
                    "networkBindings": [
                        {
                            "protocol": "tcp", 
                            "bindIP": "0.0.0.0", 
                            "containerPort": 5000, 
                            "hostPort": 5000
                        }
                    ], 
                    "lastStatus": "RUNNING", 
                    "exitCode": 0
                }
            ]
        }
    ]
}

"lastStatus": "RUNNING" となっているので docker-registry コンテナは起動していると判断出来る。

S3 バケットの確認

今回、docker-registry のストレージとして S3 を利用する為、S3 バケットも一緒に作成しているので念のために確認しておく。

% aws s3api list-buckets --output text | grep your-s3-bucket-name
BUCKETS 2015-07-24T23:45:12.000Z        your-s3-bucket-name

出来ていることを確認。

docker push してみる

サンプルとして ruby イメージを pull して tag 付けして docker-registry に push するまでを一気に。

# ruby:2.2 イメージを取得
$ docker pull ruby:2.2
2.2: Pulling from ruby
9213e81cb0f2: Pull complete
(略)
607e965985c1: Already exists
ruby:2.2: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:fb377dede47a69dc3986e7c8b568925bdd0aa44d06f9a1ee795e4ef1777b8fc4
Status: Downloaded newer image for ruby:2.2

# 取得したイメージを確認
$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ruby                      2.2                 eeb85dfaa855        24 hours ago        705.3 MB
registry                  2.0                 08f78f46653a        10 days ago         548.6 MB
amazon/amazon-ecs-agent   latest              68c50c0e92df        5 weeks ago         8.26 MB

# 取得したイメージに別途タグを付与
$ docker tag ruby:2.2 localhost:5000/ruby

# イメージの確認
$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ruby                      2.2                 eeb85dfaa855        24 hours ago        705.3 MB
localhost:5000/ruby       latest              eeb85dfaa855        24 hours ago        705.3 MB
registry                  2.0                 08f78f46653a        10 days ago         548.6 MB
amazon/amazon-ecs-agent   latest              68c50c0e92df        5 weeks ago         8.26 MB

# イメージを docker-registry に push
$ docker push localhost:5000/ruby
The push refers to a repository [localhost:5000/ruby] (len: 1)
eeb85dfaa855: Image already exists
(略)
902b87aaaec9: Image successfully pushed
Digest: sha256:393f299c046c34baa038ecbee3bc5fd8ee187c9494e42ce98b6d591027a8a493

push 完了。

念のために S3 バケットを確認。

% aws s3 ls s3://your-s3-bucket-name/ --recursive
2015-07-25 09:07:48  128188595 docker/registry/v2/blobs/sha256/0a/0a9bef0a68075d7cbca8a9b71c2f96700f7235250f4dca0357b1e2fd19eb8cfe/data
2015-07-25 09:06:21     500107 docker/registry/v2/blobs/sha256/26/2602cc40ac6573cdea7579095d30117be56da846acf819925
(略)
2015-07-25 09:09:10         71 docker/registry/v2/repositories/ruby/_manifests/tags/latest/index/sha256/393f299c046c34baa038ecbee3bc5fd8ee187c9494e42ce98b6d591027a8a493/link

docker build してみる

docker-registry に push したイメージを利用して docker build してみたいので以下のような Dockerfile を用意。

FROM localhost:5000/ruby
RUN apt-get update -qq && apt-get install -y build-essential
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
RUN bundle install
ADD . /myapp
#
EXPOSE 4567
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0"]

合わせて Gemfile と app.rb を以下の通り用意する。

source 'https://rubygems.org'

gem "sinatra"

app.rb は以下の通り。

#!/usr/bin/env ruby
#
require 'sinatra'

get '/hello' do
  'world'
end

満を持して docker build する。

$ docker build -t localhost:5000/sample-app .
Sending build context to Docker daemon 4.096 kB
Sending build context to Docker daemon
Step 0 : FROM localhost:5000/ruby
latest: Pulling from localhost:5000/ruby

9213e81cb0f2: Pull complete
68e6b69220e6: Pull complete
98d9b550773a: Pull complete
a6f910a70a8b: Pull complete
a64c664ecda2: Pull complete
4ce94d60721e: Pull complete
e041f414cc83: Pull complete
5d3233d43921: Pull complete
efa4fc77e3aa: Pull complete
094c3093fe2b: Pull complete
af003319cd08: Pull complete
eeb85dfaa855: Already exists
902b87aaaec9: Already exists
9a61b6b1315e: Already exists
1ff9f26f09fb: Already exists
607e965985c1: Already exists
Digest: sha256:393f299c046c34baa038ecbee3bc5fd8ee187c9494e42ce98b6d591027a8a493
Status: Downloaded newer image for localhost:5000/ruby:latest
 ---> eeb85dfaa855
Step 1 : RUN apt-get update -qq && apt-get install -y build-essential
 ---> Running in 247c05daa9c4
(略)
Step 7 : EXPOSE 4567
 ---> Running in 1266bf893d9d
 ---> 29dc06d7995a
Removing intermediate container 1266bf893d9d
Step 8 : CMD bundle exec ruby app.rb -o 0.0.0.0
 ---> Running in d3d054e096da
 ---> 9d7944790124
Removing intermediate container d3d054e096da
Successfully built 9d7944790124

build 終わったので run して確認まで。

# docker images
$ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
localhost:5000/sample-app   latest              9d7944790124        6 minutes ago       723.6 MB
localhost:5000/ruby         latest              eeb85dfaa855        25 hours ago        705.3 MB
registry                    2.0                 08f78f46653a        10 days ago         548.6 MB
amazon/amazon-ecs-agent     latest              68c50c0e92df        5 weeks ago         8.26 MB

# docker run
$ docker run -t -d -p 14567:4567 localhost:5000/sample-app
df7ce82fdaab0c99adaf830462ea8e2ca17f30bf297844352fae5c1e5cf3743f

# docker ps
$ docker ps
CONTAINER ID        IMAGE                              COMMAND                CREATED             STATUS              PORTS                        NAMES
df7ce82fdaab        localhost:5000/sample-app:latest   "bundle exec ruby ap   43 seconds ago      Up 42 seconds       0.0.0.0:14567->4567/tcp      boring_bell                                            
6e1e4b8bddbf        registry:2.0                       "registry cmd/regist   About an hour ago   Up About an hour    0.0.0.0:5000->5000/tcp       ecs-docker_registry-17-registry-dcb9b6ae99a881e16700   
88cd5d9bbdb8        amazon/amazon-ecs-agent:latest     "/agent"               About an hour ago   Up About an hour    127.0.0.1:51678->51678/tcp   ecs-agent

# 確認
$ curl localhost:14567/hello
world

build したコンテナイメージを docker-registry に push する。

docker push localhost:5000/sample-app

ECS の Task Definition で利用してみる

Task Definition が記述されているこの JSON ファイルを以下のように修正する。memory100 位(100MB)にしておかないとコンテナが起動しないので注意。

[
  {
    "environment": [],
    "name": "sample-app",
    "image": "localhost:5000/sample-app",
    "cpu": 0,
    "portMappings": [
      {
          "containerPort": 4567,
          "hostPort": 0
      }
    ],
    "memory": 100,
    "command": [
    ],
    "essential": true
  }
]

また、コンテナを 1 つ起動したいので以下のように修正して terraform apply で反映。

% git diff oreno_tf_ecs_ecs_service.tf 
diff --git a/oreno_tf_ecs_ecs_service.tf b/oreno_tf_ecs_ecs_service.tf
index 4c369f6..4b03b49 100644
--- a/oreno_tf_ecs_ecs_service.tf
+++ b/oreno_tf_ecs_ecs_service.tf
@@ -12,12 +12,12 @@ resource "aws_ecs_service" "kappa-sample2" {
   name = "kappa-sample2"
   cluster = "${aws_ecs_cluster.kappa-cluster.id}"
   task_definition = "${aws_ecs_task_definition.sample_app2.arn}"
-  desired_count = 0
+  desired_count = 1
 }

sample-app コンテナが起動していることを確認する。

# コンテナの起動を確認
$ docker ps
CONTAINER ID        IMAGE                              COMMAND                CREATED             STATUS              PORTS                        NAMES
aa024c957c06        localhost:5000/sample-app:latest   "bundle exec ruby ap   5 minutes ago       Up 5 minutes        0.0.0.0:32787->4567/tcp      ecs-sample_app2-21-sample-app-c0aedad1e6eda8d92c00     
6e1e4b8bddbf        registry:2.0                       "registry cmd/regist   About an hour ago   Up About an hour    0.0.0.0:5000->5000/tcp       ecs-docker_registry-17-registry-dcb9b6ae99a881e16700   
88cd5d9bbdb8        amazon/amazon-ecs-agent:latest     "/agent"               About an hour ago   Up About an hour    127.0.0.1:51678->51678/tcp   ecs-agent                                              

# sample-app コンテナにアクセスする
$ curl localhost:32787/hello
world

おお。

destroy

ひと通り弄って満足したら terraform destroyAWS リソースを削除する。

terraform destroy \
-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'
-var 's3_bucket_name=your-s3-bucket-name'

一気に削除出来るのは嬉しいところ...ではあるが、S3 バケットも一緒に削除されるので注意する。S3 バケットの削除を抑制したい場合には force_destroy = "true" を削除しておく。

% git diff oreno_tf_ecs_s3.tf                                                                                                                                                                                                     [~/git/myrepo/oreno_tf_ecs]
diff --git a/oreno_tf_ecs_s3.tf b/oreno_tf_ecs_s3.tf
index 7bcef58..7d76c78 100644
--- a/oreno_tf_ecs_s3.tf
+++ b/oreno_tf_ecs_s3.tf
@@ -4,5 +4,4 @@
 resource "aws_s3_bucket" "s3_bucket" {
   bucket = "${var.s3_bucket_name}"
   acl = "private"
-  force_destroy = "true"
 }

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

Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_ecs_task_definition.sample_app1: Refreshing state... (ID: sample_app1)
(略)
aws_instance.oreno_tf_ecs: Destruction complete

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

お疲れ様でした。


ということで

やったこと

だらだらと長くなったのでやったことを整理。

  • terraform を利用して ECS 環境内に S3 を使ったプライペートリポジトリを作成した
  • プライベートリポジトリにイメージの push
  • プライベートリポジトリのイメージを利用して ECS の Task を定義してコンテナを起動した

課題(1)docker-registry に関して

  • ローカルからのみの push を想定した構成になっているので、外部からの push に対して HTTPS でアクセスさせたい
  • 同じく push の際に認証掛けたい

課題(2)terraform と ECS に関して

  • 出来るだけ tf ファイルに固定値を避ける(ECS クラスタ名等も変数として渡すようにしたい)
  • tfstate ファイルの管理
  • AWS リソース起動のタイミングでエラーが出ることがある(エラーが出た場合には再度 apply するとエラーは無くなる)点を調べる

課題(3)ECS に関して

  • Task Definition や Service についてまだまだ理解が足りていないのでドキュメントを読む