定制内网可用的 Docker 基础镜像全攻略
概述
在完全内网隔离的环境中,为 CI/CD 流水线准备一个包含所有依赖工具的基础镜像是一项常见且关键的任务。本文将以一个真实场景为例,详细讲解两种将 curl 和 jq 工具集成到现有 maven:3.6.3-jdk-11 镜像中的方法。无论你是需要创建一个全新的工具镜像,还是在已有业务镜像上增强,本文都将提供从零开始的完整步骤、原理解析和避坑指南。
核心概念与决策
在开始前,需要理解一个核心原则:Linux 软件包格式与操作系统发行版严格绑定 。例如,Debian/Ubuntu 系使用 .deb 包和 apt 管理器,而 Red Hat/CentOS 系使用 .rpm 包和 yum 管理器。混合使用会导致失败。
由此,我们面临两种路径选择:
- 路径一:创建全新的工具镜像 。从一个纯净的官方系统镜像(如
debian:10)开始,安装所需工具,得到一个功能专一的工具镜像。 - 路径二:增强已有的业务镜像 。在现有的业务镜像(如
harbor.goertek.com/library/maven:3.6.3-jdk-11)基础上,安装额外工具,保留所有原有功能。
无论选择哪条路,都离不开一个万能工具:Docker Desktop。它让我们能在 Windows 或 macOS 上,通过容器轻松创建各种 Linux 环境来完成工作。
下面的流程图概括了两种路径的完整工作流:
(如 debian:10)"]; B1 --> B2["获取工具包
(在对应系统容器中下载)"]; B2 --> B3["编写 Dockerfile"]; B3 --> C["通用后续流程"]; D -- "路径二:增强已有业务镜像" --> A1["侦察镜像身份
(系统、版本、架构)"]; A1 --> A2["获取匹配的工具包
(必须版本一致)"]; A2 --> A3["编写 Dockerfile
(FROM 原始镜像)"]; A3 --> C; subgraph C[通用后续流程] direction LR C1["构建新镜像"] --> C2["验证新镜像"]; C2 --> C3["推送新镜像"]; C3 --> C4["更新 CI/CD 配置"]; end
路径一:创建全新的工具镜像
此路径适合打造一个功能专注、体积可控的通用工具镜像。
步骤详解
1. 前期准备
确保已安装 Docker Desktop,并以管理员身份打开 PowerShell (Windows) 或终端 (macOS/Linux)。
2. 选择并拉取基础镜像
我们选择一个官方且体积较小的 Debian 镜像作为起点。
bash
docker pull debian:10
3. 在匹配环境中下载工具包
我们需要在一个 Debian 10 环境中下载正确的 .deb 包。
步骤1:环境侦察与源修正(它是一个低成本的预演和验证。目的是确保"换源"这个关键步骤能在当前网络和环境下正确执行,避免在更耗时的第二步(下载大量文件)中途失败,我原来使用的是旧版本Debian 10 (buster))
bash
docker run --rm debian:10 bash -c "sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list && sed -i 's/security.debian.org/archive.debian.org/g' /etc/apt/sources.list && cat /etc/apt/sources.list"
步骤2:在修正的环境中进行实际下载,下载完后记住这个目录,写dockerfile时要用到
bash
# 此命令会启动一个临时容器,并在其中下载包到本机的 ~/debian_packages_clean 目录
docker run --rm -v ${HOME}\debian_packages_clean:/output debian:10 bash -c 'sed -i "s/deb.debian.org/archive.debian.org/g" /etc/apt/sources.list && sed -i "s/security.debian.org/archive.debian.org/g" /etc/apt/sources.list && cd /output && apt-get update -qq && apt-get download curl jq libcurl4 ca-certificates'
关键点 :使用 archive.debian.org 源,因为 Debian 10 已结束官方支持。
问题:为什么制定了版本还要换源?整个命令的逻辑链条是这样的:
bash
1. 启动debian:10容器(一个纯净的旧系统环境)
2. 换源:修改系统配置,让其能"找到"旧版本包的存放地。
3. apt-get update:根据新地址,刷新本地的"图书目录"。
4. apt download -t buster:使用刷新后的目录,精确查找并下载"buster专柜"里的指定包。
结论:-t buster 解决了 "下载哪个包" 的问题,而 换源 解决了 "去哪才能找到这个包" 的问题。两者必须配合,缺一不可。这是我们为旧系统准备离线包时的一个标准操作组合。
4. 编写 Dockerfile
将上边下载好的工具文件目录与Dockerfile放在一个目录下,创建一个名为 Dockerfile 的文件,内容如下:
dockerfile
# 使用我们选择的基础镜像
FROM debian:10
# 将下载好的 deb 包复制到镜像中
COPY my_tools_debs/*.deb /tmp/
# 安装包,如果依赖不全则自动修复
RUN dpkg -i /tmp/*.deb 2>/dev/null || (apt-get update && apt-get install -f -y && apt-get clean)
# 清理临时文件
RUN rm -rf /tmp/*.deb /var/lib/apt/lists/*
# 验证工具是否安装成功
RUN curl --version && jq --version
5. 构建、验证与推送
bash
# 构建镜像
docker build -f Dockerfile -t harbor.com/business-project/curl-jq:1.0 .
# 验证镜像功能
docker run --rm harbor.com/business-project/curl-jq:1.0 curl --version
# 标记并推送到私有仓库 (按需执行)
docker push harbor.com/business-project/curl-jq:1.0
路径二:增强已有的业务镜像
此路径适合在现有镜像(如已包含 Java, Maven)上添加工具,是本文的重点。
步骤详解
1. 前期准备与拉取原始镜像
首先,将你需要增强的原始镜像拉取到本地。
bash
docker login harbor.goertek.com # 如果需要
docker pull harbor.com/library/maven:3.6.3-jdk-11
2. 侦察原始镜像的"身份"(最关键的一步)
必须查明基础镜像的操作系统、版本和架构,这决定了后续所有操作。
bash
docker run --rm harbor.com/library/maven:3.6.3-jdk-11 sh -c "cat /etc/os-release | grep -E '^(ID|VERSION_CODENAME)=' && echo '---' && uname -m"
请记录输出,例如:
ID=debian
VERSION_CODENAME=buster
---
x86_64
这个结果意味着:我们必须使用 Debian 10 (buster) 系统、amd64 架构的 .deb 软件包。
3. 获取完全匹配的工具包
根据侦察结果,在匹配的环境(Debian 10容器)中下载工具。
bash
# 启动临时Debian 10容器下载包到本机的 ~/enhance_packages 目录
docker run --rm -v ${HOME}/enhance_packages:/output debian:10 bash -c "
sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list &&
cd /output &&
apt-get update -qq &&
apt-get download curl jq libcurl4 ca-certificates
"
关键点 :我们额外下载了 libcurl4 和 ca-certificates,这是 curl 能正常运行和支持 HTTPS 所必需的依赖。
4. 编写用于增强的 Dockerfile
创建一个名为 Dockerfile 的文件。FROM 指令必须指向你要增强的原始镜像。
dockerfile
# 关键:基于你要增强的原始镜像
FROM harbor.goertek.com/library/maven:3.6.3-jdk-11
# 复制离线包
COPY debian_packages_clean/*.deb /tmp/packages/
# 安装步骤(必须一气呵成)
# a. 将软件源替换为有效的存档源
# b. 尝试安装所有本地deb包
# c. 如果失败(如依赖问题),则更新源并自动修复安装
# d. 最后清理临时文件和缓存
RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list && \
sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list && \
dpkg -i /tmp/packages/*.deb 2>/dev/null || (apt-get update && apt-get install -f -y && apt-get clean) && \
rm -rf /tmp/packages /var/lib/apt/lists/*
# 验证:确保原有功能和新工具都正常工作
RUN echo "=== 验证工具链 ===" && \
curl --version && echo "---" && \
jq --version && echo "---" && \
java -version && echo "---" && \
mvn -v && echo "=== 所有工具验证通过 ==="
# 设置工作目录
WORKDIR /workspace
CMD ["/bin/bash"]
5. 构建、验证与推送增强后的镜像
bash
# 构建新镜像
docker build -f Dockerfile-t harbor.com/mpm-library/maven-with-tools:3.6.3-jdk-11 .
# 验证新镜像
docker run --rm harbor.com/mpm-library/maven-with-tools:3.6.3-jdk-11 sh -c "curl --version && jq --version"
# 推送新镜像到仓库
docker push harbor.com/mpm-library/maven-with-tools:3.6.3-jdk-11
6. 更新 CI/CD 配置
最后,修改你的 GitLab CI/CD 配置文件 .gitlab-ci.yml,让流水线使用新镜像。
yaml
default:
image:
name: harbor.com/mpm-library/maven-with-tools:3.6.3-jdk-11 # 替换为你的新镜像名
entrypoint: [""]
before_script:
- echo "所有工具(Java, Maven, curl, jq)已预装,无需在线安装。" # 可删除旧的安装脚本
提交更改后,在 GitLab 上重试之前失败的 Pipeline 任务即可。
疑难杂症与核心原理总结
在实践过程中,遇到了许多典型问题,其背后的原理和解决方案值得总结:
| 问题现象 | 根本原因 | 解决方案与原理 |
|---|---|---|
运行 curl 报错 Error loading shared library |
从 Alpine 镜像直接复制二进制文件到 Debian 镜像,底层 C 库 (musl vs glibc) 不兼容。 |
坚持使用与目标镜像同系统的原生包格式 。通过侦察确定是 Debian 后,坚持使用 .deb 包。 |
apt-get update 失败,提示 404 或 Release file not found |
旧版本 Debian(如 10 buster)的官方软件源已移至存档站。 | 在容器内或 Dockerfile 的 RUN 指令中,将 deb.debian.org 替换为 archive.debian.org。 |
| 构建时下载了超过 200MB 的无关文件 | 使用了 apt-cache depends --recurse 命令,它递归拉取了大量非直接依赖的库(如桌面组件)。 |
精准下载 :使用 apt-get download 包名1 包名2 命令,它只下载指定的包及其直接依赖。 |
curl 报告 (77) error setting certificate file |
镜像中缺少 SSL 根证书包 (ca-certificates)。 |
在下载工具包时,务必包含 ca-certificates 这个包。 |
Dockerfile 构建时 apt-get install -f -y 失败 |
apt-get update 和安装命令写在了不同的 RUN 指令中,后续指令无法感知前一步的源更改。 |
将"换源"和"安装"操作置于同一个 RUN 指令中,保证环境一致性。 |
结语
定制内网 Docker 镜像的过程,本质上是对 Linux 系统分发、软件包管理和容器分层概念的一次深入实践。成功的关键在于严谨:严谨地侦察环境、严谨地匹配软件版本、严谨地编写构建脚本。
通过本文的两种方法,你不仅可以解决 curl 和 jq 的问题,更能将此模式推广到任何需要在特定内网环境中定制软件栈的场景,从而打造出稳定、高效、可重复的 CI/CD 基础环境。