Docker 多阶段构建深度实战:Nginx Ingress Controller 的三阶段源码构建 1. 引言在云原生生态中Nginx Ingress Controller 是 Kubernetes 集群流量入口的核心组件。它通常由两部分组成Nginx 核心负责流量转发和Go 语言编写的 Controller负责监听 Kubernetes API 并动态更新 Nginx 配置。为了构建一个生产可用的 Nginx Ingress Controller 镜像我们需要经历三个关键阶段GCC 构建阶段从 Nginx 源码编译出 Nginx 二进制文件。Golang 构建阶段编译 Go 语言编写的 Controller 二进制文件。Runtime 运行阶段将前两个阶段产出的二进制文件合并到一个极简的运行镜像中。本文将结合具体的 Dockerfile深入讲解如何通过 Docker 多阶段构建实现这三个阶段的隔离与协作最终得到一个体积小、安全性高的生产级镜像。2. 为什么需要三阶段构建2.1 传统单阶段构建的困境如果在一个 Dockerfile 中同时安装 GCC、Go 工具链、Nginx 源码和所有依赖最终镜像将包含GCC、Make、Perl 等编译工具数百 MBGo 编译器及标准库数百 MBNginx 源码及编译中间产物运行时不需要的调试工具和文档这样的镜像体积通常在1.5GB 以上且包含了大量不必要的攻击面。2.2 多阶段构建的核心思想Docker 多阶段构建允许我们在一个 Dockerfile 中使用多个FROM指令每个阶段可以基于不同的基础镜像。我们可以阶段 1GCC Build使用包含 GCC、Make 等编译工具的镜像从 Nginx 源码编译出 Nginx 二进制。阶段 2Golang Build使用 Go 官方镜像编译 Controller 二进制。阶段 3Runtime使用极简的 Alpine 或 Distroless 镜像只从前面两个阶段复制编译好的二进制文件和必要的运行时依赖。这样最终镜像只包含运行所需的最小文件集体积可缩小到50MB 以下。3. 三阶段 Dockerfile 实战下面是一个完整的 Dockerfile 示例展示了如何构建一个 Nginx Ingress Controller 镜像。3.1 第一阶段GCC 构建 Nginx# 第一阶段GCC 构建 Nginx # 使用包含完整编译工具链的镜像 FROM ubuntu:22.04 AS nginx-builder # 安装编译 Nginx 所需的工具和库 RUN apt-get update apt-get install -y \ gcc \ make \ libpcre3-dev \ libssl-dev \ zlib1g-dev \ wget \ rm -rf /var/lib/apt/lists/* # 下载并解压 Nginx 源码 WORKDIR /tmp RUN wget https://nginx.org/download/nginx-1.25.3.tar.gz \ tar -xzf nginx-1.25.3.tar.gz # 编译 Nginx配置常用模块生成静态链接的二进制 WORKDIR /tmp/nginx-1.25.3 RUN ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-stream \ --with-stream_ssl_module \ --without-http_autoindex_module \ --without-http_ssi_module \ make -j$(nproc) \ make install # 将编译好的 Nginx 二进制和配置文件复制到一个临时目录方便后续阶段复制 RUN mkdir -p /nginx-output/usr/local/nginx \ cp -r /usr/local/nginx/sbin /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/conf /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/html /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/logs /nginx-output/usr/local/nginx/关键点使用ubuntu:22.04作为基础镜像它包含了 GCC、Make 等编译工具。通过apt-get install安装 Nginx 编译所需的依赖库PCRE、OpenSSL、Zlib。从源码编译 Nginx并只保留编译产物二进制、配置文件、静态文件。--without-http_autoindex_module等选项用于禁用不需要的模块进一步精简。3.2 第二阶段Golang 构建 Controller# 第二阶段Golang 构建 Controller FROM golang:1.21 AS go-builder # 设置工作目录 WORKDIR /workspace # 复制 go.mod 和 go.sum利用 Docker 缓存层 COPY go.mod go.sum ./ RUN go mod download # 复制所有源码 COPY . . # 编译 Go 项目生成静态链接的二进制文件 # CGO_ENABLED0 确保生成静态二进制不依赖 libc RUN CGO_ENABLED0 GOOSlinux go build \ -a -installsuffix cgo \ -ldflags-s -w \ -o nginx-ingress-controller \ ./cmd/nginx-ingress-controller # 将编译好的二进制文件复制到临时目录 RUN mkdir -p /go-output \ cp /workspace/nginx-ingress-controller /go-output/关键点使用golang:1.21作为 Go 编译环境。CGO_ENABLED0生成静态链接的 Go 二进制不依赖任何动态库。-ldflags-s -w用于去除调试信息进一步减小二进制体积。只复制编译好的二进制文件到临时目录Go 工具链和源码都被丢弃。3.3 第三阶段Runtime 运行环境# 第三阶段Runtime 运行环境 # 使用极简的 Alpine 镜像作为运行环境 FROM alpine:3.19 # 安装运行时依赖 # - ca-certificates用于 HTTPS 证书验证 # - tzdata时区数据 # - libcrypto3、libssl3Nginx 运行时需要的 SSL 库 RUN apk add --no-cache \ ca-certificates \ tzdata \ libcrypto3 \ libssl3 \ pcre # 从第一阶段GCC 构建复制 Nginx 二进制和配置文件 COPY --fromnginx-builder /nginx-output/usr/local/nginx /usr/local/nginx # 从第二阶段Golang 构建复制 Controller 二进制 COPY --fromgo-builder /go-output/nginx-ingress-controller /usr/local/bin/ # 创建必要的目录和用户 RUN addgroup -S nginx \ adduser -S -G nginx nginx \ mkdir -p /var/log/nginx \ mkdir -p /var/cache/nginx \ chown -R nginx:nginx /var/log/nginx /var/cache/nginx /usr/local/nginx # 暴露端口 EXPOSE 80 443 # 设置容器启动命令实际启动由 Controller 管理 Nginx CMD [nginx-ingress-controller]关键点使用alpine:3.19作为运行基础镜像体积仅约 5MB。只安装 Nginx 运行时需要的动态库libcrypto3、libssl3、pcre不安装任何编译工具。通过COPY --fromnginx-builder和COPY --fromgo-builder分别从两个构建阶段复制产物。创建非 root 用户nginx提升容器安全性。4. 三阶段构建的优势分析4.1 构建环境与运行环境完全隔离方面传统单阶段构建三阶段多阶段构建基础镜像ubuntu:22.04golang:1.21约 1.5GBalpine:3.19约 5MB包含工具GCC、Make、Go 编译器、Perl、调试工具等仅运行时动态库最终镜像体积1.2GB - 1.8GB30MB - 60MB攻击面大量不必要的工具和库最小化依赖攻击面大幅减少4.2 镜像体积对比通过三阶段构建最终镜像体积从1.5GB 降至约 50MB提升了约30 倍。这意味着更快的拉取和推送在 CI/CD 流程中镜像传输时间大幅缩短。更快的容器启动镜像解压和加载时间减少。更低的存储成本镜像仓库和节点本地存储占用减少。4.3 安全性提升最小化攻击面运行镜像中不包含 GCC、Make、Go 编译器、Perl 等工具攻击者无法利用这些工具进行提权或横向移动。静态链接的 Go 二进制CGO_ENABLED0生成的 Go 二进制不依赖libc避免了libc漏洞如 Shellshock、Ghost的影响。非 root 用户运行创建专用用户运行 Nginx 和 Controller遵循最小权限原则。4.4 构建缓存优化# 利用 Docker 缓存层避免重复下载依赖 COPY go.mod go.sum ./ RUN go mod download COPY . .将go.mod和go.sum的复制放在源码复制之前只要依赖文件没有变化Docker 就会复用go mod download的缓存层大幅加速构建过程。5. 完整 Dockerfile 汇总为了方便你直接使用下面是完整的 Dockerfile 内容# 第一阶段GCC 构建 Nginx FROM ubuntu:22.04 AS nginx-builder RUN apt-get update apt-get install -y \ gcc make libpcre3-dev libssl-dev zlib1g-dev wget \ rm -rf /var/lib/apt/lists/* WORKDIR /tmp RUN wget https://nginx.org/download/nginx-1.25.3.tar.gz \ tar -xzf nginx-1.25.3.tar.gz WORKDIR /tmp/nginx-1.25.3 RUN ./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-stream \ --with-stream_ssl_module \ --without-http_autoindex_module \ --without-http_ssi_module \ make -j$(nproc) make install RUN mkdir -p /nginx-output/usr/local/nginx \ cp -r /usr/local/nginx/sbin /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/conf /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/html /nginx-output/usr/local/nginx/ \ cp -r /usr/local/nginx/logs /nginx-output/usr/local/nginx/ # 第二阶段Golang 构建 Controller FROM golang:1.21 AS go-builder WORKDIR /workspace COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build \ -a -installsuffix cgo \ -ldflags-s -w \ -o nginx-ingress-controller \ ./cmd/nginx-ingress-controller RUN mkdir -p /go-output \ cp /workspace/nginx-ingress-controller /go-output/ # 第三阶段Runtime 运行环境 FROM alpine:3.19 RUN apk add --no-cache \ ca-certificates tzdata libcrypto3 libssl3 pcre COPY --fromnginx-builder /nginx-output/usr/local/nginx /usr/local/nginx COPY --fromgo-builder /go-output/nginx-ingress-controller /usr/local/bin/ RUN addgroup -S nginx \ adduser -S -G nginx nginx \ mkdir -p /var/log/nginx /var/cache/nginx \ chown -R nginx:nginx /var/log/nginx /var/cache/nginx /usr/local/nginx EXPOSE 80 443 CMD [nginx-ingress-controller]6. 总结通过本文的实战我们深入了解了 Docker 多阶段构建在 Nginx Ingress Controller 场景下的强大能力GCC 构建阶段从 Nginx 源码编译出 Nginx 二进制使用完整的编译工具链。Golang 构建阶段编译 Go 语言编写的 Controller生成静态链接的二进制。Runtime 运行阶段将前两个阶段的产物合并到极简的 Alpine 镜像中只保留运行时依赖。这种三阶段构建模式实现了构建环境与运行环境的完全隔离最终镜像体积从 1.5GB 降至约 50MB同时大幅提升了安全性。这是生产级容器化应用的最佳实践值得在项目中推广使用。