🐋
巨大なコンテナイメージのビルド&プッシュを高速化したい
想定する問題
ビルド後のイメージは非常に重たいため、 ビルド・プッシュの速度向上のために docker build
のキャッシュを活用したい。
しかし、何らかの理由でキャッシュを活用できない。
自分が直面した状況は以下の通り。
- 機械学習系のライブラリを使用するため、ビルド後のイメージが非常に重い
- 上記機械学習系ライブラリの実行環境を構築するために、
apt-update && apt-upgrade
実行の上で特定のライブラリのインストールが必要 -
docker build
の度にapt-update && apt-upgrade
が走り、以降の重たい処理とdocker push
にてキャッシュが効かない
これにより、ちょっとしたコードの変更でも、push完了までに非常に時間がかかっていた。
以下Dockerfileのサンプル。
RUN apt-update && apt-upgrade -y
RUN apt-get install ... # 必要なライブラリをインストール
RUN pip3 install ... # 必要なライブラリをインストール ## 重たい処理
選択した解決策
一つのイメージを、以下二つに分離。
- 非常に重い実行環境構築部分を扱うコンテナイメージ(以降ランタイムイメージと呼称)
- アプリケーションコードを扱うコンテナイメージ(以降アプリケーションイメージと呼称)
アプリケーションイメージのベースイメージに、ランタイムイメージを使用。
コンテナの重たい部分を切り離すことが出来る。
結果的に、アプリケーションイメージの更新時はビルド、プッシュ共にキャッシュを大いに活用でき、速度が向上した。
イメージとしては、 AWS Lambda を、Lambda Layer と Lambda コードに分離するのに近い。
構成図
トレードオフ
一つのコンテナイメージに全部詰め込む場合と比較して、以下の点が不便。
- ランタイムイメージを定期的に更新する手間が増える
- 構成が複雑になり、直感的に理解できない
特に構成が複雑になる点。
何故二つに分割したのか、各イメージの役割・使い方・デプロイするユースケースなどをドキュメント化し、他のメンバー向けに情報を残す必要がある。
所感
目的は達成。
アプリケーションコード修正時、リポジトリへ反映する時間を大幅に短縮できた。
ぶっちゃけ他に良い方法あると思うが、思いつかなかった。
付録
ディレクトリ構成サンプル
.
├── app
│ └── 以下メインロジック
├── application_image
│ └── Dockerfile
├── aws_resources
│ └── dev
├── push_application_image.sh
├── push_runtine_env_image.sh
├── runtime_env
│ └── Dockerfile
└── scripts
└── ecr.sh
サンプルコード
見やすさのためにいくらか改変したため、動作は保証できません。
application_image/Dockerfile
◆ ARG image_name
FROM ${image_name}
# ... 以下 app/ のコピーなど、アプリケーションコードの動作に必要な処理
◆ runtime_env/Dockerfile
# 任意のイメージ、任意のライブラリインストール
scripts/ecr.sh
◆ #!/bin/bash
set -eu
ECR_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
function login_ecr() {
aws ecr get-login-password --region ${AWS_REGION} | \
docker login --username AWS --password-stdin ${ECR_URI}
}
function build_image() {
local image_tag=$1
local dockerfile_path=${2:-.}
docker build -t ${image_tag} --file ${dockerfile_path} .
}
function push_to_ecr() {
local AWS_ECR_REPOSITORY_NAME=$1
local version_tag=$2
local image_latest_tag=${AWS_ECR_REPOSITORY_NAME}:latest
local image_version_tag=${AWS_ECR_REPOSITORY_NAME}:${version_tag}
login_ecr
# push as latest
docker tag ${image_latest_tag} ${ECR_URI}/${AWS_ECR_REPOSITORY_NAME}
docker push ${ECR_URI}/${image_latest_tag}
# push as inputed tag
docker tag ${image_latest_tag} ${ECR_URI}/${image_version_tag}
docker push ${ECR_URI}/${image_version_tag}
}
aws_resources/dev
◆ # 必要なパラメータを設定
AWS_REGION=""
AWS_ACCOUNT_ID=""
AWS_ECR_RUNTIME_ENV_IMAGE_NAME=""
AWS_ECR_APPLICATION_IMAGE_NAME=""
push_runtine_env_image.sh
◆ #!/bin/bash
set -eu
version_tag=$1
aws_env=${2:-dev}
. aws_resources/${aws_env}
. ./scripts/ecr.sh
dockerfile_path='runtime_env/Dockerfile'
image_tag=${AWS_ECR_RUNTIME_ENV_IMAGE_NAME}
image_name=${ECR_URI}/${image_tag}
docker build -t ${image_tag} --file ${dockerfile_path} --build-arg image_name=${image_name} .
build_image ${image_tag} ${dockerfile_path}
push_to_ecr ${image_tag} ${version_tag}
実行例
./push_runtine_env_image.sh 0.1.0
push_application_image.sh
◆ #!/bin/bash
set -eu
version_tag=$1
aws_env=${2:-dev}
. aws_resources/${aws_env}
. ./scripts/ecr.sh
dockerfile_path='application_env/Dockerfile'
image_tag=${AWS_ECR_APPLICATION_IMAGE_NAME}
image_name=${ECR_URI}/${image_tag}
docker build -t ${image_tag} --file ${dockerfile_path} --build-arg image_name=${image_name} .
push_to_ecr ${image_tag} ${version_tag}
実行例
./push_runtine_env_image.sh 0.1.0
Discussion