Posted at
DockerDay 10

複数のDockerイメージを単一リポジトリで管理する

この記事はDocker Advent Calendar 2018の10日目の記事です。


はじめに

インフラチームとして提供する共通利用のDockerfileはDockerfileごとにリポジトリを作ってると沢山リポジトリが増えて結構大変です。しかし単一のリポジトリに複数のDockerfileを纏めると今度はCI/CDでDockerイメージをビルドするのが難しくなります。

この記事では複数のDockerfileを単一のリポジトリにまとめた時に、差分のあったDockerfileだけをビルドする構成について紹介します。


ディレクトリ構成

今回のサンプルの構成はgithubに上げてます。

https://github.com/kanga333/dockerfiles-monolepo

プロジェクトのディレクトリ構成は以下のような感じです。

dockerfiles-monolepo

├── Makefile
├── README.md
├── dockerfiles
│   ├── proxy-nginx
│   │   ├── Dockerfile
│   │   ├── Makefile
│   │   └── files
│   │   ├── nginx.conf
│   │   └── service.conf
│   └── python-lambda-builder
│   ├── Dockerfile
│   ├── Makefile
│   └── README.md
└── scripts
└── git-diff-make.sh

dockerfilesの下にDockerfileごとのディレクトリを切っていくような構造になります。


構成のポイント


DockerfileのビルドをMakefileによって抽象化する

Dockerfile用のディレクトリにMakefileを用意してmake releaseコマンドを実行すればdocker build & pushしてくれるようにします。これによってリリース操作を抽象化します。


Makefileサンプル

NAME         := python-lambda-builder

REVISION := $(shell git rev-parse --short HEAD)
ORIGIN := $(shell git remote get-url origin | sed -e 's/^.*@//g')
RELEASE_TAGS := 1.0.0 $(REVISION)
TAGS := $(REVISION)
REGISTRY := index.docker.io
USER := kanga333

.PHONY: build
build:
@docker build \
--build-arg GIT_REVISION=$(REVISION) \
--build-arg GIT_ORIGIN=$(ORIGIN) \
--build-arg IMAGE_NAME=$(REGISTRY)/$(USER)/$(NAME) \
$(addprefix -t $(REGISTRY)/$(USER)/$(NAME):,$(TAGS)) .

.PHONY: push
push:
@for TAG in $(TAGS); do\
docker push $(REGISTRY)/$(USER)/$(NAME):$$TAG; \
done

.PHONY: release
release:
@make build TAGS="$(RELEASE_TAGS)"
@make push TAGS="$(RELEASE_TAGS)"


ちなみに以前dockerをビルドする際のMakefileについて記事を書いたのでこちらも合わせて参照いただければと思います。

https://qiita.com/kanga/items/aba6928996cb57daa460


差分のあったディレクトリを検知するShellスクリプト

このshellスクリプトは前回のコミットとの差分を比較して更新があったディレクトリでのみmakeを実行するshellスクリプトです。

#!/bin/bash

set -eu

if [ $# -ne 1 ]; then
echo "Please specify the argument of make." 1>&2
exit 1
fi

TARGETS=`find dockerfiles -maxdepth 1 -mindepth 1 -type d`
for TARGET in $TARGETS
do
HAS_DIFF=`git diff --diff-filter=ACMR --name-only HEAD\^ HEAD --relative=$TARGET | head -1`
if [ ! -z $HAS_DIFF ]; then
( cd ${TARGET} && make $1 )
fi
done

git diff --diff-filter=ACMR --name-only HEAD\^ HEAD --relative=$TARGET | head -1によって対象ディレクトリで差分があるか無いかを判断するフィルターを実現しています。

あとはCIでマスターにマージされた際にこのshellスクリプトを呼ぶことで差分のあったディレクトリのみでビルドが走るようになります。

sh scripts/git-diff-make.sh release


注釈

ただしこの方法だと前回のコミットとの比較なのでマージが動いた事が差分検知の前提になります。

PRごとにビルドをしたい場合はMasterにマージした後にsh scripts/git-diff-make.shを動かすようにCIを工夫する必要があります。