AWS Elastic BeanstalkのMulti-Container Docker Environmentsおよびebコマンドを使ったデプロイについて

先日、AWS Elastic Beanstalk Supports Multi-Container Docker Environmentsで発表がございましたが、 1つのEC2インスタンスに複数のDockerコンテナを配置することが出来るようになりました。 こちらは、Amazon EC2 Container Serviceのテクノロジーがベースになっています。 今回は、このMulti-Container Dockerを使った環境の構築と、 ついでにebというElastic Beanstalk用のコマンドラインツールを使ったアプリケーションのデプロイを試してみたいと思います。   なお、本投稿は私が個人的に行ったものであり、所属する企業や団体における公式見解ではございません。     ■ Multi-container Dockerの設定で構築   Elastic Beanstalkの用語として以下のようなものが挙げられます。 ・Application: Environmentの集合体。フォルダのようなイメージ。(動くコードそのものではない) ・Version: デプロイするコード。S3をリポジトリにしてバージョン管理。 ・Environment: Versionがデプロイされた環境。複数Environmentにそれぞれ異なるVersionをデプロイすることも出来る。 なんとなくApplicationというと、動くコードそのものをイメージしてしまいそうになるのですが、 そうではないので念の為ご注意いただければと思いますmm   で、実際の構築のやり方は Multicontainer Docker Environments with the AWS Management Console に書いてありますが、Multi-containerは上でも少し触れたように、ECS(EC2 Container Service)が元になっているので、そのAPIを叩けるようにしたり、 ログの格納用に使用するS3バケットへのputの設定をaws-elasticbeanstalk-ec2-role(EBで環境構築する際にデフォルトで作成されるIAM Role)に対して行います。 ↓のワーニングはそのことを言っています。   具体的にはIAMの画面でPolicyを作ってRoleの画面でAttache Policyの設定をします。   サンプルのアプリケーションで構築が完了すると↓のような画面になります。   払いだされたURLにアクセスすると↓こんな感じ。   試しに、プロビジョニングされたEC2インスタンスSSHで入ってみると、 ↓のようにdockerやecsが動いてるような感じしますね。   dockr psを叩いてみると、以下のように、プロキシ用のNginx、PHPのアプリ、ESCのエージェントが動いているのが分かります。

$ sudo docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS                         NAMES
a5139cf480ab        nginx:1                          "nginx -g 'daemon of   3 hours ago         Up 3 hours          443/tcp, 0.0.0.0:80->80/tcp   ecs-awseb-multiDockerTest-env-upq3tpm3fh-2-nginx-proxy-b0d1f6d898ddea93fa01
c4acd0810b38        php:5-fpm                        "php-fpm"              3 hours ago         Up 3 hours          9000/tcp                      ecs-awseb-multiDockerTest-env-upq3tpm3fh-2-php-app-e684be8ba8d7d5bbd001
b9ab2fda98d4        amazon/amazon-ecs-agent:latest   "/agent"               3 hours ago         Up 3 hours          127.0.0.1:51678->51678/tcp    ecs-agent

  上記がそれぞれどういう役割なのか?という構成に関しては、以前一緒のチームでソリューションアーキテクトとして働いてて、 今はUSでサービスチームの一員として活躍している安川さん(@thekentiest)の、 昨年のAWS Black Belt Tech Webinarの記事が分かりやすくてよく紹介させていただいているのですが、 ↓のような感じになっています。 [slideshare id=q9S6Bp0agJG2kY?startSlide=13&w=425&h=355&fb=0&mw=0&mh=0&style=border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&sc=no]

  デプロイされたものは/var/appの中に格納されています。

$ ls /var/app/current
Dockerrun.aws.json  cron.yaml  php-app  proxy
$ cd php-app/

index.phpを見てみると

<body id="sample">
  <div class="textColumn">
    <h1>Congratulations!</h1>
    <p>Your Docker Container is now running in Elastic Beanstalk on your own dedicated environment in the AWS Cloud.</p>
  </div>

  <div class="linksColumn">
    <h2>Video Tutorials</h2>
    <ul>

