Docker自动化构建实战:从手工到多阶段构建的完美进化
-
- 前言:为什么要自动化构建?
- 一、手工构建Tengine镜像:理解镜像本质
-
- [1.1 运行基础容器](#1.1 运行基础容器)
- [1.2 配置基础环境](#1.2 配置基础环境)
- [1.3 编译安装Tengine](#1.3 编译安装Tengine)
- [1.4 运行时配置优化](#1.4 运行时配置优化)
- [1.5 生成镜像](#1.5 生成镜像)
- 二、Dockerfile自动化构建:迈出第一步
-
- [2.1 项目结构](#2.1 项目结构)
- [2.2 编写Dockerfile](#2.2 编写Dockerfile)
- [2.3 构建并运行](#2.3 构建并运行)
- 三、多阶段构建:终极进化
-
- [3.1 什么是多阶段构建?](#3.1 什么是多阶段构建?)
- [3.2 多阶段Dockerfile](#3.2 多阶段Dockerfile)
- [3.3 构建对比](#3.3 构建对比)
- [3.4 三种方式对比](#3.4 三种方式对比)
- 四、镜像层次架构:企业级最佳实践
-
- [4.1 三层架构理论](#4.1 三层架构理论)
- [4.2 目录结构实战](#4.2 目录结构实战)
- [4.3 层次化Dockerfile示例](#4.3 层次化Dockerfile示例)
- [4.4 层次架构的收益](#4.4 层次架构的收益)
- 五、生产环境最佳实践总结
-
- [5.1 镜像构建演进路线](#5.1 镜像构建演进路线)
- [5.2 企业级构建清单](#5.2 企业级构建清单)
- [5.3 最终对比数据](#5.3 最终对比数据)
- 结语
前言:为什么要自动化构建?
在容器化时代,手动构建镜像存在三个核心问题:
- 不可重复:今天能成功,明天可能失败
- 无法追溯:三个月后记不清当时怎么配的
- 镜像臃肿:带着编译工具链,动辄几百MB
本文将带你从零开始,体验手工构建 → Dockerfile → 多阶段构建的完整进化之路,最终掌握企业级镜像构建的最佳实践。
一、手工构建Tengine镜像:理解镜像本质
在自动化之前,我们先手动走一遍流程,深刻理解镜像的组成。
1.1 运行基础容器
bash
# 启动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/bash
1.2 配置基础环境
bash
# 替换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 curl
1.3 编译安装Tengine
bash
# 创建规范目录
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 install
1.4 运行时配置优化
bash
# 创建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 tengine
1.5 生成镜像
bash
# 清理无用文件
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自动化构建:迈出第一步
2.1 项目结构
bash
[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.gz
2.2 编写Dockerfile
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 构建并运行
bash
# 构建镜像
[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 什么是多阶段构建?
核心思想:将"编译环境"和"运行环境"彻底分离,只把"运行必需的结果"放进最终镜像。
阶段2:Final
阶段1:Builder
COPY --from
源码
编译
编译产物
基础镜像
拷贝编译产物
最终镜像
3.2 多阶段Dockerfile
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 构建对比
bash
# 构建多阶段镜像
[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 389MB
3.4 三种方式对比
| 构建方式 | 镜像大小 | 构建速度 | 安全性 | 可维护性 | 适用场景 |
|---|---|---|---|---|---|
| 手工构建 | 389MB | 慢 | 低 | 差 | 学习理解 |
| Dockerfile | 360MB | 中 | 中 | 好 | 简单项目 |
| 多阶段构建 | 196MB | 快 | 高 | 极好 | 生产环境 |
多阶段构建的核心优势:
- ✅ 体积减少50%:196MB vs 389MB
- ✅ 安全性提升:没有gcc/make等编译工具
- ✅ 启动更快:更小的镜像拉取快、启动快
- ✅ 依赖隔离:运行时只需要必要的库
四、镜像层次架构:企业级最佳实践
4.1 三层架构理论
在大型企业中,镜像构建遵循严格的层次结构:
系统层(System)
运行时层(Runtime)
应用层(Application)
shop项目
blog项目
hospital项目
aggbook项目
JDK8
JDK11
JDK17
Python3.8
Node.js 16
CentOS 7.9
Ubuntu 20.04
CentOS 7.9-ssh
4.2 目录结构实战
bash
[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
└── Dockerfile
4.3 层次化Dockerfile示例
系统层:CentOS 7.9基础镜像
dockerfile
# df/system/centos-7.9/Dockerfile
FROM centos:7.9.2009
RUN yum install -y epel-release \
&& yum clean all
CMD ["/bin/bash"]
运行时层:JDK8镜像
dockerfile
# 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项目
dockerfile
# 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),多阶段构建还适用吗?为什么?
欢迎在评论区分享你的见解!