如何尽可能构建小体积的docker镜像

301 阅读8分钟

镜像作为 Docker 技术中关键的一环,日常开发中的一个关键工作就是将我们的应用构建成镜像,如果镜像体积过大的话,会延长应用启动时间,降低发布效率。

构建最小镜像有以下几个重要意义

  • 启动速度快:较小的镜像文件可以更快地从仓库下载到本地,同时在容器启动时加载更快,提供更快的应用启动时间,特别是在大规模容器编排系统中。
  • 节省网络带宽:小镜像占用的网络带宽较少,特别在分布式系统中,减少了容器之间镜像的传输所需的带宽。
  • 减少存储空间:小镜像占用的磁盘空间较小,可以减少存储成本,特别是在大规模部署时,这种差异将变得明显。
  • 提高安全性:精简的镜像通常减少了系统中不必要的组件和依赖,减小了潜在的安全漏洞,提高了系统的安全性。
  • 更好的可移植性:小镜像更容易在不同环境中部署和移植,降低了因为环境差异带来的问题。

基础镜像选择

使用完整的操作系统镜像可能会带来不必要的开销,特别是在容器化环境中。对于许多应用程序来说,只需基本的运行时环境和所需的依赖项,而不需要整个操作系统。这就是为什么轻量级的基础镜像(比如Alpine Linux)变得非常受欢迎的原因。这些镜像通常只包含最小化的操作系统组件和运行时库,使其更小、更快,并且更适合容器化部署

定制化的操作系统镜像可以根据具体应用的需求构建,只包含必要的组件和文件。这种定制化的方式可以大大减小镜像的体积,提高容器启动速度,同时减少了潜在的安全风险。因此,在容器化应用时,精简镜像是一种很好的实践。

直接在操作系统镜像中安装开发环境不仅引入了不必要的文件,还可能导致镜像变得庞大且不易管理。为了解决这个问题,可以使用多阶段构建(multi-stage builds)来创建定制化的镜像。

多阶段构建允许在一个Dockerfile中定义多个构建阶段,每个阶段可以基于不同的基础镜像,并且最终镜像只包含在最后一个构建阶段生成的文件。这种方法既可以保证在构建应用时拥有所需的开发环境,又能生成小巧的最终镜像,减小了部署时的开销。

多阶段构建

多阶段构建是Docker 17.05版本引入的一个重要特性,也被称为"multi-stage builds"。它允许开发者在单个Dockerfile中定义多个构建阶段,每个阶段使用不同的基础镜像,并且最终生成的镜像只包含在最后一个构建阶段生成的文件。这种特性不仅帮助减小了最终生成镜像的体积,同时也保持了Dockerfile文件的可读性和可维护性。

通过多阶段构建,开发者可以更加灵活地控制镜像的生成过程,避免不必要的依赖和文件进入最终的镜像,从而提高了镜像的安全性、性能和可移植性。这种方式在构建容器化应用时变得非常实用和推荐使用。

先解释一下下列dockerfile文件

FROM golang:1.13.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM golang:1.13.3:

这行指令从Docker Hub上拉取了一个名为"golang"的官方镜像,版本号为1.13.3。这个基础镜像包含了Golang编程语言的运行时环境和一些基本工具,可以用来构建和运行Golang应用程序。

WORKDIR /go/src/github.com/alexellis/href-counter/:

这行指令设置了容器内的工作目录,将其切换到/go/src/github.com/alexellis/href-counter/。在这个目录下,后续的命令将被执行。

RUN go get -d -v golang.org/x/net/html:

这行指令使用go get命令下载并安装"golang.org/x/net/html"包及其依赖。这个包是Golang标准库中的一部分,用于处理HTML文档。

COPY app.go .

这行指令将主机(构建上下文)中的"app.go"文件复制到容器的当前工作目录(/go/src/github.com/alexellis/href-counter/)。这是应用程序的源代码文件。

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .:

