概述
Docker依赖Dockerfile来自动构建镜像。Dockerfile的语法虽然简单,但是如何编写Dockerfile来减小镜像大小并加快镜像构建速度却需要实践经验的累积。本节介绍Dockerfile编写的一些最佳实践,以帮助编写出高效的Dockerfile。
通过.dockerignore排除文件
Docker在构建镜像时,会将Dockerfile目录中的所有文件收集到进程中。对于不需要参与构建的文件,可以通过.dockerignore文件来进行排除,从而减小镜像的大小。.dockerignore的语法类似.gitignore,示例如下:
`.git/ node_modules`
该示例排除了Dockerfile目录中的.git和node_modules两个文件夹。
容器只运行单个应用
Docker虽然支持运行多个进程,例如将前端、后端和数据库都运行在一个Docker容器中,但这样做会带来一些问题:
- 构建时间长,修改某个应用导致整体重新构建
- 镜像体积大
- 不同应用所需资源不同,扩展时导致资源浪费
因此,最好将服务拆分成不同的应用,对每个应用单独进行镜像构建和部署。例如一个依赖node.js和MySQL的服务的Dockerfile原本需要安装两者的依赖:
`RUN apt-get install -y nodejs mysql`
进行拆分之后,node.js和MySQL服务可以单独部署。
node.js服务的Dockerfile包含:
`RUN apt-get install -y nodejs`
MySQL服务的Dockerfile包含:
`RUN apt-get install -y mysql`
避免安装不必要的包
避免安装额外的或不必要的包,可以降低镜像的复杂度,减少镜像大小以及构建时间。当需要更新包时,推荐使用apt-get install -y xxx来升级指定的包,避免安装不必要的依赖。apt-get upgrade会自动更新所有的依赖包,导致构建过程不确定,可能产生不一致的镜像,因此应该尽量避免使用。
减少镜像层并利用缓存
Docker镜像是分层的,Dockerfile中的每个指令都会创建一个新的镜像层,镜像层将被缓存和复用。当Dockerfile的指令修改了,复制的文件变化了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会失效,且之后的镜像层缓存都会失效。
对于以下Dockerfile:
FROM ubuntu ADD . /app RUN apt-get update RUN apt-get install -y nodejs RUN cd /app && npm install CMD npm start
可以将两条apt-get相关的RUN指令合并,来减少镜像层,并且防止apt-get update命中缓存从而导致apt-get install安装过期的依赖。同时,将apt-get指令前移,防止因为每次源代码变动生成新的镜像层,从而导致apt-get指令缓存失效。所以最终调整的结果为:
FROM ubuntu RUN apt-get update && apt-get install -y nodejs ADD . /app RUN cd /app && npm install CMD npm start
删除指令生成的多余文件
假设我们更新了apt-get源,下载解压并安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。但是,运行应用时Docker镜像中并不需要这些文件。所以最好将它们删除,防止Docker镜像变大。示例:
RUN apt-get update \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* #删掉由apt-get update生成的目录
指定基础镜像的标签
当镜像没有指定标签时,将默认使用latest 标签。因此,FROM ubuntu 指令等同于FROM ubuntu:latest。当镜像更新时,latest标签会指向不同的镜像,这时构建镜像有可能失败。因此,若非的确需要使用最新版的基础镜像,最好指定确定的镜像标签,例如FROM ubuntu:16.04。
选择合适的基础镜像
对于不同的应用,应该选择最合适的基础镜像。例如,如果只需要运行node程序,则可以使用node镜像替代ubuntu镜像,并且通过使用极小化的alpine版本能进一步降低镜像大小。示例:
FROM node:7-alpine ADD . /app RUN cd /app && npm install CMD npm start
使用多阶段构建
多阶段构建是在Dockerfile中使用多个FROM语句来构建镜像的方法。由于每个构建阶段只包含必要的依赖项和文件,因此可以提升构建速度并减小最终镜像的大小。以下示例使用了二阶段构建,第一阶段完成源代码的编译,第二阶段在scratch空镜像中运行第一阶段得到的可执行文件,从而降低最终镜像大小。
#第一阶段 FROM golang:1.16 as builder WORKDIR /go/src COPYmyapp.go ./ RUN go buildmyapp.go -o myapp #第二阶段 FROM scratch WORKDIR /server #引用第一阶段的可执行文件 COPY --from=builder /go/src/myapp ./ CMD ["./myapp"]