无锡网站推广公司,做全英文网站,建立一个网站,电子商务服务平台文章目录 环境步骤准备例1#xff1a;基本用法例2#xff1a;缓存layer例3#xff1a;Multi-stage例4#xff1a;Mountcache mountbind mount 例5#xff1a;参数例6#xff1a;Export文件例7#xff1a;测试 参考 环境
RHEL 9.3Docker Community 24.0.7
步骤
在Dock… 文章目录 环境步骤准备例1基本用法例2缓存layer例3Multi-stage例4Mountcache mountbind mount 例5参数例6Export文件例7测试 参考 环境
RHEL 9.3Docker Community 24.0.7
步骤
在Docker的官网上 https://docs.docker.com/build/guide/ 有一个现成的hands-on例子。
准备
首先克隆 buildme 项目
git clone https://github.com/dockersamples/buildme.git其结构如下
➜ buildme git:(main) tree
.
├── chapters
│ ├── 1.Dockerfile
│ ├── 2.Dockerfile
│ ├── 3.Dockerfile
│ ├── 4.Dockerfile
│ ├── 5.Dockerfile
│ ├── 6.Dockerfile
│ ├── 7.Dockerfile
│ └── 8.Dockerfile
├── cmd
│ ├── client
│ │ ├── main.go
│ │ ├── request.go
│ │ └── ui.go
│ └── server
│ ├── main.go
│ └── translate.go
├── Dockerfile
├── go.mod
├── go.sum
├── README.md
└── Taskfile.yml4 directories, 18 files注 chapters 目录和 Taskfile.yml 文件只是为了方便快速切换 Dockerfile 文件的内容。它使用了 task 工具这是一个基于Go的构建工具其安装和用法参见 https://taskfile.dev。
实际上 task 工具和本例中的Go项目并没有直接关联。对于本例来说使用该工具只是为了方便把 chapters 目录下的某个Dockerfile文件覆盖到项目的根目录下。具体命令为 task goto:N 。若不想用该工具可以直接无视之。
例1基本用法
打开 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY . .
RUN go mod download
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ /bin/server ]基本上每一行就是一条指令 # syntaxdocker/dockerfile:1 这是一个解析器指令parser directive指定了Dockerfile的语法版本。 FROM golang:1.20-alpine 指定parent image golang 和其版本 1.20-alpine 。 WORKDIR /src 容器的工作目录即操作容器时的当前目录若目录不存在则会被创建。 COPY . . 从宿主机复制文件/目录到容器里。目标地址可以是绝对路径或相对路径若是相对路径则以前面指定的工作目录为基础。 RUN go mod download 运行命令下载所需的Go module。 RUN go build -o /bin/client ./cmd/client 同上构建client程序。 RUN go build -o /bin/server ./cmd/server 同上构建server程序。 ENTRYPOINT [ /bin/server ] 指定当启动容器时运行的命令。本例中就是启动server。
接下来开始构建
docker build --tagbuildme .运行报错了试了几次都报错如下
➜ buildme git:(main) docker build --tagbuildme .
[] Building 151.9s (10/12) docker:default [internal] load .dockerignore 0.0s transferring context: 2B 0.0s [internal] load build definition from Dockerfile 0.0s transferring dockerfile: 304B 0.0s resolve image config for docker.io/docker/dockerfile:1 0.7s CACHED docker-image://docker.io/docker/dockerfile:1sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s [internal] load metadata for docker.io/library/golang:1.20-alpine 0.7s [1/6] FROM docker.io/library/golang:1.20-alpinesha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s [internal] load build context 0.0s transferring context: 6.46kB 0.0s CACHED [2/6] WORKDIR /src 0.0s CACHED [3/6] COPY . . 0.0s ERROR [4/6] RUN go mod download 150.3s
------ [4/6] RUN go mod download:
150.3 go: github.com/atotto/clipboardv0.1.4: Get https://proxy.golang.org/github.com/atotto/clipboard/v/v0.1.4.mod: dial tcp 142.251.43.17:443: i/o timeout
------
Dockerfile:5
--------------------3 | WORKDIR /src4 | COPY . .5 | RUN go mod download6 | RUN go build -o /bin/client ./cmd/client7 | RUN go build -o /bin/server ./cmd/server
--------------------
ERROR: failed to solve: process /bin/sh -c go mod download did not complete successfully: exit code: 1注有些步骤是 CACHED 是因为运行了好几次而这些步骤在之前构建时是成功的。关于缓存详见例2。
从报错信息可以判断处网站连接有问题解决方法是设置代理。
编辑 Dockerfile 文件如下
......
COPY . .
RUN go env -w GOPROXYhttps://goproxy.cn # 添加这一行
RUN go mod download
......保存再次运行 docker build --tagbuildme . 这次成功了。
查看image
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 773f384bf110 2 minutes ago 416MB
......接下来运行容器
➜ buildme git:(main) ✗ docker run --namebuildme --rm --detach buildme
f1d6e9038ee74d6524fa6c614e7ff133852ab7fd24e59f7c188438949b7bb828其中
--rm 在退出容器时自动将其删除。--detach 在后台运行容器。
查看容器
➜ buildme git:(main) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f1d6e9038ee7 buildme /bin/server 10 seconds ago Up 9 seconds buildme进入容器
docker exec -it buildme /bin/client如下 Translate a message...╭─────────────────────────╮│ Hit Enter to translate. │╰─────────────────────────╯CtrlC to exit.输入 hello world 回车如下 Translate a message...╭───────────────────────────────────────╮│ Input: hello world ││ Translation: hohelollolo wowororloldo │╰───────────────────────────────────────╯CtrlC to exit.测试完毕按 Ctrl C 退出。
停止容器
docker stop buildme例2缓存layer
粗略的讲每一条build指令会转换为一个image layer。 构建时会尽量重用之前已经构建好的layer。如果一个layer没有修改过则builder会从build cache里获取但如果layer有修改则它和随后的layer都会重新build。
本例中如果 COPY 指令的源没有发生变化则再次构建时会从cache里获取速度快很多。
➜ buildme git:(main) ✗ docker build --tagbuildme .
[] Building 3.0s (14/14) FINISHED docker:default [internal] load build definition from Dockerfile 0.0s transferring dockerfile: 345B 0.0s [internal] load .dockerignore 0.0s transferring context: 2B 0.0s resolve image config for docker.io/docker/dockerfile:1 1.7s CACHED docker-image://docker.io/docker/dockerfile:1sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s [internal] load metadata for docker.io/library/golang:1.20-alpine 1.2s [1/7] FROM docker.io/library/golang:1.20-alpinesha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s [internal] load build context 0.0s transferring context: 6.46kB 0.0s CACHED [2/7] WORKDIR /src 0.0s CACHED [3/7] COPY . . 0.0s CACHED [4/7] RUN go env -w GOPROXYhttps://goproxy.cn 0.0s CACHED [5/7] RUN go mod download 0.0s CACHED [6/7] RUN go build -o /bin/client ./cmd/client 0.0s CACHED [7/7] RUN go build -o /bin/server ./cmd/server 0.0s exporting to image 0.0s exporting layers 0.0s writing image sha256:773f384bf110eaaf76123cec3e6072cef7868780da02929875e37909eee60c83 0.0s naming to docker.io/library/buildme可以看到从第1步到第7步都是 CACHED 。
查看image
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 773f384bf110 45 minutes ago 416MB
......从 CREATED 的值可见实际上并没有重新构建image因为每一步都没有发生变化。
接下来我们修改源文件比如对 cmd/client/main.go 文件添加一个注释然后再次构建
➜ buildme git:(main) ✗ docker build --tagbuildme .
[] Building 26.8s (14/14) FINISHED docker:default [internal] load .dockerignore 0.0s transferring context: 2B 0.0s [internal] load build definition from Dockerfile 0.0s transferring dockerfile: 345B 0.0s resolve image config for docker.io/docker/dockerfile:1 1.4s CACHED docker-image://docker.io/docker/dockerfile:1sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s [internal] load metadata for docker.io/library/golang:1.20-alpine 1.0s [1/7] FROM docker.io/library/golang:1.20-alpinesha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s [internal] load build context 0.0s transferring context: 6.69kB 0.0s CACHED [2/7] WORKDIR /src 0.0s [3/7] COPY . . 0.0s [4/7] RUN go env -w GOPROXYhttps://goproxy.cn 0.2s [5/7] RUN go mod download 3.2s [6/7] RUN go build -o /bin/client ./cmd/client 19.1s [7/7] RUN go build -o /bin/server ./cmd/server 1.2s exporting to image 0.6s exporting layers 0.6s writing image sha256:05c59ce84ab98012b090ee3a6a67f6e1f2e9e998f81c56b18fdca04fe1dc6d6a 0.0s naming to docker.io/library/buildme可见第3步没有从cache获取因为 COPY 指令的源发生变化了。注意随后的所有步骤也都重新构建了。
查看image
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 05c59ce84ab9 About a minute ago 416MB
none none 773f384bf110 47 minutes ago 416MB
......显然如果指令之间没有依赖关系那么应该尽量把不变的步骤放在前面。
本例中 go mod download 是不变的且非常耗时但问题是 go mod download 下载的package是在源代码里指定的所以不能把它放在 COPY 指令前面。
解决办法Go有两个记录项目依赖的文件叫做 go.mod 和 go.sum 注这两个文件的作用类似于JavaScript里的 package.json 和 package-lock.json 。我们可以利用这两个文件来判断 go mod download 是否需要重新构建。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXYhttps://goproxy.cn
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ /bin/server ]添加了 COPY go.mod go.sum . 。如果只是修改了源代码 go.mod 和 go.sum 没有变化则 go mod download 无需重新构建。
构建一下修改源代码然后再次构建就可以看到效果
➜ buildme git:(main) ✗ docker build --tagbuildme .
[] Building 14.6s (15/15) FINISHED docker:default [internal] load .dockerignore 0.0s transferring context: 2B 0.0s [internal] load build definition from Dockerfile 0.0s transferring dockerfile: 366B 0.0s resolve image config for docker.io/docker/dockerfile:1 0.7s CACHED docker-image://docker.io/docker/dockerfile:1sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s [internal] load metadata for docker.io/library/golang:1.20-alpine 0.7s [1/8] FROM docker.io/library/golang:1.20-alpinesha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s [internal] load build context 0.0s transferring context: 6.70kB 0.0s CACHED [2/8] WORKDIR /src 0.0s CACHED [3/8] COPY go.mod go.sum . 0.0s CACHED [4/8] RUN go env -w GOPROXYhttps://goproxy.cn 0.0s CACHED [5/8] RUN go mod download 0.0s [6/8] COPY . . 0.0s [7/8] RUN go build -o /bin/client ./cmd/client 11.1s [8/8] RUN go build -o /bin/server ./cmd/server 1.3s exporting to image 0.5s exporting layers 0.5s writing image sha256:fb86a9ea452afaec4f0c4f58248feef5a4447348fa90df089f5fc28abc8b4309 0.0s naming to docker.io/library/buildme可见从第1步到第5步都是从cache获取。
例3Multi-stage
优点
并行构建更快更高效最小化image只包含必需的东西
先前的例子里只用了一个stageimage大小为416MB但实际上里面有很多东西是不需要的。
修改 Dockerfile 文件添加一个 scratch stage注“from scratch”是“从零开始”的意思如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXYhttps://goproxy.cn
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/serverFROM scratch
COPY --from0 /bin/client /bin/server /bin/
ENTRYPOINT [ /bin/server ]在最终的image里只保留 client 和 server 两个文件。
重新构建然后查看image
➜ buildme git:(main) ✗ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c8582385a23d 23 seconds ago 15.9MB可见image从原先的416MB减小到了15.9MB。
测试image确保其工作正常。
接下来继续优化。本例中client和server是串行构建的。由于构建client和构建server相互独立为了提高效率可以改为并行构建。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXYhttps://goproxy.cn
RUN go mod download
COPY . .FROM base AS build-client
RUN go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN go build -o /bin/server ./cmd/serverFROM scratch
COPY --frombuild-client /bin/client /bin/
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]重新构建观察构建过程可见client和server是一起构建的。
测试image确保其工作正常。
经过上述优化image小了很多client和server是并行构建的。接下来还可以进一步优化把client和server分成两个不同的image。
同一个Dockerfile可以构建不同的image方法是在构建时通过 --target 选项指定目标stage。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXYhttps://goproxy.cn
RUN go mod download
COPY . .FROM base AS build-client
RUN go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]然后用不同的命令构建client和server
client
docker build --tagbuildme-client --targetclient .server
docker build --tagbuildme-server --targetserver .查看image
➜ buildme git:(main) ✗ docker images buildme*
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-server latest f26347f19b1e 3 seconds ago 7.91MB
buildme-client latest f92304f7b995 9 minutes ago 7.98MB
buildme latest 36bf26ddaf59 9 minutes ago 15.9MB可见把client和server分开后各自的image更小了。
注意如果指定了目标stage则Docker只运行相关的stage。本例中如果指定构建client则 build-server 和 server stage会被略过。同理如果指定构建server则 build-client 和 client stage会被略过。
注把client和server分开后server能够运行但是client无法连接到server因为指定的是 http://localhost 。需要做一些额外处理才行已超出本文范围不做赘述。
例4Mount
本例将涉及以下两种mount
cache mountbind mount
cache mount
顾名思义就是把文件做缓存。我感觉它像是一个全局的目录大家都可以访问它向其做读写操作。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \go mod download -x
COPY . .FROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mounttypecache,target/go/pkg/mod/ \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]注 go mod download 命令的 -x 选项会打印出下载的情况。感觉有点类似bash的 -x 选项。
在重新构建之前先清掉cache
docker builder prune -af其中
-a 表示all-f 表示force
注可以用 docker builder prune --help 命令查看帮助。
重新构建
docker build --targetclient --progressplain . 2 log1.txt注意添加了 --progressplain 选项同时把输出貌似 2 代表错误输出stderr重定向到 log1.txt 文件。
查看 log1.txt 文件
......
#12 0.299 # get https://goproxy.cn/github.com/charmbracelet/bubbletea/v/v0.23.1.mod
#12 0.299 # get https://goproxy.cn/github.com/aymanbagabas/go-osc52/v/v1.0.3.mod
#12 0.299 # get https://goproxy.cn/github.com/charmbracelet/bubbles/v/v0.14.0.mod
#12 0.300 # get https://goproxy.cn/github.com/atotto/clipboard/v/v0.1.4.mod
#12 0.361 # get https://goproxy.cn/github.com/atotto/clipboard/v/v0.1.4.mod: 200 OK (0.061s)
#12 0.361 # get https://goproxy.cn/github.com/charmbracelet/bubbletea/v/v0.23.1.mod: 200 OK (0.062s)
#12 0.361 # get https://goproxy.cn/github.com/charmbracelet/bubbles/v/v0.14.0.mod: 200 OK (0.062s)
#12 0.362 # get https://goproxy.cn/github.com/aymanbagabas/go-osc52/v/v1.0.3.mod: 200 OK (0.062s)
#12 0.363 # get https://goproxy.cn/github.com/charmbracelet/lipgloss/v/v0.6.0.mod
#12 0.363 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.0.mod
#12 0.363 # get https://goproxy.cn/github.com/containerd/console/v/v1.0.3.mod
#12 0.364 # get https://goproxy.cn/github.com/lucasb-eyer/go-colorful/v/v1.2.0.mod
#12 0.379 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.0.mod: 200 OK (0.016s)
......把package chi 的版本改为 v5.0.8
docker run -v $PWD:$PWD -w $PWD golang:1.20-alpine \go get github.com/go-chi/chi/v5v5.0.8注意原文档上是 golang:1.21-alpine 但是git里都是 golang:1.20-alpine 所以我用了后者。
由于网络连接问题运行报错如下
go: github.com/atotto/clipboardv0.1.4: Get https://proxy.golang.org/github.com/atotto/clipboard/v/v0.1.4.mod: dial tcp 172.217.163.49:443: i/o timeout解决办法还是添加代理。
查看 docker run 的帮助如下
➜ buildme git:(main) ✗ docker help runUsage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
......只能运行一条命令。要想运行多条命令需要迂回一下
docker run -v $PWD:$PWD -w $PWD golang:1.20-alpine \sh -c go env -w GOPROXYhttps://goproxy.cn; go get github.com/go-chi/chi/v5v5.0.8查看 go.mod
......github.com/go-chi/chi/v5 v5.0.8
......注原先是 v5.0.0 。
查看image
➜ buildme git:(main) ✗ docker images golang
REPOSITORY TAG IMAGE ID CREATED SIZE
golang 1.20-alpine f62d76c5566c 2 weeks ago 255MB并没有变化。
现在再构建一次
docker build --targetclient --progressplain . 2 log2.txt查看 log2.txt 文件
......
#12 [base 6/7] RUN --mounttypecache,target/go/pkg/mod/ go mod download -x
#12 0.283 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.mod
#12 0.353 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.mod: 200 OK (0.071s)
#12 0.354 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.info
#12 0.372 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.info: 200 OK (0.018s)
#12 0.374 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.zip
#12 0.393 # get https://goproxy.cn/github.com/go-chi/chi/v5/v/v5.0.8.zip: 200 OK (0.019s)
#12 DONE 0.5s
......可见只下载了和 chi 相关的package。
注这应该是与Go的module管理机制有关它用到了 /go/pkg/mod/ 目录不然它怎么知道这次只需下载 chi 的 v5.0.8 版本呢。
bind mount
在构建期把宿主机或者其它stage里的文件/目录mount过来。
本例中 go.mod 和 go.sum 这两个文件是复制到容器里的。通过bind mount可使容器直接访问宿主机上的文件从而省略所对应的 COPY 指令。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -x
COPY . .FROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mounttypecache,target/go/pkg/mod/ \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]本例中 sourcego.sum 这是宿主机上的文件貌似只能用相对路径以宿主机当前目录为基础不能用绝对路径否则会报错找不到。
另外source也可以指定为其它stage要加上 fromstage 选项。
mount的文件/目录只在构建期的当前指令范围内可见。
本例中挂载的是文件若挂载的是目录则该目录是只读的。
注docker run 命令也可以做bind mount当mount宿主机的目录时该目录并不是只读的。
同理也可以把下面的 COPY 指令做相同处理。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -xFROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]注意本例中没有指定source我在官网文档没有查到其默认值是什么不过通过试验可知source的默认值应该是 . 宿主机的当前目录。
例5参数
本例中parent image指定为 golang:1.20-alpine 。为了随时想切换到别的版本我们可以在Dockerfile里使用变量而在构建时传入所需的版本号。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
ARG GO_VERSION1.20
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -xFROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]可以在构建时通过 --build-arg 选项传入参数
docker build --targetclient --build-argGO_VERSION1.19 .如果不传入参数则使用其缺省值 1.20 。
同理也可以在构建时把参数传到源代码里。
本例中 cmd/server/main.go 文件内容如下
......
var version stringfunc main() {if version ! {log.Printf(Version: %s, version)}
......在Go语言中通过 -ldflags 选项传入参数。例如
go build -ldflags -X main.versionv0.0.1 -o /bin/server ./cmd/server因此在Dockerfile里可以设置变量 APP_VERSION 在构建时传入参数。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
ARG GO_VERSION1.20
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -xFROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSIONv0.0.0unknown
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -ldflags -X main.version$APP_VERSION -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]构建server
docker build --targetserver --build-argAPP_VERSIONv0.0.1 --tagbuildme-server .运行server
➜ buildme git:(main) ✗ docker run buildme-server
2023/12/29 14:39:02 Version: v0.0.1
2023/12/29 14:39:02 Starting server...
2023/12/29 14:39:02 Listening on HTTP port 3000例6Export文件
docker build 默认的输出是容器image。image被载入image store你可以为该image启动一个容器或者把它push到registry。这种行为使用的是缺省的exporter称为 docker exporter。
你也可以使用 local exporter其构建结果为文件。在构建时传入 --output 选项指定目标路径。例如
docker build --output. --targetserver .本例中指定目标路径为当前目录。注意实际export的路径为宿主机的 ./bin/server 这是因为Dockerfile指定的目标路径是 /bin/server 。
查看文件
➜ buildme git:(main) ✗ ll bin
total 7.6M
-rwxr-xr-x. 1 ding ding 7.6M Dec 29 22:52 server如果想要把client和server一起export可以在 Dockerfile 文件里添加一个stage如下
......
FROM scratch AS binaries
COPY --frombuild-client /bin/client /
COPY --frombuild-server /bin/server /重新构建
docker build --outputbin --targetbinaries .注为了使export的文件仍然在 ./bin 目录下因为Dockerfile里的目标路径是 / 所以构建时指定的output路径是 bin 。
查看文件
➜ buildme git:(main) ✗ ll bin
total 16M
-rwxr-xr-x. 1 ding ding 7.7M Dec 29 23:02 client
-rwxr-xr-x. 1 ding ding 7.6M Dec 29 23:02 server例7测试
本例中源代码是Go语言所以接下来我们使用 golangci-lint 来做检查比如代码中是否有错误、语法和注释是否规范等。
golangci-lint 有现成的image我们先来试用一下
docker run -v $PWD:/test -w /test \golangci/golangci-lint golangci-lint run报错如下
levelerror msgRunning error: context loading failed: failed to load packages: timed out to load packages: context deadline exceeded
levelerror msgTimeout exceeded: try increasing it by passing --timeout option解决办法按提示增加超时时间
docker run -v $PWD:/test -w /test \golangci/golangci-lint golangci-lint run --timeout10m运行结果如下
cmd/client/ui.go:5:2: strings imported and not used (typecheck)strings^
cmd/server/main.go:18:7: undefined: chi (typecheck)r : chi.NewRouter()^注和官网文档中的不太一样。我在其它几个机器操作系统分别是 Ubuntu 22.04 和 RHEL 9.2 上测试和官网文档所说的报错是一致的
cmd/server/main.go:23:10: Error return value of w.Write is not checked (errcheck)w.Write([]byte(translated))^我仔细对比了一下环境也没看出有何不同有待继续研究。
注这可能是个false alarm。如果想要修复可以把代码修改如下
......// w.Write([]byte(translated))if _, err : w.Write([]byte(translated)); err ! nil {log.Println(err)
......不过为了下面的例子演示还是先别修复了。
接下来我们将其加入到Dockerfile里。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
ARG GO_VERSION1.20
ARG GOLANGCI_LINT_VERSIONv1.52
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -xFROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSIONv0.0.0unknown
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -ldflags -X main.version$APP_VERSION -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]FROM scratch AS binaries
COPY --frombuild-client /bin/client /
COPY --frombuild-server /bin/server /FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
WORKDIR /test
RUN --mounttypebind,target. \golangci-lint run --timeout10m注意别忘了加 --timeout10m 。
构建
docker build --targetlint .运行结果如下
➜ buildme git:(main) ✗ docker build --targetlint .
[] Building 84.5s (9/9) FINISHED docker:default [internal] load .dockerignore 0.0s transferring context: 2B 0.0s [internal] load build definition from Dockerfile 0.0s transferring dockerfile: 1.25kB 0.0s resolve image config for docker.io/docker/dockerfile:1 1.5s CACHED docker-image://docker.io/docker/dockerfile:1sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s [internal] load metadata for docker.io/golangci/golangci-lint:v1.52 1.0s [lint 1/3] FROM docker.io/golangci/golangci-lint:v1.52sha256:3d2f4240905054c7efa7f4e98ba145c12a16995bbc3e605300e21400a1665cb6 0.0s [internal] load build context 0.0s transferring context: 7.00kB 0.0s CACHED [lint 2/3] WORKDIR /test 0.0s ERROR [lint 3/3] RUN --mounttypebind,target. golangci-lint run --timeout10m 81.9s
------ [lint 3/3] RUN --mounttypebind,target. golangci-lint run --timeout10m:
81.86 cmd/client/ui.go:5:2: strings imported and not used (typecheck)
81.86 strings
81.86 ^
81.86 cmd/server/main.go:18:7: undefined: chi (typecheck)
81.86 r : chi.NewRouter()
81.86 ^
------
Dockerfile:37
--------------------36 | WORKDIR /test37 | RUN --mounttypebind,target. \38 | golangci-lint run --timeout10m39 |
--------------------
ERROR: failed to solve: process /bin/sh -c golangci-lint run --timeout10m did not complete successfully: exit code: 1注意由于 golangci-lint 检测出问题exit code为1实际上构建失败了。
➜ buildme git:(main) ✗ echo $?
1注官网文档上说必须加上 --targetlint 。
接下来我们把检测结果export到文件。官网文档提供了大致思路其实和前面例6的过程是一样的步骤如下
把检测结果输出到文件。创建一个新的stage使用 scratch 作为base image复制结果文件。构建时指定 --output 选项。
官网文档没有提供具体Dockerfile而是留给读者作为练习。
要想把检测结果输出到文件可以通过输出重定向的方式我测试发现检测的结果是通过stderr输出的。
注如果想要详细的输出可以给 golangci-lint 加上 -v 选项。
另外有一点需要注意由于 golangci-lint 检测出问题实际上构建失败了随后的指令也不会再运行所以必须要忽略错误才能继续构建。
Docker是通过命令的返回值exit code来判断是否成功因此可以强制让命令返回0。
修改 Dockerfile 文件如下
# syntaxdocker/dockerfile:1
ARG GO_VERSION1.20
ARG GOLANGCI_LINT_VERSIONv1.52
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXYhttps://goproxy.cn
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,sourcego.sum,targetgo.sum \--mounttypebind,sourcego.mod,targetgo.mod \go mod download -xFROM base AS build-client
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSIONv0.0.0unknown
RUN --mounttypecache,target/go/pkg/mod/ \--mounttypebind,target. \go build -ldflags -X main.version$APP_VERSION -o /bin/server ./cmd/serverFROM scratch AS client
COPY --frombuild-client /bin/client /bin/
ENTRYPOINT [ /bin/client ]FROM scratch AS server
COPY --frombuild-server /bin/server /bin/
ENTRYPOINT [ /bin/server ]FROM scratch AS binaries
COPY --frombuild-client /bin/client /
COPY --frombuild-server /bin/server /FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
WORKDIR /test
RUN --mounttypebind,target. \golangci-lint run --timeout10m /1.out 21 || trueFROM scratch AS testresult
COPY --fromlint /1.out /注意重定向到根目录 /1.out 不能重定向到当前目录 1.out 因为在容器里当前目录是从宿主机映射而来是只读的没有指定source默认值是 . 即宿主机的当前目录。
构建
docker build --targettestresult --output. .构建成功了虽然检测出了问题。
查看 1.out 文件
cmd/client/ui.go:5:2: strings imported and not used (typecheck)strings^
cmd/server/main.go:18:7: undefined: chi (typecheck)r : chi.NewRouter()参考
https://docs.docker.com/build/guide/https://goproxy.cnhttps://www.cnblogs.com/wt645631686/p/13405161.htmlhttps://blog.51cto.com/u_16213347/7230157https://stackoverflow.com/questions/76287900/perform-docker-official-guide-still-getting-error-of-stage-build-with-docker-7https://taskfile.dev