のようになっていて、悪ノリして↓こんなことすると、   hogehogeうぇーい的な感じになりました。   で、Multi-containerって事でECSにはTaskというメモリとかCPUの条件を決めたりする概念というか、 括りがあるのですが、言葉で説明するとアレですが絵にすると↓こんな感じです。 (Multicontainer Docker Environments に出てくる挿絵です) こちらはまた別の機会に深堀りしていきたいと思います。     ■ zipファイルを使ったデプロイ   上記のように直接SSHでサーバーに入ってファイルを書き直すというのは現実的ではないと思われますので、 ローカルで編集したものをElasticBeanstalkのマネージメントコンソールからデプロイしてみたいと思います。   現状動いているサンプルをダウンロードしてきたかったのですが、Elastic Beanstalkのコンソール上の リンクを押してもリンクでドキュメントのページに飛ばされるだけで、2015年4月18日現在、Multi-containerの サンプルのzipファイルが見当たらなかったので、、   実行環境上でtar.gzにして、、

$ sudo tar cvf current.tar.gz current/*
current/Dockerrun.aws.json
current/cron.yaml
current/php-app/
current/php-app/static.html
current/php-app/index.php
current/php-app/scheduled.php
current/proxy/
current/proxy/conf.d/
current/proxy/conf.d/default.conf

  コレをS3に上げてから、ローカルにダウンロードしてもってきます。(なんとも古典的なw)

$ aws s3 cp current.tar.gz s3://justabucket2015
upload: ./current.tar.gz to s3://justabucket2015/current.tar.gz

  ローカルでindex.phpファイルを一部書き換えて、

113     <li><a href="http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/index.html?concepts.html">AWS Elastic Beanstalk concepts</a></li>
114     <li>hogehoge</li>
115     <li>local_hogehoge</li>
116     </ul>

  zipファイルに圧縮して、

$ zip -r deploy.zip .
  adding: cron.yaml (deflated 21%)
  adding: Dockerrun.aws.json (deflated 75%)
  adding: php-app/ (stored 0%)
  adding: php-app/index.php (deflated 60%)
  adding: php-app/scheduled.php (deflated 31%)
  adding: php-app/static.html (deflated 2%)
  adding: proxy/ (stored 0%)
  adding: proxy/conf.d/ (stored 0%)
  adding: proxy/conf.d/default.conf (deflated 45%)

  マネージメントコンソールからデプロイすれば、   デプロイ出来ました。   但し、コレだと毎回zipファイル作らなければならないので大変面倒です…。     ■ ebコマンドを使ったデプロイ   Elastic Beanstalkにはebというコマンドラインツールがあって、コレを使うと便利にデプロイ出来ます。 今までは2.6系が主流でgit aws.push〜とかっていうご案内をよくしていたのですが、 最近では↓に書かれているように3系に移行してきていて、少しコマンドの体系が変わってきてきますのでご注意ください http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-reference-eb.html   3系のebコマンドのインストールはpipで行います。 普通にやると $ sudo pip install awsebcli だけでよく、 サクッと↓のようにいくはずですが、

$ eb --version
EB CLI 3.2.2 (Python 2.7.8)

私の場合はpipが古かったのかupgradeとかしても上手くいかず、↓この辺のフォーラムにお世話になりました。 https://forums.aws.amazon.com/thread.jspa?threadID=164348&tstart=25 https://forums.aws.amazon.com/thread.jspa?messageID=580745   で、eb initを叩くと、既に構築されたElastic BeanstalkのApplicationが出てきて、 ↓のように選択できます。

$ eb init

Select an application to use
1) mulcon
2) multi docker test
3) docker
4) [ Create new Application ]
(default is 4): 2

  続いて、先ほどのdeploy.zipは削除してgit init .しておきます。 そうすると、.elasticbeanstalkと.gitと.gitignoreが出来ているかなと思います。

$ ls -la
total 24
drwxr-xr-x    9 eshinoha  1896053708    306  4 18 12:00 .
drwx------+ 585 eshinoha  1896053708  19890  4 18 11:17 ..
drwxr-xr-x    4 eshinoha  1896053708    136  4 18 12:00 .elasticbeanstalk
drwxr-xr-x   10 eshinoha  1896053708    340  4 18 11:21 .git
-rw-r--r--    1 eshinoha  1896053708    108  4 18 12:00 .gitignore
-rw-r--r--@   1 eshinoha  1896053708   1457  3 23 20:22 Dockerrun.aws.json
-rw-r--r--@   1 eshinoha  1896053708     90  3 23 20:22 cron.yaml
drwxr-xr-x@   5 eshinoha  1896053708    170  4 18 10:37 php-app
drwxr-xr-x@   3 eshinoha  1896053708    102  3 23 20:22 proxy

  今回はまだEnvironmentを1つしか作っていませんが、実際に開発を進めていく中で、 Dev, Staging, Prodのような形になっていくのかなと思います。 その場合は↓のようにeb useというのを使ってどのEnvironment〜といったところを指定出来るようになります。

git checkout master
eb use prod
git checkout develop
eb use dev

  では、さっそくローカルのgitに1行変更を入れてコミットしてタグを打ってみます。 ebコマンドを使ってデプロイする場合は、gitのタグをVersionのラベルに出来ます。 (上でzipをアップロードした時に付けたVersion labelの事です)

$ git add .
$ git commit -m "first commit"
$ vim php-app/index.php
114     <li>hogehoge</li>
115     <li>local_hogehoge</li>
116     <li>git_local_hogehoge</li>
117     </ul>
$ git add .
$ git commit -m "added git_local_hogehoge"
$ git tag -a v0.1 -m 'my version 0.1'

  eb deploy コマンドでデプロイ用のアーカイブを作ってくれて、S3にアップロードしてくれて、 古いECSのTaskから新しいTaskへ〜という様子が見て取れます。 で、新しいVersionがデプロイされて、Environmentの更新が成功しましたよ、と。 (ECS用語とElastic Beanstalk用語をちゃんと抑えておかないと頭が混乱しそうなのでご注意ください…^^;)

$ eb deploy
Creating application version archive "v0_1".
Uploading multi docker test/v0_1.zip to S3. This may take a while.
Upload Complete.
INFO: Environment update is starting.
INFO: Deploying new version to instance(s).
INFO: Stopping ECS task arn:aws:ecs:ap-northeast-1:832164682569:task/4e859699-b267-4664-9b4d-9553dda03702.
INFO: ECS task: arn:aws:ecs:ap-northeast-1:832164682569:task/4e859699-b267-4664-9b4d-9553dda03702 is DEAD.
INFO: Starting new ECS task with awseb-multiDockerTest-env-upq3tpm3fh.
INFO: ECS task: arn:aws:ecs:ap-northeast-1:832164682569:task/3aac1d84-03b2-402c-a170-bd57772f7162 is RUNNING.
INFO: New application version was deployed to running EC2 instances.
INFO: Environment update completed successfully.

  でもって、S3のバケットは↓のようにアーカイブされたものが入っていました。   もちろんブラウザからも↓のように確認できました。     ■ 複数インスタンスに対するebのdeploy   実際の業務では一日に何回もデプロイがされる可能性があって、その度にサービスをダウン わけにはいかないものだと思います。   Elastic Beanstalkには"Batch"という名前で複数のEC2インスタンスやContainerにデプロイする仕掛けがあります。 詳細は↓に記載されておりますが、こちらを使ってやっていきたいと思います。 http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.rolling-version-deploy.html   まずは、AutoScalingの設定で複数のインスタンスになるようにします。   PHPに多少負荷がかかるように↓のようなコードを入れて、

118 <?
119   date_default_timezone_set('UTC');
120   echo date('l jS of F Y h:i:s A');
121   for ($i = 1; $i <= 10000000; $i++) {
122     if ($i % 1000000 === 1) {
123       echo $i;
124     }
125   }
126 ?>

  複数のターミナルの窓から↓こんな感じでトラフィックを流し続けるようにします。

$ for ((i=0;i<10000;i++)) ; do  curl http://multidockertest-env.elasticbeanstalk.com/ 2>&1 >/dev/null    ; done ;

  分かりやすいようにBatchのタイプをFixedにして、必ず1台だけという設定にします。   上記のような状態で、4つのインスタンスが稼働している状態で、 eb deployしている間のELBの様子は以下のようになります。   1. 最初は4つ全てのインスタンスがELB配下にいます   2. 1つのインスタンスがELBから切り離されて3インスタンスになります。その間に裏でデプロイが行われます   3. 上記2. で切り離されたインスタンスが戻ってきます(Out Of Serviceなのはヘルスチェック中の為)   4. 再び別のインスタンスが切り離され、デプロイのプロセスに入ります   5. デプロイが終わったインスタンスは再びヘルスチェック後にELB配下に入ります   上記のような動きを繰り返して、1つ1つデプロイをしていくことで、サービスへの影響を最小限にします。      ■ その他   ・eb deployを使用した場合に.gitignoreの扱いについて   例えばeb deployでデプロイを行った時に .elasticbeanstalk の設定は持っていく必要が無いわけですが、 .gitignoreには以下の記載があります。

$ cat .gitignore

# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml

  eb sshを使用して1つのインスタンスSSHでログイン後、

$ eb ssh

Select an instance to ssh into
1) i-79ffa98a
2) i-64489991
3) i-cc489939
4) i-8ff4a27c
(default is 1): 1

  叩いてみたら存在しなかったので、ebコマンドはちゃんと.gitignoreをチェックしてそうです。

[ec2-user@ip-172-31-11-114 ~]$ cd /var/app/current
[ec2-user@ip-172-31-11-114 current]$ ls -la
合計 28
drwxr-xr-x 4 root root 4096  4月 18 07:55 .
drwxr-xr-x 3 root root 4096  4月 18 07:55 ..
-rw-r--r-- 1 root root  108  4月 18 07:50 .gitignore
-rw-r--r-- 1 root root 1459  4月 18 07:50 Dockerrun.aws.json
-rw-r--r-- 1 root root   90  4月 18 07:50 cron.yaml
drwxr-xr-x 2 root root 4096  4月 18 07:50 php-app
drwxr-xr-x 3 root root 4096  4月 18 07:50 proxy

    ・hookを使ったデプロイのカスタマイズについて   ↓は2013年の安川さんのWebinar資料ですが、ココにデプロイ前後のhookに関する記載があります。 [slideshare id=FR6BXJW9heoBv4?startSlide=58&w=425&h=355&fb=0&mw=0&mh=0&style=border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;&sc=no]

  通常、Elastic Beanstalkでプロビジョニングされる環境の構成変更は .ebextensions というディレクトリの中に yamlファイルを配置してそこに記載しますが、デプロイの過程の中で、"ココ!"っていうタイミングで処理をしたい場合が 出てくることもあるかと思います。   例えばデプロイ前であればデフォルトでは↓のようにディレクトリ内をalphabeticalな順番で処理がされていきますが、 01unzip.sh で解凍された後に、、的な事をやろうとした場合に関して、 ディレクトリ内に01x_afterunzip.shみたいな名前で配置してやれば良い、と。

$ pwd
/opt/elasticbeanstalk/hooks/appdeploy/pre
[ec2-user@ip-172-31-11-114 pre]$ ls -l
合計 20
-rwxr-xr-x 1 root root  862  4月  8 17:29 00enable-eb-ecs.sh
-rwxr-xr-x 1 root root  842  3月 10 23:44 00stop-task.sh
-rwxr-xr-x 1 root root 2095  3月 13 00:52 01unzip.sh
-rwxr-xr-x 1 root root 2992  4月  8 17:29 02update-credentials.sh
-rwxr-xr-x 1 root root  782  3月 11 18:01 03start-task.sh

  で、コレを1インスタンスずつ配置していたら日が暮れてしまうので、 .ebextensionsで〜的なやり方で行うと、よりBlack Beltな感じかなと思います。   こちらは、これまた安川さんがAWSのフォーラムに、Railsのアセットコンパイルを 事前に行ってS3に置いておいて、それをコピってくるだけで、デプロイの時間を短縮させましょう的な例を (英語で)ガッツリ記載していますので↓を一読されるとよろしいかと思います。 Customizing ElasticBeanstalk deployment hooks(https://forums.aws.amazon.com/thread.jspa?threadID=137136)   .ebextensionsに関しては↓に記載されているように、細かく定義が出来て便利なのですが、 http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html あまりやり過ぎると後から辛い目に遭ったりする可能性もあるので、ご利用は計画的に。 (AWSにはOpsWorksというChefベースのプロビジョニング/デプロイサービスがございます)