はじめに
ECS を弄っていたら docker-registry でプライベートなリポジトリが欲しいと思ったので terraform で作ってみた。
そもそも ECS には docker build
するステップは無くてビルド済みのコンテナイメージをどうこうするようなサービスだと思うので、実際に ECS を運用しようとなるとコンテナイメージを public に晒したくない等の要望からプライベートリポジトリへのニーズはおのずと高まるのではないかと思ったりしている。
尚、利用する terraform のバージョンは以下の通り。
% terraform -version
Terraform v0.6.1
図
構成概要図
以下のような環境を terraform 一発で作成してみる。
terraform graph
terraform graph
と graphviz を利用して構成を以下のようなイメージとして出力することが出来る。
% terraform graph | dot -Tpng > graph.png % open graph.png
Mac の場合には Preview が起動して以下のような画像が表示される。
各リソースの関連付けが一発で確認出来るのが嬉しい。
デモ
事前に用意しておくこと
- 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 ファイルを以下のように修正する。memory
は 100
位(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 destroy
で AWS リソースを削除する。
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 についてまだまだ理解が足りていないのでドキュメントを読む