やること
Docker + Go言語でAPIサーバを立てたいという時の環境構築をします。その際、以下のことを満たせるようにDockerfile等を設計していきます。
- multi stage buildを用いて、production環境でのimageサイズをなるべく小さくする
- Dockerfileはdev環境とprod環境で共通のものを用いる
Dockerfileの書き方
まず、Docker 17.05でmulti stage buildという機能が導入されているので、それを使います。
次のようにFROM
が2つあるようにし、中間ステージのものに対してはAS build
のように名前をつけておきます。
FROM golang:latest AS build
WORKDIR /go/src/github.com/nkjmsss/sample
ENV GO111MODULE=on
# make cache
COPY go.mod .
COPY go.sum .
RUN go mod download
# COPY the source code as the last step
COPY . .
# build app for next stage
RUN go build -o /go/bin/app
FROM alpine:latest
WORKDIR /app
COPY --from=build /go/bin/app .
# add user
RUN addgroup go && \
adduser -D -G go go && \
chown -R go:go /app/app
CMD [ "./cms" ]
これの大事な部分について説明していきます。
1. GO111MODULE
これは、Go 1.11より導入されたバージョン管理機能を有効にするためのフラグです。v1.11と現バージョンのv1.12では、これはデフォルトでautoとなっているので、これを強制的にonにしておきます(gopathの中では、GO111MODULEのautoはoffと等価です)。なお、秋頃リリースと噂のGo 1.13ではこの環境変数がデフォルトでonとなるようなので、過渡期の今限定で設定しなければならないものです。
なぜこれをわざわざ指定しているかというと、例えばechoというフレームワークでAPIサーバを立てる場合、v4を明示的に指定する必要があるためです。個人的にはecho + swaggoでの開発が好きなのですが、そうする場合次のようにimportすることになります。GO111MODULEを指定していない段階ではそもそも組み合わせるのがかなり困難だったので、指定するようにしましょう。
import (
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger/v2"
)
2. cache
imageのビルドを高速化するにはかなり重要な部分です。
Go modulesが有効な状態では、go.modとgo.sumの2つのファイルで使用するパッケージのバージョンを指定しています。そして、これらは通常の開発の場合はパッケージを追加インストールする時くらいしか更新されません。なので、これらをdockerの中の単独なレイヤーにしておくことで、ビルド時にdockerがcacheを使えるようになり、go mod download
を高速化できます。
最初にCOPY . .
としてしまうとcacheが上手に使えず、ビルドの度にgo mod download
でネット上からダウンロードしようとするので非常に時間がかかってしまいます。
3. 中間ステージからのCOPY
後半にCOPY --from=build /go/bin/app .
というものがありますが、--from=build
でbuildステージで予めビルドしておいたものをコピーしてきます。これのおかげでproduction環境の方はGoをインストールする必要がなくなるので、イメージサイズを小さくすることが可能になります。
4. ユーザの追加
公式docにあるように、rootでないユーザで実行するようにしたほうがセキュリティの観点上良いと思われます。
docker-compose.ymlの書き方
version: "3.5"
services:
echo:
build:
context: .
target: build
volumes:
- .:/go/src/github.com/nkjmsss/sample
ports:
- "1323:1323"
command: go run main.go
これの大事なところは、versionを3.4以上に指定してbuild時にbuild stageをtargetにすることです。
docker-compose.prod.ymlの書き方
version: "3.5"
services:
echo:
build: .
ports:
- "1323:1323"
こちらでは、別にversion3.5を指定する必要は特にないです。
おわりに
$ docker-compose build
$ docker-compose up -d
とすると、dev環境が立ち上がり、
$ docker-compose -f docker-compose.prod.yml build
$ docker-compose -f docker-compose.prod.yml up -d
とするとprod環境が立ち上がります。
今回は説明の都合から最小構成で書いていますが、
- docker-compose.ymlにdev環境かどうかを指定する環境変数を追加する
- realize等のツールを用いて、dev環境では自動リロードできるようにする
- makefileで長い
docker-compose -f docker-compose.prod.yml build
等を簡潔にする
などをしておけば、より快適な開発環境が整うのではないでしょうか。
ちなみに、自分の最近作ったAPIサーバでは、build stageが1GB程度あったものが、prod環境では90MBとイメージサイズを1/10程度にすることができています。