在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式:
全部放入一个 Dockerfile
一种方式是将所有的构建过程编包含在一个 Dockerfile
中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:
- 镜像层次多,镜像体积较大,部署时间变长
- 源代码存在泄露的风险
例如,编写 app.go
文件,该程序输出 Hello World!
package main
import "fmt"
func main(){
fmt.Printf("Hello World!");
}
编写 Dockerfile.one
文件
FROM golang:alpine
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root
WORKDIR /root/
CMD ["./app"]
构建镜像
$ docker build -t go/helloworld:1 -f Dockerfile.one .
多阶段构建的优势
自 Docker 17.05 版本起,引入了多阶段构建(multi-stage builds)的概念,这种方式能有效解决上述问题。多阶段构建允许我们在一个 Dockerfile
中定义多个 FROM
指令,从而在构建过程中生成多个临时镜像,这样可以减小最终镜像的体积,并避免将源代码暴露在生产环境中。
多阶段构建的示例
以下是一个使用多阶段构建的示例,演示如何构建一个更为精简的 Docker 镜像。
编写 Dockerfile
文件:
# 第一阶段:构建阶段
FROM golang:alpine AS builder
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
# 获取依赖并构建
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 第二阶段:运行阶段
FROM alpine:latest
# 复制构建好的二进制文件
WORKDIR /root/
COPY /go/src/github.com/go/helloworld/app .
# 运行应用程序
CMD ["./app"]
构建镜像
使用以下命令构建镜像:
$ docker build -t go/helloworld:multi .
运行镜像
使用以下命令运行生成的镜像:
$ docker run --rm go/helloworld:multi
总结
多阶段构建 Docker 镜像的方式,不仅可以优化镜像的体积,还能提高构建的安全性,避免将源代码暴露到最终镜像中。通过使用 COPY --from=builder
指令,我们可以轻松地将需要的文件从构建阶段复制到运行阶段,确保最终镜像中仅包含必要的二进制文件和资源。这种构建方式对于构建微服务和容器化应用特别有用,能够提升持续集成和持续部署的效率。