Memento memo.

Today I Learned.

Docker の multi-stage build で golang の 軽量 image を作る

f:id:shotat_jp:20170805214939p:plain

Multi-Stage Build

docs.docker.com

Docker の 17.05 (ce) から multi-stage build 機能が追加されました。 multi-stage build とは、ベースイメージを複数利用し、多段階で build を行う機能です。

利用シーンはビルド環境とランタイム環境の分離が挙げられます。 ビルド環境とランタイム環境を分けるとランタイムがネイティブである Go のような言語では Docker image を劇的に小さくすることができます。

golang の ベースイメージは 数百 MB ありますが、alpine のような 軽量 Linux イメージ単体では 数 MB のため、効果としては 90 % 以上のダイエットに繋がります。

Docker image サイズを小さくするメリットや、一般的なプラクティスについてはページ下部のリンクが非常に参考になります。

Dockerfile の作成

multi-stage build を使って Go のコンテナアプリケーションを作成するテンプレートです。

github.com

Dockerfile の中身はこんな感じになります。

FROM golang:latest as builder

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /go/src/github.com/shotat/light-golang-container-template
COPY . .
RUN make

# runtime image
FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /go/src/github.com/shotat/light-golang-container-template/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]

Multi-stage build なので FROM が二回登場しています。 一行目の FROM では as xxx としてビルド環境に名前をつけています。 as で指定した名前は COPY --from=builder /go/src/github.com/shotat/light-golang-container-template/app /app で参照しています。

builder で GOOS GOARCH CGO_ENABLED環境変数を指定し、 alpine 用にビルドを行います。 amd64の場合 CGO_ENABLED=0 を指定しないとビルド環境依存が発生してしまうため指定は必須です。

RUN apk add --no-cache ca-certificatesSSL/TLS 通信を行うために必要で、ほとんどアプリケーションで利用するはずです。 通信の必要がなければ alpine ではなく busyboxscratch を使ってより小さなイメージが作れます。

Build する

ビルド自体は make だけで簡潔するようにしています。 Makefile の中身はこんな感じです。パッケージ管理には dep を使います。

# 適当に設定を書く
# NAME     := hello
# VERSION  := v1
# ...

all: setup build

setup:
    go get -u github.com/golang/dep/cmd/dep
    dep ensure

build:
    go build -o app
$ docker build . -t app:dev

アプリケーションの中身です。 適当に外部ライブラリを入れたかったので logger として logrus を使っています。

package main

import (
    log "github.com/sirupsen/logrus"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    log.Info("Hello")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

実際にビルドしてみます。

$ docker build . -t app:dev

Run !

動かします。

$ docker run --rm -p 8080:8080 app:dev

$ curl localhost:8080
# container output
time="2017-08-05T12:43:25Z" level=info msg=Hello

無事いい感じに動作しています。 デプロイサイクル短縮の鼓動を感じました。

参考書籍

Docker 実践ガイド (impress top gear)

Docker 実践ガイド (impress top gear)

WEB+DB PRESS Vol.99

WEB+DB PRESS Vol.99

  • 作者: ?橋健一,谷口禎英,井本大登,山崎勝平,大和田純,内村元樹,坂東昌哉,平田敏之,牧大輔,板敷康洋,大?浩崇,穴井宏幸,原口宗悟,久田真寛,ふしはらかん,のざきひろふみ,うらがみ,ひげぽん,池田拓司,はまちや2,竹原,片田雄樹,渋江一晃,WEB+DB PRESS編集部編
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/06/24
  • メディア: 大型本
  • この商品を含むブログを見る

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

参考資料