go のモジュールを dockerize してみる
はじめに
私のPJでは 既存のCICD stackの開発と適用が進んでおり、それを利用しているだけで済んでしまうので core な仕様の理解まで及んでいない。
日頃 業務で コンテナ を使うときは ECSやEKSにログインしてログをみてアプリケーションのトラブルシュートするケースが主である。
そこで、自分で0からやってみようと思った。
今回は 俗世間の web エンジニアなどがよくやっている repo の dockerize(docker化)? を試していきたい。
無知なのでまず勉強
わかった気になるのが一番怖いのでまずは知識をinput する
Dockerfile の書き方について勉強する。
inputしたものは下記
- 公式docker docs にそれっぽいのがあった。主にこれが勉強になった。これに沿ってやっていくのが良さそう。
https://docs.docker.com/language/golang/build-images/
学んだことを吐き出してみる
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 を排除できるのです。
詳しくは下記
# 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で使うためにはモニタリング機能作りこまんと厳しいですね。
コンテナを立ち上げる
さあおまちかね、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 上でどうやるのか気になるのでそこを調べたい。
Discussion