webアプリケーションのデプロイを、Dockerを用いてダウンタイムなく行う手順をサーバーのプロビジョニング、アプリの用意、運用方法に分けまとめた。
はじめに
ECSやGKEとかが、Dockerのクラスタ管理ではデファクトっぽい位置づけになってる気がするが、そういうサービスが使えない場合でかつ、公式ツールを使ってデプロイする場合の方法を調べまとめた。
成果物
下記にまとめています。
記事を参照しながら見てください。
https://github.com/togana/sample-docker-deploy
構成
デプロイのイメージ図
使用するツール
Docker version 1.13.1
docker-machine version 0.9.0
docker-compose version 1.11.1
VirtualBox version 5.1.10
用意する仮想サーバー
サービス用サーバー: 1台
ビルドサーバー: 1台
レジストリサーバー: 1台
サーバのプロビジョニング
docker-hostの構築
レジストリサーバー
$ docker-machine create -d virtualbox container-registry
サービス用サーバー
$ docker-machine create -d virtualbox \
--engine-insecure-registry $(docker-machine ip container-registry):5000 \
service
ビルドサーバー
$ docker-machine create -d virtualbox \
--engine-insecure-registry $(docker-machine ip container-registry):5000 \
build
オプション説明
--engine-insecure-registry : 作成するエンジンが、指定した非安全なレジストリと通信できるようにします。
非安全なレジストリとはこちらを参照するといいと思います。
ざっくりいうとTLS通信できればOK。CA証明書を用意してあげればこの設定は必要ないので用意できる方はオプションではなくそちらを使用したほうがいいです。
レジストリサーバーの構築
レジストリに関する作業ディレクトリは registry
で行うことにします。
$ mkdir registry
$ cd registry
dockerレジストリはdockerで用意できます。下記の docker-compose.yml
を用意して実行します。
ボリュームのマウントをしているので、違う場所に作りたい人は適宜置き換えてください。
version: '3'
services:
registry:
image: registry:2.6.0
ports:
- 5000:5000
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /registry
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- /user/local/data/registry:/registry
web:
image: hyper/docker-registry-web:v0.1.2
ports:
- 8080:8080
environment:
REGISTRY_HOST: registry
REGISTRY_URL: 'http://registry:5000/v2'
REGISTRY_NAME: 'registry:5000'
REGISTRY_READONLY: 'false'
# 環境変数をレジストリサーバーに向ける
$ eval $(docker-machine env container-registry)
# registryと管理ツールのコンテナ起動
$ docker-compose up -d
サービス用のサーバーを構築
swarm modeで実行するにはこのへんでいろいろ試したので見ると参考になると思います。
今回は動かすだけなので下記のコマンドを実行します。
# 環境変数をサービスサーバーに向ける
$ eval $(docker-machine env service)
# swarm mode 初期化
$ docker swarm init --advertise-addr $(docker-machine ip service)
アプリの用意
提供するサービスのDockerfileを用意する
サービスに関する作業ディレクトリは nginx-app
で行うことにします。
$ mkdir nginx-app
$ cd nginx-app
今回は、ssiをonにしたnginxコンフィグを用意して <!--#echo var="hostname" -->
とすることで、hostname=コンテナidなのでコンテナidを表示するだけのwebページを作成します。こうすることでコンテナが切り替わったか確認しやすいコンテナが作成できます。
FROM nginx:1.11.9-alpine
COPY default.conf /etc/nginx/conf.d/
COPY index.html /usr/share/nginx/html/
server {
listen 80;
server_name localhost;
ssi on;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
<!DOCTYPE html>
<html>
<body>
<h1>HostName: <!--#echo var="hostname" --></h1>
</body>
</html>
動作確認
下記のようにcurlを実行してhtmlが帰ってきていれば問題なくビルド実行できてる。
$ eval $(docker-machine env build)
$ docker build -t nginx-app .
$ docker run -dp 80:80 nginx-app
$ curl $(docker-machine ip build)
<!DOCTYPE html>
<html>
<body>
<h1>HostName: 18a4af130a5a</h1>
</body>
</html>
$ docker ps -aq | xargs docker stop | xargs docker rm
18a4af130a5a
運用方法
ビルド
ビルドの実行
先程作ったDockerfileとdefault.confとindex.htmlのあるディレクトリで下記のコマンドを実行します。
今回はタグを(registryのip:ポート番号)/nginx-app:v1.0として作成しています。
nginx-app:v1.0(イメージ名:バージョン)は任意の値を入れることができます。
# 環境変数をビルドサーバーに向ける
$ eval $(docker-machine env build)
# ビルド実行
$ docker build -t $(docker-machine ip container-registry):5000/nginx-app:v1.0 .
# レジストリサーバーにプッシュ
$ docker push $(docker-machine ip container-registry):5000/nginx-app:v1.0
ビルドコマンドをまとめる
下記の build.sh
を sh
ディレクトリに用意する。
$ mkdir sh
$ cd sh
#!/bin/sh
set -eu
tag=${1:-"latest"}
cd $(cd $(dirname $0);pwd)/../nginx-app
docker-machine env build
eval $(docker-machine env build)
docker build -t $(docker-machine ip container-registry):5000/nginx-app:${tag} .
docker push $(docker-machine ip container-registry):5000/nginx-app:${tag}
もちろん実行権限を付与する
$ chmod +x ./build.sh
ビルドが下記コマンドでできるようになる。
# ./build.sh tag
$ ./build.sh v1.0
デプロイ
デプロイの実行
イメージをレジストリサーバーにプッシュできたので、サービスサーバーで実際に動かします。
まず下記のような docker-compose.yml
を用意します。
192.168.99.100
は $(docker-machine ip container-registry)
を実行して表示されてたipに適宜読み替えてください。
version: '3'
services:
web:
image: 192.168.99.100:5000/nginx-app:v1.0
ports:
- '80:80'
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
オプション説明
version : Compose ファイル形式を指定
servises : 各コンテナをサービスとして定義
web : 今回実行するコンテナ名(任意の名前に変更可能)
image : コンテナ実行時の元となるイメージを指定
ports : ホスト側:コンテナ側 の書式でポートを割り当てる
deploy : サービスの展開と実行に関連する構成を指定
version: 3でのみ指定できる
Swarmにデプロイするときのみ有効
docker-compose upのときなどは無視される
replicas : 実行する必要があるコンテナの数を指定
update_config : サービスの更新方法を設定
parallelism : 一度に更新するコンテナの数を指定
delay : 更新するまでの待機時間を指定
restart_policy : コンテナが終了したときに再起動するか設定
condition : 終了ステータスによって再起動を検討
その他オプション
modeオプションをglobalにすることですべてのswarmワーカーに一つづつコンテナを配置することができます
docker stack deployコマンドを使用するときはbuildは使用できない
placementオプションを指定することで配置制約を決めることもできます
もっと知りたければ公式リファレンスを見てください
実際にデプロイします。
# 環境変数をビルドサーバーに向ける
$ eval $(docker-machine env service)
# デプロイ sample-deployはstack名になるので自由に決めれます
$ docker stack deploy --compose-file docker-compose.yml sample-deploy
デプロイコマンドをまとめる
下記の deploy.sh
を sh
ディレクトリに用意する。
毎回docker-compose.ymlを再生成するので .gitignore
に追加してもいいかもしれない。
#!/bin/sh
set -eu
tag=${1:-"latest"}
docker-machine env service
eval $(docker-machine env service)
cat << EOS > docker-compose.yml
version: '3'
services:
web:
image: $(docker-machine ip container-registry):5000/nginx-app:${tag}
ports:
- '80:80'
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
EOS
docker stack deploy --compose-file docker-compose.yml service
もちろん実行権限を付与する
$ chmod +x ./deploy.sh
デプロイが下記コマンドでできるようになる。
# ./deploy.sh tag
$ ./deploy.sh v1.0
動作確認
下記コマンドを複数回実行するとコンテナIDが表示されるのがわかると思います
$ curl http://$(docker-machine ip service)
アプリの更新
v1.1にアップデート
先程作ったindex.htmlに変更を加えます。
<!DOCTYPE html>
<html>
<body>
<h1>HostName: <!--#echo var="hostname" --></h1>
+ <h1>version: v1.1</h1>
</body>
</html>
タグを指定してビルドします。
$ ./build.sh v1.1
v1.1をデプロイ
タグを指定してデプロイを実行します。
$ ./deploy.sh v1.1
デプロイコマンドが終了してもすぐデプロイが終わってるわけではないところに注意が必要です。
実際には裏でローリングアップデートが始まります。
アップデート時の挙動図解
アップデート前
デプロイコマンド実行
一つ目のコンテナがstopされ
新しいイメージでコンテナ実行
10秒待ったのちに2つ目のコンテナがstopされ
新しいイメージでコンテナ実行
こんな感じでローリングアップデートが行われる
まとめ
ビルド時にタグを付けてレジストリにプッシュ、デプロイ時に使用するイメージのタグを指定することで新しいイメージで動くコンテナをダウンタイムなくデプロイすることができました。
運用時は、タグの指定をコマンドでできるようにすることで、ビルドのしやすさ、デプロイ時にタグを切り替えることで気軽にロールバック等もできるようになりました。
最後に
僕の調べた範囲で書いています。間違ってることなどあれば教えてください!
また、導入目的や目標が定まってない場合、無理にコンテナを導入したりすると炎上不可避なので計画的にコンテナ化しましょう!!
よいコンテナライフを!!!