お題
k8sの勉強しがてら、GoのアプリでもGKEに乗せてみようと思いつつ、そういえば、表題の組み合わせでDockerコンテナ作ったことないなと気づく。
前回最後にDockerコンテナ作った時は、Go1.9+dep だった、たしか。
というわけで、知識をアップデートしておく。
開発環境
# OS
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
# Docker
$ sudo docker version
Client:
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        6247962
 Built:             Tue Feb 26 23:52:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false
Server:
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       6247962
  Built:            Wed Feb 13 00:24:14 2019
  OS/Arch:          linux/amd64
  Experimental:     false
実践
Goアプリ
まあ、アプリの内容は何でもいいので、とりあえず http://localhost:8080/sample でアクセスしたら 200 OK をJSON形式で返すのを書いておく。
package main
import (
	"net/http"
	"github.com/labstack/echo"
)
func main() {
	e := echo.New()
	e.GET("/sample", func(c echo.Context) error {
		return c.JSON(
			http.StatusOK,
			struct {
				Code int    `json:"code"`
				Text string `json:"text"`
			}{
				Code: http.StatusOK,
				Text: http.StatusText(http.StatusOK),
			},
		)
	})
	e.Logger.Fatal(e.Start(":8080"))
}
ローカル環境で実行すると↓のように8080ポートで待ち受ける。
$ go run main.go 
   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
ちなみに、go.mod はこんな感じ。
module t01
require (
	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
	github.com/labstack/echo v3.3.10+incompatible
	github.com/labstack/gommon v0.2.8 // indirect
	github.com/mattn/go-colorable v0.1.1 // indirect
	github.com/mattn/go-isatty v0.0.7 // indirect
	github.com/valyala/fasttemplate v1.0.1 // indirect
	golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 // indirect
)
Dockerfile
以下を参考に。
https://medium.com/@petomalina/using-go-mod-download-to-speed-up-golang-docker-builds-707591336888
# step 1: build
FROM golang:1.12.4-alpine3.9 as build-step
# for go mod download
RUN apk add --update --no-cache ca-certificates git
RUN mkdir /go-app
WORKDIR /go-app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /go/bin/go-app
# -----------------------------------------------------------------------------
# step 2: exec
FROM scratch
COPY --from=build-step /go/bin/go-app /go/bin/go-app
ENTRYPOINT ["/go/bin/go-app"]
特記事項
■ベースイメージを「Docker Hub」から取得しているのでdocker loginが必要
してないと、こうなる。
$ sudo docker build -t sky0621/go-app:v0.1 .
Sending build context to Docker daemon  7.168kB
Step 1/8 : FROM "1.11.9-alpine3.9" AS builder
pull access denied for 1.11.9-alpine3.9, repository does not exist or may require 'docker login'
なので、docker loginする。
【参考】
http://docs.docker.jp/docker-hub/accounts.html
$ sudo docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: sky0621dhub
Password: 
WARNING! Your password will be stored unencrypted in /home/sky0621/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
■「RUN apk add --update --no-cache ca-certificates git」がないと、RUN go mod downloadが失敗する。
Step 6/11 : RUN go mod download
 ---> Running in 1b5ecc7774b5
go: github.com/dgrijalva/jwt-go@v3.2.0+incompatible: git init --bare in /go/pkg/mod/cache/vcs/11fb0427c36fe2d22a58c95464426a2d142ff24c438e1248eeb9ff450be5af41: exec: "git": executable file not found in $PATH
  〜〜 省略 〜〜
