Dockerfile 标准
一、原则
精简,安全,够用
二、基础镜像选择
常用的 Linux 系统镜像一般有 Ubuntu、CentOs、Alpine,其中Alpine更推荐使用。大小对比如下:
> docker images
REPOSITORY TAG IMAGE ID SIZE
ubuntu latest 74f8760a2a8b 82.4MB
alpine latest 11cd0b38bc3c 4.41MB
centos 7 49f7960eb7e4 200MB
debian latest 3bbb526d2608 101MB
alpine是一个高度精简又包含了基本工具的轻量级Linux发行版,本身的Docker镜像只有4~5M大小。各开发语言和框架
都有基于alpine制作的基础镜像,在开发自己应用的镜像时,选择这些镜像作为基础镜像,可以大大减小镜像的体积。
语言
| 推荐镜像
| 额外说明
|
Java
| openjdk:8-jdk-alpine,openjdk:8-jre-alpine
| |
Tomcat
| tomcat:8.5-alpine
| |
Nodejs
| node:9-alpine
| |
Python
| python:3-alpine
| |
PHP
| php:7-fpm-alpine,php:5-fpm-alpine
| |
Go
| 可执行文件 - 直接基于alpine镜像,把编译后的可执行文件打入镜像。
| 重点编译参数:CGO_ENABLED=0
|
nginx
| nginx:1-alpine
|
三、镜像通用设置
3.1 编码
RUN ENV LANG en_US.UTF-8
3.2 时区
RUN echo "Asia/Shanghai" > /etc/timezone
3.3 字体库
dejavu是很受欢迎的开源字体之一,没有商业版权限制。
RUN apk add --update ttf-dejavu fontconfig
3.4 用户权限
创建特定的用户组和用户给容器
RUN addgroup --system --gid 1000 appuser && adduser -S -s /bin/sh -G appuser -u 1000 appuser
USER appuser
WORKDIR /home/appuser
四、串联 Dockerfile 指令
太多的使用RUN指令,经常会导致镜像有特别多的层,镜像很臃肿,而且甚至会碰到超出最大层数(127层)限制的
问题,遵循 Dockerfile 最佳实践,我们应该把多个命令串联合并为一个 RUN(通过运算符&&和/ 来实现)
#FROM openjdk:8u191-alpine FROM alpine
COPY java_pre_stop-1.0.jar /home/appuser/java_pre_stop-1.0.jar
RUN echo http://mirrors.aliyun.com/alpine/v3.13/main/ > /etc/apk/repositories && \
echo http://mirrors.aliyun.com/alpine/v3.13/community/ >> /etc/apk/repositories && \
addgroup --system --gid 1000 appuser && adduser -S -s /bin/sh -G appuser -u 1000 appuser && \
chown -R appuser:appuser /opt && \
chown -R appuser:appuser /usr && \
chown -R appuser:appuser /home/appuser && \
apk update && \
apk add openjdk8 && \
apk add --no-cache tini && \
echo "Asia/Shanghai" > /etc/timezone && \
apk add --update ttf-dejavu fontconfig && \
rm -rf /var/cache/apk/* && rm -rf /etc/apk/cache
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/bin
USER appuser
WORKDIR /home/appuser
ENV LANG en_US.UTF-8
五、多阶构建
程序构建和产物的执行最好不要放在同一个镜像里面,例如:springboot 的编译需要依赖 maven 环境,但是最
终 java 的执行并不需要 maven,为了让我们生成的镜像足够精简,这2部分要分开处理。目前我们构建是在 CI 的流程中额外拉取镜像来做的,如果非要放在同一个 dockerfile 里面,就得用以下模式(“建造者模式”)
FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build
RUN mkdir /project
COPY . /project
WORKDIR /project
RUN mvn clean package -DskipTests
FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
RUN mkdir /app
COPY --from=build /project/target/java-application.jar /app/java-application.jar
WORKDIR /app
CMD "java" "-jar" "java-application.jar"
六、容器的首进程
- 第一个应用程序将以进程ID为1(PID=1)运行
- Linux内核会以特殊方式处理PID为1的进程,ps 无法查询到首进程的进程信息
- 容器被杀死或者停止的时候会发送 `SIGTERM` 信号给首进程
不建议的做法
ENTRYPOINT java -jar /home/appuser/app.jar
建议做法
ENTRYPOINT /sbin/tini java -jar /home/appuser/app.jar
引入 tini 作为首进程,tini 会帮忙将 `SIGTERM` 信号发送给他的所有子进程,以防出现僵死进程。
七、.dockerignore
镜像 build 的时候,同目录的所有文件都会加载到容器的上下文引擎(不仅仅是 copy,add)的文件,所以有2个做法:
- 保持 dockerfile 目录的干净整洁
- 用.dockerignore 过滤掉不需要的文件。
八、瘦身工具
8.1 dive
可以方便查看镜像的目录结构,了解各个目录文件占用大小
9.2 Docker-squash
自动压缩
Alpine DNS 解析有缺陷,慎用。