🧰

go のモジュールを dockerize してみる

2022/06/22に公開

はじめに

私のPJでは 既存のCICD stackの開発と適用が進んでおり、それを利用しているだけで済んでしまうので core な仕様の理解まで及んでいない。
日頃 業務で コンテナ を使うときは ECSやEKSにログインしてログをみてアプリケーションのトラブルシュートするケースが主である。

そこで、自分で0からやってみようと思った。
今回は 俗世間の web エンジニアなどがよくやっている repo の dockerize(docker化)? を試していきたい。

無知なのでまず勉強

わかった気になるのが一番怖いのでまずは知識をinput する
Dockerfile の書き方について勉強する。
inputしたものは下記

学んだことを吐き出してみる

Dockerfileは 各々の環境に合った docker image を作成することができ、どのような仕様の imageにするか記述できる。
環境に必要な 複数の image を docker hub からかき集めて所望の imageを作る階層構造をイメージするとよいと思われる。

Dockerfile を書いてみる

※ 1行目は docker の build kit 1系のlatestを使用するという意味。
docker の recommend らしいのでこれは以後使っていきたい。

# syntax=docker/dockerfile:1

# Alpine is chosen for its small footprint
# compared to Ubuntu
FROM golang:1.16-alpine

WORKDIR /app

# Download necessary Go modules
COPY go.mod ./
COPY go.sum ./

RUN go mod download

COPY *.go ./

RUN go build -o /web-service-gin
EXPOSE 8080

CMD [ "/web-service-gin" ]

内容を言語化してみる。

  • base imageは go 入りの alpineを使用(alpineはlinuxのミニマム版)
  • image に work dirを作り、そこに go.mod,go.sum をコピーし依存をダウンロード
  • 作成した go file をコピーし、 build
  • コンテナがリッスンする 8080 port を開け、buildバイナリを CMDで実行

imageを buildしてみる

作成した Dockerfileをもとに、imageをbuildしてみる。
打ったコマンドは下記です。