go: golang.org/x/crypto@v0.0.0-20190418165655-df01cb2cc480: git init --bare in /go/pkg/mod/cache/vcs/de5fd3af413a4f3f969455ae522b4002fcb7bb4c158f339396dfc77710c9007d: exec: "git": executable file not found in $PATH
go: error loading module requirements
The command '/bin/sh -c go mod download' returned a non-zero code: 1
■「CGO_ENABLED=0」がないと、docker runが失敗する。
$ sudo docker run -p 80:8080 sky0621/go-app:v0.1
standard_init_linux.go:207: exec user process caused "no such file or directory"
cgoというのは、GoからCのコードを呼び出すコマンドらしい。
それを有効にする(=1)か無効にする(=0)かの指定が「CGO_ENABLED」でデフォルトでは有効とのこと。
(ただし、クロスコンパイル時はデフォルトで無効)
有効の場合、ビルド制約としてcgoがセットされる。
【参考】
https://golang.org/cmd/cgo/
standard_init_linux.goの中身は追っていないので、ここでエラーになった原因であると断定は出来ないのだけど、ビルド制約cgoを外すことでdocker run時の失敗は回避された。
ビルド
事前にdockerプロセスもイメージもないことを確認。
$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
では、実行。
$ ls -l
total 16
-rw-r--r-- 1 sky0621 sky0621  464 Apr 20 08:52 Dockerfile
-rw-r--r-- 1 sky0621 sky0621  420 Apr 19 08:00 go.mod
-rw-r--r-- 1 sky0621 sky0621 1895 Apr 19 08:00 go.sum
-rw-r--r-- 1 sky0621 sky0621  375 Apr 19 09:23 main.go
$
$ date
Sat Apr 20 09:11:41 JST 2019
$
$ sudo docker build -t sky0621/go-app:v0.1 .
Sending build context to Docker daemon  6.656kB
Step 1/12 : FROM golang:1.12.4-alpine3.9 as build-step
1.12.4-alpine3.9: Pulling from library/golang
bdf0201b3a05: Pull complete 
38f114998adb: Pull complete 
21134b1a9e68: Pull complete 
cff36310a8c5: Pull complete 
02f293f0b51b: Pull complete 
Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Status: Downloaded newer image for golang:1.12.4-alpine3.9
 ---> b97a72b8e97d
Step 2/12 : RUN apk add --update --no-cache ca-certificates git
 ---> Running in 0c5bb1e980c9
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/6) Installing nghttp2-libs (1.35.1-r0)
(2/6) Installing libssh2 (1.8.2-r0)
(3/6) Installing libcurl (7.64.0-r1)
(4/6) Installing expat (2.2.6-r0)
(5/6) Installing pcre2 (10.32-r1)
(6/6) Installing git (2.20.1-r0)
Executing busybox-1.29.3-r10.trigger
OK: 20 MiB in 21 packages
Removing intermediate container 0c5bb1e980c9
 ---> 0625379ca405
Step 3/12 : RUN mkdir /go-app
 ---> Running in 82c9e6be294d
Removing intermediate container 82c9e6be294d
 ---> 0e99542d29eb
Step 4/12 : WORKDIR /go-app
 ---> Running in 7929d16e6ce4
Removing intermediate container 7929d16e6ce4
 ---> 2bb8099a90aa
Step 5/12 : COPY go.mod .
 ---> 77a7fb906751
Step 6/12 : COPY go.sum .
 ---> da9177c9aaa1
Step 7/12 : RUN go mod download
 ---> Running in a60a8f304669
go: finding github.com/labstack/echo v3.3.10+incompatible
go: finding github.com/mattn/go-colorable v0.1.1
go: finding github.com/dgrijalva/jwt-go v3.2.0+incompatible
go: finding github.com/labstack/gommon v0.2.8
go: finding github.com/mattn/go-isatty v0.0.7
go: finding github.com/valyala/fasttemplate v1.0.1
go: finding golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480
go: finding github.com/mattn/go-isatty v0.0.5
go: finding github.com/valyala/bytebufferpool v1.0.0
go: finding golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e
go: finding golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223
Removing intermediate container a60a8f304669
 ---> 44992184e9e2
Step 8/12 : COPY . .
 ---> 8e8041d6ff86
Step 9/12 : RUN CGO_ENABLED=0 go build -o /go/bin/go-app
 ---> Running in 1baafdb9189b
Removing intermediate container 1baafdb9189b
 ---> 51d15d46907b
Step 10/12 : FROM scratch
 ---> 
Step 11/12 : COPY --from=build-step /go/bin/go-app /go/bin/go-app
 ---> 6031f71f0586
