はじめに
ソリューションアーキテクトの安川 (@thekentiest)です。AWS ElasticBeanstalkがDockerに対応したという発表がされて以来、実際に利用してくれるお客様や、ブログを書いてくれる方がたくさんいて、嬉しい限りです。
最近ではローンチ当初に比べてDeployの仕組みも改善され、Deploy時にはStagingコンテナが立ち上がり、その立ち上げが完了した後に旧コンテナとの切り替えが行われるようになり、よりダウンタイムが短くなったので、使い勝手も良くなってきたかと思っています。
ところで皆さん、Dockerを使ったDevOpsを行う際、コンテナイメージを置くレポジトリはどうされていますでしょうか?ベースイメージだけを公開レポジトリ等から取得して、毎回コンテナをBuildする場合にはそれほど悩まないかもしれませんが、構築済みのコンテナイメージをPullしてDeployしたい場合には、どこかのレポジトリにイメージを置くことになるかと思います。公開可能なコンテナについては公開レポジトリでも良いかもしれませんが、そうでなければプライベートレポジトリが必要です。また、Deploy時間の短縮のためにネットワーク的に近い場所にレポジトリが欲しいという場合にもやはりプライベートレポジトリを用意することになるかと思います。
プライベートレポジトリ自体は簡単に構築可能ですが、開発環境と本番環境どちらからもセキュアにアクセスが出来て、大規模なクラスタへのDeployにも耐えられるキャパシティと高い可用性を持つように運用するのはなかなか手間がかかります。
そこで今回は、ElasticBeanstalkの各インスタンスに、Amazon S3をバックエンドにしたDockerプライベートレポジトリを用意して、SPOFのない、お手軽・低コスト・高信頼なプライベートレポジトリ経由のDeployを行う方法を解説します。
Dockerのプライベートレポジトリ
Dockerのプライベートレポジトリを立てるのは簡単です。Docker registryというgunicornのアプリケーションを動作させればプライベートレポジトリとして動作します。Docker registryはオープンソースで公開されており、バックエンドのストレージにはAmazon S3のバケットを指定する機能があるので、Docker Registryを適切な設定で立ち上げれば、いつでも高い堅牢性を持つプライベートレポジトリを用意することが出来ます。
しかもDocker registryをセットアップ済みのコンテナイメージが公開されているので、それを立ち上げるだけであれば、Dockerの動く環境で下記のコマンドを叩くだけで指定したS3バケットをバックエンドとしたプライベートレポジトリが立ち上がります。
環境変数で指定されたS3バケットをストレージとしたDocker registryを立ち上げて、127.0.0.1:49000でアクセス出来るようにする例:
docker run \
--name registry \
-e SETTINGS_FLAVOR=prod \
-e DOCKER_REGISTRY_CONFIG=/docker-registry/config/config_sample.yml \
-e AWS_BUCKET=$DOCKER_REPOSITORY_BUCKET \
-e STORAGE_PATH=/ \
-e AWS_KEY=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET=$AWS_SECRET_KEY \
-e SEARCH_BACKEND=sqlalchemy \
-p 49000:5000 -d \
registry docker-registry
(20140604修正: Docker 0.6.9以前の場合はコンフィグファイルのパスを次ように指定する必要あり: "-e DOCKER_REGISTRY_CONFIG=/docker-registry/config/config_s3.yml"
)
インスタンスローカルなDocker Registry
さて、立ち上げるのが簡単だということはわかりましたが、Docker Registryを載せたインスタンスを、必要なアクセス制限を行いつつ、必要なキャパシティと可用性を維持しながら運用することを考えると、運用コストもインスタンスコストも増えてしまいます。特にアクセス制限について考えると、S3バケットはAWSのクレデンシャルがなければアクセス出来ないように設定可能ですが、Docker registryの認証はベーシック認証のため、SSLが必要なのはもちろん、ユーザ名/パスワードの管理運用が発生してしまいます。
そこで今回ご紹介する方法は、S3をバックエンドとするDocker Registryを、必要な時だけインスタンス上にローカルサービスとして立てるというものです。上述の通り、Docker RegistryはDocker環境があればすぐ立ち上げられますし、バックエンドにS3バケットを使っていればステートレスであるため、必要な時だけ立ち上げる形でも問題ありません。また、ローカルでのみ利用するので、認証周りに神経質にならなくても済みます。
さらに、Docker Registry自体はS3にアクセスするためのクレデンシャルさえ渡せばどこでも実行可能なので、
- 開発環境でコンテナイメージをBuild
- イメージを開発マシン上に立てたローカルのDocker Registry経由でpush (これでS3バケット上に最新のイメージが保存される)
- ElasticBeanstalk環境にDeployを実施
- ElasticBeanstalk環境の各インスタンスは自身のローカルのDocker Registry経由でイメージをPullしてDeploy
という流れが実現できます。ElasticBeanstalk環境のインスタンスではBuild済みのコンテナイメージをPullするだけになるので、イメージをBuildするための作業をそれぞれで行う必要がなく、Deploy時のリソース消費や所要時間の削減が可能です。
実現方法
上記の仕組みを実装するには、ElasticBeanstalkがアプリケーションをDeployする前後に実行するフックに必要な処理を記述したスクリプトを登録する方法が考えられます。つまり、Docker Registryを起動するスクリプトをアプリケーションDeploy前のフックに、停止するスクリプトをDeploy後のフックに登録すれば、目的の動作を実現できるというわけです。
具体的には、
- /opt/elasticbeanstalk/hooks/appdeploy/preにDocker Registryを起動するスクリプト
- /opt/elasticbeanstalk/hooks/appdeploy/postにDocker Registryを停止するスクリプト
を配置することで、Deploy時にだけローカルのDocker Registryを起動するという目的を実現できます。
次に、どうやってElasticBeanstalkのインスタンスに上記のようなスクリプトを配置すればよいでしょうか?SSHログインして置いたとしても、新規作成されるインスタンスには反映されません。カスタムAMIを用いることも出来ますが、その後AMIの更新の面倒を見なければいけなくなってしまいます。
ElasticBeanstalkにはこういう時に便利な仕組みとして、Deployするプロジェクト内にコンフィグファイルを用意することで、環境をカスタマイズする仕組みがあります。具体的には、Deployするプロジェクト内に.ebextensionsというディレクトリを用意して、*.configという名称のファイルを配置してDeployすれば、その内容を参照して環境をカスタマイズしてくれます。
上記の仕組みを使って上記のようにフックを登録する例を以下に示します。 (あえてスクリプトを外部URLからダウンロードする例と、スクリプトを直接記述する例を混ぜてあります)
files:
"/opt/elasticbeanstalk/hooks/appdeploy/pre/02start_docker_registry.sh":
mode: "000755"
owner: root
group: root
source: <スクリプトが配置されたURL>
"/opt/elasticbeanstalk/hooks/appdeploy/post/00stop_docker_registry.sh":
mode: "000755"
owner: root
group: root
content: |
#!/bin/sh
docker stop registry
docker rm registry
上記の内容を.ebextensions/setup-docker-registry.configなどのファイル名で保存して、DockerfileやDockerrun.aws.jsonと共にDeployすれば、期待通りDeploy時にのみローカルにプライベートレポジトリのフロントエンドが立ち上げる環境が構築できます。
ディレクトリ構成例は以下の通りです。
./Dockerfile
./.ebextensions/setup-docker-registry.config
後はDockerfileやDockerrun.aws.jsonでイメージを指定する際に、127.0.0.1:49000/imageなどのように指定すれば、DockerはローカルのDocker Registryを経由して、指定したS3バケット上のコンテナイメージを取得するという動作になります。
下記はそのようなDockerfileの例です。
FROM 127.0.0.1:49000/express
EXPOSE 3000
ENTRYPOINT cd /myapp && npm start
Docker Registryイメージの取得
上記の方法では、スケールアウト等の理由で新しいインスタンスが立ち上がる際、毎回Docker Registryのイメージを取得することになりますが、特に指定しなければ公開レポジトリからの取得となるため、インスタンスタイプや配置するリージョンにもよりますが、結構時間がかかります。
この問題はS3バケットにDocker RegistryのイメージをExportしたアーカイブを置いておいて、ダウンロードしてImportとすることで解消できます。
Docker RegistryのExportとS3へのアップロード
Docker RegistryのExportは下記のようにして実行できます。(コンテナ名がregistryと仮定)
# sudo docker export registry | gzip - > registry-image.tgz
次に下記のようにすれば、出来上がったアーカイブをプライベートレポジトリとして利用するS3バケットに保存できます。
$ aws s3 cp registry-image.tgz s3://<プライベートレポジトリとして利用するバケット>/registry-image.tgz
新規インスタンスの起動時にはそれをダウンロードしてImport
$ aws s3 cp s3://<プライベートレポジトリとして利用するバケット>/registry.tgz /tmp/registry.tgz
# cat /tmp/registry.tgz | docker import - registry:latest
上記の方法であれば、公開レポジトリからのPullに比べて大分所要時間を短縮できます。 実際に試してみたところ、t1.microでも1, 2分で完了しました。新インスタンスの起動時にのみ発生するオーバーヘッドなので、現実的には大きな問題にならない範囲かと思います。上記のような動作を実行するスクリプトを用意して、下記のように環境カスタマイズのファイルに追加しておけば、新規に起動したインスタンスもすぐに準備を整えてクラスタに参加することが出来ます。スクリプトでは、既にregistryという名称でイメージが登録されているかどうかをチェックすることで、2回目以降の無駄な作業の発生を防げます。
commands:
install_registry_image:
command: curl -L <registryイメージがなかったらダウンロードしてImportするスクリプト> | bash
ignoreErrors: true
(このコマンドが失敗しても、公開レポジトリからイメージを取得することで動作可能なのでignoreErrorsをtrueに指定しています)
一連の設定を実践
上記の流れを実現するためのShellスクリプトのサンプルをご参考までに下記に置きました。
また、上記のスクリプトを使いながら、一連の流れを組み込むためのカスタムコンフィグファイルが下記は下記の通りです。実際に試してみたい方はまずこちらを使っていただくのが早道かもしれません。
commands:
install_registry_image:
command: wget -q -O - https://gist.githubusercontent.com/kntyskw/5ab96ac8e6f3712c67ad/raw | bash
ignoreErrors: true
files:
"/opt/elasticbeanstalk/hooks/appdeploy/pre/02start_docker_registry.sh":
mode: "000755"
owner: root
group: root
source: https://gist.githubusercontent.com/kntyskw/c9ec984b18249272f7e4/raw
"/opt/elasticbeanstalk/hooks/appdeploy/post/00stop_docker_registry.sh":
mode: "000755"
owner: root
group: root
content: |
#!/bin/sh
docker stop registry
docker rm registry
なお、上記の設定はElasticBeanstalkの環境設定で下記の環境変数が正しく設定されていて、バケットの作成等が済んでいることが前提になっています。
- DOCKER_REPOSITORY_BUCKET: 利用するS3バケット名
- AWS_ACCESS_KEY_ID: AWSアクセスキーID
- AWS_SECRET_KEY: AWSシークレットキー
また、repositoryイメージのアーカイブが指定されたS3バケットの直下に置かれていないとダウンロードが失敗して公開レポジトリからイメージ取得となり、時間がかかるかと思いますのでご注意ください。
おわりに
いかがでしたでしょうか?この方法なら一度導入すれば低コスト・高信頼なプライベートレポジトリとしてS3を利用できて、しかも同一リージョン内にレポジトリを置けるので、Deployの高速化にも繋がると思います。ElasticBeanstalk + Dockerを活用してImmutable Infrastructureを実現する一助になれば幸いです。
フィードバックやご不明点などあればお知らせください!