Docker in Docker で Go 製のバイナリを持った軽量な Docker イメージを作る
go get で取得した cli ツールのバイナリを持った軽量な Docker イメージをつくる - tehepero note(・ω<) を読んで、別解として Docker in Docker で作れないかなーと思ってやってみました。
やりたいこと
根本的な動機は元記事をご参照ください。
go get で取得した cli ツールのバイナリを持った軽量な Docker イメージをつくる - tehepero note(・ω<)
元記事では Alpine ベースの golang イメージで go get
したバイナリを docker cp
で取得し、実行用のイメージを作成するフローを CircleCI で実行しています。
この記事では CI を用いずに Docker in Docker なビルド用のイメージを用いることでコンテナ内で go get
から docker build
まで済ませてみようかと思います。
なお、 docker push
せずに成果物のイメージを取り出したかったので、今回は Docker のデーモンを起動せずにホストの /var/run/docker.sock
をコンテナと共有します。
実行用のイメージ
先に実行用イメージ作成の構成を説明したほうがわかりやすいので、ビルド用イメージの解説の前にぺろっと紹介。
ディレクトリ構成はこんな感じです。
Dockerfile
pre.sh*
pre.sh
で下準備をして、 Dockerfile
を docker build
する想定です。
今回は goose
のバイナリを入れたいので、下準備は go get
です。
pre.sh
は以下。
#/bin/bash
go get -v bitbucket.org/liamstask/goose/cmd/goose
cp $GOPATH/bin/goose ./goose
goose
のバイナリがカレントディレクトリに配置されます。
Dockerfile はバイナリをコピーするだけ。
FROM alpine:3.5
COPY ./goose /usr/local/bin/
ENTRYPOINT goose
この実行用のイメージのビルドを Alpine ベースの Docker コンテナ内で実行するのが目標です。
ビルド用のイメージ
Docker のイメージ作成は依存系の解決と実行用のアレコレ配置を別イメージにするのが好きなので、二段階でやります。
( CI 等、キャッシュがない環境で依存系のビルドが何度も走るとしんどいという理由)
Docker in Docker with golang のイメージ
下準備で Docker と golang の入った Docker イメージを用意します。
今回はバージョンにこだわらずに Docker のベースイメージに apk
で取れる go
を入れます。(記事作成時点では 1.7.3
でした)
もし最新バージョンの golang を入れたい場合は golang の公式イメージの Dockerfile を参考にするのがいいかと。
FROM docker
RUN \
apk --no-cache --update add \
build-base \
git \
go \
&& \
mkdir /go
ENV GOPATH /go
ENV PATH $PATH:$GOPATH/bin
こちらを
docker build -t horiryota/docker-golang .
として下準備。
ビルド用のイメージ
本題です。
ビルド用イメージ作成用のディレクトリはこんな感じです。
build.sh*
Dockerfile
usage.sh*
核となる build.sh
は
#/bin/bash
srcDir='/srcDir'
if [ -z "$IMAGE_NAME" ]; then
echo '$IMAGE_NAME unbound'
usage.sh
exit 1
fi
if [ ! -d "${srcDir}" ]; then
echo 'srcDir not found'
usage.sh
exit 1
fi
if [ ! -r "${srcDir}/Dockerfile" ]; then
echo 'Dockerfile not found'
usage.sh
exit 1
fi
if [ ! -r "${srcDir}/pre.sh" ]; then
echo 'pre.sh not found'
usage.sh
exit 1
fi
cp -R /srcDir/* ./
./pre.sh && \
docker build -t $IMAGE_NAME .
です。
つらつら書いている if
文は必要なものが存在するかのチェックです。
最終的にはビルド用イメージを doker run
する際に /srcDir
に Dockerfile
と pre.sh
の入ったディレクトリをマウントすることで、 pre.sh
を実行した後に docker build
する構成です。
mount する /srcDir
を荒らさないよう、ファイルは ./
にコピーしています。
usage.sh
はイメージの使い方が出力されるだけのスクリプトなので割愛。
Dockerfile は以下です。
FROM horiryota/docker-golang
WORKDIR /workdir
COPY usage.sh /usr/local/bin/
COPY build.sh /usr/local/bin/
ENTRYPOINT "build.sh"
build.sh
を発火するだけです。
WORKDIR /workdir
にしているのは /
ディレクトリにある Dockerfile で docker build
をしようとすると権限問題で失敗するためです。
ビルド用イメージのビルドをして準備完了です。
docker build -t horiryota/docker-golang-builder .
作成実行
実行コマンドはこんな感じ。
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock -v $DOCKERFILE_DIR:/srcDir:ro -e IMAGE_NAME=$IMAGE_NAME horiryota/docker-golang-builder
今回は /var/run/docker.sock
を mount したのでホストの docker にイメージが生成されます。
$DOCKERFILE_DIR
実行用イメージ作成用のファイルがあるディレクトリ。カレントディレクトリであれば$(pwd)
でできるかなと$IMAGE_NAME
作成したいイメージ名。build.sh
のdocker build -t $IMAGE_NAME
で使うのでお好きに。
ちなみに /srcDir
の mount で後ろについている :ro
は read only の意味です。ホストのディレクトリを不用意に荒らさないで済むので付けておくことを推奨します。
ということで、カレントディレクトリで horiryota/docker-goose
のイメージを作成するならこんな感じでしょうか。
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/srcDir:ro -e IMAGE_NAME=horiryota/docker-goose horiryota/docker-golang-builder
生成イメージの確認
REPOSITORY TAG IMAGE ID CREATED SIZE
horiryota/docker-goose latest fa633023a994 About an hour ago 17.4 MB
諸々のバージョンが違うので同じにはなりませんでしたが、およそ元記事と同じくらいのイメージサイズになったことが確認できました。
今回のコード
使ったコードを公開しておきます。
以上
Docker のコンテナ内で docker build
することで Alpine
ベースの軽量イメージが作成できました。
Docker in Docker で準備用のスクリプトと Dockerfile を実行するのが主旨なので、 golang のバイナリ用以外にも汎用的にビルドに使えるようにできたんじゃないかなーと思っています。
ちなみに Docker ベースのイメージを使わなくともホストの /usr/bin/docker
をコンテナに mount するという手もあるようですが荒業感が強すぎるので止めました( Docker for Mac だと /usr
以下を mount しようとすると怒られる模様)。
コンテナ内で Docker のデーモンを立ち上げれば /var/run/docker.sock
を mount しなくても docker push
までコンテナ内で完結できるはずなので、必要にかられたら試してみます。