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 ./goosegoose のバイナリがカレントディレクトリに配置されます。
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 までコンテナ内で完結できるはずなので、必要にかられたら試してみます。