- 01. 基础镜像考虑社区官方发布的镜像,定期更新版本
- 02. 基础镜像必须指定镜像tag,不能使用latest
- 03. MAINTAINER 在docker 17.x以上版本逐步废弃
- 04. 将多条RUN命令合并执行,多条ENV命令合并执行
- 05. 不要使用apk upgrade命令
- 06. 使用apk add安装依赖包,指定版本号,使用无缓存方式,不使用apk update
- 07. 不要使用apt-get upgrade命令
- 08. 使用apt-get install安装依赖包要指定版本号
- 09. 安装依赖包后,需要删除 apt-get lists
- 10. 不使用apt命令, 使用apt-get命令
- 11. 使用pip安装依赖包指定安装版本,使用无缓存方式
- 12. 使用npm指定版本安装
- 13. gem指定版本安装依赖包
- 14. 拷贝多个文件到同一个目标路径,使用一条COPY命令,最后一个参数目标目录使用/结尾
- 15. 使用ADD命令解压压缩包
- 16. 有效的UNIX端口范围0 to 65535
- 17. 强制安装依赖包
- 18. 避免安装额外的依赖包
- 19. 挂载宿主机目录到容器内,不要对容器内目录执行mkdir、chown、chmod操作
- 20. WORKDIR 要使用绝对路径
- 21. 使用WORKDIR 切换目录
- 22. 不要使用ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig等命令
- 23. 容器内普通用户运行进程
- 24. 不要使用sudo,使用root用户安装依赖包
- 25. 使用COPY代替ADD 添加文件和目录
- 26. COPY —from 需要使用已定义的FROM 别名
- 27. COPY —from 不能引用基础镜像的别名
- 28. 多阶段构建别名必须是唯一的
- 29. CMD和ENTRYPOINT使用exec格式
- 30. 使用可信镜像仓库registry
- 31. 不要同时使用wget和curl
- 32. 不要使用多个CMD
- 33. 不要使用多个ENTRYPOINT
- 34. 不使用默认shell配置
- 35. 非root运行业务,使用COPY —chown
- 36. CMD和ENTRYPOINT使业务进程响应SIGTERM信号,实现优雅退出
01. 基础镜像考虑社区官方发布的镜像,定期更新版本
如无特殊要求,基础镜像推荐使用alpine 镜像,尽量考虑官方发布的镜像。
如无需安装依赖包,推荐使用scratch基础镜像。
跟踪社区的发布情况,定期更新镜像的版本,确保使用镜像的安全可信。不要使用最新版本发布到生成环境,避免基础镜像引入故障。
02. 基础镜像必须指定镜像tag,不能使用latest
不指定tag,默认为latest,latest版本无法做到版本控制(scratch基础镜像、多阶段构建这两种情况可以不指定tag)
## 错误示例:FROM alpine## 正确示例:FROM alpine:3.9
03. MAINTAINER 在docker 17.x以上版本逐步废弃
推荐使用LABEL代替MAINTAINER。
## 错误示例:MAINTAINER xxxx.xxx@xxx.xxx.xx## 正确示例:LABEL maintainer xxxx.xxx@xxx.xxx.xx
04. 将多条RUN命令合并执行,多条ENV命令合并执行
将多条RUN命令合并执行,每行写一条命令,减少RUN命令的数量。 将多条ENV命令合并执行,每行写一条命令,减少ENV命令的数量。
注意:不是要求只能使用1条RUN或ENV命令。
重要意义:可以减少镜像层数,减少构建镜像过程中启动容器的次数,缩短构建镜像的时间。
## 错误示例:RUN mkdir -p /app/conf && chown -R 1230:1230 /appRUN chmod 755 /home/run.sh## 正确示例:RUN mkdir -p /app/conf \&& chown -R 1230:1230 /app \&& chmod 755 /home/run.sh## -----------## 错误示例:ENV DEBUG_PORT=8777ENV HTTP_PORT=12080ENV PROGRAM_FILE_CONF=/home/conf## 正确示例:ENV DEBUG_PORT=8777 \HTTP_PORT=12080 \PROGRAM_FILE_HOME_CONF=/home/conf
05. 不要使用apk upgrade命令
使用upgrade会升级已安装的依赖包,因此,不要使用apk upgrade命令。
06. 使用apk add安装依赖包,指定版本号,使用无缓存方式,不使用apk update
安装依赖包,一定要指定版本号,控制版本。无缓存方式安装,可以避免update后再清除缓存。
## 错误示例:RUN apk update \&& apk add foo=1.0 \&& rm -rf /var/cache/apk/*RUN apk add --update foo=1.0&& rm -rf /var/cache/apk/*## 正确示例:RUN apk --no-cache add foo=1.0
07. 不要使用apt-get upgrade命令
## 错误示例:RUN apt-get upgrade \&& apt-get install python=2.7## 正确示例:RUN apt-get install python=2.7
08. 使用apt-get install安装依赖包要指定版本号
## 错误示例:RUN apt-get install python## 正确示例:RUN apt-get install python=2.7
09. 安装依赖包后,需要删除 apt-get lists
## 错误示例:RUN apt-get update && apt-get install -y python## 正确示例:RUN apt-get update && apt-get install -y python=2.7 \&& apt-get clean \&& rm -rf /var/lib/apt/lists/*
10. 不使用apt命令, 使用apt-get命令
## 错误示例:RUN apt install curl=1.1.0## 正确示例:RUN apt-get install curl=1.1.0
11. 使用pip安装依赖包指定安装版本,使用无缓存方式
## 错误示例:RUN pip install django## 正确示例:RUN pip install --no-cache-dir django==1.9
12. 使用npm指定版本安装
## 错误示例:RUN npm install expressRUN npm install @myorg/privatepackageRUN npm install express sax@0.1.1## 正确示例:RUN npm install express@4.1.1 \&& npm install @myorg/privatepackage@">=0.1.0" \&& npm install express@"4.1.1" sax@0.1.1
13. gem指定版本安装依赖包
## 错误示例:RUN gem install bundler## 正确示例:RUN gem install bundler:1.1
14. 拷贝多个文件到同一个目标路径,使用一条COPY命令,最后一个参数目标目录使用/结尾
## 错误示例:COPY ansible-playbook /usr/local/bin/COPY ansible /usr/local/bin/COPY libmysqlclient.so /usr/lib/x86_64-linux-gnu/COPY libmysqlclient.so.18 /usr/lib/x86_64-linux-gnu/COPY libmysqlclient.so.18.0.0 /usr/lib/x86_64-linux-gnu/## 正确示例:COPY ansible-playbook ansible /usr/local/bin/COPY libmysqlclient.so libmysqlclient.so.18 libmysqlclient.so.18.0.0 /usr/lib/x86_64-linux-gnu/
15. 使用ADD命令解压压缩包
## 错误示例:COPY posd-*.tar.gz /tmp/RUN cd /tmp && tar -zxvf posd-*.tar.gz && rm -fr posd-*.tar.gz## 正确示例:ADD posd-*.tar.gz /tmp/
16. 有效的UNIX端口范围0 to 65535
## 错误示例:EXPOSE 80000## 正确示例:EXPOSE 65535
17. 强制安装依赖包
## 错误示例:RUN apt-get install python=2.7## 正确示例:RUN apt-get install -y python=2.7
18. 避免安装额外的依赖包
## 错误示例:RUN apt-get install -y python=2.7## 正确示例:RUN apt-get install -y --no-install-recommends python=2.7
19. 挂载宿主机目录到容器内,不要对容器内目录执行mkdir、chown、chmod操作
宿主机指定的日志目录(/paasdata/oplog/*),配置目录(/paasdata/opconf/*)等,需要在宿主机完成mkdir、chown和chmod。## 错误示例:RUN mkdir -p /paasdata/oplog/cnrm-manager \&& mkdir -p /paasdata/opconf/cnrm-manager \&& addgroup -g 1340 -S cnrm-manager \&& adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340 \&& chown -R cnrm-manager:cnrm-manager /paasdata/oplog/cnrm-manager /paasdata/opconf/cnrm-manager## 正确示例:RUN addgroup -g 1340 -S cnrm-manager \&& adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340
20. WORKDIR 要使用绝对路径
## 错误示例:WORKDIR usr/src/app## 正确示例:WORKDIR /usr/src/app
21. 使用WORKDIR 切换目录
## 错误示例:RUN cd /usr/src/app## 正确示例:WORKDIR /usr/src/app
22. 不要使用ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig等命令
## 错误示例:FROM busyboxRUN top
23. 容器内普通用户运行进程
一般业务容器内使用普通用户运行业务进程,使用USER命令切换用户或su-exec切换到普通用户。
## 正确示例:RUN addgroup -g 1340 -S cnrm-manager \&& adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340USER cnrm-manager:cnrm-manager
24. 不要使用sudo,使用root用户安装依赖包
## 错误示例:RUN sudo apt-get install foo==1.0## 正确示例:RUN apt-get install foo==1.0
25. 使用COPY代替ADD 添加文件和目录
## 错误示例:ADD requirements.txt /usr/src/app/## 正确示例:COPY requirements.txt /usr/src/app/
26. COPY —from 需要使用已定义的FROM 别名
## 错误示例:FROM debian:jesseRUN stuffFFROM debian:jesseCOPY --from=build some stuff ./## 正确示例:FROM debian:jesse as buildRUN stuffFROM debian:jesseCOPY --from=build some stuff ./
27. COPY —from 不能引用基础镜像的别名
## 错误示例:FROM debian:jesse as buildCOPY --from=build some stuff ./## 正确示例:FROM debian:jesse as buildRUN stuffFROM debian:jesseCOPY --from=build some stuff ./
28. 多阶段构建别名必须是唯一的
## 错误示例:FROM debian:jesse as buildRUN stuffFROM debian:jesse as buildRUN more_stuff## 正确示例:FROM debian:jesse as buildRUN stuffFROM debian:jesse as another-aliasRUN more_stuff
29. CMD和ENTRYPOINT使用exec格式
如果容器内业务进程无法响应SIGTERM信号,那么无法实现优雅退出。docker stop停止容器时,dockerd向容器内进程发送SIGTERM信号,终止业务。如果容器内1号进程,无法响应SIGTERM信号,则dockerd向让容器内进程发送SIGKILL信号。
## 错误示例:ENTRYPOINT s3cmdENTRYPOINT /entrypoint.shCMD my-service serverCMD /run.sh## 正确示例:ENTRYPOINT ["s3cmd"]ENTRYPOINT ["/entrypoint.sh"]CMD ["my-service", "server"]CMD ["/run.sh"]
30. 使用可信镜像仓库registry
## 错误示例:FROM randomguy/python:3.6...## 正确示例:FROM my-registry.com/python:3.6...
31. 不要同时使用wget和curl
## 错误示例:RUN wget http://google.comRUN curl http://bing.com## 正确示例:RUN curl http://google.comRUN curl http://bing.com
32. 不要使用多个CMD
使用多个CMD ,只有最后一个CMD生效。
## 错误示例:CMD /bin/trueCMD /bin/false## 正确示例:CMD /bin/false
33. 不要使用多个ENTRYPOINT
使用多个ENTRYPOINT ,只有最后一个ENTRYPOINT 生效。
## 错误示例:ENTRYPOINT /bin/trueENTRYPOINT /bin/false## 正确示例:ENTRYPOINT /bin/false
34. 不使用默认shell配置
RUN 指令中使用管理操作符 | ,Docker 只关注最后一个命令执行与否成功。如下错误示例,即使wget 失败,wc成功也会成功构建镜像。
因此,如果希望执行过程中产生任何错误都失败,需要设置pipefail。
## 错误示例:RUN wget -O - https://some.site | wc -l > /number## 正确示例:RUN set -o pipefail \&& wget -O - https://some.site | wc -l > /number
35. 非root运行业务,使用COPY —chown
容器内非root运行业务,要对目录和文件chown,增加镜像大小。构建镜像时,使用COPY —chown,使用 buildkit 或docker v17.x及以上版本。
## 错误示例:COPY data /home/confRUN addgroup -g 1340 -S app \&& adduser app -D -s /sbin/nologin -G app -u 1340 \&& mkdir /home/conf&& chown -R app:app /home/conf## 正确示例:RUN addgroup -g 1340 -S app \&& adduser app -D /home/app -s /sbin/nologin -G app -u 1340 \COPY --chown app:app data /home/app/conf
36. CMD和ENTRYPOINT使业务进程响应SIGTERM信号,实现优雅退出
如果容器内业务进程无法响应SIGTERM信号,那么无法实现优雅退出。docker stop停止容器时,dockerd向容器内进程发送SIGTERM信号,终止业务。如果容器内1号进程,无法响应SIGTERM信号,则dockerd向让容器内进程发送SIGKILL信号。
## 错误示例:shell格式,容器内1号进程是sh,sh进程无法响应SIGTERM信号ENTRYPOINT /entrypoint.sh或者CMD /run.sh## 正确示例:exce格式1:使容器内业务进程成为1号进程,可以响应SIGTERM信号ENTRYPOINT ["s3cmd"]或者CMD ["s3cmd"]exec格式2:使用exec将业务进程替换为容器内1号进程ENTRYPOINT ["/entrypoint.sh"]entrypoint.sh脚本中使用 exec @#!/bin/shif [ -d "/my-project/logs" ]; thencd /my-projectchown app:app -R logsfi# switch user and run your applicaiton (top is a demo app)set -- su-exec app:app top 2>&1exec "$@"exec格式3:使用exec将业务进程替换为容器内1号进程CMD exec ${app_path}/start.shstart.sh 脚本中使用 exec 启动业务进程#!/bin/bash...exec /app/opsapiserver -pthreshold=${log_level} >> ${app_path}/logs/opsapiserver/opsapiserver.log 2>&1自定义函数:sh脚本作为容器内1号进程,自定义函数响应SIGTERM信号ENTRYPOINT ["/run.sh"]run.sh 脚本中自定义函数#!/bin/shset -xpid=0# SIGTERM-handlerterm_handler() {if [ $pid -ne 0 ]; thenkill -SIGTERM "$pid"wait "$pid"fiexit 143; # 128 + 15 -- SIGTERM}# setup handlerstrap 'term_handler' SIGTERM# run applicationapp &pid="$!"wait