这行指令使用go build命令编译应用程序。具体参数的含义如下:

  • CGO_ENABLED=0:禁用CGO,确保生成的二进制文件与基础镜像兼容。
  • GOOS=linux:指定编译后的二进制文件可以在Linux系统上运行。
  • -a:强制重新构建所有包,即使它们看起来是最新的。
  • -installsuffix cgo:安装后缀为cgo,用于确保在CGO禁用的情况下链接正确的库。
  • -o app .:将输出二进制文件命名为"app",并且将编译的源代码文件在当前目录中。

这段Dockerfile的作用是在容器内部构建了一个名为"app"的Golang应用程序,该程序的源代码文件是"app.go"。生成的二进制文件可以在Linux系统上运行,最终构建的镜像包含了这个二进制文件,可以用来运行该Golang应用程序。

对于上述代码,如果采取简单的部署方式,直接修改原dockerfile文件

FROM golang:1.13.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

CMD ["./app"]

scratch是一个特殊的Docker基础镜像,它为空镜像,不包含任何文件系统。因此,如果你的应用程序是静态编译的,不依赖于操作系统的文件,可以使用scratch镜像作为基础,从而生成非常小巧的镜像。这种方式避免了引入操作系统的任何额外文件,使得镜像非常轻量。

然而使用COPY指令将文件复制到容器中可能引入额外的文件和依赖,增大镜像的体积。为了避免这个问题,文中提到了通过使用卷(volume)的方式,将容器内的目录映射到宿主机的目录,然后进行手动拷贝文件。但是,这种手动操作可能引入人为错误,并且不够自动化,容易出现问题。

为了解决上述问题,多阶段构建允许在同一个Dockerfile中定义多个构建阶段,每个阶段可以使用不同的基础镜像。在每个阶段中,你可以按需安装依赖、复制文件、编译代码等。最终,只将你需要的文件从一个或多个构建阶段复制到最终的镜像中,从而生成一个精简、小巧的镜像。这样的做法既可以保持自动化,又可以避免引入不必要的文件和依赖,生成的镜像体积更小,适用于生产环境的部署。


多阶段构建允许在一个Dockerfile中使用多个FROM指令。每个FROM指令定义了一个构建阶段(stage),每个阶段可以使用不同的基础镜像。在每个阶段中,可以执行一系列的命令,例如安装依赖、编译代码等。每个阶段的结果被保留下来,但只有最后一个阶段的结果会成为最终的镜像。这种方式可以帮助精简镜像,去除构建过程中产生的临时文件和不必要的依赖,最终生成一个更小、更高效的Docker镜像。

FROM golang:1.13.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  

第一个阶段(基础版本):

FROM golang:1.13.3:使用Golang 1.13.3作为基础镜像。

WORKDIR /go/src/github.com/alexellis/href-counter/:设置工作目录,后续的命令将在这个目录下执行。

RUN go get -d -v golang.org/x/net/html:下载并安装应用程序的依赖。

COPY app.go .:将主机上的app.go文件复制到容器的当前工作目录。

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .:编译应用程序,并将生成的可执行文件命名为app。

第二个阶段(构建最终镜像):

FROM alpine:latest:使用Alpine Linux作为基础镜像。

RUN apk --no-cache add ca-certificates:安装CA证书,确保应用程序可以进行HTTPS请求。

WORKDIR /root/:设置工作目录为/root/。

COPY --from=0 /go/src/github.com/alexellis/href-counter/app .:从第一个阶段的构建结果中,将编译好的app可执行文件复制到当前工作目录。

CMD ["./app"]:在容器启动时执行./app命令,启动应用程序。

这个Dockerfile的优化在于使用了两个阶段。第一个阶段构建Golang应用程序,第二个阶段只保留了必要的文件(即可执行文件和CA证书),去除了编译过程中产生的不必要的依赖和临时文件。最终生成的镜像相对较小,只包含运行应用程序所需的文件和依赖,适