$ docker build --tag go-challenge/web-service-gin .
[+] Building 45.4s (14/15)                                                                                                                                                                                                            
 => [internal] load build definition from Dockerfile                                                                                                                                                                             0.1s
 => => transferring dockerfile: 365B                                                                                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                0.0s
 => => transferring context: 2B                                                                                                                                                                                                  0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                                                                                                                                       9.3s
 => docker-image://docker.io/docker/dockerfile:1@sha256:443aab4ca21183e069e7d8b2dc68006594f40bddf1b15bbd83f5137bd93e80e2                                                                                                         1.9s
 => => resolve docker.io/docker/dockerfile:1@sha256:443aab4ca21183e069e7d8b2dc68006594f40bddf1b15bbd83f5137bd93e80e2                                                                                                             0.0s
 => => sha256:84495a15555de1a8f4738f58268fa8949547068198f8d0fa2a3e3a693d7f923f 2.37kB / 2.37kB                                                                                                                                   0.0s
 => => sha256:09768fef35f2ee387f57e401ae685727d12d1c70c6fd8545a422850167bf1940 9.94MB / 9.94MB                                                                                                                                   1.4s
 => => sha256:443aab4ca21183e069e7d8b2dc68006594f40bddf1b15bbd83f5137bd93e80e2 2.00kB / 2.00kB                                                                                                                                   0.0s
 => => sha256:24d064a369eda7bc7839b6c1c227eac7212d06ca09a8235a4bed467f8acf180d 528B / 528B                                                                                                                                       0.0s
 => => extracting sha256:09768fef35f2ee387f57e401ae685727d12d1c70c6fd8545a422850167bf1940                                                                                                                                        0.3s
 => [internal] load .dockerignore                                                                                                                                                                                                0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                             0.0s
 => [internal] load metadata for docker.io/library/golang:1.16-alpine                                                                                                                                                           11.4s
 => [internal] load build context                                                                                                                                                                                                0.0s
 => => transferring context: 4.76kB                                                                                                                                                                                              0.0s
 => [1/7] FROM docker.io/library/golang:1.16-alpine@sha256:5616dca835fa90ef13a843824ba58394dad356b7d56198fb7c93cbe76d7d67fe                                                                                                     15.3s
 => => resolve docker.io/library/golang:1.16-alpine@sha256:5616dca835fa90ef13a843824ba58394dad356b7d56198fb7c93cbe76d7d67fe                                                                                                      0.0s
 => => sha256:5616dca835fa90ef13a843824ba58394dad356b7d56198fb7c93cbe76d7d67fe 1.65kB / 1.65kB                                                                                                                                   0.0s
 => => sha256:9743f230f26d1e300545f0330fd4a514f554c535d967563ee77bf634906502b6 1.36kB / 1.36kB                                                                                                                                   0.0s
 => => sha256:7642119cd16177d874dcbfe1c550affd336dbe4cabb3339ef685ac1d6ec71ccc 5.17kB / 5.17kB                                                                                                                                   0.0s
 => => sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3 2.82MB / 2.82MB                                                                                                                                   0.6s
 => => sha256:666ba61612fd7c93393f9a5bc1751d8a9929e32d51501dba691da9e8232bc87b 282.16kB / 282.16kB                                                                                                                               0.8s
 => => sha256:8ed8ca4862056a130f714accb3538decfa0663fec84e635d8b5a0a3305353dee 155B / 155B                                                                                                                                       0.6s
 => => extracting sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3                                                                                                                                        0.1s
 => => sha256:ca4bf87e467a8cacf62c9b01c7d6d43f6ca4bb9b0fc9146fbb906b05aee56cc1 105.89MB / 105.89MB                                                                                                                              11.7s
 => => sha256:0435e09637941e35cccdf9cf9171571811d2fdfe72c27e39543a718d38d28668 156B / 156B                                                                                                                                       0.9s
 => => extracting sha256:666ba61612fd7c93393f9a5bc1751d8a9929e32d51501dba691da9e8232bc87b                                                                                                                                        0.1s
 => => extracting sha256:8ed8ca4862056a130f714accb3538decfa0663fec84e635d8b5a0a3305353dee                                                                                                                                        0.0s
 => => extracting sha256:ca4bf87e467a8cacf62c9b01c7d6d43f6ca4bb9b0fc9146fbb906b05aee56cc1                                                                                                                                        3.2s
 => => extracting sha256:0435e09637941e35cccdf9cf9171571811d2fdfe72c27e39543a718d38d28668                                                                                                                                        0.0s
 => [2/7] WORKDIR /app                                                                                                                                                                                                           1.0s
 => [3/7] COPY go.mod ./                                                                                                                                                                                                         0.0s
 => [4/7] COPY go.sum ./                                                                                                                                                                                                         0.0s
 => [5/7] RUN go mod download                                                                                                                                                                                                    5.9s
 => ERROR [6/7] COPY web-service-gin/*.go ./                                                                                                                                                                                     0.0s
------
 > [6/7] COPY web-service-gin/*.go ./:
------
lstat /var/lib/docker/tmp/buildkit-mount2300658010/web-service-gin: no such file or directory
xdotsuka@JARVIS:~/projects/go-challenge/web-service-gin$ docker build --tag go-challenge/web-service-gin .
[+] Building 6.5s (16/16) FINISHED                                                                                                                                                                                                    
 => [internal] load build definition from Dockerfile                                                                                                                                                                             0.0s
 => => transferring dockerfile: 349B                                                                                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                0.0s
 => => transferring context: 2B                                                                                                                                                                                                  0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                                                                                                                                       1.3s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:443aab4ca21183e069e7d8b2dc68006594f40bddf1b15bbd83f5137bd93e80e2                                                                                                  0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                0.0s
 => [internal] load metadata for docker.io/library/golang:1.16-alpine                                                                                                                                                            1.2s
 => [internal] load build context                                                                                                                                                                                                0.0s
 => => transferring context: 1.81kB                                                                                                                                                                                              0.0s
 => [1/7] FROM docker.io/library/golang:1.16-alpine@sha256:5616dca835fa90ef13a843824ba58394dad356b7d56198fb7c93cbe76d7d67fe                                                                                                      0.0s
 => CACHED [2/7] WORKDIR /app                                                                                                                                                                                                    0.0s
 => CACHED [3/7] COPY go.mod ./                                                                                                                                                                                                  0.0s
 => CACHED [4/7] COPY go.sum ./                                                                                                                                                                                                  0.0s
 => CACHED [5/7] RUN go mod download                                                                                                                                                                                             0.0s
 => [6/7] COPY *.go ./                                                                                                                                                                                                           0.0s
 => [7/7] RUN go build -o /web-service-gin                                                                                                                                                                                       2.5s
 => exporting to image                                                                                                                                                                                                           1.1s
 => => exporting layers                                                                                                                                                                                                          1.1s
 => => writing image sha256:9268c88b01e46edca48f6d1a1e7bb62529385707ab654c55f60b8f094d825cdb                                                                                                                                     0.0s
 => => naming to docker.io/go-challenge/web-service-gin                                                                                                                                                                          0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them


imageが作れました。

$ docker image ls | grep go-challenge
go-challenge/web-service-gin                              latest                                                                       9268c88b01e4   15 minutes ago   451MB

Multi-stage builds とやら

前の section で image を作成しました。
しかし、 image の size を確認すると 451MB もありました。
gin で sample 程度に作ったAPIでさえこの程度なのでやはり base imageも含めてbundleすると sizeが大きくなりやすいようです。
そこで docker には Multi-stage builds というソリューションがあります。
Build, Deployの stageごとに image を分けることができ、その stage で必要なものを別々に書くことができるそうです。

goは Build stageで build した バイナリさえ配置すれば動作する。
そのため、Deploy stage では base imageから golang を排除できるのです。

詳しくは下記
https://docs.docker.com/language/golang/build-images/#multi-stage-builds

# syntax=docker/dockerfile:1

##
## Build
##
FROM golang:1.16-buster AS build

WORKDIR /app

# Download necessary Go modules
COPY go.mod ./
COPY go.sum ./

RUN go mod download

COPY *.go ./

RUN go build -o /web-service-gin

##
## Deploy
##
FROM gcr.io/distroless/base-debian10

WORKDIR /

COPY --from=build /web-service-gin /web-service-gin

EXPOSE 8080

USER nonroot:nonroot

ENTRYPOINT ["/web-service-gin"]

$ docker image ls | grep go-challenge
go-challenge/web-service-gin                              latest                                                                       9594d4ff3919   37 seconds ago   28.9MB

28.9MBまで小さくなった。これは今後も活用していきたい。
ちなみに今回用いた base image は package managerや shellさえ有していない超コンパクトなものらしい。prodで使うためにはモニタリング機能作りこまんと厳しいですね。
https://github.com/GoogleContainerTools/distroless

コンテナを立ち上げる

さあおまちかね、docker run する。portは 8080 to 8081で開ける。

$ docker run -d --publish 8080:8081 go-challenge/web-service-gin
6044531d60171ef494d52096f10b9b9c6e3588e2c3ef07bac850480cb6729b05

そして 疎通確認

curl -v  http://localhost:8080/albums
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /albums HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Wed, 22 Jun 2022 14:35:45 GMT
< Content-Length: 382
< 
[
    {
        "id": "1",
        "title": "Blue Train",
        "artist": "John Coltrane",
        "price": 56.99
    },
    {
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
    },
    {
        "id": "3",
        "title": "Sarah Vaughan and Clifford Brown",
        "artist": "Sarah Vaughan",
        "price": 39.99
    }
* Connection #0 to host localhost left intact

事前に arrayに登録しておいた ものが取得できました。
dockerize完了w

さいごに

go の sample を docker コンテナ上で動作させることができた。
次は local machine で build debug するのを docker 上でどうやるのか気になるのでそこを調べたい。

GitHubで編集を提案

Discussion