外观
项目 7:基础软件与容器环境初始化
约 3205 字大约 11 分钟
apt镜像源Dockercontainerd
2026-05-31
项目目标
本课继续完善服务器初始化流程:编写 init_runtime.sh,自动备份并替换 apt 软件源,安装基础工具,安装 Docker 或 containerd,并按参数配置容器镜像源。
脚本完成后,学生应能解释 apt 源、系统代号、容器运行时和 registry mirror 的区别,并能用命令验证服务状态。
一、基础软件初始化解决什么问题
一台新服务器经常缺少运维常用工具,例如 vim、git、curl、wget、ca-certificates。如果每次都手工安装,不仅慢,还容易漏装。更麻烦的是,apt 默认源访问不稳定时,安装过程会卡住或失败。
本课把这类操作沉淀成脚本:
这里有一个容易混淆的点:Docker 和 containerd 都与容器有关,但定位不同。
| 项目 | 定位 | 常见命令 | 适合场景 |
|---|---|---|---|
| Docker | 面向开发者的一整套容器工具链 | docker run、docker ps | 单机练习、应用打包、快速验证 |
| containerd | 更底层的容器运行时 | ctr、crictl | Kubernetes 节点运行时 |
在后续云原生场景中,containerd 更接近 Kubernetes 节点的真实运行环境;如果只是完成单机容器练习,Docker 使用体验更直接。
1.1 APT 包管理器的分层架构
APT 包管理架构演示
直观展示 `apt update` 和 `apt install` 在底层到底做了什么
远程软件源 (Internet)
☁️archive.ubuntu.com
☁️security.ubuntu.com
/etc/apt/sources.list本地系统架构
用户命令接口
apt依赖解析与下载
apt-get / apt-cache本地包索引库 (过期)
🗄️
底层解包与安装
dpkg💿 系统磁盘: curl 未安装 ❌
要写好 apt 相关的初始化脚本,需要理解 apt 的工作层次。apt 不是一个单一程序,而是一组工具的统称:
| 层级 | 工具 | 职责 |
|---|---|---|
| 底层 | dpkg | 直接安装/卸载 .deb 包,不处理依赖关系 |
| 中层 | apt-get、apt-cache | 解析依赖、从仓库下载包、调用 dpkg 安装 |
| 上层 | apt | 面向用户的统一命令,整合了 apt-get 和 apt-cache 的常用功能 |
| 仓库配置 | /etc/apt/sources.list 及 sources.list.d/*.list | 定义从哪些 URL 获取软件包索引 |
apt update 的本质是从 sources.list 中定义的每个 URL 下载 Packages.gz 或 InRelease 索引文件,更新本地的包数据库。apt install 则根据这个本地数据库解析依赖树,再逐一下载和安装。理解了这一点,就不难明白为什么换源后必须先 apt update——本地的包数据库还是旧源的,直接 install 会找不到包或拉到过期版本。
1.2 sources.list 文件的结构
一条典型的 apt 源条目包含四个字段:
deb http://archive.ubuntu.com/ubuntu jammy main restricted universe multiverse| 字段 | 含义 | 示例 |
|---|---|---|
deb / deb-src | 二进制包 / 源码包 | deb |
| URL | 软件源根地址 | http://archive.ubuntu.com/ubuntu |
| 发行代号 | 系统版本的代号 | jammy(22.04)、noble(24.04) |
| 组件 | 软件包的分类仓库 | main、universe、multiverse |
组件决定了你能安装哪些类型的软件:
| 组件 | 说明 |
|---|---|
main | Canonical 官方支持的免费软件 |
universe | 社区维护的免费软件 |
restricted | 官方支持的专有驱动 |
multiverse | 非自由软件 |
安全更新使用独立的 -security 后缀(如 jammy-security),通常放在单独的源条目中。脚本在生成源文件时,必须确保安全更新源也被正确替换,否则系统将无法获取安全补丁。
二、为什么替换 apt 源要先识别系统代号
apt 源地址里通常包含系统代号,例如 jammy、noble。直接把别人的源文件复制过来,最常见的错误就是系统版本不匹配。例如把 noble(24.04)的源写入 jammy(22.04)系统,apt update 会报大量 404。
查看系统代号:
lsb_release -cs如果输出是 jammy,源里就应该包含 jammy、jammy-updates、jammy-security 等条目;如果输出是 noble,则应使用 noble 对应条目。
脚本不能把代号写死,而应该自动识别:
CODENAME="$(lsb_release -cs)"然后使用变量拼接源条目:
cat > /etc/apt/sources.list << EOF
deb ${APT_MIRROR} ${CODENAME} main restricted universe multiverse
deb ${APT_MIRROR} ${CODENAME}-updates main restricted universe multiverse
deb ${APT_MIRROR} ${CODENAME}-security main restricted universe multiverse
EOF这样做的好处是:同一套脚本可以无修改地运行在 22.04 和 24.04 上。
问题:用于输出当前系统发行版代号的命令是什么?
三、容器运行时的技术栈
3.1 从 OCI 规范说起
容器运行时架构对比:Docker vs containerd
点击按钮模拟在不同工具链下“运行一个容器”的指令传递路径
docker CLIctr / crictldockerdDocker Daemon (处理网络、构建、卷)
containerd核心容器运行时管理器 (CRI 支持)
containerd-shim容器进程的守护者,解耦 containerd 与容器进程
runc (OCI Runtime)实际通过 Linux namespace 和 cgroup 启动容器
要理解 Docker 和 containerd 的关系,需要了解 OCI(Open Container Initiative)定义的两个核心规范:
| 规范 | 全称 | 定义内容 |
|---|---|---|
| runtime-spec | OCI Runtime Specification | 如何运行一个容器——定义 config.json 格式和容器生命周期 |
| image-spec | OCI Image Specification | 容器镜像的格式——定义 manifest、config、layer 的结构 |
这两个规范让不同的容器工具可以互操作:Docker 构建的镜像可以被 containerd 运行,因为大家都遵守 OCI image-spec;runc 和 crun 都可以作为 OCI runtime-spec 的实现,被上层编排工具调用。
┌──────────────────────────────────────────────┐
│ Docker CLI │ crictl │ ctr │ nerdctl │ ← 用户命令
├──────────────────────────────────────────────┤
│ dockerd │ containerd │ CRI-O │ ← 容器管理器
├──────────────────────────────────────────────┤
│ containerd-shim │ ← 容器生命周期代理
├──────────────────────────────────────────────┤
│ runc / crun / kata / gVisor │ ← OCI Runtime 实现
└──────────────────────────────────────────────┘这个分层架构解释了为什么 Kubernetes 在 1.24 之后弃用了 dockershim:Kubernetes 通过 CRI(Container Runtime Interface)与容器运行时通信,而 Docker 不支持原生 CRI,需要额外的 dockershim 适配层。containerd 和 CRI-O 原生支持 CRI,链路更短,维护成本更低。
3.2 Docker 与 containerd 的配置区别
两者虽然都能运行容器,但配置体系完全不同:
| 对比维度 | Docker | containerd |
|---|---|---|
| 主配置文件 | /etc/docker/daemon.json | /etc/containerd/config.toml |
| 配置格式 | JSON | TOML |
| 镜像源配置 | "registry-mirrors": ["..."] | [plugins."io.containerd.grpc.v1.cri".registry.mirrors] |
| 默认 socket | /var/run/docker.sock | /run/containerd/containerd.sock |
| 原生 CLI | docker | ctr(调试用)、crictl(CRI 兼容) |
3.3 Docker CE 的安装链路
Docker 在 Ubuntu 上的安装有多种方式:
| 方式 | 命令 | 推荐度 |
|---|---|---|
| 系统仓库 | apt install docker.io | 版本旧,不推荐生产使用 |
| 官方仓库 | 添加 download.docker.com 源后安装 docker-ce | 推荐,版本最新 |
| 官方脚本 | curl -fsSL get.docker.com | sh | 方便,但审计困难 |
脚本应该优先使用官方仓库安装方式,因为这可以后续通过 apt upgrade 统一更新,而不需要重新执行安装脚本。安装完成后,将当前用户加入 docker 组可以免 sudo 使用 Docker:
sudo usermod -aG docker "$USER"但要注意:加入 docker 组等价于授予 root 权限,因为在 Docker 中挂载宿主机目录或使用 --privileged 都可以突破容器隔离。生产环境中应谨慎使用。
四、容器镜像源配置思路
容器镜像拉取慢时,可以配置 registry mirror。需要注意两点:
| 注意点 | 说明 |
|---|---|
| 镜像源要可替换 | 不要把某个地址写死为唯一方案,课堂和生产环境可能不同 |
| Docker 与 containerd 配置文件不同 | Docker 常用 /etc/docker/daemon.json,containerd 可使用 /etc/containerd/certs.d/docker.io/hosts.toml |
4.1 Docker daemon.json 结构
Docker 的 daemon.json 不仅用于配置镜像源,还可以控制日志驱动、存储驱动、iptables 行为等:
{
"registry-mirrors": ["https://mirror.example.com"],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"iptables": true,
"live-restore": true
}关键字段说明:
| 字段 | 作用 |
|---|---|
registry-mirrors | 镜像加速地址列表,Docker 会依次尝试 |
log-driver | 容器日志驱动,json-file 最常用 |
storage-driver | 存储后端,Ubuntu 推荐 overlay2 |
iptables | 是否由 Docker 管理 iptables 规则 |
live-restore | Docker 重启时是否保持容器运行 |
修改 daemon.json 后需要重启 Docker:
sudo systemctl restart docker4.2 containerd registry mirror 配置
containerd 推荐把镜像站点配置放到 certs.d 目录下。下方是一个模板,实际地址由脚本参数写入。
server = "https://registry-1.docker.io"
[host."https://你的镜像源地址"]
capabilities = ["pull", "resolve"]containerd 的镜像源配置相对 Docker 更细粒度:可以按 registry 域名分别指定 mirror,每个 mirror 还可以定义不同的能力(pull、resolve、push)。这为混合场景——例如内网 registry 做 push、外网 mirror 做 pull——提供了更大的灵活性。
问题:containerd registry hosts 配置通常使用的 TOML 文件名是什么?
五、运行脚本
将第二区解锁的脚本保存为 init_runtime.sh:
chmod +x init_runtime.sh5.1 只安装基础工具与 containerd
sudo ./init_runtime.sh \
--mirror https://mirrors.tuna.tsinghua.edu.cn/ubuntu \
--runtime containerd \
--apply5.2 为 containerd 配置镜像源
sudo ./init_runtime.sh \
--mirror https://mirrors.tuna.tsinghua.edu.cn/ubuntu \
--runtime containerd \
--registry-mirror https://你的镜像源地址 \
--apply5.3 改为安装 Docker
sudo ./init_runtime.sh \
--mirror https://mirrors.tuna.tsinghua.edu.cn/ubuntu \
--runtime docker \
--registry-mirror https://你的镜像源地址 \
--apply镜像源可用性
registry mirror 地址可能随时间变化。脚本应把地址做成参数,并在拉取失败时提示更换备用地址,而不是在代码里固定某个不可替换的值。同时建议脚本在写入 mirror 配置后自动做一次 docker pull hello-world 或 ctr image pull 来验证镜像源是否可达。
六、验证命令
6.1 验证 apt 和基础工具
apt-cache policy | head # 查看 apt 源优先级
vim --version | head -n 1
git --version
curl --version | head -n 1
wget --version | head -n 1apt-cache policy 可以显示当前系统中每个包的候选版本和来源仓库,是验证镜像源是否生效的最直接手段。如果某个源返回 404 或连接超时,policy 输出中该源的优先级会显示为负数或被忽略。
6.2 验证 containerd
systemctl is-active containerd
containerd --version
sudo ctr version
sudo crictl info 2>/dev/null | head -20ctr 是 containerd 自带的调试客户端,crictl 是 Kubernetes 社区提供的 CRI 兼容客户端。如果 containerd 配置了 CRI 插件,crictl 应该能正常输出节点信息。注意 ctr 和 crictl 操作的镜像和容器命名空间是隔离的——用 ctr 拉取的镜像在 crictl 中不一定可见。
6.3 验证 Docker
systemctl is-active docker
docker --version
sudo docker run --rm hello-world
docker info | grep -A5 "Registry Mirrors"docker info 输出的 "Registry Mirrors" 行直接反映了 daemon.json 中的 mirror 配置是否生效。如果显示为空列表,说明 daemon.json 没有正确放置或 JSON 语法有误。
如果 Docker 被墙或镜像源不可用,hello-world 可能拉取失败。此时重点检查 daemon.json 是否写入正确,以及 registry mirror 地址是否可访问。
七、提交物清单
| 提交物 | 要求 |
|---|---|
init_runtime.sh | 支持 apt 源备份、基础工具安装、Docker/containerd 二选一、镜像源配置 |
| apt 源备份文件 | 能说明备份路径和恢复方法(/etc/apt/sources.list.bak.时间戳) |
| 容器服务状态截图 | systemctl is-active docker 或 systemctl is-active containerd,以及对应的版本信息 |
| 运行记录 | 记录所选 runtime、镜像源参数和验证命令输出,说明 apt-cache policy 输出中如何确认镜像源已生效 |
八、故障排查
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
apt update 出现 404 | 系统代号写错,或镜像源不支持当前版本 | 用 lsb_release -cs 核对代号,换支持该版本的镜像源 |
docker run hello-world 拉取失败 | registry mirror 不可用或网络不可达 | 更换镜像源地址,systemctl restart docker 后重试;用 docker info 确认 mirror 配置 |
| containerd 配置后不生效 | config_path 未启用,或服务未重启 | 检查 /etc/containerd/config.toml 中的 config_path 配置,执行 systemctl restart containerd |
| Docker 和 containerd 都装了但不知道用哪个 | 两者定位混淆 | 本课只验收你选择的一个 runtime,后续 K8s 方向优先 containerd |
systemctl is-active docker 返回 inactive | Docker 安装失败或未自动启动 | 检查安装日志,手动 systemctl start docker,查看 journalctl -u docker |
containerd 的 crictl 命令报 connection refused | containerd 的 CRI 插件未启用或 socket 路径错误 | 检查 /etc/containerd/config.toml 中 CRI 插件配置,确认 socket 路径为 /run/containerd/containerd.sock |
镜像拉取成功但 crictl images 不显示 | ctr 和 crictl 使用不同命名空间 | crictl 默认使用 k8s.io 命名空间;也可以用 ctr -n k8s.io images ls 查看 |
九、进阶任务
探索任务 1:增加恢复 apt 源参数
为脚本增加 --restore-apt-source <backup-file> 参数,用备份文件恢复原始 apt 源,并重新执行 apt update 验证。恢复后应能通过 apt-cache policy 确认源已切回原状。
探索任务 2:比较 Docker 和 containerd 的镜像操作
在同一台机器上分别用 docker pull alpine 和 ctr image pull docker.io/library/alpine:latest 拉取同一个镜像,观察命令语法差异。用 docker images 和 ctr images ls 分别查看,注意命名空间的区别。
探索任务 3:为 containerd 配置多个 mirror
在 hosts.toml 中配置两个 mirror 地址,通过 ctr image pull 测试哪个优先被使用。理解 containerd 的 mirror 优先级机制——配置文件中靠前的 host 条目优先被尝试。
