Amazon ECSを使ってHubotが動いているdockerコンテナをEC2へ自動デプロイする
最近dockerをよく使っているので、Amazon ECSも触ってみることにした。
せっかくなのでCircleCIも使って、githubにpushしたら自動でデプロイするように設定する。
hubot構築
まずはローカルで動かしてみるところから。
作業環境はMacです。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.3 BuildVersion: 15D21
Dockerfileを作って、そこからビルドする。
あと、Slackと連携させるための設定も入れる。
$ cat Dockerfile FROM ubuntu MAINTAINER yukofeb <yuko.february@gmail.com> # Install packages RUN apt-get update RUN apt-get -y install expect redis-server nodejs npm RUN ln -s /usr/bin/nodejs /usr/bin/node RUN npm install -g coffee-script RUN npm install -g yo generator-hubot # Create hubot user RUN useradd -d /hubot -m -s /bin/bash -U hubot # Log in as hubot user USER hubot WORKDIR /hubot # Install hubot Run yo hubot --owner="yukofeb" --name="Hubot" --description="Hubot in docker" --defaults # Configure ADD files/external-scripts.json ./ ADD scripts/*.coffee scripts/ # Support slack RUN npm install hubot-slack --save && npm install CMD bin/hubot -a slack
これを元にイメージを作る。
追ってdockerhubにアップするので、(user_name)/(repo_name)
の名称で保存しておく。
$ docker build -t yukofeb/hubot-in-docker . # 確認 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE yukofeb/hubot_in_docker latest d4de1ddf744c 33 seconds ago 452.8 MB
このイメージをもとにrunする。
Slack用の変数については、Slack Integration設定画面から確認できる。
$ docker run -e HUBOT_SLACK_TOKEN=*** -d yukofeb/hubot_in_docker # 確認 $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a12644feaf43 yukofeb/hubot_in_docker "/bin/sh -c 'bin/hubo" 5 seconds ago Up 4 seconds hubot_test
これで自分のSlackにhubotが出現しているはず。
dockerだとhubot構築がほんとに簡単すぎてびっくりする。。
dockerhubへアップロード
dockerhubに登録後、createボタンから該当レポジトリを登録しておく。
アップロードのために事前に認証コマンドを実行しておく必要がある。
$ docker login --username=*** --email=***@***
手動でアップロードするコマンド。
$ docker push (user_name)/(repo_name)
その他詳細は公式ページを参照。
Tag, push, & pull your image
CircleCI設定
今までやってきた"dockerイメージ作成、起動、dockerhubへのpush"を自動でできるように設定する。
$ cat circle.yml machine: services: - docker dependencies: override: - docker build -t yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM . test: override: - docker run -d yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM deployment: hub: branch: master commands: - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - docker push yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM
dockerhubログインのための情報はレポジトリにあげたくないので、CircleCIの環境変数で対応する。
Builds > Settings > Tweaks > Environment variables
から設定可能。
ここから$DOCKER_EMAIL, $DOCKER_USER, $DOCKER_PASS
を入れる。
(あとで追加で環境変数を追加するが、それはまたあとで。。)
ここまでやれば、githubへpushすれば自動でdockerhubプッシュまでやってくれるはず。
EC2へのデプロイ
次はいよいよECSを使ってEC2にdockerをデプロイしていく。
基本的にはLaunching an Amazon ECS Container Instance - Amazon EC2 Container Serviceに従ってやっていけばOK。
また作業する前にAmazon EC2 Container Service(ECS)の概念整理 - Qiitaをさらっと見ておくと理解が深まるので進めやすい。
IAM作成
まずはIAMを作成する。
ポリシーはAmazonEC2ContainerServiceforEC2Role
で設定。
このIAMは、ECSがインスタンスとして扱うEC2に適用する。
EC2作成
次はECSインスタンス用のEC2を作成する。
ECS用のイメージがあるので、それを選択する。
2016/03現在はami-b3afa2dd
を使うといいらしい。
最新の情報は公式ドキュメントを確認してください。
IAMロールの項目については、先ほど作成したものを設定する。
また、高度な詳細項目で設定できるユーザーデータ欄に以下を入力する必要がある。
(これを設定しないとEC2のインスタンスとして認識されない)
(your_cluster_name)には次項目で設定するcluster名を入力すること。
#!/bin/bash echo ECS_CLUSTER=(your_cluster_name) >> /etc/ecs/ecs.config
ECS設定
ここまで出来たらようやくECSコンソールに移動。
cluster作成
何はともあれCreate Cluster
ボタンを押す。
今回はCluster名はhubotにする。
先ほどのEC2作成がうまくいっていれば、それがECS Instances
にリストされる。
Task Definition作成
次にTask Definitionを作成していく。
Task Difinitionとは、Task(Dockerコンテナをグルーピングするもの)のテンプレみたいなもの。
GUIでも作成できるが、今後のためにymlにまとめた。
$ cat ecs_task_definition.json [ { "image": "yukofeb/hubot_in_docker:v_***", "name": "myhubot", "cpu": 10, "memory": 400, "essential": true, "entryPoint": [ "bin/hubot", "-a", "slack", "-d" ], "environment": [ { "name" : "HUBOT_SLACK_TOKEN", "value" : "***" } ], "portMappings": [ { "containerPort": 80, "hostPort": 80 } ] } ]
以下のコマンドで、アップロードできる。
$ aws ecs register-task-definition --family myhubot --container-definitions file://./ecs_task_definition.json # 確認 $ aws ecs list-task-definitions
アップロードしたTask DefinitionはGUIでも確認できる。
ちなみにawscliのインストール方法はこちら。
Macだとhomebrewでインストールできるから簡単。
Installing the AWS Command Line Interface - AWS Command Line Interface
Task作成
Taskは、先ほどのTask Definitionを実体化したものと考えればOK。
今回はコマンドラインから作成。
$ aws ecs run-task --cluster hubot --task-definition myhubot:1 -count 1 # 確認 $ aws ecs list-tasks --cluster hubot { "taskArns": [ "arn:aws:ecs:ap-northeast-1:***" ] }
GUIからも確認できる。
Service作成
最後にServiceを作成する。
Serviceとは、複数あるいは単独のTaskの集合体。
$ aws ecs create-service --service-name hubot --task-definition hubot:1 --desired-count 1 # 確認 $ aws ecs list-services --cluster hubot { "serviceArns": [ "arn:aws:ecs:ap-northeast-1:***:service/hubot" ] }
GUIからも確認できる。
ここまで出来れば、すでにhubotは動く状態になっているはず。
CircleCI設定
次に、CircleCIから自動デプロイする設定を行う。
CircleCIからEC2へのアクセス設定についてはここでは省略するが、
CircleCIへの秘密鍵設定、EC2への公開鍵設定などが必要となる。
(方法はネット上に沢山あるのでそちらにお任せします。。)
機密情報への対応
CircleCIにデプロイしてもらうために全てのファイルはgithubにアップロードするが、
このままではSlackのトークンをレポジトリに公開することになってしまう。
これを隠蔽するためにCircleCIの変数機能を利用し対応する。
ということで、先ほど書いたスクリプトを一部修正する。
まずTask Definitionを定義したjsonファイルを手直し。
隠蔽したいSlackのトークンと、Pullするイメージのバージョンは環境変数から引っ張ってくる。
なので、あらかじめCircleCIの環境変数設定画面からHUBOT_SLACK_TOKEN
を環境変数として入力する必要がある。
CIRCLE_BUILD_NUM
はCircleCIが入れてくれるので対応不要。
# Task Definition修正版 $ cat ecs_task_definition.json [ { "image": "yukofeb/hubot_in_docker:v_--CIRCLE_BUILD_NUM--", "name": "myhubot", "cpu": 10, "memory": 400, "essential": true, "entryPoint": [ "bin/hubot", "-a", "slack", "-d" ], "environment": [ { "name" : "HUBOT_SLACK_TOKEN", "value" : "--HUBOT_SLACK_TOKEN--" } ], "portMappings": [ { "containerPort": 80, "hostPort": 80 } ] } ] # 変数を置き換えるスクリプト # 以下のスクリプトを参考にさせていただきました # https://github.com/noboru-i/backlog-to-chatwork/blob/master/bin/env2file $ cat replace_variables.sh #!/bin/bash # Replace --***-- with $***(environment variables) ORIGINAL_FILE=$1 REPLACED_FILE=$2 cp ./$ORIGINAL_FILE ./$REPLACED_FILE cat $REPLACED_FILE | while read line do if [[ $line =~ --([A-Z_]+)-- ]] ; then KEY=${BASH_REMATCH[1]} eval VALUE='$'${KEY} sed -i -e "s!${BASH_REMATCH[0]}!$VALUE!g" $REPLACED_FILE fi done
CircleCIからのTask DefinitionとService更新
Task DefinitionとServiceをCircleCIから更新するためのスクリプトを作成する。
$ cat update_ecs.sh #!/bin/bash REPLACED_FILE=replaced_ecs_task_definition.json MYSECURITYGROUP=*** MYIP=`curl -s ifconfig.me` echo "MYIP is $MYIP" # CircleCIの環境変数を利用してファイルを書き換える bash ./replace_variables.sh ecs_task_definition.json $REPLACED_FILE # awscliを使うための認証を行う aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY aws configure set region $AWS_REGION # セキュリティ対策のため、都度ログインできるIPをセキュリティグループに登録している aws ec2 authorize-security-group-ingress --group-id $MYSECURITYGROUP --protocol tcp --port 22 --cidr $MYIP/32 # Task Definition更新 # 結果はdevnullに捨てている # (出力結果に環境変数が含まれているので、そのままではCircleCIのコンソールにそれらの情報が出力されてしまうため) result=`aws ecs register-task-definition --family myhubot --container-definitions file://./$REPLACED_FILE` >/dev/null 2>&1 # 先ほどの出力結果から登録されたTask Definitionのrevision番号を取得する revision=`echo $result | jq '.["taskDefinition"]["revision"]'` # 登録されたTask Definitionを使ってServiceを更新する aws ecs update-service --cluster hubot --service hubot --task-definition myhubot:$revision --desired-count 1 # 先ほど許可したログイン可能IP情報を削除する aws ec2 revoke-security-group-ingress --group-id $MYSECURITYGROUP --protocol tcp --port 22 --cidr $MYIP/32 # いらないファイルを削除する rm -rf $REPLACED_FILE
CircleCIに実行させるスクリプトに追加。
また先ほどのスクリプト内で、jsonパースのためにjqを使っているので、それもインストールする。
$ cat circle.yml machine: services: - docker dependencies: pre: - sudo apt-get install jq - sudo pip install awscli override: - docker build -t yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM . test: override: - docker run -d yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM deployment: hub: branch: master commands: - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - docker push yukofeb/hubot_in_docker:v_$CIRCLE_BUILD_NUM - bash ./update_ecs.sh
思ったよりも長くなったけど、これで完了!yukofeb/hubot_in_docker
これで快適なhubot生活が送れる(=゚ω゚)!!
References
How to Run HuBot in Docker on AWS EC2 Container Services - Part 1 - Philipp´s Blog
Slackと連携するHubotをDocker in AWSで構築 - Qiita
Amazon EC2 Container Service (ECS)を試してみた | Developers.IO
Amazon EC2 Container Service(ECS)で静的Webサイトをデプロイする | Developers.IO