Posted at

Dockerでダウンタイムのないデプロイ

More than 1 year has passed since last update.

webアプリケーションのデプロイを、Dockerを用いてダウンタイムなく行う手順をサーバーのプロビジョニング、アプリの用意、運用方法に分けまとめた。


はじめに

ECSやGKEとかが、Dockerのクラスタ管理ではデファクトっぽい位置づけになってる気がするが、そういうサービスが使えない場合でかつ、公式ツールを使ってデプロイする場合の方法を調べまとめた。


成果物

下記にまとめています。

記事を参照しながら見てください。


https://github.com/togana/sample-docker-deploy


構成


デプロイのイメージ図

01.png


使用するツール

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 を用意して実行します。


ボリュームのマウントをしているので、違う場所に作りたい人は適宜置き換えてください。


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ページを作成します。こうすることでコンテナが切り替わったか確認しやすいコンテナが作成できます。


Dockerfile

FROM nginx:1.11.9-alpine

COPY default.conf /etc/nginx/conf.d/
COPY index.html /usr/share/nginx/html/



default.conf

server {

listen 80;
server_name localhost;
ssi on;

location / {
root /usr/share/nginx/html;
index index.html;
}
}



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.shsh ディレクトリに用意する。

$ mkdir sh

$ cd sh


build.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に適宜読み替えてください。


docker-compose.yml

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.shsh ディレクトリに用意する。

毎回docker-compose.ymlを再生成するので .gitignore に追加してもいいかもしれない。


deploy.sh

#!/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に変更を加えます。


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

デプロイコマンドが終了してもすぐデプロイが終わってるわけではないところに注意が必要です。

実際には裏でローリングアップデートが始まります。


アップデート時の挙動図解

アップデート前

02.png

デプロイコマンド実行

一つ目のコンテナがstopされ

03.png

新しいイメージでコンテナ実行

04.png

10秒待ったのちに2つ目のコンテナがstopされ

05.png

新しいイメージでコンテナ実行

06.png

こんな感じでローリングアップデートが行われる


まとめ

ビルド時にタグを付けてレジストリにプッシュ、デプロイ時に使用するイメージのタグを指定することで新しいイメージで動くコンテナをダウンタイムなくデプロイすることができました。


運用時は、タグの指定をコマンドでできるようにすることで、ビルドのしやすさ、デプロイ時にタグを切り替えることで気軽にロールバック等もできるようになりました。


最後に

僕の調べた範囲で書いています。間違ってることなどあれば教えてください!

また、導入目的や目標が定まってない場合、無理にコンテナを導入したりすると炎上不可避なので計画的にコンテナ化しましょう!!

よいコンテナライフを!!!