概要
development, staging, productionといった環境に分けてdocker imageを利用する場合のバージョン管理について考えてみました。
環境
要件
今回対応する時に考慮した要件は以下。
- gitのtagと連動
- ただし最新のimageの
TAG
を確認する手間は省きたい
- ただし最新のimageの
- dev, stg, prd分けができる
- ただしimageの差異は極力なくしたい
- リリース後の切り戻しが容易
まず結論
以下のようなシェルスクリプトを用意しました。
#!/bin/bash set -eu DOCKER_REGISTRY=xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com REPOSITORY=api-server IMAGE=${DOCKER_REGISTRY}/${REPOSITORY} if [ $# -lt 2 ]; then echo "Usage: ./deploy.sh (dev|stg|prd) TAG" exit 1 fi ENV=$1 TAG=$2 if [ ${ENV} = "dev" ]; then TAG=latest docker build -f Dockerfile -t ${IMAGE}:${TAG} . docker push ${IMAGE}:${TAG} elif [ ${ENV} = "stg" ]; then docker pull ${IMAGE}:latest docker build -f Dockerfile --cache-from ${IMAGE}:latest -t ${IMAGE}:${TAG} . docker push ${IMAGE}:${TAG} elif [ ${ENV} = "prd" ]; then docker pull ${IMAGE}:${TAG} docker tag ${IMAGE}:${TAG} ${IMAGE}:stable docker push ${IMAGE}:stable TAG=stable else exit 1 fi ./scripts/ecs-deploy -t 600 -c ${ENV}-api-cluster -n ${ENV}-api-service -i ${IMAGE}:${TAG} exit 0
ecs-deploy
というのは
ECSでコンテナのrolling update - Carpe Diem
でも紹介している以下のスクリプトです。単にECSのデプロイに使用してるだけです。
シェルスクリプトの解説
dev環境
$ ./deploy.sh dev latest
という形で使います。
if [ ${ENV} = "dev" ]; then docker build -f Dockerfile -t ${IMAGE}:${TAG} . docker push ${IMAGE}:${TAG}
dev環境では通常通りimageをbuildし、pushしています。
stg環境
$ ./deploy.sh stg 1.0.1
という形で使います。この1.0.1
というTAG
はgitのrelease tagと連動させるのがポイントです。
elif [ ${ENV} = "stg" ]; then docker pull ${IMAGE}:latest docker build -f Dockerfile --cache-from ${IMAGE}:latest -t ${IMAGE}:${TAG} . docker push ${IMAGE}:${TAG}
stg環境ではまず最初にlatestのimageを持ってきます。
そしてbuild時に--cache-from
でそのimageを参照させます。
これはimageが全くない状態でbuildすると、devの時に作ったものと途中のレイヤーなどが別のものとして扱われてしまうためです。
prd環境
$ ./deploy.sh prd 1.0.1
という形で使います。指定するTAG
はstgで用意したTAG
です。
elif [ ${ENV} = "prd" ]; then docker pull ${IMAGE}:${TAG} docker tag ${IMAGE}:${TAG} ${IMAGE}:stable docker push ${IMAGE}:stable TAG=stable
prd環境ではstgでpushしたimageを持ってきて、それに対してstable
TAGを打ちます。
その後pushしていますが、imageはstgと全く同じものです。
stgでセットしているTAG
に対してstable
TAGを毎回移動する感じです。
リリースフロー
- gitからrelease tagを切る
- TravisCIなどのCIツールがそれを検知
- 先に挙げたスクリプトに、gitのtagを渡して実行
- imageがbuild & pushされる
- 前述のデプロイスクリプトが実行される
- リリース完了
このような流れで進めればうまくいくと思います。
Q & A
Dockerfileが同じなら別にもう一度ビルドするくらいいいのでは?
と思う方もいますが、Dockerfileの中身によっては問題があります。
例えば依存するパッケージ(apt, apk, yumなど)やライブラリのバージョンが、実行した日時によって異なるケースです。
そもそもdockerのメリットはポータビリティ(どこでも同じ状態で扱える)なのに、毎環境imageを作るとなるとそのメリットが無くなってしまいます。
なので極力Image間の差異をなくせるよう、tagだけ追加するか、--cache-from
で出来る限り同じimageを使う方が良いです。
stgでもlatestに対してバージョンタグをセットするだけにすれば同じImageでビルドの手間を省けるのでは?
elif [ ${ENV} = "stg" ]; then docker pull ${IMAGE}:latest docker build -f Dockerfile --cache-from ${IMAGE}:latest -t ${IMAGE}:${TAG} . docker push ${IMAGE}:${TAG}
ではなく
elif [ ${ENV} = "stg" ]; then docker pull ${IMAGE}:latest docker tag ${IMAGE}:latest ${IMAGE}:${TAG} docker push ${IMAGE}:${TAG}
でも良いのではないか?という考えです。prdと同じ形ですね。
ただこの場合、もしgitで過去のコミットに対してtagを切りたいとき、latest
はgitのmaster
を見ているためその状態のimageを作ることができません。
なのでstgではgitのtagに完全に連動できるように、という点を重視してこの形にしました。
そういった要件がなければ、prdと同じようにすれば良いと思います。
stableタグって必要?
リリースが頻繁なアプリケーションデプロイの場合はあった方が便利です。
特に
- ECSのtask definitionでのimage指定
- EC2のuser-dataでimageを取得する
などの場合、毎回最新のTAG
を確認するのは手間です。
stable
という常に最新を追うTAG
を利用すればそれを解消できます。
バージョンタグって必要?latestとstableだけで良いのでは?
リリース後にバグが発覚し、すぐに旧バージョンに切り戻しが必要になった時に対応がしやすいよう、間にgitのtagと連動させたTAG
を付けた方が良いです。