之前的做法
关于构建镜像最具挑战性的事情之一是保持镜像体积小巧。 Dockerfile 中的每条指令都会在镜像中增加一层,并且在移动到下一层之前,需要记住清除不需要的构件。要编写一个非常高效的 Dockerfile,你通常需要使用 shell 技巧和其它方式来尽可能地减少层数,并确保每一层都具有上一层所需的构件,而其它任何东西都不需要。
实际上最常见的是,有一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容),而另一个裁剪过的用于生产环境,它只包含您的应用程序以及运行它所需的内容。这被称为“构建器模式”。但是维护两个 Dockerfile 并不理想。
在Docker17.05版本之前,我们构建Docker镜像时,通常会采用两种方式:
全部放入一个Dockerfile
一种方式是将所有的构建过程编包含在一个Dockerfile中,包括项目及其依赖 库的编译、测试、打包等流程,这里可能会带来的一些问题:
- Dockerfile特别长,可维护性降低
- 镜像层次多,镜像体积较大,部署时间变长
- 源代码存在泄露的风险
例如编写app.go文件,该程序输出HelloWorld!
package main import "fmt" func main(){ fmt.Printf("Hello World!"); }
编写 Dockerfile.one 文件
FROM golang:1.9-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 .
分散到多个Dockerfile
另一种方式,就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包 好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 Dockerfile 和一 些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种 方式存在的风险,但明显部署过程较复杂。
例如
编写 Dockerfile.build 文件
FROM golang:1.9-alpine RUN apk --no-cache add git 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 .
编写Dockerfile.copy 文件
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
新建build.sh
#!/bin/sh echo Building go/helloworld:build docker build -t go/helloworld:build . -f Dockerfile.build docker create --name extract go/helloworld:build docker cp extract:/go/src/github.com/go/helloworld/app ./app docker rm -f extract echo Building go/helloworld:2 docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy rm ./app
现在运行脚本即可构建镜像
$ chmod +x build.sh $ ./build.sh
对比两种方式生成的镜像大小
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
使用多阶段构建
为解决以上问题,Dockerv17.05开始支持多阶段构建(multistagebuilds)。 使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个Dockerfile:
例如
编写 Dockerfile 文件
FROM golang:1.9-alpine as builder RUN apk --no-cache add git WORKDIR /go/src/github.com/go/helloworld/ RUN go get -d -v github.com/go-sql-driver/mysql COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o a pp . FROM alpine:latest as prod RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/go/helloworld/app . CMD ["./app"]
构建镜像
$ docker build -t go/helloworld:3 .
对比三个镜像大小
很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。
只构建某一阶段的镜像
我们可以使用 as 来为某一阶段命名,例如
FROM golang:1.9-alpine as builder
例如当我们只想构建 builder 阶段的镜像时,我们可以在使用 dockerbuild命令时加上 --target 参数即可
$ docker build --target builder -t username/imagename:tag .
构建时从其他镜像复制文件
上面例子中我们使用COPY--from=0 /go/src/github.com/go/helloworld/app. 从上一阶段的镜像中复制文件,我 们也可以复制任意镜像中的文件。
$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
篇幅有限,关于docker多阶段构建就介绍到这了,感兴趣的朋友可以在本地虚拟机尝试下哦~
后面会分享更多关于devops和DBA内容,感兴趣的朋友可以关注下~