Carpe Diem

備忘録

Docker Imageのバージョン管理について

概要

development, staging, productionといった環境に分けてdocker imageを利用する場合のバージョン管理について考えてみました。

環境

  • AWS ECS
  • AWS ECR
  • Docker 17.03.1

要件

今回対応する時に考慮した要件は以下。

  • gitのtagと連動
    • ただし最新のimageのTAGを確認する手間は省きたい
  • 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のデプロイに使用してるだけです。

github.com

シェルスクリプトの解説

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というTAGgitの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

という形で使います。指定するTAGstgで用意した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を持ってきて、それに対してstableTAGを打ちます。
その後pushしていますが、imageはstgと全く同じものです。
stgでセットしているTAGに対してstableTAGを毎回移動する感じです。

リリースフロー

  1. gitからrelease tagを切る
  2. TravisCIなどのCIツールがそれを検知
  3. 先に挙げたスクリプトに、gitのtagを渡して実行
  4. imageがbuild & pushされる
  5. 前述のデプロイスクリプトが実行される
  6. リリース完了

このような流れで進めればうまくいくと思います。

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を付けた方が良いです。

ソース