お題
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