ElasticBeanstalk x Docker のベストプラクティスっぽいものまとめ

@j5ik2o と ElasticBenstalk(以下 eb)の話を飲み屋でしてたら、案外共有したほうが良さそうなことがあったのでそのために共有です。

# 前提知識のために読んできて欲しいドキュメント

- http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker_console.html
- http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker_image.html

# Source Bundle の作り方

> Dockerfile と Dockerrun.aws.json ファイルの両方を提供する場合は、イメージに関する情報を Dockerrun.aws.json ファイルに含めないでください。Elastic Beanstalk は Dockerfile で指定されているイメージを使用し、Dockerrun.aws.json ファイルのイメージは無視します。
> http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker_image.html

ここを読み逃すと痛い目を見る。Source Bundle はただの .zip アーカイブなのだが、それを作る際に `eb deploy` などのコマンドを使うとまず間違いなく痛い目を見るので、基本的に `eb` コマンドは使ってはいけない。

Source Bundle とはすなわちデプロイ時に指定する Application Version の元となるソースセットのアーカイブだが、これに `Dockerfile` が入っていると、 Source Bundle を各インスタンスに配布後、eb はインスタンス内で docker build を実行してしまう。例えば Rails Assets Precompile 然り、事前に実行してあとはサヨナラで済ませたいことまで含まれてしまう。なので Docker Image は基本的に docker hub ないし quay.io 上へ配置した Docker Image を Pull するだけに留めたい。

なので、 Source Bundle を作る際は、基本的に `Dockerrun.aws.json` と `.ebextensions` のみを含む .zip を作るのが正しい。尚、Dockerrun.aws.json の記法については、 [このページ](http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker_image.html)の下部に書かれたリファレンスを参考にしてほしい。コレ以上の記述はないし、簡単だと思う。

## aws.json の Authentication に書かれた S3 Object へのアクセス

`Authentication` には `.dockercfg` が配置されている S3 の Bucket / Key 名を指定するとかかれているが、当然ながらアクセスについては制限を伴う。これは eb インスタンスに付与される IAM に S3 へのアクセス権を付与することで解決するほかない。環境変数などに AWS_ACCESS_KEY ... とか指定しても動かないので、素直に IAM に権限を付与すること。

# 実際のデプロイ時に使うコマンド

`eb` コマンドを使うなと言った手前、どうやって Environment に Application Version を配置するかについて記述しておく。

詳しい解説はしないので、各位やっていく気持ちでググって調べて欲しい。そんなに難しくもないから。

- `zip bundle.zip Dockerrun.aws.json` : Source Bundle の作成。 ebext がある場合はそちらも含める
- `s3cmd put bundle.zip s3://apps/bundles/${COMMIT_SHA1}.zip` : Source Bundle を S3 へアップロード
- `aws elasticbeanstalk create-application-version --application-name ${APP_NAME} --version-label ${COMMIT_SHA1} --source-bundle S3Bucket="apps",S3Key="bundles/${COMMIT_SHA1}.zip"` : eb 上に新しい Application Version を作成
- `aws elasticbeanstalk update-environment --environment-id ${ENVIRONMENT_ID} --version-label ${COMMIT_SHA1}` : ↑で作成した Application Version を Environment に配置

この際、 `eb deploy` なら最後に Events のポーリングが始まってデプロイ状況を追跡できるが、この例ではその限りではないので、もしそういったものが必要であればご自分で polling して頂くしかないのであしからず。投げっぱなしデプロイというやつである。

# Docker Image 溜まりすぎ問題

上記のような仕組みでどんどんデプロイできる状況になると、次は docker deploy を何度もやりすぎて Docker Image がディスクを圧迫、最終的に Disk full でデプロイ出来ないと言った事態が発生しうる。

pull 時に disk full を起こしてくれれば単なる deploy fail で済むのだが、 pull には成功したが container が立ち上がらないといった場合はインスタンス上にアプリケーションがいなくなってしまう。もちろん、それを検知して Auto Scalling で別インスタンスを立ち上げて回避という手はとれるのだが、たかがデプロイにそこまで派手に行う必要はない。

ということで cron などを利用して一定時間毎に `docker rmi $(docker images -q -a)` などを実行すると良い。これは `.ebextensions` で実現可能なので、[ここらへん](http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/customize-containers-cw.html)を参考に作って配置すると良い。なお、僕はまだここまで触っていない。出来上がったら GitHub かなんかで共有するのでまってて。

# Logging Mapping について

`Dockerrun.aws.json` では `Logging` というセクションがあり、ここに **コンテナ内** のディレクトリ名を書いておくことで、eb の管理下にある Log ディレクトリにログを履かせることができるようになる上、 eb 管理コンソールから Log の閲覧も可能になるため、できれば指定しておきたい。

ただし、例えば Rails アプリケーションで `app/log` ディレクトリを指定したとして、アプリケーション側で `app/log/sidekiq` などのディレクトリが存在することを前提としていると、マッピングされた先の eb インスタンス側にはそのディレクトリが存在しないため、書き込みエラーが起こってデプロイ不可能などの状態が発生してしまう。

留意の上、指定しよう。

# まとめ

eb 便利なんだけどハマりどころ多すぎるからマジ気をつけてな。