Dockerfile最佳实践:
通用优化:
########################################### 体积与性能 #############################################
- 1、使用alpine系统,保证最终镜像的体积不会太大
- 2、使用多阶段构建,使最终的镜像避免包含不必要的文件
- 3、尽量一个RUN 来跑完所有事情,当然使用多阶段构建可以解决多层影响性能的问题
- 4、不会发生改变的层放在上边,来使用cache,比如ENV,CMD,EXPOSE
- 5、使用各种中国镜像加速站来安装依赖
########################################### 安全启动 #############################################
- 1、使用 tini作为PID为1的进程来启动程序,避免停止需要10秒的强制停止和僵尸进程
- 2、较复杂的应用使用 entrypoint.sh 来初始化配置,同时用 exec来替换进程
- 3、使用 非root用户启动,保证安全性,不用USER,使用gosu来切换启动用户
- 4、针对gosu 和 exec的使用,alpine有一个 su-exec
########################################### 其它优化 #############################################
- 1、使用LABEL 来表示作者等信息
- 2、安装依赖程序的时候使用固定版本
- 3、时间/时区等设置正确
- 4、多使用ARG 可以在构建时传入参数:比如镜像版本,某个依赖的版本,时区; ARG 在FROM前后是分开的
- 5、优雅停止, STOPSIGNAL
- 6、健康检查,HEALTHCHECK,在k8s里有健康检查可以不写这个
一、体积与性能
1.1、使用alpine系统,保证最终镜像的体积不会太大
需要用当前的最新版本,要使用版本号,不要用latest,任何时候都避免用latest!
1.2、使用多阶段构建,使最终的镜像避免包含不必要的文件
FROM xxx AS builder
FROM builder AS tester
FROM builder
1.3、尽量一个RUN 来跑完所有事情,当然使用多阶段构建可以解决多层影响性能的问题
RUN xxxx &&\
xxxx &&\
xxxx
1.4、不会发生改变的层放在上边,来使用cache,比如ENV,CMD,EXPOSE
1.5、使用各种中国镜像加速站来安装依赖
- ubuntu/debain/apt-get
RUN echo 'deb http://mirrors.aliyun.com/debian/ buster main non-free contrib \n\
deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib \n\
deb http://mirrors.aliyun.com/debian-security buster/updates main \n\
deb-src http://mirrors.aliyun.com/debian-security buster/updates main \n\
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib \n\
deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib \n\
deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib \n\
deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib \n'\
> /etc/apt/sources.list
- alpine/apk
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g" /etc/apk/repositories &&\
apk update &&\
apk add --no-cache tini
golang
ENV GOPROXY https://goproxy.cn
python/pip
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
nodejs
RUN echo 'registry=https://registry.npm.taobao.org/ \n\
sass_binary_site="https://npm.taobao.org/mirrors/node-sass/" \n\
phantomjs_cdnurl="http://cnpmjs.org/downloads" \n\
electron_mirror="https://npm.taobao.org/mirrors/electron/" \n\
sqlite3_binary_host_mirror="https://foxgis.oss-cn-shanghai.aliyuncs.com/" \n\
profiler_binary_host_mirror="https://npm.taobao.org/mirrors/node-inspector/" \n\
chromedriver_cdnurl="https://cdn.npm.taobao.org/dist/chromedriver" \n\
sentrycli_cdnurl=https://npm.taobao.org/mirrors/sentry-cli \n'\
> ./npmrc
二、安全启动
2.1、使用 tini作为PID为1的进程来启动程序
2.2、较复杂的应用使用 entrypoint.sh 来初始化配置,同时用 exec来替换进程
2.3、使用 非root用户启动,保证安全性,不用USER,使用gosu来切换启动用户
2.4、针对gosu 和 exec的使用,alpine有一个 su-exec
因为容器中没有init/systemd,所以会导致很多问题,shell启动进程,shell不会将停止信号发给子进程,docker会等待10s,15秒后强制删除。
参考:tini优化docker停止缓慢10s,进程收不到停止信号
gosu
使用 tini来启动进程,作为PID为1的进程。
tini是专为容器设计的轻量init进程。
RUN groupadd -r someone && useradd -r -g someone someone
WORKSPACE /app
COPY . .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["tini", "--", "top"]
## entrypoint.sh
#!/bin/bash
set -ex
## 正常启动用 tini启动
if [ "$1" = 'tini' -a "$(id -u)" = '0' ]; then
exec gosu someone "$0" "$@";
fi
exec "$@"
#!/bin/bash
set -ex
## 正常启动用 tini启动
if [ "$1" = 'tini' -a "$(id -u)" = '0' ]; then
/usr/local/bin/python3.8 manage.py compilemessages;
/usr/local/bin/python3.8 manage.py migrate;
# 无法修改configmap挂载的配置文件
# chown -R someone:someone /var/someone;
exec gosu someone "$0" "$@";
fi
exec "$@"
ENTRYPOINT ["/var/arkid/docker-entrypoint.sh"]
CMD ["tini", "--", "/usr/local/bin/python3.8", "manage.py", "runserver", "0.0.0.0:80"]
someone 1 0 tini -- /usr/local/bin/python3.8 manage.py runserver 0.0.0.0:80
someone 199 1 /usr/local/bin/python3.8 manage.py runserver 0.0.0.0:80
someone 292 199 /usr/local/bin/python3.8 manage.py runserver 0.0.0.0:80
# exec形式,首选形式, exec 执行命令.
ENTRYPOINT ["executable", "param1", "param2"]
# shell形式,sh -c .
ENTRYPOINT command param1 param2
# exec形式,这是首选形式.
CMD ["executable","param1","param2"]
# 提供给ENTRYPOINT的默认参数.
CMD ["param1","param2"]
# shell形式, sh -c .
CMD command param1 param2
## gosu无法绑定0-1023
0-1023 是系统保留端口,只能root或者sudo启动才能绑定:
Error: You don't have permission to access that port.
可以配置一下,内核版本也有要求>=2.6.24
setcap 'cap_net_bind_service=+ep' /usr/local/bin/python3.8
三、其它优化
3.1、使用LABEL
LABEL maintainer="dev@someproject.org"
LABEL build_date="2017-09-05"
3.2、安装依赖程序的时候使用固定版本
3.3、时间/时区等设置正确
ARG tz="Asia/Shanghai"
ARG apk="mirrors.ustc.edu.cn"
RUN sed -i "s/dl-cdn.alpinelinux.org/${apk}/g" /etc/apk/repositories &&\
apk update &&\
apk add tini tzdata su-exec &&\
cp /usr/share/zoneinfo/${tz} /etc/localtime &&\
echo ${tz} > /etc/timezone
3.4、多使用ARG 可以在构建时传入参数:比如镜像版本,某个依赖的版本,时区; ARG 在FROM前后是分开的
3.5、优雅停止, STOPSIGNAL
3.6、健康检查,HEALTHCHECK,在k8s里有健康检查这个就不重要了
HEALTHCHECK --interval=2m --timeout=10s --start-period=5s --retries=3 CMD curl -f http://127.0.0.1:8080/ || exit 1
四、其它问题解决
openjdk(jenkins等)时区问题
# 在构建Dockerfile时解决
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime &&\
echo ${TZ} > /etc/timezone
# 在启动时挂载
-v /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
# timezone文件里写 Asia/Shanghai
-v /xxx/timezone:/etc/timezone
# pod里使用configmap挂载
apiVersion: v1
Kind: ConfigMap
data:
timezone: Asia/Shanghai
metadata:
name: timezone
volumeMounts:
- mountPath: /etc/timezone
name: config-volume
subPath: timezone
volumes:
- name: config-volume
configMap:
name: timezone
COPY指令
不能使用 ./* , 加*无法复制文件夹
COPY 文件不能用相对路径,只能用绝对路径,源和目的都是。