Step 12/12 : ENTRYPOINT ["/go/bin/go-app"]
 ---> Running in a27a508d1102
Removing intermediate container a27a508d1102
 ---> 1ecb02037408
Successfully built 1ecb02037408
Successfully tagged sky0621/go-app:v0.1
$
$ date
Sat Apr 20 09:12:23 JST 2019
かかった時間は、ざっと「42」秒。けっこうかかるね。。
Sat Apr 20 09:11:41 JST 2019
Sat Apr 20 09:12:23 JST 2019
イメージの確認
Goのビルドなどに使ったイメージはそれなりのサイズではあるものの、実際に動かすGoアプリのイメージは「8.43MB」と軽量。
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
sky0621/go-app      v0.1                1ecb02037408        13 minutes ago      8.43MB
<none>              <none>              51d15d46907b        13 minutes ago      413MB
golang              1.12.4-alpine3.9    b97a72b8e97d        7 days ago          350MB
実行
$ sudo docker run -p 80:8080 sky0621/go-app:v0.1
   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
OK。
キャッシュ確認
Dockerfileのつくりによるキャッシュの効果を確認するために、Goアプリを微修正して再ビルドしてみる。
ビルド時間が短くなることを期待。
Goアプリを微修正
受け付けるパスを「/sample」→「/sample2」に変えただけ。
$ git diff
diff --git a/chXX_try/app/main.go b/chXX_try/app/main.go
index acb93c1..522a0e4 100644
--- a/chXX_try/app/main.go
+++ b/chXX_try/app/main.go
@@ -8,7 +8,7 @@ import (
 
 func main() {
        e := echo.New()
-       e.GET("/sample", func(c echo.Context) error {
+       e.GET("/sample2", func(c echo.Context) error {
                return c.JSON(
                        http.StatusOK,
                        struct {
再ビルド
$ date
Sat Apr 20 09:43:21 JST 2019
$
$ sudo docker build -t sky0621/go-app:v0.2 .
Sending build context to Docker daemon  6.656kB
Step 1/12 : FROM golang:1.12.4-alpine3.9 as build-step
 ---> b97a72b8e97d
Step 2/12 : RUN apk add --update --no-cache ca-certificates git
 ---> Using cache
 ---> 0625379ca405
Step 3/12 : RUN mkdir /go-app
 ---> Using cache
 ---> 0e99542d29eb
Step 4/12 : WORKDIR /go-app
 ---> Using cache
 ---> 2bb8099a90aa
Step 5/12 : COPY go.mod .
 ---> Using cache
 ---> 77a7fb906751
Step 6/12 : COPY go.sum .
 ---> Using cache
 ---> da9177c9aaa1
Step 7/12 : RUN go mod download
 ---> Using cache
 ---> 44992184e9e2
Step 8/12 : COPY . .
 ---> c9a3ca188d54
Step 9/12 : RUN CGO_ENABLED=0 go build -o /go/bin/go-app
 ---> Running in 4aaa5be99f06
Removing intermediate container 4aaa5be99f06
 ---> f3ab3206497a
Step 10/12 : FROM scratch
 ---> 
Step 11/12 : COPY --from=build-step /go/bin/go-app /go/bin/go-app
 ---> fa6b7e4787a8
Step 12/12 : ENTRYPOINT ["/go/bin/go-app"]
 ---> Running in d17c0c300e5b
Removing intermediate container d17c0c300e5b
 ---> 0b607262dca1
Successfully built 0b607262dca1
Successfully tagged sky0621/go-app:v0.2
$
$ date
Sat Apr 20 09:43:33 JST 2019
早くなってる。「12」秒。(初回は「42」秒だった。)
キャッシュ効果、大きいね。
Sat Apr 20 09:43:21 JST 2019
Sat Apr 20 09:43:33 JST 2019
まとめ
簡素なアプリで試しただけではあるものの、ひとまずGo1.12+go modでのDockerコンテナ化は完了。
Dockerfileに関しては定期的に↓など再確認しないとなぁと思った。
http://docs.docker.jp/v1.9/engine/articles/dockerfile_best-practice.html
