之前的做法
关于构建镜像最具挑战性的事情之一是保持镜像体积小巧。 docker file 中的每条指令都会在镜像中增加一层,并且在移动到下一层之前,需要记住清除不需要的构件。要编写一个非常高效的 Dockerfile,你通常需要使用 shell 技巧和其它方式来尽可能地减少层数,并确保每一层都具有上一层所需的构件,而其它任何东西都不需要。
实际上最常见的是,有一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容),而另一个裁剪过的用于生产环境,它只包含您的应用程序以及运行它所需的内容。这被称为“构建器模式”。但是维护两个 Dockerfile 并不理想。
在 Docker 17.05版本之前,我们构建Docker镜像时,通常会采用两种方式:
全部放入一个Dockerfile
一种方式是将所有的构建过程编包含在一个Dockerfile中,包括项目及其依赖 库的编译、测试、打包等流程,这里可能会带来的一些问题:
- Dockerfile特别长,可维护性降低
- 镜像层次多,镜像体积较大,部署时间变长
- 源代码存在泄露的风险
例如编写app.go文件,该程序输出HelloWorld!
packagemain import"fmt" funcmain(){ fmt.Printf("HelloWorld!"); }
编写 Dockerfile.one 文件
FROMgolang:1.9-alpine RUNapk--no-cacheadd git ca-certificates WORKDIR/go/src/ github .com/go/ helloworld / COPYapp.go. RUNgoget-d-vgithub.com/go-sql-driver/mysql\&&CGO_ENABLED=0GOOS= linux gobuild-a-installsuffixcgo-oapp.\&&cp/go/src/github.com/go/helloworld/app/root WORKDIR/root/ CMD["./app"]
构建镜像
$dockerbuild-tgo/helloworld:1-fDockerfile.one.
分散到多个Dockerfile
另一种方式,就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包 好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 Dockerfile 和一 些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种 方式存在的风险,但明显部署过程较复杂。
例如
编写 Dockerfile.build 文件
FROMgolang:1.9-alpine RUNapk--no-cacheaddgit WORKDIR/go/src/github.com/go/helloworld COPYapp.go. RUNgoget-d-vgithub.com/go-sql-driver/mysql\&&CGO_ENABLED=0GOOS=linuxgobuild-a-installsuffixcgo-oapp.
编写Dockerfile.copy 文件
FROMalpine:latest RUNapk--no-cacheaddca-certificates WORKDIR/root/ COPYapp. CMD["./app"]
新建build.sh
#!/bin/sh
echoBuildinggo/helloworld:build
dockerbuild-tgo/helloworld:build.-fDockerfile.build
dockercreate--name extract go/helloworld:build dockercpextract:/go/src/github.com/go/helloworld/app./app dockerrm-fextract
echoBuildinggo/helloworld:2
dockerbuild--no-cache-tgo/helloworld:2.-fDockerfile.copy rm./app
现在运行脚本即可构建镜像
$chmod+xbuild.sh $./build.sh
对比两种方式生成的镜像大小
$dockerimagels REPOSITORYTAGIMAGEIDCREATED SIZE go/helloworld2f7cf3465432c22 seconds ago 6.47MB go/helloworld1f55d3e16affc2minutesago295MB
使用多阶段构建
为解决以上问题,Dockerv17.05开始支持多阶段构建(multistagebuilds)。 使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个Dockerfile:
例如
编写 Dockerfile 文件
FROMgolang:1.9-alpineasbuilder RUNapk--no-cacheaddgit WORKDIR/go/src/github.com/go/helloworld/ RUNgoget-d-vgithub.com/go-sql-driver/mysql COPYapp.go. RUNCGO_ENABLED=0GOOS=linuxgobuild-a-installsuffixcgo-oa pp. FROMalpine:latestasprod RUNapk--no-cacheaddca-certificates WORKDIR/root/ COPY--from=0/go/src/github.com/go/helloworld/app. CMD["./app"]
构建镜像
$dockerbuild-tgo/helloworld:3.
对比三个镜像大小
很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。
只构建某一阶段的镜像
我们可以使用 as 来为某一阶段命名,例如
FROMgolang:1.9-alpineasbuilder
例如当我们只想构建 builder 阶段的镜像时,我们可以在使用 dockerbuild命令时加上 –target 参数即可
$dockerbuild--targetbuilder-tusername/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内容,感兴趣的朋友可以关注下~