编写了一个go程序:
package main
import "fmt"
func main () {
fmt.Println("Hello, world!")
}
编写Dockerfile文件:
FROM golang # 基础镜像
COPY hello.go # 复制内容到镜像
RUN go build hello.go # 运行go命令
CMD ["./hello"] # 执行生成的hello文件
执行Dockerfile
docker build -f dockerfile -t myhellogo .
-f:指定Dockerfile文件名;
-t:指定生成的新镜像的镜像名
.:表示当前目录
运行生成的这个镜像:
docker run -it myhellogo
我们会发现,编译hello.go生成的可执行文件的大小只有2MB。
然而所生成镜像的大小:852MB!!!
这是因为我们FROM golang,该镜像包含了整个golang镜像的内容。解决办法两个:1. 多阶段构建;2.
多阶段构建
B站学习视频
主要思想:我不想在最终的镜像中包含一堆例如Go编译器和整个编译工具链,我只需要一个编译好的可执行文件。
多阶段构建实际上是由多个FROM
指令识别,每一个FROM语句表示一个新的构建阶段,同时可以用AS参数指定构建阶段名。
如下:
FROM golang
WORKDIR /src
COPY hello.go .
RUN go build hello.go
FROM ubuntu
COPY --from=0 /src/hello .
CMD ["./hello"]
docker build之后会发现生成了一个新的镜像,我们来看一下这两个镜像大小的对比:
使用多阶段构建构建出来的镜像单阶段构建的镜像少了约91%。
另外,建议在第一阶段使用经典的基础镜像,例如Ubuntu、Centoos、Debian、Fedora
。(Alpine虽然很小但是还是存在一些坑的)。
COPY --from,使用的是绝对路径
从上一个构建阶段拷贝文件时,使用的路径是相对于上一阶段的根目录的。
最好的方法是在第一阶段指定 WORKDIR,在第二阶段使用绝对路径拷贝文件,这样即使基础镜像修改了 WORKDIR,也不会影响到镜像的构建。
FROM scratch
scratch 是一个虚拟镜像,不能被 pull,也不能运行,因为它表示空、nothing!这就意味着新镜像的构建是从零开始,不存在其他的镜像层。
FROM golang
WORKDIR /src
COPY hello.go .
RUN go build hello.go
FROM scratch
COPY --from=0 /src/hello .
CMD ["./hello"]
docker build之后我们查看镜像:
本来hello.go这个文件生成的可执行文件只有2MB,我们使用这个方法构建出来的镜像最终大小变成了2MB。压缩到极致了。
但是使用scratch仍有许多不便:缺少shell。
这就意味着 CMD/RUN 语句中不能使用字符串。解决方法:使用 JSON 语法取代字符串语法。
如下是错误的:
FROM scratch
COPY --from=0 /go/hello .
CMD ./hello
镜像中并不包含 /bin/sh,所以无法运行程序。这是因为当你在 CMD/RUN 语句中使用字符串作为参数时,这些参数会被放到 /bin/sh 中执行( = CMD /bin/sh -c “./hello”)。
改成如下就正确了:
FROM scratch
COPY --from=0 /go/hello .
CMD ["./hello"]
这样 Docker 就会直接运行程序,不会把它放到 shell 中运行。
参考。