容器的每一个阶段都应该是短暂的
通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂 (ephemeral)。短暂意味着可以很快地启动并且终止
使用 .dockerignore 排除构建无关文件
.dockerignore 语法与 .gitignore 语法一致。使用它排除构建无关的文件及目录,如 node_modules
使用multistage构建
多阶段构建可以有效减小镜像体积,特别是对于需编译语言而言,一个应用的构建过程往往如下
- 安装编译工具
- 安装第三方库依赖
- 编译构建应用
在前两步会有大量的镜像体积冗余,使用多阶段构建可以避免这一问题,参考前端应用的dockerfile示例。
FROM node:8.16.2-alpine AS build
WORKDIR /
COPY package.json yarn.lock ./
RUN yarn install
COPY / /
RUN yarn build
### stage: app ###
FROM nginx:1.15-alpine AS app
LABEL maintainer=" An<an@email.com>"
LABEL app=Website
COPY nginx/ /etc/nginx/
COPY build/ /usr/share/nginx/html
EXPOSE 80
避免安装不必要的包
减小体积,减少构建时间。如前端应用使用 npm install --production 只装生产环境所依赖的包。
减小容器耦合
如一个web应用将会包含三个部分,web 服务,数据库与缓存。把他们解耦到多个容器中,方便横向扩展。如果你需要网络通信,则可以将他们至于一个网络下。
减少镜像层数
- 只有 RUN, COPY, ADD 会创建层数, 其它指令不会增加镜像的体积
- 尽可能使用多阶段构建
使用以下方法安装依赖
# 使用以下方法安装依赖
RUN yum install -y node python go
# 错误的方法安装依赖,这将增加镜像层数
RUN yum install -y node
RUN yum install -y python
RUN yum install -y go
充分利用构建的缓存
在镜像的构建过程中 docker 会遍历 Dockerfile 文件中的所有指令,顺序执行。对于每一条指令,docker 都会在缓存中查找是否已存在可重用的镜像,否则会创建一个新的镜像
跳过缓存
我们可以使用 docker build --no-cache 跳过缓存
- ADD 和 COPY 将会计算文件的 checksum 是否改变来决定是否利用缓存
- RUN 仅仅查看命令字符串是否命中缓存,如 RUN apt-get -y update 可能会有问题
如一个 node 应用,可以先拷贝 package.json 进行依赖安装,然后再添加整个目录,可以做到充分利用缓存的目的。
FROM node:8.16.2-alpine AS build
WORKDIR /
COPY package.json yarn.lock ./
#此处可以充分利用缓存
RUN yarn install --production
COPY / /
RUN yarn build
开发在遇到的问题(解决办法汇总)
目前的镜像存在两个问题,导致每次部署时间过长,不利于产品的快速交付,没有快速交付,也就没有敏捷开发 (Agile)
- 构建镜像时间过长
- 构建镜像大小过大,多时甚至 1G+
解决办法:
从devdependencies下手
对于每次部署,如果能够减少无用包的下载,便能够节省很多镜像构建时间。eslint,mocha,chai等代码风格测试模块可以放到devDependencies中。在生产环境中使用npm install --production装包。
我们注意到,相对于项目的源文件来讲,package.json是相对稳定的。如果没有新的安装包需要下载,则再次构建镜像时,无需重新装包。则可以在 npm install 上节省一半的时间。
利用镜像缓存
对于 ADD 来讲,如果需要添加的文件内容的 checksum 没有发生变化,则可以利用缓存。把 package.json 与源文件分隔开写入镜像是一个很好的选择。目前,如果没有新的安装包更新的话,可以节省一半时间
多阶段构建
得益于缓存,现在镜像构建时间已经快了不少。但是,此时镜像的体积依旧过于庞大,这也将会导致部署时间的加长。原因如下
考虑下每次 CI/CD 部署的流程
- 在构建服务器构建镜像
- 把镜像推至镜像仓库服务器
- 在生产服务器拉取镜像,启动容器
显而易见,镜像体积过大会造成传输效率低下,增加每次部署的延时
即使,构建服务器与生产服务器在同一节点下,没有延时的问题。减少镜像体积也能够节省磁盘空间(使用较早期的镜像)
使用文件存储服务
分析一下 50M+ 的镜像体积,nginx:10-alpine 的镜像是16M,剩下的40M是静态资源。
如果把静态资源给上传到文件存储服务,即OSS,并使用 CDN 对 OSS 进行加速。则没有必要打入镜像了,此时镜像大小会控制在 20M 以下
关于静态资源,可以分类成两部分
- /static,此类文件在项目中直接引用根路径,打包时复制进 /public 下,需要被打入镜像
- /build,此类文件需要 require/import 引用,会被 webpack 打包并加 hash 值,并通过 publicPath 修改资源地址。可以把此类文件上传至 oss,并加上永久缓存,不需要打入镜像
最佳实践
FROM node:10-alpine as builder
ENV PROJECT_ENV production
ENV NODE_ENV production
# http-server 不变动也可以利用缓存
WORKDIR /code
ADD package.json /code
RUN npm install --production
ADD . /code
# npm run uploadOss 是把静态资源上传至 oss 上的脚本文件
RNN npm run build && npm run uploadOss
# 选择更小体积的基础镜像
FROM nginx:10-alpine
COPY --from=builder code/public/index.html
code/public/favicon.ico /usr/share/nginx/html/
COPY --from=builder code/public/static /usr/share/nginx/html/static