Docker镜像构建进化论:从手工操作到多阶段构建的实战指南
在容器化时代,镜像构建是每个开发者必须掌握的技能。手工构建虽然能让你理解镜像本质,但效率低下、不可重复;而多阶段构建则能大幅减小镜像体积、提升安全性。本文将带你从零开始,逐步掌握从手工构建到Dockerfile、再到多阶段构建的完整进化路径,最终落地企业级最佳实践。
一、手工构建:理解镜像的本质
在自动化之前,我们先手工走一遍流程,深刻理解镜像的组成。手工构建能让你直观感受到镜像的每一层是如何叠加的,以及为什么需要优化。
1.1 运行基础容器
首先,基于CentOS 7.9启动一个基础容器:
# 启动Ubuntu容器
[root@hadoop108 ~]# docker run -itd --name ubt_tengine_2.3.3 ubuntu:20.04 /bin/bash
# 进入容器
[root@hadoop108 ~]# docker exec -it ubt_tengine_2.3.3 /bin/bash1.2 配置基础环境
安装编译工具和依赖库:
# 替换apt源为阿里云(国内加速)
root@16552f5daf21:/# sed -ri 's#archive.ubuntu.com|security.ubuntu.com#mirrors.aliyun.com#g' /etc/apt/sources.list
# 更新缓存并安装基础工具
root@16552f5daf21:/# apt update && apt install -y wget vim curl1.3 编译安装Tengine
下载源码并编译安装:
# 创建规范目录
root@16552f5daf21:/# mkdir -p /opt/module /opt/software
# 下载源码
root@16552f5daf21:~# wget -P /opt/software/ http://tengine.taobao.org/download/tengine-2.3.3.tar.gz
# 安装编译依赖
root@16552f5daf21:~# apt install -y libssl-dev make gcc pcre2-utils libpcre3-dev zlib1g-dev
# 解压并编译
root@16552f5daf21:/opt/software# tar -xzvf tengine-2.3.3.tar.gz
cd tengine-2.3.3/
# 配置(开启所需模块)
./configure --prefix=/opt/module/tengine-2.3.3/ \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_mp4_module \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module \
--add-module=modules/ngx_http_upstream_check_module/ \
--add-module=modules/ngx_http_upstream_session_sticky_module
# 编译安装
make -j `nproc` && make install1.4 运行时配置优化
调整配置文件以提升性能:
# 创建nginx用户
root@16552f5daf21:~# groupadd nginx && useradd -g nginx nginx
# 创建软链接(便于使用)
root@16552f5daf21:~# ln -s /opt/module/tengine-2.3.3 /opt/module/tengine
root@16552f5daf21:~# ln -s /opt/module/tengine/sbin/nginx /sbin/nginx
# 日志重定向(Docker最佳实践)
root@16552f5daf21:~# ln -s /dev/stdout /opt/module/tengine/logs/access.log
root@16552f5daf21:~# ln -s /dev/stderr /opt/module/tengine/logs/error.log
# 测试页面
root@16552f5daf21:~# echo 'docker tengine' > /opt/module/tengine/html/index.html
# 启动测试
root@16552f5daf21:~# nginx && curl localhost
docker tengine1.5 生成镜像
将容器提交为镜像:
# 清理无用文件
root@16552f5daf21:~# rm -rf /opt/software/* /var/cache/*
# 提交为镜像
[root@hadoop108 ~]# docker commit ubt_tengine_2.3.3 tengine:2.3.3
# 查看镜像大小
[root@hadoop108 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tengine 2.3.3 25f29f6c517a 9 seconds ago 389MB手工构建的痛点:
- 操作繁琐,容易遗漏步骤
- 镜像体积大(389MB)
- 无法版本控制
- 不可重复构建
二、Dockerfile自动化构建:迈出第一步
Dockerfile将构建过程代码化,实现可重复、可版本控制的构建。这是从手工到自动化的关键一步。
2.1 项目结构
合理的项目结构有助于维护:
[root@hadoop108 ~]# mkdir -p /opt/module/tengine
[root@hadoop108 ~]# cd /opt/module/tengine
# 准备文件
[root@hadoop108 tengine]# ll
总用量 2792
-rw-r--r-- 1 root root 1447 Dockerfile
-rw-r--r-- 1 root root 32 index.html
-r-------- 1 root root 2848144 tengine-2.3.3.tar.gz2.2 编写Dockerfile
将手工步骤转化为指令:
# /opt/module/tengine/Dockerfile
FROM ubuntu:20.04
LABEL author="礼拜天没时间" \url="https://blog.csdn.net/weixin_73059914"
# =====================
# 环境变量
# =====================
ENV TENGINE_VERSION=2.3.3
ENV TENGINE_NAME=tengine-${TENGINE_VERSION}
ENV NGINX_USER=nginx
ENV INSTALL_PREFIX=/opt/module
# Tengine编译配置选项
ENV TENGINE_CONFIGURE_OPTS="./configure \--prefix=${INSTALL_PREFIX}/${TENGINE_NAME} \--user=${NGINX_USER} \--group=${NGINX_USER} \--with-http_ssl_module \--with-http_v2_module \--with-http_realip_module \--with-http_stub_status_module \--with-http_mp4_module \--with-stream \--with-stream_ssl_module \--with-stream_realip_module \--add-module=modules/ngx_http_upstream_check_module/ \--add-module=modules/ngx_http_upstream_session_sticky_module"
# =====================
# 1. 替换apt源为阿里云
# =====================
RUN sed -ri 's#archive.ubuntu.com|security.ubuntu.com#mirrors.aliyun.com#g' /etc/apt/sources.list \&& apt update
# =====================
# 2. 添加源码包
# =====================
ADD ${TENGINE_NAME}.tar.gz /tmp/
# =====================
# 3. 编译安装
# =====================
RUN apt install -y libssl-dev make gcc pcre2-utils libpcre3-dev zlib1g-dev \&& cd /tmp/${TENGINE_NAME} \&& ${TENGINE_CONFIGURE_OPTS} \&& make -j $(nproc) \&& make install
# =====================
# 4. 运行时配置
# =====================
RUN groupadd ${NGINX_USER} \&& useradd -g ${NGINX_USER} ${NGINX_USER} \&& ln -s ${INSTALL_PREFIX}/${TENGINE_NAME} ${INSTALL_PREFIX}/tengine \&& ln -s ${INSTALL_PREFIX}/tengine/sbin/nginx /sbin/nginx \&& ln -sf /dev/stdout ${INSTALL_PREFIX}/tengine/logs/access.log \&& ln -sf /dev/stderr ${INSTALL_PREFIX}/tengine/logs/error.log \&& rm -rf /tmp/* /var/cache/*
# =====================
# 5. 添加网页文件
# =====================
ADD index.html ${INSTALL_PREFIX}/tengine/html/
# =====================
# 6. 暴露端口
# =====================
EXPOSE 80 443
# =====================
# 7. 启动命令
# =====================
CMD ["nginx", "-g", "daemon off;"]2.3 构建并运行
执行构建命令并启动容器:
# 构建镜像
[root@hadoop108 tengine]# docker build -t "tengine-dockerfile:2.3.3" .
# 查看镜像
[root@hadoop108 tengine]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tengine-dockerfile 2.3.3 276c35d21239 11 seconds ago 360MB
# 运行容器
[root@hadoop108 tengine]# docker run --name tengine -p 80:80 -d tengine-dockerfile:2.3.3
# 测试访问
curl http://localhost✅ Dockerfile带来的改进:
- 可重复构建
- 版本控制友好
- 操作文档化
- 镜像体积略有减小(360MB)
❌ 依然存在的问题:
- 镜像仍包含gcc、make等编译工具
- 安全风险(编译工具可能被利用)
- 体积还是偏大
三、多阶段构建:终极进化
多阶段构建的核心思想是将编译环境和运行环境彻底分离,只把运行必需的结果放进最终镜像。这极大减小了镜像体积,提升了安全性。
3.1 什么是多阶段构建?
阶段1(Builder)安装编译工具并编译源码;阶段2(Final)只拷贝编译产物到精简基础镜像中。
3.2 多阶段Dockerfile
# /opt/module/tengine-multi/Dockerfile
# ==============================
# 阶段1:编译环境
# ==============================
FROM ubuntu:20.04 AS builder
LABEL author="礼拜天没时间" \url="https://blog.csdn.net/weixin_73059914"
# 环境变量
ENV TENGINE_VERSION=2.3.3
ENV TENGINE_NAME=tengine-${TENGINE_VERSION}
ENV NGINX_USER=nginx
ENV INSTALL_PREFIX=/opt/module
# Tengine编译选项
ENV TENGINE_CONFIGURE_OPTS="./configure \--prefix=${INSTALL_PREFIX}/${TENGINE_NAME} \--user=${NGINX_USER} \--group=${NGINX_USER} \--with-http_ssl_module \--with-http_v2_module \--with-http_realip_module \--with-http_stub_status_module \--with-http_mp4_module \--with-stream \--with-stream_ssl_module \--with-stream_realip_module \--add-module=modules/ngx_http_upstream_check_module/ \--add-module=modules/ngx_http_upstream_session_sticky_module"
# 替换apt源
RUN sed -ri 's#archive.ubuntu.com|security.ubuntu.com#mirrors.aliyun.com#g' /etc/apt/sources.list \&& apt update
# 添加源码
ADD ${TENGINE_NAME}.tar.gz /tmp/
# 编译安装
RUN apt install -y libssl-dev make gcc pcre2-utils libpcre3-dev zlib1g-dev \&& cd /tmp/${TENGINE_NAME} \&& ${TENGINE_CONFIGURE_OPTS} \&& make -j $(nproc) \&& make install
# ==============================
# 阶段2:运行环境
# ==============================
FROM ubuntu:20.04
LABEL author="礼拜天没时间" \url="https://blog.csdn.net/weixin_73059914"
# 环境变量
ENV TENGINE_VERSION=2.3.3
ENV TENGINE_NAME=tengine-${TENGINE_VERSION}
ENV NGINX_USER=nginx
ENV INSTALL_PREFIX=/opt/module
# 1. 只拷贝编译产物
COPY --from=builder ${INSTALL_PREFIX}/ ${INSTALL_PREFIX}/
# 2. 安装运行依赖(只需要运行时库)
RUN sed -ri 's#archive.ubuntu.com|security.ubuntu.com#mirrors.aliyun.com#g' /etc/apt/sources.list \&& apt update \&& apt install -y libssl-dev pcre2-utils libpcre3-dev zlib1g-dev
# 3. 运行时配置
RUN groupadd ${NGINX_USER} \&& useradd -g ${NGINX_USER} ${NGINX_USER} \&& ln -s ${INSTALL_PREFIX}/${TENGINE_NAME} ${INSTALL_PREFIX}/tengine \&& ln -s ${INSTALL_PREFIX}/tengine/sbin/nginx /sbin/nginx \&& ln -sf /dev/stdout ${INSTALL_PREFIX}/tengine/logs/access.log \&& ln -sf /dev/stderr ${INSTALL_PREFIX}/tengine/logs/error.log \&& rm -rf /tmp/* /var/cache/*
# 4. 添加网页文件
ADD index.html ${INSTALL_PREFIX}/tengine/html/
# 5. 暴露端口
EXPOSE 80 443
# 6. 启动命令
CMD ["nginx", "-g", "daemon off;"]3.3 构建对比
# 构建多阶段镜像
[root@hadoop108 tengine-multi]# docker build -t "tengine-multi:2.3.3" .
# 查看镜像大小
[root@hadoop108 tengine-multi]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tengine-multi 2.3.3 e5ecfb93e5b9 58 seconds ago 196MB
tengine-dockerfile 2.3.3 276c35d21239 10 minutes ago 360MB
tengine 2.3.3 25f29f6c517a 1 hour ago 389MB3.4 三种方式对比
| 构建方式 | 镜像大小 | 构建速度 | 安全性 | 可维护性 | 适用场景 |
|---|---|---|---|---|---|
| 手工构建 | 389MB | 慢 | 低 | 差 | 学习理解 |
| Dockerfile | 360MB | 中 | 中 | 好 | 简单项目 |
| 多阶段构建 | 196MB | 快 | 高 | 极好 | 生产环境 |
多阶段构建的核心优势:
- 体积减少50%:196MB vs 389MB
- 安全性提升:没有gcc/make等编译工具
- 启动更快:更小的镜像拉取快、启动快
- 依赖隔离:运行时只需要必要的库
四、镜像层次架构:企业级最佳实践
在大型企业中,镜像构建遵循严格的层次结构,以实现最大化的复用和标准化。
4.1 三层架构理论
系统层(System)提供操作系统基础;运行时层(Runtime)提供语言环境(如JDK、Python);应用层(Application)包含具体项目代码。
4.2 目录结构实战
[root@hadoop108 module]# tree df/ -L 3
df/
├── project # 应用层:具体业务项目
│ ├── aggbook
│ │ ├── build.sh
│ │ └── Dockerfile
│ ├── blog
│ │ ├── build.sh
│ │ └── Dockerfile
│ └── hospital
│ ├── build.sh
│ └── Dockerfile
├── runtime # 运行时层:各种语言环境
│ ├── centos-7.9-jdk8
│ │ ├── build.sh
│ │ └── Dockerfile
│ ├── centos-7.9-jdk11
│ │ ├── build.sh
│ │ └── Dockerfile
│ ├── centos-7.9-python-3.8
│ │ ├── build.sh
│ │ └── Dockerfile
│ └── centos-7.9-nodejs-16
│ ├── build.sh
│ └── Dockerfile
└── system # 系统层:基础操作系统
├── centos-7.9
│ ├── build.sh
│ └── Dockerfile
├── centos-7.9-ssh
│ ├── build.sh
│ └── Dockerfile
└── ubuntu-20.04
├── build.sh
└── Dockerfile4.3 层次化Dockerfile示例
系统层:CentOS 7.9基础镜像
# df/system/centos-7.9/Dockerfile
FROM centos:7.9.2009
RUN yum install -y epel-release \&& yum clean all
CMD ["/bin/bash"]运行时层:JDK8镜像
# df/runtime/centos-7.9-jdk8/Dockerfile
FROM centos-7.9:latest
ENV JAVA_HOME=/usr/local/jdk1.8.0_202
ENV PATH=$PATH:$JAVA_HOME/bin
ADD jdk-8u202-linux-x64.tar.gz /usr/local/
CMD ["/bin/bash"]应用层:Spring Boot项目
# df/project/shop/Dockerfile
FROM centos-7.9-jdk8:latest
WORKDIR /app
ADD shop.jar /app/
ADD start.sh /app/
EXPOSE 8080
CMD ["/app/start.sh"]4.4 层次架构的收益
| 层级 | 复用性 | 变更频率 | 构建次数 | 典型大小 |
|---|---|---|---|---|
| 系统层 | 全公司共用 | 极低(季度/年) | 1次 | 200MB |
| 运行时层 | 部门共用 | 低(月/季度) | 10+次 | 300-500MB |
| 应用层 | 项目独用 | 高(每天) | 100+次 | 50-200MB |
核心收益:
- 存储节省:10个Java项目只需要1份系统层+1份JDK层
- 构建加速:改代码只需重建应用层,利用缓存秒级完成
- 标准化:统一的基础环境,减少“在我机器能跑”的问题
- 安全可控:基础镜像由平台组统一维护,打满安全补丁
五、生产环境最佳实践总结
5.1 镜像构建演进路线
手工构建 → Dockerfile → 多阶段构建 → 层次化架构 → CI/CD集成
5.2 企业级构建清单
- 基础镜像:选择官方镜像或自建基础镜像
- .dockerignore:排除无用文件
- 多阶段构建:分离编译和运行环境
- 非root用户:运行容器不使用root
- 标签规范:版本号+commit+构建时间
- 镜像扫描:集成trivy/clair扫描漏洞
- 签名验证:保证镜像完整性
5.3 最终对比数据
| 指标 | 手工构建 | Dockerfile | 多阶段构建 | 层次化架构 |
|---|---|---|---|---|
| 镜像大小 | 389MB | 360MB | 196MB | 150MB |
| 构建时间 | 15min | 8min | 8min | 3min |
| 安全风险 | 高 | 中 | 低 | 极低 |
| 可维护性 | 差 | 中 | 好 | 极好 |
| 存储复用 | 无 | 无 | 部分 | 完全 |
结语
从手工构建到多阶段构建,我们见证了Docker镜像构建的完整进化史。手工构建是理解镜像本质的必经之路;Dockerfile是自动化的第一步;多阶段构建是生产环境的标准答案;层次化架构则是企业级大规模实践的终极方案。记住这个原则:编译环境要胖,运行环境要瘦。现在,是时候重构你的Dockerfile了!
思考题:如果你的项目是用apt/yum安装的软件(如MySQL、Redis),多阶段构建还适用吗?为什么?
欢迎在评论区分享你的见解!
