企业级容器化技术 Docker:扩展实践
1. Podman 安装与环境
1.1 Podman 的定位
Podman(Pod Manager)是支持 OCI 容器和镜像的开源管理工具,采用无长期驻留中心 daemon 的本地管理架构,通过 crun、runc 等运行时启动容器,并使用 conmon 等组件管理运行过程。它提供 rootful 和 rootless 两种运行方式。
Podman 的许多 CLI 命令与 Docker 相近,可以构建 Dockerfile/Containerfile,并提供兼容的 API 服务;这不代表所有 Docker 参数、网络、插件、Compose、API 和存储配置都可以无条件替换。Podman 的 API 服务需要按需启动,不能把“无中心 daemon”理解成没有辅助进程或服务。
Podman 支持 Pod,使一组容器共享选定命名空间。共享网络很常见,但卷并非因为进入同一个 Pod 就自动全部共享。Compose 可通过外部兼容提供程序,例如 podman-compose 或 Docker Compose,配合 Podman socket 使用,仍需核对版本。
rootless Podman 通常使用用户命名空间映射容器身份;rootful 模式不能一概认为默认将容器 root 映射成普通宿主机用户。Docker 同样支持 rootless,加入 docker 组则通常意味着拥有接近主机 root 的管理能力。
| 特性 | Podman | Docker Engine |
|---|---|---|
| 核心架构 | 本地操作无中心常驻 daemon;可按需提供 API 服务 | 通过 dockerd 提供服务 |
| 用户模式 | rootful、rootless | rootful、rootless |
| 常见数据目录 | rootful:/var/lib/containers/storage;rootless:~/.local/share/containers/storage |
rootful 常见 /var/lib/docker;rootless 按用户环境配置 |
| 编排 | 内置 Pod 概念,Compose 依赖提供程序 | Compose、Swarm 等;Kubernetes 使用 CRI 运行时 |
| 镜像 | 支持 OCI 与兼容 Docker 镜像格式 | 支持 Docker 与 OCI 兼容格式 |
| 安全机制 | 用户命名空间、能力、seccomp、SELinux/AppArmor 等 | 也可结合相应内核机制,具体由模式和配置决定 |
适用场景包括开发机、希望使用 rootless 的环境、容器调试和受限资源系统。Podman 的 Pod 概念有助于理解 Kubernetes,但真实迁移仍要适配配置、存储、网络和编排对象,不能保证无缝迁移。
1.2 发行版软件源
是否预装取决于安装镜像和软件组,不应按发行版名称作绝对判断。Ubuntu 20.04 的 universe 中也提供过 Podman;Rocky 8 通常可从 AppStream/container-tools 获取,不是必须启用 PowerTools 才能安装。
查询包版本:
# Ubuntu
apt-cache madison podman
# Rocky
yum list --showduplicates podman
原课件 Ubuntu 24.04 的仓库记录:
podman | 4.9.3+ds1-1ubuntu0.2 | noble-updates/universe amd64
podman | 4.9.3+ds1-1ubuntu0.1 | noble-security/universe amd64
podman | 4.9.3+ds1-1build2 | noble/universe amd64
Rocky 记录:
已安装的软件包
podman.x86_64 4:4.9.4-3.el9_4 @AppStream
可安装的软件包
podman.x86_64 5:5.4.0-10.el9_6 appstream
1.3 Ubuntu 安装与信息
apt update
apt install -y podman
podman --version
podman info | wc -l
podman info
原稿版本为 podman version 4.9.3,podman info | wc -l 得到 143。行数由版本和格式决定,不能用来比较工具能力。
原稿环境信息,按层级整理如下;这是 root 用户运行的 rootful 实验:
host:
arch: amd64
buildahVersion: 1.33.7
cgroupControllers: [cpuset, cpu, io, memory, hugetlb, pids, rdma, misc]
cgroupManager: systemd
cgroupVersion: v2
conmon:
package: conmon_2.1.10+ds1-1build2_amd64
path: /usr/bin/conmon
version: 'conmon version 2.1.10, commit: unknown'
cpuUtilization:
idlePercent: 87.5
systemPercent: 6.73
userPercent: 5.77
cpus: 2
databaseBackend: sqlite
distribution:
codename: noble
distribution: ubuntu
version: '24.04'
eventLogger: journald
freeLocks: 2048
hostname: ubuntu24-13
idMappings:
gidmap: null
uidmap: null
kernel: 6.11.0-26-generic
linkmode: dynamic
logDriver: journald
memFree: 1952002048
memTotal: 4054519808
networkBackend: netavark
networkBackendInfo:
backend: netavark
dns:
package: aardvark-dns_1.4.0-5_amd64
path: /usr/lib/podman/aardvark-dns
version: aardvark-dns 1.4.0
package: netavark_1.4.0-4_amd64
path: /usr/lib/podman/netavark
version: netavark 1.4.0
ociRuntime:
name: crun
package: crun_1.14.1-1_amd64
path: /usr/bin/crun
version: |-
crun version 1.14.1
commit: de537a7965bfbe9992e2cfae0baeb56a08128171
rundir: /run/user/0/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +WASM:wasmedge +YAJL
os: linux
pasta:
executable: /usr/bin/pasta
package: passt_0.0~git20240220.1e6f92b-1_amd64
version: pasta unknown version
remoteSocket:
exists: true
path: /run/podman/podman.sock
security:
apparmorEnabled: true
capabilities: CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT
rootless: false
seccompEnabled: true
seccompProfilePath: /usr/share/containers/seccomp.json
selinuxEnabled: false
serviceIsRemote: false
slirp4netns:
executable: /usr/bin/slirp4netns
package: slirp4netns_1.2.1-1build2_amd64
version: |-
slirp4netns version 1.2.1
commit: 09e31e92fa3d2a1d3ca261adaeb012c8d75a8194
libslirp: 4.7.0
SLIRP_CONFIG_VERSION_MAX: 4
libseccomp: 2.5.5
swapFree: 4055887872
swapTotal: 4055887872
uptime: 0h 6m 33.00s
variant: ''
plugins:
authorization: null
log: [k8s-file, none, passthrough, journald]
network: [bridge, macvlan, ipvlan]
volume: [local]
registries: {}
store:
configFile: /usr/share/containers/storage.conf
containerStore:
number: 0
paused: 0
running: 0
stopped: 0
graphDriverName: overlay
graphOptions: {}
graphRoot: /var/lib/containers/storage
graphRootAllocated: 210238414848
graphRootUsed: 12464111616
graphStatus:
Backing Filesystem: extfs
Native Overlay Diff: 'true'
Supports d_type: 'true'
Supports shifting: 'true'
Supports volatile: 'true'
Using metacopy: 'false'
imageCopyTmpDir: /var/tmp
imageStore:
number: 0
runRoot: /run/containers/storage
transientStore: false
volumePath: /var/lib/containers/storage/volumes
version:
APIVersion: 4.9.3
Built: 0
BuiltTime: Thu Jan 1 08:00:00 1970
GitCommit: ''
GoVersion: go1.22.2
Os: linux
OsArch: linux/amd64
Version: 4.9.3
卸载软件与删除容器数据是两件事。以下清理目录仅对应不再保留数据的实验主机;原稿的 /run/container* 通配符扩大了范围,改为明确目录:
apt autoremove --purge podman
rm -rf /etc/containers /var/lib/containers /run/containers /run/podman
reboot
rootless 用户数据还有各自的数据目录,卸载 rootful 软件不等于已清理全部用户数据。重启不是卸载软件包必需步骤。
1.4 Rocky 安装与信息
Docker 的安装不保证会安装 Podman;先检查现有环境。
podman --version
podman version
yum install -y podman
podman --version
podman info
原稿旧版本记录:
podman version 4.9.4-rhel
Client: Podman Engine
Version: 4.9.4-rhel
API Version: 4.9.4-rhel
Go Version: go1.21.9 (Red Hat 1.21.9-2.el9_4)
Built: Wed May 1 09:15:44 2024
OS/Arch: linux/amd64
安装更新后为 podman version 5.4.0,原稿信息:
host:
arch: amd64
buildahVersion: 1.39.4
cgroupControllers: [cpuset, cpu, io, memory, hugetlb, pids, rdma, misc]
cgroupManager: systemd
cgroupVersion: v2
conmon:
package: conmon-2.1.10-1.el9.x86_64
path: /usr/bin/conmon
version: 'conmon version 2.1.10, commit: 3ea3d7f99779af0fcd69ec16c211a7dc3b4efb60'
cpuUtilization:
idlePercent: 95.37
systemPercent: 2.21
userPercent: 2.42
cpus: 2
databaseBackend: sqlite
distribution:
distribution: rocky
version: '9.4'
eventLogger: journald
freeLocks: 2048
hostname: rocky9-12
idMappings:
gidmap: null
uidmap: null
kernel: 5.14.0-427.13.1.el9_4.x86_64
linkmode: dynamic
logDriver: journald
memFree: 2200293376
memTotal: 3803803648
networkBackend: netavark
networkBackendInfo:
backend: netavark
dns:
package: aardvark-dns-1.10.0-3.el9_4.x86_64
path: /usr/libexec/podman/aardvark-dns
version: aardvark-dns 1.10.0
package: netavark-1.10.3-1.el9.x86_64
path: /usr/libexec/podman/netavark
version: netavark 1.10.3
ociRuntime:
name: crun
package: crun-1.14.3-1.el9.x86_64
path: /usr/bin/crun
version: |-
crun version 1.14.3
commit: 1961d211ba98f532ea52d2e80f4c20359f241a98
rundir: /run/user/0/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
os: linux
pasta:
executable: /usr/bin/pasta
package: passt-0^20250217.ga1e48a0-1.el9.x86_64
version: ''
remoteSocket:
exists: true
path: /run/podman/podman.sock
rootlessNetworkCmd: pasta
security:
apparmorEnabled: false
capabilities: CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT
rootless: false
seccompEnabled: true
seccompProfilePath: /usr/share/containers/seccomp.json
selinuxEnabled: true
serviceIsRemote: false
slirp4netns:
executable: /usr/bin/slirp4netns
package: slirp4netns-1.2.3-1.el9.x86_64
version: |-
slirp4netns version 1.2.3
commit: c22fde291bb35b354e6ca44d13be181c76a0a432
libslirp: 4.4.0
SLIRP_CONFIG_VERSION_MAX: 3
libseccomp: 2.5.2
swapFree: 4206882816
swapTotal: 4206882816
uptime: 0h 21m 20.00s
variant: ''
plugins:
authorization: null
log: [k8s-file, none, passthrough, journald]
network: [bridge, macvlan, ipvlan]
volume: [local]
registries:
search: [registry.access.redhat.com, registry.redhat.io, docker.io]
store:
configFile: /etc/containers/storage.conf
containerStore:
number: 0
paused: 0
running: 0
stopped: 0
graphDriverName: overlay
graphOptions:
overlay.mountopt: nodev,metacopy=on
graphRoot: /var/lib/containers/storage
graphRootAllocated: 75094818816
graphRootUsed: 5430738944
graphStatus:
Backing Filesystem: xfs
Native Overlay Diff: 'false'
Supports d_type: 'true'
Supports shifting: 'false'
Supports volatile: 'true'
Using metacopy: 'true'
imageCopyTmpDir: /var/tmp
imageStore:
number: 0
runRoot: /run/containers/storage
transientStore: false
volumePath: /var/lib/containers/storage/volumes
version:
APIVersion: 5.4.0
Built: 1750148132
BuiltTime: Tue Jun 17 16:15:32 2025
GitCommit: ''
GoVersion: go1.23.9 (Red Hat 1.23.9-1.el9_6)
Os: linux
OsArch: linux/amd64
Version: 5.4.0
移除实验环境:
yum remove 'podman*'
rm -rf /etc/containers /var/lib/containers /run/containers /run/podman
reboot
2. Windows 安装 Docker Desktop
2.1 下载与安装
访问 https://www.docker.com/,选择适合 Windows 和本机 CPU 架构的安装包。原课件使用 AMD64。

直接下载地址:https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe。

双击安装包,按系统提示运行,选择所需后端和快捷方式,再确认安装。


原课件点击【Close and restart】后重启 Windows,重新登录后启动 Docker Desktop,阅读并接受相应服务条款。

可按实际产品要求登录,原截图提供【Continue without signing in】入口。出现更新提示时可按环境要求处理,不必为了启动容器无条件更新全部功能。


2.2 WSL 2 后端
Docker Desktop 运行 Linux 容器需要受支持的虚拟化后端。使用 WSL 2 时应满足 Windows、WSL、CPU 虚拟化及内核要求。网络受限可能妨碍下载,但“必须翻墙”和“默认一定无法启动”都不是普遍条件。

在【Settings → Resources → WSL integration】查看与发行版的集成;该页面与安装/更新 WSL 本身不是完全相同的操作。

根据系统提示在管理员 PowerShell 中检查并安装/更新 WSL:
wsl --status
wsl --install
wsl --update
按实际提示重启系统或 WSL,不要在已满足条件的系统上机械重复初始化。


完成后点击【Apply】或【Apply & restart】,必要时使用界面的【Restart Docker】,确认引擎从 Stopped 变为 Running。

2.3 Hyper-V 后端的另一条安装路径
原稿安装时不勾选“Use WSL 2 instead of Hyper-V”。这表示选择其他受支持的虚拟化后端,不能保证所有 Windows 版本都支持,也不能解决未启用虚拟化等所有问题。应核对 Windows 版本、Hyper-V 功能与 Docker Desktop 当前系统要求。


2.4 内置终端
点击右下角 >_,在 Terminal 页面启用相应终端功能,再输入 Docker 命令。


docker info
原稿输出开头:
Client:
Version: 28.5.1
Context: desktop-linux
Debug Mode: false
Plugins:
ai: Docker AI Agent - Ask Gordon (Docker Inc.)
...
3. Docker 代理
3.1 场景和调用链
企业内网、受限网络或连接质量较差的环境中,可通过获准的代理访问外部 Registry。代理可以统一转发、审计和控制请求,但它不自动保护所有容器入站流量,也不代替防火墙或容器隔离。
直接拉取:Docker CLI → dockerd → Registry
配置代理:Docker CLI → dockerd → HTTP(S) 代理 → Registry
容器应用:应用自身 → 按应用配置使用代理 → 目标服务
daemon 的代理与容器内应用代理是两个层次。通过登录 Shell 设置环境变量,不保证 systemd 管理的 dockerd 自动继承;Docker Desktop 通常通过其产品设置管理代理,也不能机械照搬 Linux daemon 配置。
| 场景 | 代理的作用 | 镜像加速的作用 |
|---|---|---|
| 内网拉取公网镜像 | 提供可达的外连路径 | 可结合缓存减少拉取时间 |
| 到 Docker Hub 链路慢 | 改变网络路径,速度不一定提高 | 缓存或优化分发链路 |
| 多节点同时拉取 | 普通转发仍可能集中消耗上游带宽 | P2P/缓存有助于减少重复传输 |
| 容器应用访问公网 API | 需要给应用单独配置 | Registry mirror 不负责普通 API 请求 |
代理和镜像 mirror 可以配合使用。HTTPS 目标经 HTTP CONNECT 代理时,HTTPS_PROXY 的代理地址仍常是 http://;地址协议必须与代理服务真正支持的协议一致,不能一概禁止 https://。
3.2 配置入口
参考:https://docs.docker.com/engine/daemon/proxy/。
Docker Engine 支持代理配置的版本中,可在 daemon.json 合并以下内容:
{
"proxies": {
"http-proxy": "http://proxy.example.com:3128",
"https-proxy": "http://proxy.example.com:3128",
"no-proxy": "localhost,127.0.0.0/8,.test.example.com,.example.org"
}
}
或通过 systemd drop-in 为服务设置环境变量:
[Service]
Environment="HTTP_PROXY=http://10.0.0.1:10809/"
Environment="HTTPS_PROXY=http://10.0.0.1:10809/"
Environment="NO_PROXY=localhost,127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,10.244.0.0/16,192.168.0.0/16,wang.org,cluster.local"
daemon 配置的代理通常优先于服务环境变量;尽量选择一个明确来源维护,排错时检查是否有其他来源覆盖。
容器级配置:
docker run -it \
--env http_proxy=http://proxy.example.com:8080 \
--env https_proxy=http://proxy.example.com:8080 \
--env no_proxy=localhost,127.0.0.1 \
your_image_name
Dockerfile 也可使用 ENV,但会把固定代理信息留在镜像配置中,不适合写入含凭证的代理地址:
ENV http_proxy=http://proxy.example.com:8080
ENV https_proxy=http://proxy.example.com:8080
ENV no_proxy=localhost,127.0.0.1
进入已有容器的临时 Shell 后设置:
docker exec -it container_name /bin/bash
容器 Shell 内:
export http_proxy=http://proxy.example.com:8080
export https_proxy=http://proxy.example.com:8080
export no_proxy=localhost,127.0.0.1
它只影响该 Shell 及其后续子进程,不修改已经运行的主服务或容器永久配置。
测试代理:
curl -x http://10.0.0.1:10809 https://registry-1.docker.io/v2/
原响应:
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}
这说明通过代理得到了 Registry API 的认证响应;不等于所有镜像和认证流程都已通过。
3.3 systemd 代理实验
原实验 Linux 地址为 10.0.0.13,Windows 代理地址为 10.0.0.1:10809。
ss -nt
telnet 10.0.0.1 10809
原连接信息:
ESTAB ... [::ffff:10.0.0.13]:22 ... [::ffff:10.0.0.1]:8978 ...
Trying 10.0.0.1...
Connected to 10.0.0.1.
Escape character is '^]'.
Connection closed by foreign host.
代理必须监听 Linux 主机可达的地址,并允许相应来源;只监听 Windows 的 127.0.0.1 通常无法被另一台主机访问。监听所有地址时也要通过访问控制限制使用范围。
PROXY_SERVER_IP=10.0.0.1
PROXY_PORT=10809
mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<PROXYCONF
[Service]
Environment="HTTP_PROXY=http://${PROXY_SERVER_IP}:${PROXY_PORT}/"
Environment="HTTPS_PROXY=http://${PROXY_SERVER_IP}:${PROXY_PORT}/"
Environment="NO_PROXY=localhost,127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,192.168.0.0/16,10.244.0.0/16,cluster.local"
PROXYCONF
systemctl daemon-reload
systemctl restart docker.service
docker info | grep -i proxy
配置后的预期:
HTTP Proxy: http://10.0.0.1:10809/
HTTPS Proxy: http://10.0.0.1:10809/
No Proxy: localhost,127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,192.168.0.0/16,10.244.0.0/16,cluster.local
原稿拉取旧 Kubernetes 镜像的记录:
docker pull gcr.io/google-containers/kube-proxy:v1.18.0
v1.18.0: Pulling from google-containers/kube-proxy
597de8ba0c30: Pull complete
...
51432749cbec: Pull complete
Digest: sha256:9e858386d52d0abaf936c1d10a763648ab7d85c8eb0af08a50a64238146e5571
Status: Downloaded newer image for gcr.io/google-containers/kube-proxy:v1.18.0
gcr.io/google-containers/kube-proxy:v1.18.0
这些是历史镜像路径和版本,不保证当前仍可拉取。
取消此处代理时,只删本实验的 drop-in,不删除包含其他设置的整个目录:
rm -f /etc/systemd/system/docker.service.d/http-proxy.conf
systemctl daemon-reload
systemctl restart docker
docker info | grep -i proxy
若没有其他代理来源,查询不再输出代理。原稿取消后拉取 v1.19.0 的失败记录:
Error response from daemon: Get "https://gcr.io/v2/": context deadline exceeded
是否必须使用代理取决于本机实际网络,不能据此推断所有主机都无法直连。
3.4 daemon.json 代理实验
把以下对象合并到已有 /etc/docker/daemon.json,不要覆盖镜像加速等其他设置:
{
"proxies": {
"http-proxy": "http://10.0.0.1:10809",
"https-proxy": "http://10.0.0.1:10809",
"no-proxy": "localhost,127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,192.168.0.0/16,10.244.0.0/16,.cluster.local"
}
}
systemctl restart docker
docker info | grep -i proxy
docker pull gcr.io/google-containers/kube-proxy:v1.17.0
原稿仅用 systemctl start docker,服务已运行时不会重新读取该变化,已改为 restart。
原拉取记录:
v1.17.0: Pulling from google-containers/kube-proxy
597de8ba0c30: Already exists
...
d3609306ce38: Already exists
d7c1c982f192: Pull complete
Digest: sha256:b2ba9441af30261465e5c41be63e462d0050b09ad280001ae731f399b2b00b75
Status: Downloaded newer image for gcr.io/google-containers/kube-proxy:v1.17.0
gcr.io/google-containers/kube-proxy:v1.17.0
3.5 配套脚本入口
原稿引用 scripts/docker_set_proxy.sh,PDF 没有提供脚本全文,本文不补造实现:
Usage: docker_set_proxy.sh {daemon|service} {start|stop}
daemon 使用 daemon.json 配置
service 使用 systemd 配置
start 设置代理
stop 取消代理
/bin/bash docker_set_proxy.sh daemon start
docker info | grep -i proxy
/bin/bash docker_set_proxy.sh daemon stop
docker info | grep -i proxy
/bin/bash docker_set_proxy.sh service start
docker info | grep -i proxy
/bin/bash docker_set_proxy.sh service stop
docker info | grep -i proxy
原稿 daemon stop 输出:
正在通过daemon.json取消Docker代理配置...
已备份原配置文件到 /etc/docker/daemon.json.bak [OK]
已移除代理配置(使用jq) [OK]
重新加载systemd配置 [OK]
正在重启Docker服务...
Docker服务重启成功 [OK]
Docker代理配置已成功移除! [OK]
当前Docker代理配置:
无代理相关配置
原稿 service start 输出:
正在通过systemd配置Docker代理...
配置目录 /etc/systemd/system/docker.service.d 已存在 [OK]
写入代理配置到 /etc/systemd/system/docker.service.d/http-proxy.conf [OK]
重新加载systemd配置 [OK]
正在重启Docker服务...
Docker服务重启成功 [OK]
Docker服务代理配置验证成功! [OK]
当前Docker代理配置:
Environment=HTTP_PROXY=http://10.0.0.1:10809 HTTPS_PROXY=https://10.0.0.1:10809
NO_PROXY=127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,10.244.0.0/16,192.168.0.0/16,wang.org,cluster.local
原脚本曾显示 HTTPS_PROXY=https://10.0.0.1:10809,而手工实验使用 HTTP 代理。只有服务本身支持 HTTPS 代理协议时前者才正确;脚本报告“配置成功”不能替代实际协议请求验证。
原稿 service stop 输出:
正在通过systemd取消Docker代理配置...
移除代理配置 /etc/systemd/system/docker.service.d [OK]
重新加载systemd配置 [OK]
正在重启Docker服务...
Docker服务重启成功 [OK]
Docker服务代理配置已成功移除! [OK]
当前Docker环境变量配置:
无代理相关配置
这是原脚本的行为记录。手工操作采用上一节只删除专属 drop-in 的方式,避免移除其他服务配置。
4. Rocky Linux 安装 Docker
参考:https://developer.aliyun.com/mirror/docker-ce、https://docs.docker.com/engine/install/、https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/。课程使用 EL9 环境,软件源与软件包适配性应按实际发行版确认。
4.1 软件源和安装
yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache
yum list docker-ce --showduplicates
原稿候选版本:
docker-ce.x86_64 3:28.2.2-1.el9 docker-ce-stable
docker-ce.x86_64 3:28.2.1-1.el9 docker-ce-stable
指定版本与安装仓库当前版本的方式分别为:
# 版本必须真实存在;CLI 的 epoch 不一定与 Engine 相同
yum install docker-ce-3:28.2.2-1.el9 docker-ce-cli-1:28.2.2-1.el9 containerd.io
# 或安装当前仓库版本及插件
yum install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
原稿固定版本安装事务记录:
| 类型 | 软件包 | 版本 | 大小 |
|---|---|---|---|
| 安装 | docker-ce | 3:28.2.2-1.el9 | 20 M |
| 升级 | selinux-policy | 38.1.53-5.el9_6 | 44 k |
| 升级 | selinux-policy-targeted | 38.1.53-5.el9_6 | 6.5 M |
| 依赖 | container-selinux | 4:2.235.0-2.el9_6 | 58 k |
| 依赖 | containerd.io | 1.7.27-3.1.el9 | 44 M |
| 依赖 | docker-ce-cli | 1:28.2.2-1.el9 | 8.5 M |
| 依赖 | fuse-overlayfs | 1.14-1.el9 | 66 k |
| 依赖 | libslirp | 4.4.0-8.el9 | 67 k |
| 依赖 | slirp4netns | 1.3.2-1.el9 | 46 k |
| 弱依赖 | docker-buildx-plugin | 0.24.0-1.el9 | 16 M |
| 弱依赖 | docker-ce-rootless-extras | 28.2.2-1.el9 | 3.4 M |
| 弱依赖 | docker-compose-plugin | 2.36.2-1.el9 | 15 M |
原事务为安装 10 包、升级 2 包,总下载 114 M;这是当时仓库求解结果,不是每次安装都相同。
4.2 服务与环境信息
systemctl is-active docker
systemctl start docker
systemctl is-active docker
docker version
docker info
原稿第一次为 inactive,启动后为 active。客户端版本即使 daemon 不可达也可能显示;服务端部分需要能够连接 daemon。
原稿 docker version:
Client: Docker Engine - Community
Version: 28.2.2
API version: 1.50
Go version: go1.24.3
Git commit: e6534b4
Built: Fri May 30 12:09:15 2025
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 28.2.2
API version: 1.50 (minimum version 1.24)
Go version: go1.24.3
Git commit: 45873be
Built: Fri May 30 12:07:23 2025
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.7.27
GitCommit: 05044ec0a9a75232cad458027ca83437aae3f4da
runc:
Version: 1.2.5
GitCommit: v1.2.5-0-g59923ef
docker-init:
Version: 0.19.0
GitCommit: de40ad0
原稿 docker info:
Client: Docker Engine - Community
Version: 28.2.2
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.24.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.36.2
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 28.2.2
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: systemd
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
CDI spec directories:
/etc/cdi
/var/run/cdi
Swarm: inactive
Runtimes: runc io.containerd.runc.v2
Default Runtime: runc
Init Binary: docker-init
containerd version: 05044ec0a9a75232cad458027ca83437aae3f4da
runc version: v1.2.5-0-g59923ef
init version: de40ad0
Security Options:
seccomp
Profile: builtin
cgroupns
Kernel Version: 5.14.0-427.13.1.el9_4.x86_64
Operating System: Rocky Linux 9.4 (Blue Onyx)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 1.703GiB
Name: rocky9-12
ID: 7848764d-c041-4bc5-ba75-787886e367b5
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
::1/128
127.0.0.0/8
Live Restore Enabled: false
4.3 docker0 与防火墙
ip addr show docker0
nft list ruleset | wc -l
iptables -vnL
原稿地址:
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether 36:47:80:a6:d2:0a brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
没有接入的活动容器时,网桥可以显示 NO-CARRIER/DOWN,不代表 daemon 必然未启动。默认地址常为 172.17.0.1/16,但配置或地址冲突可能使其不同。
原 nft 输出提示与计数:
Warning: table ip nat is managed by iptables-nft, do not touch!
Warning: table ip filter is managed by iptables-nft, do not touch!
Warning: table ip6 nat is managed by iptables-nft, do not touch!
102
原稿 filter 表结构(计数初始为 0):
INPUT policy ACCEPT
FORWARD policy DROP
DOCKER-USER
DOCKER-FORWARD
OUTPUT policy ACCEPT
DOCKER:
DROP all -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0
DOCKER-BRIDGE:
DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
DOCKER-CT:
ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER-FORWARD:
DOCKER-CT
DOCKER-ISOLATION-STAGE-1
DOCKER-BRIDGE
ACCEPT all -- docker0 * 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1:
DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-2:
DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
DOCKER-USER:
无自定义规则
网桥负责二层连接,veth 将容器接入网桥,路由和防火墙规则负责转发/NAT及访问控制;不是“网桥网络由防火墙规则本身创建”。链名和策略随 Engine 版本及 iptables/nftables 后端变化。
原稿分别展示了 Rocky/Ubuntu 的检查(其中 Rocky 小节提示符误写 Ubuntu,不据此判断发行版):
ip addr show docker0
route -n
iptables -vnL
| 项目 | Rocky 实验 | Ubuntu 实验 |
|---|---|---|
| docker0 MAC | a6:c3:c6:03:72:39 | 8e:3c:e9:0a:8c:4f |
| docker0 地址 | 172.17.0.1/16 | 172.17.0.1/16 |
| 默认路由 | 0.0.0.0 → 10.0.0.2,ens160,metric 100 | 0.0.0.0 → 10.0.0.2,ens33,metric 100 |
| 宿主机网段 | 10.0.0.0/24,ens160 | 10.0.0.0/24,ens33 |
| 容器路由 | 172.17.0.0/16,docker0,metric 0 | 172.17.0.0/16,docker0,metric 0 |
| filter 表 | FORWARD 为 DROP,经 Docker 链放行需要的流量 | 同类 Docker 转发与隔离链 |
4.4 镜像加速与 hello-world
原稿没有可用外连时:
Unable to find image 'hello-world:latest' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/":
net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
Run 'docker run --help' for more information
镜像加速配置示例:
{
"registry-mirrors": [
"https://docker.xuanyuan.me"
]
}
原稿还使用账户专属 https://82ue03hk.mirror.aliyuncs.com;实际应替换为自己获准使用的有效地址。这些地址本次未做联网可用性验证。JSON 最后一项不能有多余逗号,不能写注释。
systemctl restart docker
docker info | grep -A2 Regi
curl https://docker.xuanyuan.me/v2/
docker run hello-world
只改 daemon.json 不需要 systemctl daemon-reload;后者在 systemd 单元或 drop-in 改变时才需要。
原稿配置和测试记录:
Insecure Registries:
::1/128
127.0.0.0/8
Registry Mirrors:
https://docker.xuanyuan.me/
Live Restore Enabled: false
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
e6590344b1a5: Pull complete
Digest: sha256:940c619fbd418f9b2b1b63e25d8861f9cc1b46e3fc8b018ccfe8b78f19b8cc4f
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
hello-world 的后续说明概括了四步:客户端连接 daemon,daemon 拉取镜像、创建并运行容器,将程序输出流回客户端。还提示可尝试 docker run -it ubuntu bash,参考 https://hub.docker.com/ 和 https://docs.docker.com/get-started/。
4.5 卸载与数据清理
docker ps -a
docker rm -f edc
systemctl stop docker.service docker.socket
yum remove docker-ce docker-ce-cli docker-buildx-plugin \
docker-ce-rootless-extras docker-compose-plugin containerd.io
原 hello-world 容器 ID edcd4d02087b,状态 Exited (0),名称 objective_nightingale。原卸载事务移除了 10 个包,回收约 430 M:Docker 的 5 个软件包,以及未继续使用的 container-selinux、containerd.io、fuse-overlayfs、libslirp、slirp4netns 等依赖;实际依赖去留由包管理器和本机其他软件决定。
确认数据无需保留后,原稿的清理和重启:
rm -rf /etc/docker /var/lib/docker /var/lib/containerd
rm -f /etc/yum.repos.d/docker-ce.repo
reboot
5. Docker 服务与配置进阶
5.1 systemd 服务文件
rpm -ql docker-ce | grep docker.service
cat /usr/lib/systemd/system/docker.service
原包路径为 /usr/lib/systemd/system/docker.service,其主要内容:
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target nss-lookup.target docker.socket firewalld.service containerd.service time-set.target
Wants=network-online.target containerd.service
Requires=docker.socket
StartLimitBurst=3
StartLimitIntervalSec=60
[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutStartSec=0
RestartSec=2
Restart=always
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
OOMScoreAdjust=-500
[Install]
WantedBy=multi-user.target
| 属性 | 含义 |
|---|---|
| After/Wants/Requires | 启动顺序和依赖关系;Requires 的要求限于采用该单元的部署,不是所有 dockerd 都必须使用 docker.socket |
| Type=notify | 通过 sd_notify 报告就绪 |
-H fd:// |
接收 systemd socket 激活传入的监听描述符 |
--containerd |
指定连接的 containerd socket,不是让 Docker 在这个路径对 CLI 监听 |
| ExecReload | 向主进程发 HUP,重载其支持动态变更的配置 |
| Restart=always | 自动重启进程,但 systemctl stop 等显式管理停止不会因此立即反复拉起 |
| StartLimit* | 限制一定时间内启动尝试次数 |
| LimitNPROC/CORE、TasksMax | 设置服务层进程/任务及 core 限制;不等于系统资源本身无限 |
| Delegate=yes | 允许服务管理其被委派的 cgroup 层级 |
| KillMode=process | 停止时主要针对服务主进程,不按普通 control-group 模式杀整个组 |
| OOMScoreAdjust=-500 | 降低 daemon 被 OOM killer 选择的倾向,不能保证所有场景先杀容器 |
原单元的历史英文注释提到不默认使用 systemd cgroup;实际实验 docker info 已为 Cgroup Driver: systemd,应以运行配置为准,不能沿用旧注释得出相反结论。
本地 CLI 可连接 -H unix:///var/run/docker.sock;socket 权限控制访问,能访问 rootful Docker socket 通常就具有极高主机权限,并非“只在本机就天然安全”。--containerd=/run/containerd/containerd.sock 则是 daemon 与 containerd 的内部管理连接,也需保护访问权限。
5.2 daemon.json 常用设置
rootful daemon 常用配置文件为 /etc/docker/daemon.json,安装后不一定已创建。它使用严格 JSON,不支持 #、// 或 /* */ 注释。以下各段按用途展示,实际使用需合并为一个对象并核对当前版本支持的键,不要逐段覆盖。
基础示例:
{
"registry-mirrors": ["https://mirror.example.com"],
"data-root": "/var/lib/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"experimental": false
}
修改后部分设置可 HUP 重载,其他设置需重启。daemon 启动参数与配置文件重复声明同一键可能导致冲突。
仓库、运行时与并发
{
"registry-mirrors": ["https://mirror.example.com"],
"insecure-registries": ["myregistry.local:5000"],
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 5
}
insecure-registries 允许相应仓库使用不安全传输或证书方式;使用有效证书的 HTTPS 更合适。原稿的 registry.docker-cn.com 是历史地址,不能当成当前有效服务。
原稿还直接设 "default-runtime": "nvidia",但只有先安装并注册相应 NVIDIA runtime、且实际希望作为默认运行时时才可设置,不能单独复制该键就获得 GPU 支持。
存储
{
"data-root": "/mnt/docker-data",
"storage-driver": "overlay2"
}
更换数据根目录或驱动前先规划迁移和回退。原稿的 overlay2.override_kernel_check=true 属于历史驱动选项,当前版本可能不支持,不应用它绕过宿主机兼容性检查。采用 containerd image store 的 Engine 安装布局与经典 overlay2 存储也可能不同。
日志
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"labels": "production_status",
"env": "os,customer"
}
}
日志选项中数值常需以字符串表示。labels/env 选择可带入日志的附加属性,docker logs --details 可按日志驱动支持情况显示。新默认值一般只影响新创建容器,已有容器需重建以采用新的日志配置。
网络
{
"bip": "172.17.0.1/16",
"mtu": 1450,
"default-address-pools": [
{"base": "172.28.0.0/16", "size": 24},
{"base": "172.29.0.0/16", "size": 24}
]
}
bip 配置默认 bridge 地址;地址池用于自动分配相应新网络。原稿 172.80.0.0/16、172.90.0.0/16 不是 RFC1918 私有地址,已换成私有网段示例,实际仍需避免与现有局域网、VPN、集群冲突。MTU 应匹配真实链路,不能机械固定。
安全与运行控制
{
"userns-remap": "default",
"seccomp-profile": "/etc/docker/seccomp.json",
"cgroup-parent": "docker.slice",
"live-restore": true
}
- userns-remap 为 rootful daemon 使用的容器身份提供映射;切换时已有数据可见性、卷权限及兼容性要另外处理。
- 自定义 seccomp 文件必须真实存在且为有效策略。
- cgroup-parent 必须符合所用 cgroup 驱动的格式和实际层级;systemd 常用 slice 名。
- live-restore 可在受支持的 daemon 不可用/重启场景保持容器运行,不是零停机升级或任意跨版本迁移保证。
调试和指标
{
"debug": true,
"experimental": true,
"metrics-addr": "127.0.0.1:9323"
}
原稿把指标绑定到所有地址,改为本地监听示例,需远程采集时再按受控网络开放。BuildKit 不应笼统列为所有新版都需 experimental 才能启用;Docker 也不是通过 experimental 自动启用 Kubernetes CRI 插件。
原稿 "features": {"buildkit": true} 属于特定时期的启用方式,是否接受应检查当前 Engine 和 builder 配置,不要混用已移除的功能开关。
远程监听、IPv6 和默认限制
{
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"]
}
这是未加密 API 的本机监听示例;远程优先使用 SSH 或配置相互认证的 TLS。若单元已有 -H fd://,必须消除与 hosts 的重复声明,详见后面的远程管理实验。
{
"ipv6": true,
"fixed-cidr-v6": "2001:db8:1::/64"
}
2001:db8::/32 是文档专用地址,不用于实际互联网路由;真实 IPv6 配置需可用前缀、路由与防火墙。
{
"default-ulimits": {
"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 65536}
},
"default-shm-size": "256m"
}
5.3 Podman/Buildah 的 registries.conf
/etc/containers/registries.conf 属于 containers/image 等工具栈,不是 Docker Engine 的 daemon 配置;是否预装由软件包决定。现代格式是 TOML,不是原稿所称的通用 INI,也没有原稿示例中的任意 search."docker.io"、priority、cert_dir、authfile 等同级键。
下面是按现代格式修正的同等用途示例:
unqualified-search-registries = ["docker.io", "quay.io", "registry.access.redhat.com"]
short-name-mode = "enforcing"
[[registry]]
prefix = "docker.io"
location = "docker.io"
insecure = false
[[registry.mirror]]
location = "mirror1.example.com"
insecure = false
[[registry.mirror]]
location = "mirror2.example.com"
insecure = false
[[registry]]
prefix = "myregistry.example.com"
location = "myregistry.example.com"
insecure = true
[aliases]
"nginx" = "docker.io/library/nginx"
| 原稿想表达的功能 | 正确配置入口 |
|---|---|
| 短名称搜索列表 | unqualified-search-registries;优先使用完整镜像名减少歧义 |
| 注册表匹配和地址重写 | [[registry]] 的 prefix、location |
| mirror | 对应 registry 下的 [[registry.mirror]] |
| 允许 HTTP/跳过相应 TLS 验证 | registry/mirror 的 insecure,按工具文档语义使用 |
| 自定义 CA | /etc/containers/certs.d/<host[:port]>/ 或命令支持的 --cert-dir |
| 认证文件 | podman login、--authfile、REGISTRY_AUTH_FILE 等支持入口,不是 registries.conf 随意插入的 authfile |
| 搜索优先顺序 | 搜索列表顺序及具体匹配规则,不是任意 priority 数字 |
| 拆分配置 | /etc/containers/registries.conf.d/*.conf 等工具支持目录,不是随意添加 include 键 |
示例 mirror 和 registry 域名为占位地址,实际使用需替换为确实提供对应内容的服务。prefix 匹配镜像引用前缀,不等同于“所有 nginx 自动加此前缀”;短名固定映射可通过 aliases 或完整镜像引用表达。
5.4 配置文件与 swap 警告
rpm -ql docker-ce
docker info
原包列表包含:
/etc/docker
/usr/bin/docker-proxy
/usr/bin/dockerd
/usr/lib/.build-id/29/7ae7d963b1d086bda9a175fdea23aede84174b
/usr/lib/.build-id/5e/a3b63c402bc4ede3e624dae4bba674c1a5cbdf
/usr/lib/.build-id/97/d094c5e36ef7995b54069b3c2a6f699d4df640
/usr/lib/systemd/system/docker.service
/usr/lib/systemd/system/docker.socket
/usr/libexec/docker/docker-init
/usr/share/man/man8/dockerd.8.gz
某些内核/cgroup 环境会显示:
WARNING: No swap limit support
它表示相应限制能力不可用,不等于宿主机没有 swap。先查当前内核、cgroup v1/v2 和控制器支持,不要对现代系统无条件追加旧内核启动参数。
参考:https://docs.docker.com/engine/containers/resource_constraints/、https://docs.docker.com/engine/daemon/troubleshoot/。
原稿针对需要旧式 memory/swap accounting 启动参数的环境给出以下入口;应保留已有内核参数,只添加已经确认需要的项。
Ubuntu 的 /etc/default/grub:
GRUB_CMDLINE_LINUX="<原有参数> cgroup_enable=memory swapaccount=1"
update-grub
reboot
Rocky/RHEL 原稿的示例包含原 LVM 参数:
GRUB_CMDLINE_LINUX="... rd.lvm.lv=rl_bogon/swap cgroup_enable=memory swapaccount=1"
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
<原有参数>、... 是说明占位,不能原样写入。Rocky 的 BIOS/UEFI、BLS 与 grubby 管理方式因版本而异,应按发行版确认真正的更新入口;不要写错引导配置或把另一台机器的 LVM 参数复制过来。
6. 存储驱动实验
6.1 存储驱动的职责
存储驱动/快照器管理镜像层和容器可写文件系统。经典 Docker Linux 存储可使用 overlay2、btrfs、zfs、vfs 等;rootless 环境还可能涉及 fuse-overlayfs。具体支持范围、默认驱动和 containerd image store 状态应以当前 Engine、内核与文件系统为准。
| 驱动 | 特点 |
|---|---|
| overlay2 | 基于内核 OverlayFS,为经典 Linux Docker 安装常用选择 |
| fuse-overlayfs | 某些 rootless/内核环境使用的用户态实现,是否需要依赖实际支持 |
| btrfs / zfs | 利用相应后备文件系统的快照等能力,需正确规划和维护 |
| vfs | 使用目录深复制,不使用同样的 CoW 机制,兼容性好但磁盘和 I/O 开销较大,适合测试 |
更换驱动会使原驱动下的镜像和容器暂时不可见,不等于自动把旧数据物理删除。迁移前备份镜像、卷及运行配置,规划回退。
参考:https://docs.docker.com/engine/storage/drivers/、https://docs.docker.com/engine/storage/drivers/overlayfs-driver/、https://docs.docker.com/engine/storage/drivers/vfs-driver/。
6.2 查看默认布局
docker info | grep -i driver
ls /var/lib/docker
ls /var/lib/docker/overlay2
ls /var/lib/docker/overlay2/l
原稿:
Storage Driver: overlay2
Logging Driver: json-file
Cgroup Driver: systemd
buildkit containers engine-id image network overlay2 plugins runtimes swarm tmp volumes
/var/lib/docker/overlay2/:
backingFsBlockDev l
| 目录 | 含义 |
|---|---|
| buildkit | 构建器相关数据 |
| containers | 容器元数据和与日志驱动有关的数据 |
| engine-id | 引擎标识 |
| image、overlay2 | 经典镜像元数据与分层存储 |
| network | 网络管理数据 |
| plugins、runtimes | 插件和运行时相关数据 |
| swarm | Swarm 状态 |
| tmp | 临时数据 |
| volumes | 本地卷相关数据 |
overlay2/l/ 中是用于缩短引用路径的符号链接,不是硬链接。Docker 的内部存储不应直接手工修改。
6.3 准备实验磁盘
原稿给虚拟机添加独立 20 GiB 磁盘;若启动顺序被改变,应在 BIOS 中把真正系统盘放回前面。


fdisk -l | grep dev
原设备记录:
/dev/nvme0n1 200 GiB 214748364800 bytes 419430400 sectors
/dev/nvme0n1p1 2048 .. 2099199 1G Linux
/dev/nvme0n1p2 2099200 .. 419430399 199G Linux LVM
/dev/mapper/rl_bogon-root 70 GiB 75161927680 bytes
/dev/mapper/rl_bogon-swap 2.02 GiB 2164260864 bytes
/dev/mapper/rl_bogon-home 126.98 GiB 136344240128 bytes
/dev/sda 20 GiB 21474836480 bytes 41943040 sectors
设备名只属于原实验:以下 /dev/sda 必须确认是独立、无需保留数据的实验盘,格式化不是普通查看命令。
6.4 XFS 的 d_type / ftype
overlay2 使用 XFS 后备文件系统时需要支持 d_type,通常表现为 ftype=1。这个要求来自 OverlayFS,不是 vfs 自身必须使用 XFS。
# 仅对应已核实为空的实验盘
mkfs.xfs -n ftype=1 /dev/sda
原格式化输出:
meta-data=/dev/sda isize=512 agcount=4 agsize=1310720 blks
sectsz=512 attr=2 projid32bit=1
crc=1 finobt=1 sparse=1 rmapbt=0
reflink=1 bigtime=1 inobtcount=1 nrext64=0
data bsize=4096 blocks=5242880 imaxpct=25
sunit=0 swidth=0 blks
naming=version 2 bsize=4096 ascii-ci=0 ftype=1
log=internal log bsize=4096 blocks=16384 version=2
sectsz=512 sunit=0 blks lazy-count=1
realtime=none extsz=4096 blocks=0 rtextents=0
修正原稿的挂载顺序:先停止相关实验容器和 daemon,避免在使用中的存储目录上覆盖挂载。下面的独立空文件系统会遮住 /var/lib/docker 原目录内容,但不会自动搬迁旧数据。
systemctl stop docker.service docker.socket
mount /dev/sda /var/lib/docker
ls /var/lib/docker
该命令只建立当前挂载,持久化挂载另需按实际 UUID 和系统策略配置;原稿未提供 fstab 内容。
6.5 切换到 vfs 并观察
实验 daemon.json:
{
"registry-mirrors": ["https://docker.xuanyuan.me"],
"storage-driver": "vfs"
}
systemctl start docker
docker info | grep -i driver
ls /var/lib/docker
ls /var/lib/docker/vfs
docker pull busybox
ls /var/lib/docker/vfs
ls /var/lib/docker/vfs/dir
原记录:
Storage Driver: vfs
Logging Driver: json-file
Cgroup Driver: systemd
buildkit containers engine-id image network plugins runtimes swarm tmp vfs volumes
# 拉取前 vfs 中
backingFsBlockDev
Using default tag: latest
latest: Pulling from library/busybox
90b9666d4aed: Pull complete
Digest: sha256:f85340bf132ae937d2c2a763b8335c9bab35d6e8293f70f606b9c6178d84f42b
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
# 拉取后
backingFsBlockDev dir
e8b88f6b47377e7803c60bcb49815b57298772515980f50d25f8f239291fbe86
ls /var/lib/docker/vfs/dir/e8b88f6b47377e7803c60bcb49815b57298772515980f50d25f8f239291fbe86/
ls -l /var/lib/docker/vfs
du -sh /var/lib/docker/vfs
docker images
原输出:
bin dev etc home lib lib64 root tmp usr var
brw-------. 1 root root 8,0 ... backingFsBlockDev
drwx--x---. 3 root root 78 ... dir
4.2M /var/lib/docker/vfs
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 6d3e4188a38a 9 months ago 4.28MB
vfs 为各层建立目录快照,通常深复制已有内容,不能把每个目录理解为仅保存增量差异。此单层 BusyBox 样本的容量接近不证明多层镜像的磁盘利用率相同。
6.6 恢复原环境
直接卸载可能遇到:
umount: /var/lib/docker/: 目标忙.
修正后的顺序是停止 daemon/socket、卸载独立实验盘,再去掉 storage-driver 配置并启动。若仍忙,检查挂载和占用进程,不强行忽略。
systemctl stop docker.service docker.socket
umount /var/lib/docker
恢复原配置,例如:
{
"registry-mirrors": ["https://docker.xuanyuan.me"]
}
systemctl start docker
docker info | grep -i driver
ls /var/lib/docker
原稿恢复后为 overlay2,目录重新含 overlay2。实际默认驱动仍以原配置和当前 Engine 存储模式为准。
7. 远程管理与 TLS
7.1 Docker 与 containerd 的代理补充
Docker 代理设置见第 3 章。独立管理的 containerd 服务若确实需要代理,应给自己的 systemd 服务配置,不会因为 dockerd 设置过代理而自动获得相同环境。
PROXY_SERVER_IP=10.0.0.1
PROXY_PORT=10809
mkdir -p /etc/systemd/system/containerd.service.d
cat > /etc/systemd/system/containerd.service.d/http-proxy.conf <<PROXYCONF
[Service]
Environment="HTTP_PROXY=http://${PROXY_SERVER_IP}:${PROXY_PORT}/"
Environment="HTTPS_PROXY=http://${PROXY_SERVER_IP}:${PROXY_PORT}/"
Environment="NO_PROXY=localhost,127.0.0.0/8,172.17.0.0/16,10.0.0.0/24,10.244.0.0/16,192.168.0.0/16,wang.org,cluster.local"
PROXYCONF
systemctl daemon-reload
systemctl restart containerd.service
这会影响相关容器运行时服务,应在对应实验环境操作;此处仅记录命令,未执行。
7.2 远程访问方式与配置冲突
远程管理用于跨机器操作、CI 工具连接及集中管理。Docker API 管理权限很高,原稿演示的未认证 TCP 2375 只适合理解隔离实验中的行为,不应直接公开到公网。可采用 SSH,或配置服务端/客户端证书相互认证的 TLS。
两种监听来源:
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
}
[Service]
ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
同一个 hosts 参数不能既由 daemon.json 又由 -H 启动参数提供。Unix socket 保留本地访问;0.0.0.0 是监听全部 IPv4 地址,不是客户端应该连接的目标地址。
可使用的帮助入口包括:
docker --help
dockerd --help
man docker
man dockerd
原稿称“只能 man 查看,不能 --help”不正确。客户端 docker -H 与服务端 dockerd -H 分别选择连接目标和监听地址。历史默认 TCP 端口常见未启用 TLS 时 2375、启用 TLS 时 2376;是否启用认证由实际配置决定,端口号自身不提供安全。
7.3 Rocky:通过 daemon.json 监听
原稿直接编辑包管理服务文件,容易被升级覆盖。下面用 drop-in 清除原 ExecStart,再保留 containerd 参数、去掉 -H fd://,让 hosts 由 JSON 提供。
mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/remote-api.conf <<'UNITCONF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock
UNITCONF
将以下设置与已有配置合并:
{
"registry-mirrors": ["https://docker.xuanyuan.me"],
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
}
systemctl daemon-reload
systemctl restart docker
ss -lntp
docker info
原稿监听记录:
tcp6 0 0 :::2375 :::* LISTEN 2990/dockerd
端口是 2375,原稿“2357”为笔误。旧版 Engine 的警告:
[DEPRECATION NOTICE]: API is accessible on http://0.0.0.0:2375 without encryption.
Access to the remote API is equivalent to root access on the host.
In future versions this will be a hard failure preventing the daemon from starting!
实际拒绝启动或警告行为随版本变化,不要为了消除提示而忽略认证。参考:https://docs.docker.com/engine/security/protect-access/。
7.4 客户端连接
# 服务器本地 Unix socket
docker -H unix:///var/run/docker.sock ps
# 原实验中另一台主机访问 TCP API
docker -H tcp://10.0.0.12:2375 version
原结果客户端/服务端均为 Docker 28.2.2。
通过 SSH 连接无需开放 Docker 的未认证 TCP 监听:
ssh-keygen
ssh-copy-id root@10.0.0.12
docker -H ssh://root@10.0.0.12 version
这是原实验的 root 账号示例;使用其他账号时需具有远端 socket 的合法访问权限。Docker Context 只是保存连接配置,安全性来自底层 SSH 或 TLS,而不是 context 名本身。
7.5 Ubuntu:通过服务参数监听
原实验 Ubuntu 使用二进制路径 /data/server/docker/dockerd,不是所有 Ubuntu 默认都没有 fd://;包安装和自定义服务文件可能不同,先查看真实服务单元。
[Service]
ExecStart=
ExecStart=/data/server/docker/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
此 drop-in 与上一节是另一种方案,不要叠加冲突的 remote-api.conf/hosts 配置。若实际二进制为 /usr/bin/dockerd,使用相应路径并保留原服务需要的其他参数。
systemctl daemon-reload
systemctl restart docker.service
ss -lntp
原监听:
tcp6 0 0 :::2375 :::* LISTEN 3180/dockerd
Rocky 客户端查询:
docker -H tcp://10.0.0.13:2375 version
原结果客户端/服务端均为 28.2.2。
7.6 REST API 查询
API 默认常通过 Unix socket 提供,也可在显式配置后通过 TCP 提供;它支持容器、镜像、网络和卷等操作。
apt install -y jq
# 明确连接本机地址,不使用 0.0.0.0 作为目标
curl -s http://127.0.0.1:2375/version | jq
# 不开 TCP 监听也可查询本地 API
curl --unix-socket /var/run/docker.sock http://localhost/version | jq
原稿响应字段:
{
"Platform": {"Name": "Docker Engine - Community"},
"Components": [{"Name": "Engine"}],
"Version": "28.3.0",
"ApiVersion": "1.51",
"MinAPIVersion": "1.24",
"GitCommit": "265f709",
"GoVersion": "go1.24.4",
"Os": "linux",
"Arch": "amd64",
"KernelVersion": "6.11.0-28-generic",
"BuildTime": "2025-06-24T15:44:12.000000000+00:00"
}
上面省去原响应中未展开的 Components 细节,保留原稿给出的字段,不把它当成新版 API 的固定结构。
7.7 TLS 实验入口
原稿引用 scripts/deploy_docker_tls.sh,没有提供脚本源代码。它计划生成 CA、服务端和客户端证书并修改 daemon;仅凭没有 warning 不能证明双向 TLS 已正确启用,应核对服务端验证配置、证书用途、SAN、私钥权限和真实连接结果。
/bin/bash deploy_docker_tls.sh server 10.0.0.12
原稿输出:
正在生成 TLS 证书...
Certificate request self-signature ok
subject=CN=*
Certificate request self-signature ok
subject=CN=client
证书生成完成!
正在配置 Docker 守护进程...
重启 Docker 服务...
Docker 服务已重启!
服务端证书需要包含客户端实际访问的 IP/DNS SAN,单独 CN=* 不能保证现代客户端验证通过。只有完整脚本和证书实际符合要求后才使用。
配置客户端的入口:
/bin/bash deploy_docker_tls.sh client 10.0.0.13
原交互:
正在配置客户端...
Docker服务器地址是(10.0.0.12):
请复制如下命令进行测试
按原稿,把客户端认证文件传到 10.0.0.13:
ssh root@10.0.0.13 mkdir -p /root/.docker
scp /etc/docker/certs.d/{ca.pem,cert.pem,key.pem} root@10.0.0.13:/root/.docker/
在客户端执行:
docker --tlsverify \
--tlscacert=/root/.docker/ca.pem \
--tlscert=/root/.docker/cert.pem \
--tlskey=/root/.docker/key.pem \
-H tcp://10.0.0.12:2375 info
原脚本沿用 2375 做 TLS,并非 TLS 必须用 2375;若服务端按惯例改为 2376,客户端也要改为同一端口。TLS 是否有效由握手和认证决定,不由“端口看起来像 TLS”决定。CA 私钥不属于要发送给客户端的文件。
8. 镜像结构与镜像仓库
8.1 镜像分层与 overlay2
镜像是标准化的文件系统差异层与配置;overlay2 是经典 Docker 存储驱动对 Linux OverlayFS 的使用方式,不是内核里一个名为 overlay2 的独立文件系统。相同镜像也可以由其他受支持的快照/存储机制管理。
镜像层只读,容器增加可写层;底层内容需要修改时,OverlayFS 可能把文件 copy-up 到上层,然后修改上层。上层同路径内容会遮住下层,删除也可以用 whiteout 表达;因此不能说联合挂载“从不覆盖任何内容”。
多个容器可以共享镜像只读层,各自有可写层。构建缓存复用取决于输入和依赖,不是每条 Dockerfile 指令都对应文件系统差异层,也不能认为只重建一条变化指令而后续永不失效。
| 对比 | 镜像分层 | overlay2 |
|---|---|---|
| 定位 | 镜像内容组织与分发模型 | 本地经典存储驱动 |
| 内容 | 文件系统差异层与配置、manifest 等 | 将已有层与可写层呈现为容器文件系统 |
| 机制 | 内容寻址、复用、构建缓存 | 联合挂载、copy-up、whiteout 等 |
| 收益 | 减少重复存储和传输 | 减少整棵根文件系统复制 |
8.2 rootfs 与容器
普通 Linux 容器镜像提供用户态根文件系统内容,不为每个容器携带并启动独立内核。原稿用 bootfs/rootfs 分层示意来解释共享内核;这不是对所有 Linux 启动流程或镜像格式的严格定义,不能把 kernel/bootloader 当成普通容器镜像的一层。

/dev、/proc、/sys 等目录中的运行时内容通常由容器运行时挂载或生成。镜像体积受版本、架构、工具和压缩格式影响,原稿 BusyBox 1.22 MB、Alpine 5 MB、Ubuntu 60 MB、CentOS 200 MB 只是不同历史样本。

父镜像为构建提供已有层和默认配置;子镜像在此基础上添加变化。容器由镜像创建;修改容器可通过 commit 生成新镜像,或优先把修改写入 Dockerfile 重新构建。重启同一个容器通常不会丢弃其可写层,删除重建才需要重新取得运行数据。
8.3 Nginx 分层实验
docker images
docker pull nginx
docker images
docker history nginx
docker image inspect nginx
初始只有表头,拉取记录:
Using default tag: latest
latest: Pulling from library/nginx
dad67da3f26b: Pull complete
4eb3a9835b30: Pull complete
021db26e13de: Pull complete
397cc88dcd41: Pull complete
5f4a88bd8474: Pull complete
66467f827546: Pull complete
f05e87039331: Pull complete
Digest: sha256:dc53c8f25a10f9109190ed5b59bda2d707a3bde0e45857ce9e1efaa32ff9cbc1
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 9a9a9fd723f1 2 days ago 192MB

原稿镜像详情中的配置与标识:
{
"Id": "sha256:9a9a9fd723f1d2ba52b914ece050f298eec04ef490a9065c52805e46779c4c43",
"RepoTags": ["nginx:latest"],
"RepoDigests": ["nginx@sha256:dc53c8f25a10f9109190ed5b59bda2d707a3bde0e45857ce9e1efaa32ff9cbc1"],
"Parent": "",
"Comment": "buildkit.dockerfile.v0",
"Created": "2025-06-24T20:52:14Z",
"DockerVersion": "",
"Author": "",
"Config": {
"ExposedPorts": {"80/tcp": {}},
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.29.0",
"NJS_VERSION=0.9.0",
"NJS_RELEASE=1~bookworm",
"PKG_RELEASE=1~bookworm",
"DYNPKG_RELEASE=1~bookworm"
],
"Entrypoint": ["/docker-entrypoint.sh"],
"Cmd": ["nginx", "-g", "daemon off;"],
"Labels": {"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"},
"StopSignal": "SIGQUIT"
},
"Architecture": "amd64",
"Os": "linux",
"Size": 192226221,
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:7fb72a7d1a8e984ccd01277432de660162a547a00de77151518dc9033cfb8cb4",
"sha256:151f9feea56362593fb4b85e2020dc8c8b69d33703c6d5cf2c8769b190cc54b3",
"sha256:13fcb2d303e83bc04c39fdd29bd0df540067619915b5deac96fc853d5c404c47",
"sha256:6380429cac565ac597936095d7643e6fca83c978bf2ea73078e54ed571c99d2f",
"sha256:126eaee18409d97a383bda20c227c11cbae5a58cf204af68721dac5e0dd2be53",
"sha256:d35594dd7e6de0e30f5d19afb38855299aec1ef5cbb70f97539eca365cab1e46",
"sha256:cd38dca3d98283db306a43da1a7e80d6bcb722ca701ce01b91dadc6baabf8235"
]
},
"Metadata": {"LastTagTime": "0001-01-01T00:00:00Z"}
}
GraphDriver 部分另列为 JSON,便于阅读原稿长路径;它们只是该实验主机的内部位置,不应复制到另一台主机配置里:
{
"Name": "overlay2",
"Data": {
"LowerDir": "/var/lib/docker/overlay2/45b817b58eb9ec12d1c6d5db87d8dec3580fca2c8b1c2bc7083562e367f74098/diff:/var/lib/docker/overlay2/2aa20330c6f353ce7faf6a6faf67c415f6709bc5ee0f2cadaa8894863c692c34/diff:/var/lib/docker/overlay2/8af8b0582053d09e6f0ccab76ec2acbdbbd52902cdbf1534452ba9ae0c1a20dc/diff:/var/lib/docker/overlay2/f4eaea38d360fa0330a71c44256b5a65c8a4393bfa8da1b679cad1d5e11acaa2/diff:/var/lib/docker/overlay2/8e189bb1298b2644b58ba1595b589211c82d771bd67e434918af59e99613540a/diff:/var/lib/docker/overlay2/49e54dd7c264c9b30d25883ce5f065804d0934b342051faf268428ecdf227777/diff",
"MergedDir": "/var/lib/docker/overlay2/fec9ac6936916cf7f2b89976fcdc089cb86788c51e588110c8de46b4aad62d47/merged",
"UpperDir": "/var/lib/docker/overlay2/fec9ac6936916cf7f2b89976fcdc089cb86788c51e588110c8de46b4aad62d47/diff",
"WorkDir": "/var/lib/docker/overlay2/fec9ac6936916cf7f2b89976fcdc089cb86788c51e588110c8de46b4aad62d47/work"
}
}
镜像 ID、仓库摘要和层 DiffID 是不同对象的摘要,不应因为同为 sha256 就混为一谈。inspect 字段也会随存储模式和 API 版本变化。
8.4 保存归档并观察布局
docker save -o nginx.tar nginx
mkdir -p data
tar xf nginx.tar -C data
ls -l data
ls -l data/blobs
ls -l data/blobs/sha256
原稿将归档命名为 nginx.tar.gz,大小 196392960 字节,但 docker save -o 不会按后缀自动 gzip;上面更名为 tar。需要压缩应使用:
docker save nginx | gzip > nginx.tar.gz
原稿解包后的列表:
drwxr-xr-x. 3 root root 20 ... blobs
-rw-r--r--. 1 root root 362 ... index.json
-rw-r--r--. 1 root root 2286 ... manifest.json
-rw-r--r--. 1 root root 31 ... oci-layout
-rw-r--r--. 1 root root 88 ... repositories
blobs/:
drwxr-xr-x. 2 root root 4096 ... sha256
blobs/sha256/:
-rw-r--r--. 1 root root 1127 ... 0ca7b78ec97fca54170e751d074142fa02f5ec6669df8048adf0bfc292b5d42c
...
-rw-r--r--. 1 root root 482 ... fcd3176f43badcbf6aa68dd3eb303c36fade3a26eeb7ab4d045e4bcd038d6c16
blobs 可能包含层、镜像配置和 manifest 等多种内容,不是每个 blob 都是文件系统层。index.json、manifest.json 描述不同格式的索引/引用关系;oci-layout 描述 OCI 布局版本;repositories 是兼容 Docker 归档格式的仓库映射,不是 OCI 的必需文件。具体归档结构随 Engine 和输出格式变化。
8.5 镜像仓库网站
正式 Docker Hub:https://hub.docker.com/。它支持发布、检索、拉取和管理公共/私有镜像,镜像来源应区分官方、上游维护者和普通用户。

原 PDF 此首页插图重复一次,images/image-025.png 保留为相同原图,不再重复展示。
原稿还列出 http://dockerhub.com 与 https://hub-stage.docker.com/。应优先使用正式域名;测试/预发布站点的可达性和用途不构成生产使用承诺,本次未联网核验。

其他仓库与资料入口:
- 阿里云:https://cr.console.aliyun.com/
- 腾讯云 TCR:https://cloud.tencent.com/product/tcr
- 中科大镜像资料:https://mirrors.ustc.edu.cn/
- 原稿历史镜像地址:
http://hub-mirror.c.163.com。 - 原稿列举的第三方地址:
docker.registry.cyou、dockerproxy.cn、docker.hlyun.org。
镜像软件站、Registry 服务和代理加速器不是同一概念。第三方服务的内容、认证、时效及可用性需分别确认。
8.6 在 Docker Hub 查找 Nginx
搜索 nginx,区分结果中的官方镜像、下载热度和具体发布者。

进入镜像概述查看使用方法、支持标签和平台。

【Official Images】可打开官方镜像列表,不代表搜索出的所有相似名字都由 Docker 或 Nginx 维护。

【Tag summary】只显示摘要;完整标签和平台信息在相应 Tags 页面查看。


镜像信息旁给出拉取命令;点击标签/摘要可进一步检查层及详情。

8.7 Alpine 与 BusyBox
原稿“Alpha 镜像”实际指 Alpine。

Alpine 使用 musl libc、BusyBox 和 apk,镜像较小,适合能够兼容其用户态环境的应用;glibc 应用不一定可直接运行。体积小不等于自动没有安全问题,也没有“所有应用都应从 Ubuntu 改用 Alpine”的通用要求。
参考:
- https://www.alpinelinux.org/
- https://github.com/alpinelinux
- https://hub.docker.com/_/alpine/
- https://github.com/alpinelinux/docker-alpine
- 原稿同时列出的历史项目:https://github.com/gliderlabs/docker-alpine
- https://mirrors.aliyun.com/alpine/
- https://pkgs.alpinelinux.org/packages
BusyBox 将 ls、cp、mv、grep、ping 等许多工具做成一个紧凑可执行文件;具体 applet 由构建决定,不等于完整 GNU/Linux 工具集。常用于临时调试、轻型 HTTP 服务或脚本、嵌入式用户态环境;容器仍需要宿主机内核。
| 镜像引用 | 定位与限制 |
|---|---|
docker.io/library/busybox |
官方镜像,常为数 MB,平台和 libc 变体需核对标签 |
docker.io/radial/busyboxplus |
第三方扩展镜像,不能仅凭名字保证有 opkg、nginx 或 Python;应查看标签、构建文件和内容 |
arm64v8/busybox |
面向 ARM64 的镜像命名空间;现代官方 busybox 多平台标签通常也可选择 ARM64 |
docker.io/networkstatic/busybox |
原稿列举的第三方网络调试镜像,工具与维护状态需另行确认 |
原稿给官方 BusyBox 约 1~4 MB、busyboxplus 约 5~10 MB 等大小,都是版本相关样本,不能作为固定容量保证。
9. 镜像命令扩展
9.1 标签与无标签镜像
标签不能写成空字符串,例如:
docker tag nginx:latest nginx:
原错误:
Error parsing reference: "nginx:" is not a valid repository/tag: invalid reference format
完全不提供 tag 与显式写空冒号不同:nginx 通常按 nginx:latest 解释。未命名的本地镜像可显示 <none>:<none>,可按镜像 ID 运行,并非不能使用。pull 支持标签或 digest;不能说任何拉取都必须有 tag。
| 情形 | 说明 |
|---|---|
| 同标签重新构建/拉取 | 旧镜像可能失去原标签 |
| 中间构建结果 | 可由构建器缓存管理,不一定都作为普通镜像列出 |
| commit 不提供目标名称 | 可生成只有 ID 的镜像 |
| 删除某个标签 | 若还有其他引用或容器使用,不一定立即删除内容 |
| 构建中断 | 可能留下缓存等数据,不保证必然产生悬空镜像 |
| tag 操作 | 增加新引用,不会自动删除源标签 |
9.2 自定义镜像显示
常用模板字段:{{.Repository}}、{{.Tag}}、{{.ID}}、{{.Digest}}、{{.CreatedAt}}、{{.Size}}。
docker images --format '{{.Repository}}\t{{.Tag}}\t{{.Size}}'
docker images --format '{{.CreatedAt}}\t{{.Repository}}:{{.Tag}}' | sort -k1 -r
docker images --format '{{.Repository}}:{{.Tag}}'
原名称列表:
nginx:latest
alpine:latest
redis:latest
busybox:latest
web-tomcat:v1
web-django:v1
web-nginx:v1
docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}'
nginx:latest 192MB 2025-06-25 04:52:14 +0800 CST
alpine:latest 8.31MB 2025-05-31 00:20:41 +0800 CST
redis:latest 128MB 2025-05-30 00:02:07 +0800 CST
busybox:latest 4.28MB 2024-09-27 05:31:42 +0800 CST
web-tomcat:v1 458MB 2024-01-16 09:09:19 +0800 CST
web-django:v1 436MB 2024-01-15 22:51:50 +0800 CST
web-nginx:v1 187MB 2024-01-15 22:50:23 +0800 CST
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}'
REPOSITORY TAG IMAGE ID SIZE
nginx latest 9592f5595f2b 192MB
alpine latest cea2ff433c61 8.31MB
redis latest ed3a2af6d0d4 128MB
busybox latest 6d3e4188a38a 4.28MB
web-tomcat v1 299452ffece3 458MB
web-django v1 d001c15a4640 436MB
web-nginx v1 48d5de12bb81 187MB
原稿不同实验时间的 Nginx ID 不相同,不能把 latest 当成固定不可变版本。
docker images --format '{"name":"{{.Repository}}","tag":"{{.Tag}}","size":"{{.Size}}"}'
docker images --format '{"name":"{{.Repository}}","tag":"{{.Tag}}"}' | jq .
原逐行 JSON:
{"name":"nginx","tag":"latest","size":"192MB"}
{"name":"alpine","tag":"latest","size":"8.31MB"}
{"name":"redis","tag":"latest","size":"128MB"}
{"name":"busybox","tag":"latest","size":"4.28MB"}
{"name":"web-tomcat","tag":"v1","size":"458MB"}
{"name":"web-django","tag":"v1","size":"436MB"}
{"name":"web-nginx","tag":"v1","size":"187MB"}
jq 会把每行独立对象格式化,不自动把它们合成一个 JSON 数组。
9.3 大小统计与过滤
docker images --format '{{.Size}}'
docker system df
docker system df -v
上面七项 SIZE 的数值均以 MB 表示,原稿简单相加为 1413.59MB。原演示使用:
# 仅能演示这个“所有值都为 MB”的样本求和,不能作为通用磁盘统计
# docker images --format '{{.Size}}' | awk -F'M' '{sum+=$1} END {print sum "MB"}'
真正输出可能混有 kB、MB、GB,镜像又可能共享层或有多个标签;这个和不代表磁盘独占占用。应通过 system df 观察共享和可回收空间。
过滤语法:
docker images --filter 'dangling=true'
docker images -f 'dangling=false'
默认 docker images 也可能显示悬空镜像,并非默认等于 dangling=false。悬空对象与构建缓存的分类依存储后端/版本变化;只看到 <none> 不能断言它无用或无法运行。
原稿过滤样本:
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> e395fa7ced4e 28 minutes ago 631MB
<none> <none> 0b75057d6a95 28 minutes ago 631MB
<none> <none> 82053db84dd7 32 minutes ago 627MB
<none> <none> c173bb524ea7 32 minutes ago 627MB
<none> <none> ed55b7e58a39 32 minutes ago 627MB
<none> <none> 46c144c7b667 32 minutes ago 627MB
<none> <none> cff6fceebb23 33 minutes ago 627MB
10. 容器状态、启动与查询
10.1 生命周期

| 状态/事件 | 含义及操作 |
|---|---|
| created | create 已完成,但主进程尚未启动;docker start 启动 |
| running | 主进程运行中 |
| paused | pause 冻结任务,unpause 恢复 |
| exited | 主进程结束,容器元数据/可写层仍在;可 start 或 rm |
| restarting | 正按重启策略重启 |
| removing / dead | 删除中或异常未完全清理的状态,需结合具体错误处理 |
| 删除完成 | 对象已移除,不再作为普通 inspect 状态存在;卷和绑定目录不一定一并删除 |
原图的 stopped 是教学上的“已停止”,实际常见状态为 exited;die、oom 是事件/状态属性,不能都当成 .State.Status 的独立枚举。OOM 后是否退出、重启要看被杀进程和策略。
create → start → running
running → pause → paused → unpause → running
running → 主进程退出/stop/kill → exited
exited → start → running
created/exited → rm → 删除
running → rm -f → 强制终止并删除
重启策略有 no、on-failure[:N]、always、unless-stopped;手动 stop 会暂停自动重启,always 在 daemon 再启动后与 unless-stopped 的行为不同。重启策略不是服务高可用保证。
10.2 启动流程和参数

整体包括检查/按策略拉取镜像、创建容器配置和可写文件系统、准备网络及挂载、设置 namespace/cgroup、安全约束,最后执行入口命令。实际执行顺序由 Engine/runtime 实现协调,不能把“create 已分配好运行进程 PID”当成所有场景的既定事实。
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
| 类别 | 参数 | 用途 |
|---|---|---|
| 启动 | -d, --detach |
后台运行并返回 ID |
| 启动 | --rm |
退出后删除容器及相关匿名卷,具名卷除外 |
| 启动 | --name |
自定义名称 |
| 启动 | --restart |
重启策略,不能与 --rm 共用 |
| 进程 | -i / -t |
保持标准输入 / 分配伪终端 |
| 进程 | --init |
使用轻量 init 处理信号转发及孤儿/僵尸回收 |
| 进程 | --entrypoint |
覆盖入口可执行程序 |
| 进程 | --user 1000:1000 |
设置 UID:GID |
| 镜像 | --pull always/missing/never |
运行前拉取策略,默认通常 missing |
| 镜像 | --platform linux/amd64 |
指定平台,仍需支持该平台运行 |
| 镜像 | --disable-content-trust |
历史内容信任签名流程开关,不是关闭镜像层摘要完整性校验 |
| 网络 | -p |
发布指定端口 |
| 网络 | -P |
将 EXPOSE 等声明的暴露端口发布为随机宿主机端口 |
| 网络 | --network |
网络模式/网络连接 |
| 网络 | --dns |
自定义 DNS 服务器 |
| 网络 | --ip |
在支持静态地址的自定义网络中指定 IP |
| 存储 | -v、--mount |
卷或绑定挂载 |
| 存储 | --tmpfs |
临时内存文件系统;可能涉及 swap,不能保证所有字节永不落盘 |
| 存储 | --volumes-from |
复用另一个容器的卷挂载定义 |
| 资源 | -m, --memory |
内存限制 |
| 资源 | --cpu-shares |
CPU 竞争时相对权重,不是固定 CPU 配额 |
| 资源 | --cpus |
CPU 时间配额,不必固定到指定物理核心 |
| 资源 | --pids-limit |
任务数限制,线程通常也计入 |
| 安全 | --privileged |
大幅扩展容器权限 |
| 安全 | --cap-add、--cap-drop |
增减 Linux capability |
| 安全 | --security-opt |
seccomp、LSM 和其他支持的安全选项 |
| 资源 | --cgroup-parent |
使用符合驱动格式的父 cgroup/slice |
| 日志 | --log-driver、--log-opt |
选择日志驱动和选项 |
| 健康 | --health-cmd、--health-interval |
健康检查命令和间隔 |
| namespace | --pid container:web |
与指定容器共享 PID namespace |
| namespace | --ipc host/private/... |
IPC namespace 模式 |
| namespace | --uts host |
共享宿主机 UTS namespace |
--cap-drop 是独立参数,不能笼统说所有 capability 都通过 security-opt 配置;设置系统时间的能力为 SYS_TIME,不是原稿的 SETTIME。默认能力通常已不含 SYS_TIME。配置 seccomp 可限制系统调用。
cgroup-parent 用于层级资源管控,但共享父组本身不自动产生 CPU/内存限制,仍需给父层设置额度;现代 Kubernetes 使用 CRI 运行时,不能把所有 Docker 容器默认都归属 kubepods。
10.3 ps 查询
docker ps -a
docker ps -q
docker ps -a -q
docker ps -aq
docker ps -l
docker ps --latest
-a 是 ps 的“列出全部”,不是 run 的同义选项。默认 ps 只列运行容器;-q 只输出 ID;-l 为最近创建的容器,未必运行中。
原样本:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f5229c2e1d2d nginx "/docker-entrypoint.…" 51 seconds ago Up 50 seconds 80/tcp inspiring_wright
407ab2601c03 nginx "/docker-entrypoint.…" 2 minutes ago Exited (0) About a minute ago loving_ishizaka
80/tcp 是容器暴露端口信息,没有宿主机端口映射。是否能从宿主机直接到容器 IP 访问还取决于运行平台、路由和防火墙,不能据此说局域网都已可访问。
# docker ps -q
f5229c2e1d2d
# docker ps -aq
f5229c2e1d2d
407ab2601c03
条件过滤
docker ps --filter 'status=running'
docker ps -a --filter 'status=exited'
docker ps -a --filter 'status=created'
docker ps --filter 'name=web'
docker ps --filter 'name=^/web$'
docker ps --filter 'label=app=web'
docker ps --filter 'label=app'
docker ps -a --filter 'since=nginx'
docker ps -a --filter 'before=db'
docker ps --filter 'status=running' --filter 'label=env=prod'
docker ps -f 'name=inspiring_wright'
不同键通常组合为 AND,同一个键重复可能为 OR;以具体过滤器为准。名称精确匹配常需考虑内部名称前缀 /。
ps 不支持原稿的 until=5m、until=24h 时间过滤,until 也不是“运行不足五分钟”。要按时间删除已停止容器,支持的入口为:
# 会删除满足条件的已停止容器;它不是只读查询
docker container prune --filter 'until=24h'
仅观察创建/启动时间时可用 inspect 提取 .Created、.State.StartedAt,不要把 prune 当查看命令。
定制输出
模板字段包括 ID、Image、Command、CreatedAt、RunningFor、Ports、Status、Names、Labels、Mounts、Networks。
docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}'
docker ps --format '{{.ID}}: {{.Names}} ({{.Status}})'
docker ps --format json
docker ps --format json | jq
原稿:
CONTAINER ID NAMES STATUS PORTS
f73428f6a34e modest_kilby Up 3 minutes 80/tcp
f73428f6a34e: modest_kilby (Up 4 minutes)
原单条 JSON 格式化后:
{
"Command": "\"/docker-entrypoint.…\"",
"CreatedAt": "2025-07-08 15:07:59 +0800 CST",
"ID": "f73428f6a34e",
"Image": "nginx",
"Labels": "maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>",
"LocalVolumes": "0",
"Mounts": "",
"Names": "modest_kilby",
"Networks": "bridge",
"Platform": null,
"Ports": "80/tcp",
"RunningFor": "4 minutes ago",
"Size": "0B",
"State": "running",
"Status": "Up 4 minutes"
}
10.4 inspect
docker inspect [OPTIONS] NAME|ID [NAME|ID...]
--format/-f 用 Go 模板提取;--type 可明确 container、image、network、volume 等对象类型。默认结果是 JSON 数组,即使只查一个对象,也需注意 jq 的 [0] 索引。
docker inspect sharp_gates

原 PDF 将同一长图跨页重复展示,原文件 image-038.png 仍保留,这里只展示一次。
原稿按旧版默认 bridge 的字段 .NetworkSettings.IPAddress 查询得到 172.17.0.2;新版该字段可能为空或移除,按网络名称查询更可靠:
docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' sharp_gates
docker inspect -f '{{(index .NetworkSettings.Networks "app-net").IPAddress}}' sharp_gates

查询容器创建时间,不是镜像创建时间:
docker inspect --format '{{.Created}}' sharp_gates
2025-07-08T09:55:58.998837931Z

docker inspect -f '{{.Config.Cmd}}' sharp_gates
[nginx -g daemon off;]
Cmd 只是命令配置的一部分,完整启动还需结合 Entrypoint。
docker image inspect --format '{{json .RootFS.Layers}}' nginx | jq
原另一个 Nginx 样本的层:
[
"sha256:1bb35e8b4de116e84b2ccf614cce4e309b6043bf2cd35543d8394edeaeb587e3",
"sha256:1bf33238ab09f13272838701bf1b0a795f1ca25a4197d6df0e9e1033ce9f66c0",
"sha256:81a9d30670ecf867ffd9c7b952444efce07b8712a597e6009c9188e6ec223c1f",
"sha256:f941308035cf657934e5dbf83c5c18819c2f9aa2ed687152caec85b3774cf223",
"sha256:e6c40b7bdc831f8cbb96c77ce6b826b7e4442373542111e0f224d60c7a9b7870",
"sha256:de2ef8ceb76a4bcd39a8a60962963c8aee0a0460eb57e4ca1bb5107e2b826b4a",
"sha256:07eaefc6ebf2f83b1da015d65d9179f5b952f20b670a1da6aec9a63811bb5734"
]

docker network ls
docker network inspect bridge --format '{{json .IPAM.Config}}' | jq
docker inspect sharp_gates | jq '.[0].HostConfig.NetworkMode'
docker inspect sharp_gates | jq -r '.[0].NetworkSettings.Networks.bridge.IPAddress'
原结果:
NETWORK ID NAME DRIVER SCOPE
89e043ba18dc bridge bridge local
44cae059432e host host local
68539ce0cbce none null local
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]
"bridge"
172.17.0.2

NetworkMode 是网络模式/连接配置,不是“网段类型”的同义词。inspect 可能含环境变量、挂载等敏感数据,分享时应按实际内容处理;字段随 API、网络和存储模式变化。
10.5 日志命令
docker logs [OPTIONS] CONTAINER
| 选项 | 用途 |
|---|---|
--follow/-f |
持续跟踪新日志 |
--tail N |
最近 N 行 |
--since、--until |
时间范围 |
--timestamps/-t |
每行增加 Docker 时间戳 |
--details |
输出配置允许的附加属性 |
日志主要来自容器进程 stdout/stderr,不会自动采集容器里任意日志文件。停止但未删除的容器,通常仍可读取日志驱动保留的数据。
docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.Names}}'
docker logs sharp_gates
CONTAINER ID IMAGE COMMAND NAMES
43007f11dfe8 nginx "/docker-entrypoint.…" upbeat_goldberg
145b5a7f6b84 nginx "/docker-entrypoint.…" sharp_gates
原启动日志:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/07/08 09:55:59 [notice] 1#1: using the "epoll" event method
2025/07/08 09:55:59 [notice] 1#1: nginx/1.29.0
2025/07/08 09:55:59 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14+deb12u1)
2025/07/08 09:55:59 [notice] 1#1: OS: Linux 5.14.0-427.13.1.el9_4.x86_64
2025/07/08 09:55:59 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
2025/07/08 09:55:59 [notice] 1#1: start worker processes
2025/07/08 09:55:59 [notice] 1#1: start worker process 29
2025/07/08 09:55:59 [notice] 1#1: start worker process 30
2025/07/08 09:55:59 [notice] 1#1: start worker process 31
2025/07/08 09:55:59 [notice] 1#1: start worker process 32
docker logs --tail 5 sharp_gates
docker logs --tail 5 -t sharp_gates
docker stop sharp_gates
docker logs sharp_gates
docker start sharp_gates
docker logs -f sharp_gates
tail 5 对应上述最后五行。加时间戳的原样本:
2025-07-08T09:55:59.183974909Z 2025/07/08 09:55:59 [notice] 1#1: start worker processes
2025-07-08T09:55:59.184280893Z 2025/07/08 09:55:59 [notice] 1#1: start worker process 29
2025-07-08T09:55:59.184294912Z 2025/07/08 09:55:59 [notice] 1#1: start worker process 30
2025-07-08T09:55:59.184386036Z 2025/07/08 09:55:59 [notice] 1#1: start worker process 31
2025-07-08T09:55:59.184619227Z 2025/07/08 09:55:59 [notice] 1#1: start worker process 32
logs -f 初始还会显示已保留日志,Ctrl+C 停止客户端跟踪,通常不影响容器主进程。大量日志读取和输出仍会消耗资源。
10.6 日志存储与轮转
json-file、local、journald、syslog、none 等驱动的存储和读取能力不同。经典默认数据目录配合 json-file 时,常见文件为 /var/lib/docker/containers/<ID>/<ID>-json.log;不能把该路径当成所有日志驱动的统一位置。
docker inspect --format '{{json .HostConfig.LogConfig}}' sharp_gates | jq
docker run --log-opt max-size=10m --log-opt max-file=3 -d --name nginx-log nginx
docker inspect --format '{{json .HostConfig.LogConfig}}' nginx-log | jq
docker run --log-driver=syslog -d --name nginx-driver nginx
docker inspect --format '{{json .HostConfig.LogConfig}}' nginx-driver | jq
原结果依次为:
{"Type":"json-file","Config":{}}
{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}
{"Type":"syslog","Config":{}}
原创建 ID:4154387d9fc56cd93a61b7c809bc0e8ed22bc5b23c11e46181f53f08431804b0(nginx-log),ee99e9380ab9ee0f221819fafe1dc1642c6cc264c36c025b0665925572211346(nginx-driver)。默认无轮转配置的 json-file 可能持续增长,限制大小应在创建时设置,或修改默认后重建相应容器。
docker logs nginx-driver
ls /var/lib/docker/containers
ls /var/lib/docker/containers/ee99e9380ab9ee0f221819fafe1dc1642c6cc264c36c025b0665925572211346/
原目录还包括 sharp_gates、upbeat_goldberg 和 nginx-log 的 ID。syslog 容器目录清单:
checkpoints config.v2.json container-cached.log hostconfig.json hostname
hosts mounts resolv.conf resolv.conf.hash


container-cached.log 可与支持远程日志驱动读取的本地双重日志缓存有关,内部格式不应当成普通文本日志接口。原稿用 cat 查看它,正确的业务查询入口仍是 docker logs 或配置的 syslog 接收端。某些驱动/缓存配置不支持 docker logs,需按具体版本确认。
10.7 attach 与信号
attach 连接已有主进程 I/O,不创建新 Shell,不等于 exec,也不是通用 VNC;它并非无用命令。只有容器已有交互式 Shell 主进程时才能通过其输入运行 Shell 命令。
docker attach [OPTIONS] CONTAINER
常用选项:--detach-keys 自定义脱离键,--no-stdin 不附加输入,--sig-proxy 控制信号代理。
docker run -d --name nginx-attatch nginx
docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' nginx-attatch
# A 终端
docker attach nginx-attatch
# B 终端
curl -I 172.17.0.2
原 B 终端:
HTTP/1.1 200 OK
Server: nginx/1.29.0
Date: Wed, 09 Jul 2025 13:24:55 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 24 Jun 2025 17:22:41 GMT
Connection: keep-alive
ETag: "685adee1-267"
Accept-Ranges: bytes
A 终端看到:
172.17.0.1 - - [09/Jul/2025:13:24:55 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.76.1" "-"
原实验 Ctrl+C 后,传入的是 SIGINT(2),不是 SIGKILL;原日志本身也印证了这一点:
^C
2025/07/09 13:26:45 [notice] 1#1: signal 2 (SIGINT) received, exiting
2025/07/09 13:26:45 [notice] 33#33: exiting
2025/07/09 13:26:45 [notice] 33#33: exit
2025/07/09 13:26:45 [notice] 30#30: exiting
2025/07/09 13:26:45 [notice] 30#30: exit
2025/07/09 13:26:45 [notice] 31#31: exiting
2025/07/09 13:26:45 [notice] 31#31: exit
2025/07/09 13:26:45 [notice] 32#32: exiting
2025/07/09 13:26:45 [notice] 32#32: exit
2025/07/09 13:26:45 [notice] 1#1: signal 17 (SIGCHLD) received from 32
2025/07/09 13:26:45 [notice] 1#1: worker process 32 exited with code 0
2025/07/09 13:26:45 [notice] 1#1: signal 29 (SIGIO) received
2025/07/09 13:26:45 [notice] 1#1: signal 17 (SIGCHLD) received from 31
2025/07/09 13:26:45 [notice] 1#1: worker process 30 exited with code 0
2025/07/09 13:26:45 [notice] 1#1: worker process 31 exited with code 0
2025/07/09 13:26:45 [notice] 1#1: worker process 33 exited with code 0
2025/07/09 13:26:45 [notice] 1#1: exit
容器随后为 Exited (0),原 ID eb46b2227bec。
docker start nginx-attatch
docker attach nginx-attatch
docker attach --detach-keys='ctrl-e,ctrl-d' nginx-attatch
默认脱离序列为先 Ctrl+P,再 Ctrl+Q。该机制与容器是否交互式/TTY、是否附加 stdin、终端按键传递有关;原稿用普通 -d nginx 的非交互主服务实验,不能据此断言脱离键一般无效。查看日志优先 logs -f,创建独立交互进程优先 exec -it;对主 Shell 使用 exit 会使该主进程退出,而对 Nginx 输入文字 exit 并不会自动成为 Shell 命令。
11. 端口发布、docker-proxy 与 DNS
11.1 bridge 与端口发布
默认 Linux bridge 模式下,容器通常经 veth 接入网桥,出站访问可使用 NAT。外部主机默认通常不能直接路由到容器私有 IP;访问能力由实际路由、Docker 网关模式、防火墙和端口发布决定,不能把所有容器网络都叫 NAT 网络。
docker run -d nginx
docker port heuristic_bose
原未发布容器:
23c2ea030423 heuristic_bose 80/tcp
port 没有输出宿主机映射。-P 发布的是已声明暴露的端口,不是扫描并发布容器内所有监听端口:
docker run -d -P nginx
docker port zen_kirch
6755c0badab7 zen_kirch 0.0.0.0:32768->80/tcp, [::]:32768->80/tcp
80/tcp -> 0.0.0.0:32768
80/tcp -> [::]:32768
11.2 -p 指定端口
docker run -p [宿主机IP:]宿主机端口:容器端口[/协议] [其他参数] 镜像
多个映射使用多个 -p。映射目标不必预先 EXPOSE,Docker 通常也不会因为容器内尚无进程监听就拒绝创建;这种情况下访问会失败,原稿“容器端口不存在就报错”不准确。
缺少参数的错误演示:
docker run -p -d nginx
docker: invalid containerPort: -d
Run 'docker run --help' for more information
省略宿主机端口可自动分配:
docker run -p :80 -d nginx
10afe1b4f165 clever_colden 0.0.0.0:32769->80/tcp, [::]:32769->80/tcp
指定宿主机端口:
docker run -p 80:80 -d nginx
4f1e58ff9bc8 flamboyant_jang 0.0.0.0:80->80/tcp, [::]:80->80/tcp
指定本机已有地址:
docker run -p 10.0.0.12:81:80 -d nginx
451f797d618c priceless_panini 10.0.0.12:81->80/tcp
原课件某些命令提示符写 ubuntu24-13,但绑定地址为 10.0.0.12。实际应使用执行 run 的宿主机拥有的地址,而不是照抄另一台主机的地址。
11.3 更改已有端口发布
docker container update --help
update 支持资源与重启策略等设置,常见选项包括:
| 选项 | 内容 |
|---|---|
--blkio-weight |
块 I/O 相对权重,后端支持条件不同 |
--cpu-period、--cpu-quota |
CFS 配额周期和时间 |
--cpu-rt-period、--cpu-rt-runtime |
实时 CPU 限制,依内核及 daemon 支持 |
--cpu-shares、--cpus |
CPU 权重/配额 |
--cpuset-cpus、--cpuset-mems |
CPU/NUMA 范围 |
--memory、--memory-reservation |
硬限制/相应软约束 |
--memory-swap |
内存与 swap 的组合限制语义 |
--pids-limit |
任务数 |
--restart |
重启策略 |
它没有直接修改已有 -p 的受支持选项。可靠做法是记录原运行配置、持久化数据,再以新映射重建容器。原例是普通 nginx 容器,最小示意:
docker inspect priceless_panini
docker stop priceless_panini
docker rename priceless_panini priceless_panini-old
docker run -d --name priceless_panini -p 10.0.0.12:88:80 nginx
真实业务还需带回原环境变量、挂载、网络、镜像版本和其他参数,不能只复制 -p。
原稿另记录了直接改内部状态文件的实验,它不是受支持接口。路径为 /var/lib/docker/containers/<ID>/hostconfig.json,原 PortBindings:
{
"NetworkMode": "bridge",
"PortBindings": {
"80/tcp": [{"HostIp":"10.0.0.12","HostPort":"81"}]
}
}
原操作先 systemctl stop docker,用 vim 把 HostPort 81 改为 88,再 systemctl start docker、docker start priceless_panini,最后显示:
451f797d618c priceless_panini 10.0.0.12:88->80/tcp
这只证明原版本一次内部文件修改的结果,不保证元数据、网络规则和缓存一致,也不能在 daemon 工作时直接修改。本文使用重建作为修正后的操作路径。
11.4 docker-proxy 与防火墙
netstat -tnulp
iptables -vnL DOCKER
iptables -t nat -vnL DOCKER
原用户态代理监听样本:
tcp 0 0 0.0.0.0:32768 ... LISTEN 5150/docker-proxy
tcp6 0 0 :::32768 ... LISTEN 5156/docker-proxy
rootful Linux bridge 发布端口可以涉及内核 DNAT/转发规则和用户态代理。哪些连接经过 docker-proxy 取决于来源、网络、IP 族和配置;不能说每个访问必然依次经过用户态代理。-p 和 -P 都可能创建代理,也不能说只有 -P 才有。
防火墙规则由 Docker daemon 的网络管理逻辑维护,不是 docker-proxy 自己创建和管理 iptables 规则。
原 filter 规则:
Chain DOCKER
ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.3 tcp dpt:80
DROP all -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0
再启动:
docker run -d -P nginx
原映射:
13b17803d487 angry_proskuriakova 0.0.0.0:32769->80/tcp, [::]:32769->80/tcp
6755c0badab7 zen_kirch 0.0.0.0:32768->80/tcp, [::]:32768->80/tcp
23c2ea030423 heuristic_bose 80/tcp
filter 表相应多一条允许去 172.17.0.4:80 的规则,nat 表也会有匹配宿主机发布端口的 DNAT。具体后端并不保证都使用完全一样的链名。
11.5 禁用用户态代理实验
下面的方法不会关闭宿主机代理,只会给容器传一个普通环境变量:
# 错误思路示范
docker run -P --env DOCKER_PROXY=false nginx
真正的 daemon 设置为:
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://82ue03hk.mirror.aliyuncs.com"
],
"userland-proxy": false
}
systemctl restart docker
docker run -d -P nginx
docker port compassionate_elbakyan
netstat -tnulp | grep docker
iptables -vnL DOCKER
iptables -t nat -vnL DOCKER
原记录:
98ac63968f90 compassionate_elbakyan 0.0.0.0:32768->80/tcp
80/tcp -> 0.0.0.0:32768
tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN 7456/dockerd
# filter DOCKER
ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:80
DROP all -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0
# nat DOCKER
DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:32768 to:172.17.0.2:80
有些版本 daemon 仍可能保留绑定 socket,这不表示用户态转发仍在执行;检查配置、进程和实际连接路径。docker info 不一定显示 userland-proxy,不能凭没有字段判断配置没生效。禁用的兼容性和性能收益需按具体连接场景验证,不能保证普遍提速。
恢复时从合并配置中移除该键或恢复原值,再:
systemctl restart docker
11.6 hosts 配置
docker run -d nginx
docker exec practical_mendel cat /etc/hosts
原容器 ID b4fc910af29a,hosts:
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 b4fc910af29a
默认主机名常为短 ID,也可通过参数配置。宿主机的任意 /etc/hosts 条目不会自动全部传入容器,修改容器 hosts 也不等于修改宿主机 hosts。
docker run --add-host myserver:10.0.0.100 -d nginx
docker exec unruffled_lovelace cat /etc/hosts
原新增项:
10.0.0.100 myserver
172.17.0.2 e1ef9f794280
多个条目:
docker run --add-host myserver1:10.0.0.100 \
--add-host myserver2:10.0.0.101 -d nginx
docker exec adoring_shirley cat /etc/hosts
10.0.0.100 myserver1
10.0.0.101 myserver2
172.17.0.3 ffd55c3ba78e
11.7 resolv.conf、--dns 与全局 DNS
原默认 bridge 的宿主机和容器示例:
cat /etc/resolv.conf
docker exec adoring_shirley cat /etc/resolv.conf
# 宿主机:Generated by NetworkManager
nameserver 10.0.0.2
# 容器:Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it has been modified.
nameserver 10.0.0.2
# Based on host file: '/etc/resolv.conf' (legacy)
# Overrides: []
自定义 bridge 常使用 Docker 内置 DNS(如 127.0.0.11),而非简单把主机 resolv.conf 原样复制。DNS 服务器的回退、轮换与查询行为由实际 resolver、选项和网络决定,不是一律第一个严格主、后面严格备用。
容器内部执行重定向需要由容器 Shell 处理:
docker exec adoring_shirley bash -c 'echo "nameserver 8.8.8.8" >> /etc/resolv.conf'
docker exec adoring_shirley cat /etc/resolv.conf
若把 >> /etc/resolv.conf 放在 docker exec 的外部,它会由宿主机 Shell 重定向。上面被单引号保护的命令不会正常地修改宿主机文件;原稿随后展示宿主机也多了 8.8.8.8,与该命令本身不一致,不能当作预期结果。
原稿记录容器 stop/start 后附加项消失;但同一原输出也写明手工修改后不再自动管理。不同版本和网络情况下的维护行为不应笼统概括为“修改后必定每次重启覆盖”。持久配置 DNS 应使用 --dns 或 daemon 配置,不依赖手工改生成文件。
docker stop adoring_shirley
docker start adoring_shirley
docker exec adoring_shirley cat /etc/resolv.conf
原重启后记录为 nameserver 10.0.0.2,属于原实验观察。
容器级 DNS:
docker run --dns 8.8.8.8 --dns 2.2.2.2 -d nginx
docker exec admiring_varahamihira cat /etc/resolv.conf
原结果:
nameserver 8.8.8.8
nameserver 2.2.2.2
# Overrides: [nameservers]
2.2.2.2 仅为原实验配置值,不能因成功写入就认定它是可用公共 DNS。实际应使用所在网络允许且经验证可达的 resolver。
全局默认示例:
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://82ue03hk.mirror.aliyuncs.com"
],
"dns": ["114.114.114.114", "8.8.8.8"]
}
systemctl restart docker
docker run -d nginx:latest
docker exec adoring_stonebraker cat /etc/resolv.conf
原新容器 86d185a044ec:
nameserver 114.114.114.114
nameserver 8.8.8.8
# Overrides: [nameservers]
--dns 可以覆盖 daemon 的默认:
docker run -d --dns 2.2.2.2 nginx:latest
docker exec brave_newton grep nameserver /etc/resolv.conf
原容器 6695e181c69d 显示 nameserver 2.2.2.2。这只验证配置选择,不验证外部域名解析成功;更改 daemon 默认不等于自动改写所有已有容器的配置。
12. 文件复制与时区
12.1 cp 的路径与属性
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
至少一端是容器,另一端是本地文件系统;单次 cp 不是直接在两个容器间复制。可复制文件和目录,容器不必一直运行。-L 跟随源端软链接;-a 主要保留源 UID/GID 等归档属性,不保证 ACL、扩展属性、inode、ctime、创建时间全部不变。
原稿频繁使用 rm -rf /tmp/* 清空环境,下面改为独立实验目录,避免删除无关临时文件。
docker run -d --name zealous_hofstadter nginx:latest
docker exec zealous_hofstadter bash -c 'mkdir -p /data/server/dir{1..3}'
mkdir -p /tmp/docker-copy-lab
12.2 容器目录复制到宿主机
docker cp zealous_hofstadter:/data/server/ /tmp/docker-copy-lab/
ls /tmp/docker-copy-lab
ls /tmp/docker-copy-lab/server
与原稿相同的目录内容:
Successfully copied 3.07kB ...
server
dir1 dir2 dir3
目标目录已存在时,源尾部普通 / 不会自动变成“只复制目录内容”:
docker cp zealous_hofstadter:/data/server /tmp/docker-copy-lab
结果仍为目标下的 server。只复制源目录内容应明确使用:
docker cp zealous_hofstadter:/data/server/. /tmp/docker-copy-lab/
12.3 单文件与重命名
docker cp zealous_hofstadter:/etc/hosts /tmp/docker-copy-lab/
docker cp zealous_hofstadter:/etc/hosts /tmp/docker-copy-lab/hosts-con
ls /tmp/docker-copy-lab
原稿每次显示 Successfully copied 2.05kB,得到 hosts、hosts-con。该传输大小包含归档开销,不必等于 hosts 实际文件字节数。
12.4 -a、时间戳和文件所有者
docker exec zealous_hofstadter stat /etc/hosts
docker cp -a zealous_hofstadter:/etc/hosts /tmp/docker-copy-lab/hosts-attr
stat /tmp/docker-copy-lab/hosts-attr
原 stat 记录对比(路径仍按原记录):
| 属性 | 容器 /etc/hosts |
宿主机 /tmp/hosts-attr |
|---|---|---|
| Size | 172 | 172 |
| Blocks / IO Block | 8 / 4096 | 8 / 4096 |
| Mode | 0644,普通文件 | 0644,普通文件 |
| UID/GID | 0/0 | 0/0 |
| Device / Inode | 253,0 / 68342191 | fd00h/64768d / 136526159 |
| Links | 1 | 1 |
| Access | 2025-07-10 07:25:43.617413992 +0000 | 2025-07-10 15:15:20.000000000 +0800 |
| Modify | 2025-07-10 07:15:20.810039686 +0000 | 2025-07-10 15:15:20.000000000 +0800 |
| Change | 2025-07-10 07:25:43.616413969 +0000 | 2025-07-10 15:31:50.313844129 +0800 |
| Birth | 2025-07-10 07:15:20.693036971 +0000 | 2025-07-10 15:31:50.313844129 +0800 |
这组数据不是所有时间戳完全相同:mtime 到秒的时刻对应,但样本精度不同;atime、ctime、birth 也不同。时区只是显示方式,不会导致 UID/GID 或文件内容复制失效。ctime/创建时间不能通常作为普通归档属性任意恢复。
docker exec zealous_hofstadter ls -l /etc/localtime
ls -l /etc/localtime
原时区链接:容器 /usr/share/zoneinfo/Etc/UTC,宿主机 ../usr/share/zoneinfo/Asia/Shanghai。这些解释了显示时差,不能据此得出“容器时间机制使 -a 失效”。
12.5 宿主机到容器
docker run -d --name naughty_engelbart nginx:latest
docker cp /etc/hosts naughty_engelbart:/tmp/
docker exec naughty_engelbart cat /tmp/hosts
mkdir -p /tmp/docker-copy-lab/server/dir{1..3}
docker cp /tmp/docker-copy-lab/server naughty_engelbart:/tmp/
docker exec naughty_engelbart ls /tmp/
docker exec naughty_engelbart ls /tmp/server
原 hosts 内容:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
原目录输出为 hosts、server,server 下为 dir1、dir2、dir3。
12.6 UID/GID 的数字与名称
原宿主机已有实验用户 sswang,UID/GID 为 1000,加入 wheel:
touch /tmp/docker-copy-lab/sswang.txt
chown sswang:sswang /tmp/docker-copy-lab/sswang.txt
id sswang
docker cp -a /tmp/docker-copy-lab/sswang.txt naughty_engelbart:/tmp/
docker exec naughty_engelbart ls -l /tmp/sswang.txt
用户id=1000(sswang) 组id=1000(sswang) 组=1000(sswang),10(wheel)
-rw-r--r-- 1 1000 1000 0 Jul 10 08:40 /tmp/sswang.txt
文件保存的是数字 UID/GID;本地名称服务没有相应条目时显示数字。有对应数字但名字不同则显示该端解析出的名字,并不会为了同名用户自动重映射。
docker exec naughty_engelbart grep nginx /etc/passwd
# 原样本:nginx:x:101:101:nginx user:/nonexistent:/bin/false
getent passwd 101
docker exec naughty_engelbart touch /tmp/nginx.txt
docker exec naughty_engelbart chown nginx:nginx /tmp/nginx.txt
docker exec naughty_engelbart chmod +x /tmp/nginx.txt
docker exec naughty_engelbart ls -l /tmp/nginx.txt
docker cp naughty_engelbart:/tmp/nginx.txt /tmp/docker-copy-lab/
ls -l /tmp/docker-copy-lab/nginx.txt
原容器内为:
-rwxr-xr-x 1 nginx nginx 0 Jul 10 08:48 /tmp/nginx.txt
原宿主机以 root 执行不带 -a的复制后:
-rwxr-xr-x 1 root root 0 Jul 10 16:48 /tmp/nginx.txt
默认复制到宿主机时通常归属于执行命令的用户;通过 sudo 默认以 root 执行,得到的仍为 root:
su - sswang
sudo docker cp naughty_engelbart:/tmp/nginx.txt /tmp/nginx-sswang.txt
ls -l /tmp/nginx-sswang.txt
原输出同样为 root root。需要保留源数字所有者时使用 -a 并具备相应权限;userns-remap/rootless 下还需考虑用户映射。
12.7 TZ 与本地时间文件
TZ 影响支持该变量的进程如何显示时间,不修改宿主机系统时钟,不一定改写容器里的 /etc/localtime、/etc/timezone。还需镜像内存在相应 zoneinfo 或运行库支持。
docker run -d --name silly_leakey -e TZ=Asia/Shanghai nginx
docker exec silly_leakey printenv TZ
docker exec silly_leakey ls -l /etc/localtime
docker exec silly_leakey cat /etc/timezone
原结果:
Asia/Shanghai
/etc/localtime -> /usr/share/zoneinfo/Etc/UTC
Etc/UTC
可以绑定宿主机时区文件:
docker run -d --name angry_kilby \
-v /etc/localtime:/etc/localtime:ro -e TZ=Asia/Shanghai nginx
docker exec angry_kilby ls -l /etc/localtime
date
docker exec angry_kilby date
docker exec angry_kilby md5sum /etc/localtime
md5sum /etc/localtime
docker exec angry_kilby printenv TZ
原时间和摘要:
2025年 07月 10日 星期四 16:06:40 CST
Thu Jul 10 16:06:40 CST 2025
09dd479d2f22832ce98c27c4db7ab97c /etc/localtime
09dd479d2f22832ce98c27c4db7ab97c /etc/localtime
Asia/Shanghai
ls -l 仍可能显示镜像原符号链接名,需结合挂载和实际读取内容理解。相同摘要表示内容相同,不表示是同一个 inode;这里调整的是时区表现,不是另给容器进行系统时钟同步。
13. 清理、export/import 与 commit
13.1 系统清理
正确命令为 docker system prune,原文 purge 为错误命令名。
docker system prune
docker system df
docker system prune -a
默认清理停止容器、无容器使用的网络、悬空镜像和未使用构建缓存;默认不清理卷。-a 扩大到所有未被容器引用的镜像;--volumes 在现代常见版本中另清理未使用匿名卷,并非无条件删除一切具名卷;-f 只跳过确认,不杀运行容器。
原默认清理交互:
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
ufaaz35mq69lnxqz7nbevigo1
6tc9414h9feyp6mxn67wwzk08
Total reclaimed space: 19B
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 9 1 2.838GB 2.721GB (95%)
Containers 1 1 1.253kB 0B (0%)
Local Volumes 7 0 1.134GB 1.134GB (100%)
Build Cache 1 0 0B 0B
-a 的镜像警告改成 all images without at least one container associated to them。原清理后还剩运行中的 naughty_engelbart,引用的 Nginx 镜像 9592f5595f2b、192 MB。
13.2 准备镜像环境
ls ./*.tar
for archive in ./*.tar; do
[ -f "$archive" ] || continue
docker load -i "$archive"
done
/bin/bash get_ali_images.sh -a
docker images
get_ali_images.sh 是配套脚本,PDF 未包含实现。原归档为 alpine.tar、busybox.tar、debian.tar、multiple_images.tar、nginx.tar、ubuntu.tar。
原镜像表:
REPOSITORY TAG IMAGE ID CREATED SIZE
debian latest b3a422523a11 10 days ago 117MB
nginx latest 9592f5595f2b 2 weeks ago 192MB
wordpress php8.4-apache d65e5ede33da 4 weeks ago 715MB
alpine latest cea2ff433c61 5 weeks ago 8.31MB
redis latest ed3a2af6d0d4 5 weeks ago 128MB
ubuntu latest bf16bdcff9c9 6 weeks ago 78.1MB
mysql oraclelinux9 6f11f923fa04 2 months ago 859MB
busybox latest 6d3e4188a38a 9 months ago 4.28MB
13.3 命令模型
| 方法 | 对象/内容 | 保留范围 |
|---|---|---|
| export | 容器根文件系统 tar | 无镜像历史、默认入口和环境配置,不保存内存,也不导出挂载卷数据 |
| import | 从根文件系统 tar、URL 或 stdin 创建镜像 | 新的扁平层,可指定名称/标签,并通过支持的 change 设置配置 |
| commit | 从容器可写文件系统变化生成镜像 | 保留/修改镜像配置,不含内存、挂载卷内容及全部 HostConfig |
| save/load | 保存/加载镜像归档 | 镜像层、配置和归档中保存的引用,不等于迁移完整运行容器 |
归档大小由内容、历史层和压缩决定,不能保证 export 永远小于 save。完整业务迁移还需卷、绑定目录、运行参数、网络及外部依赖。
docker export -o container_fs.tar my_container
docker export my_container | gzip > container_fs.tar.gz
docker import container_fs.tar my_new_image:latest
gunzip -c container_fs.tar.gz | docker import - my_new_image:latest
docker export [OPTIONS] CONTAINER
docker import [OPTIONS] FILE|URL|- [REPOSITORY[:TAG]]
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
commit 的 -a 设置镜像作者元数据,-m 设置说明,-p 默认暂停容器以减少提交过程变化,-c/--change 修改支持的镜像配置。暂停不等于保证外部数据库或多服务事务一致性。
手工 commit 难以复现,多次提交会增加层,密码或密钥可能被封进镜像;可重复交付通常优先 Dockerfile。
13.4 自定义 Nginx 文件系统
docker run -d --name determined_hoover nginx:latest
dcn determined_hoover
docker exec -it determined_hoover bash
容器内:
ls /usr/share/nginx/html
history -c
echo 'Nginx Web Page by sswang' > /usr/share/nginx/html/index.html
exit
原目录为 50x.html、index.html。清除当前交互历史不是清理镜像内全部敏感信息的保证。
curl 172.17.0.2
Nginx Web Page by sswang
13.5 export/import 实验
docker export -o my_nginx.tar determined_hoover
ls -lh my_nginx.tar
tar -tvf my_nginx.tar | head -n5
docker import my_nginx.tar mynginx:v1
docker images mynginx
docker history mynginx:v1
原结果:
-rw------- 1 root root 186M Jul 10 17:55 my_nginx.tar
-rwxr-xr-x 0/0 0 2025-07-10 17:45 .dockerenv
lrwxrwxrwx 0/0 0 2025-06-30 08:00 bin -> usr/bin
drwxr-xr-x 0/0 0 2025-05-09 22:50 boot/
drwxr-xr-x 0/0 0 2025-07-10 17:45 dev/
-rwxr-xr-x 0/0 0 2025-07-10 17:45 dev/console
sha256:2ad366d4f19db4704a6278dbd78817990ef24acf756ec1e0cff1ed331822a716
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx v1 2ad366d4f19d 23 seconds ago 191MB
IMAGE CREATED CREATED BY SIZE COMMENT
2ad366d4f19d 44 seconds ago 191MB Imported from -
镜像没有默认命令时:
docker run -d --name mynginx mynginx:v1
docker: Error response from daemon: no command specified
Run 'docker run --help' for more information
显式传入入口命令:
docker run -d --name mynginx mynginx:v1 nginx -g 'daemon off;'
dcn mynginx
curl 172.17.0.3
docker rm -f mynginx
原新容器 9625dc6cd579,IP 为 172.17.0.3,返回 Nginx Web Page by sswang。原列表中它的 PORTS 为空,但程序依然能监听 80;EXPOSE 元数据不是程序能否监听的开关。
13.6 commit 实验
docker exec determined_hoover bash -c "echo 'Nginx Web Page by sswang with commit' > /usr/share/nginx/html/index.html"
docker commit -a 'sswang' -m 'new image' determined_hoover mynginx:v2
docker images mynginx
docker run -d --name mynginx mynginx:v2
dcn mynginx
curl 172.17.0.3
原结果:
sha256:7e1858cc3051d2078f765d2692085b41bffc341e6394bf1447e4559d6b47d2fa
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx v2 7e1858cc3051 7 seconds ago 192MB
mynginx v1 2ad366d4f19d 29 minutes ago 191MB
Nginx Web Page by sswang with commit
新容器 88b6afcf528c 可以继承源 Nginx 镜像的入口配置运行。卷数据、端口发布和全部宿主机运行设置仍不在 commit 中。
docker rm -f mynginx determined_hoover
14. CRIU 检查点与跨主机恢复实验
14.1 能力与边界
CRIU(Checkpoint/Restore In Userspace)可以冻结 Linux 进程,将内存、进程关系、打开文件及部分内核状态保存到检查点,再在兼容条件下恢复。它起源于 Virtuozzo 相关项目,并与 OpenVZ、LXC/LXD、Podman、Docker 等生态存在集成。

参考:https://criu.org/Main_Page、https://docs.docker.com/reference/cli/docker/checkpoint/。
Docker checkpoint 属于实验能力,支持范围与 Engine、runtime、CRIU、内核、权限、CPU 和应用使用的资源有关。下面不是保证服务零中断的通用热迁移流程:原实验创建检查点后源容器停止,过几分钟才在目标恢复。
检查点不自动包含完整镜像、容器可写层和挂载卷数据;目标还需相同镜像、兼容运行参数,以及一致的文件、挂载和网络环境。网络连接、设备、外部资源和分布式数据一致性不能靠复制一个目录就自动恢复。
Kubernetes 通常在另一节点重建 Pod,不是默认冻结内存再在另一节点恢复;Swarm 的重新调度通常也创建新的任务,而非透明迁移原进程内存。高可用常需副本、负载均衡和应用层恢复,不能把普通重新调度与 CRIU 热迁移混为一谈。
14.2 Docker 接口

docker checkpoint create CONTAINER CHECKPOINT
docker checkpoint ls CONTAINER
docker checkpoint rm CONTAINER CHECKPOINT
docker start --checkpoint CHECKPOINT CONTAINER
原稿的 docker checkpoint restore 不是有效子命令,恢复通过 docker start --checkpoint 完成。
典型流程为:准备兼容环境与文件系统 → 创建检查点 → 传输检查点及必需数据 → 在目标创建等价容器 → 从检查点启动 → 验证状态、网络与数据。灾难恢复也必须有可用备份/检查点和外部状态,不能在源主机已损坏后凭空提取状态。
14.3 三台实验主机
| 角色 | 原主机 | 原版本记录 |
|---|---|---|
| 源 | Rocky1,10.0.0.12 | CRIU 3.19,Rocky 9 系列内核 |
| 目标一 | Ubuntu,10.0.0.13 | CRIU 4.1,6.11.0-28-generic |
| 目标二 | Rocky2,10.0.0.15 | CRIU 3.19,5.14.0-427.13.1.el9_4.x86_64 |
原稿部分 Rocky1 提示符误写 ubuntu24-13,应按角色/地址理解。原结果不能证明任意 Rocky 与 Ubuntu 组合都可以恢复。
Rocky1 / Rocky2 的 CRIU
两台 Rocky 分别准备:
yum install -y criu
criu --version
criu check
grep CHECKPOINT "/boot/config-$(uname -r)"
原输出:
Version: 3.19
Warn (criu/kerndat.c:1593): CRIU was built without libnftables support
Looks good.
CONFIG_CHECKPOINT_RESTORE=y
原软件来源为其启用的 Rocky 软件源;并非所有桌面版都已经安装,也不能不看仓库状态就承诺有相同版本。Looks good 检查基础能力,不保证某个真实应用可完整恢复;没有 libnftables 支持提示相关功能缺失,是否影响场景需要具体测试,不能保证“基本端口映射一定无影响”。
Rocky 的 Docker 安装入口:
yum install -y yum-utils
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache
yum install -y docker-ce docker-ce-cli containerd.io
Ubuntu 的 CRIU
原稿使用 PPA 获取 CRIU,实际先检查发行版包是否满足要求;第三方源还需匹配 Ubuntu 版本。
add-apt-repository ppa:criu/ppa -y
apt update
apt install criu
criu --version
criu check
grep CHECKPOINT "/boot/config-$(uname -r)"
原记录:
Version: 4.1
Looks good.
CONFIG_CHECKPOINT_RESTORE=y
Ubuntu Docker 安装入口:
apt-get update
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io
三台主机启用实验功能
在各自主机合并配置:
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://82ue03hk.mirror.aliyuncs.com"
],
"experimental": true
}
systemctl restart docker
docker checkpoint
原未开启时:
docker checkpoint is only supported on a Docker daemon with experimental features enabled
开启后帮助:
Usage: docker checkpoint COMMAND
Manage checkpoints
Commands:
create Create a checkpoint from a running container
ls List checkpoints for a container
rm Remove a checkpoint
14.4 源主机创建计数容器
三台主机需使用同一 BusyBox 镜像;原实验已通过 docker save 得到 busybox.tar。
源 Rocky1:
docker load -i busybox.tar
docker run --name cr -d --network host --security-opt seccomp=unconfined \
busybox /bin/sh -c 'i=0; while true; do echo "$i $(date +%F_%T)"; i=$((i + 1)); sleep 1; done'
docker ps
docker logs cr
这里保留原实验的 host 网络和关闭 seccomp 设置,用于减少该受控恢复实验的约束,不代表业务容器默认应该如此设置。host 网络仍需与目标主机资源兼容。
原日志:
0 2025-07-15_02:17:51
1 2025-07-15_02:17:52
2 2025-07-15_02:17:53
3 2025-07-15_02:17:54
4 2025-07-15_02:17:55
...
原源码写法用 expr 和反引号,本文改为等价 Shell 算术/命令替换。源容器 ID 在原不同截图出现 f1a17acae68f、f3bc052e27b2,显然来自不同轮演示,不能把所有 ID 当同一次执行。
14.5 两个目标主机创建等价容器
Ubuntu 与 Rocky2 分别执行:
scp root@10.0.0.12:busybox.tar ./
docker load -i busybox.tar
docker images
docker create --name cr --network host --security-opt seccomp=unconfined \
busybox /bin/sh -c 'i=0; while true; do echo "$i $(date +%F_%T)"; i=$((i + 1)); sleep 1; done'
docker ps -a
原加载记录:
65014c70e84b: Loading layer 4.516MB/4.516MB
Loaded image: busybox:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 6d3e4188a38a 9 months ago 4.28MB
原目标容器:
# Ubuntu
917ded84818e busybox "/bin/sh -c 'i=0; wh…" Created cr
# Rocky2
c4afb52c5b8b busybox "/bin/sh -c 'i=0; wh…" Created cr
源和目标的镜像、启动命令及会影响恢复的配置应一致;不要在目标先普通 start 使其从零运行。
14.6 创建并检查检查点
源 Rocky1:
mkdir -p /data/server/srv
docker checkpoint create --checkpoint-dir /data/server/srv cr checkpoint1
ls /data/server/srv
du -sh /data/server/srv
ls /data/server/srv/checkpoint1
docker checkpoint ls cr
docker checkpoint ls --checkpoint-dir /data/server/srv cr
docker ps -a
docker logs cr | tail -n2
原数据量 408K,自定义目录下为 checkpoint1。文件清单包含:
cgroup.img core-1.img core-624.img descriptors.json fdinfo-2.img fdinfo-3.img
files.img fs-1.img fs-624.img ids-1.img ids-624.img inventory.img ipcns-var-11.img
mm-1.img mm-624.img mountpoints-13.img pagemap-1.img pagemap-624.img
pages-1.img pages-2.img pstree.img seccomp.img timens-0.img
utsns-12.img tmpfs-dev-50.tar.gz.img tmpfs-dev-53.tar.gz.img
tmpfs-dev-54.tar.gz.img tmpfs-dev-55.tar.gz.img tmpfs-dev-56.tar.gz.img
tmpfs-dev-57.tar.gz.img
默认路径通常为 /var/lib/docker/containers/<ID>/checkpoints/<名称>;创建在自定义 checkpoint-dir 后,查询也需指明同一目录。原默认 checkpoint ls cr 只显示表头,自定义路径查询显示 checkpoint1。
默认创建完成后源容器停止,原记录:
cr Exited (137) 38 seconds ago
23 2025-07-15_02:18:15
24 2025-07-15_02:18:16
该 137 在这里属于原 checkpoint 终止行为记录,不能只凭退出码认定发生 OOM。某些版本支持 leave-running 参数,但保留源运行会产生状态继续变化和双实例问题,不自动构成安全迁移。
14.7 Ubuntu 恢复
原稿把检查点手工复制到目标容器内部目录;为避免依赖固定容器 ID,下面使用 Docker 支持的外部 checkpoint-dir 参数表达同一流程:
mkdir -p /data/server/srv
scp -r root@10.0.0.12:/data/server/srv/checkpoint1 /data/server/srv/
du -sh /data/server/srv/checkpoint1
docker checkpoint ls --checkpoint-dir /data/server/srv cr
docker start --checkpoint-dir /data/server/srv --checkpoint checkpoint1 cr
docker logs cr
原内部目录为:
/var/lib/docker/containers/917ded84818e4e6566c1700cadc0239a6477cef6d4c07c70ff93aea4c197c45b/checkpoints/
原复制后的大小 408K,列表含 checkpoint1;原恢复日志:
25 2025-07-15_02:21:35
26 2025-07-15_02:21:36
27 2025-07-15_02:21:37
28 2025-07-15_02:21:38
29 2025-07-15_02:21:39
30 2025-07-15_02:21:40
计数继续从 25 开始体现了进程状态恢复;时间重新读取当前时钟,不能把中间三分多钟的缺口称为没有停机。以上为课件成功记录,未在本次整理中重做实验。
14.8 Rocky2 恢复
在 Rocky2 使用等价的外部目录流程:
mkdir -p /data/server/srv
scp -r root@10.0.0.12:/data/server/srv/checkpoint1 /data/server/srv/
docker checkpoint ls --checkpoint-dir /data/server/srv cr
docker start --checkpoint-dir /data/server/srv --checkpoint checkpoint1 cr
docker ps
docker logs cr
原稿该节在复制后漏写恢复命令,本文已补上 docker start --checkpoint ...。原内部路径:
/var/lib/docker/containers/c4afb52c5b8b907409826424fe7034f93ae07d8617772f3514d731687a7f9722/checkpoints/
原列表 cr 为 Up 2 seconds,日志:
25 2025-07-15_02:23:04
26 2025-07-15_02:23:05
27 2025-07-15_02:23:06
28 2025-07-15_02:23:07
29 2025-07-15_02:23:08
30 2025-07-15_02:23:09
31 2025-07-15_02:23:10
32 2025-07-15_02:23:11
同时在两个目标恢复同一个源进程是课堂验证,不是有状态业务的生产切换方案。
14.9 网络 namespace 错误
原非 host 网络/不兼容环境出现:
Error response from daemon: bind-mount /proc/0/ns/net ->
/var/run/docker/netns/9aef6091180e: no such file or directory
另一轮路径末尾为 c5fa75f5cf66。查询:
journalctl -u docker.service | tail -n20
原错误日志的关键内容:
2025-07-15T10:09:53.643257910+08:00 level=error msg="copy stream failed" error="reading from a closed fifo" stream=stdout
2025-07-15T10:09:53.643329889+08:00 level=error msg="copy stream failed" error="reading from a closed fifo" stream=stderr
2025-07-15T10:09:53.655749638+08:00 level=info msg="ignoring event"
container=fed1cee27ad98bae4440eb67db809c4ed52faf449eb3beaaa1fc9efdaaf74453
module=libcontainerd namespace=moby topic=/tasks/delete type="*events.TaskDelete"
2025-07-15T10:09:53.685333795+08:00 level=error
msg="Handler for POST /v1.51/containers/cr/start returned error: bind-mount /proc/0/ns/net -> /var/run/docker/netns/9aef6091180e: no such file or directory"
/proc/0/ns/net 不存在,表示恢复流程没得到可用的网络 namespace 所属进程等状态;这可能涉及 Docker/runtime/CRIU 兼容性及网络恢复实现,不能仅凭它断言“换 CRIU 版本必然解决”或“原 namespace 必须始终保持不变”。应检查 CRIU 日志、进程 PID 和完整版本组合。
原稿建议 host 网络以绕过该实验的一部分网络 namespace 限制,但 host 不是所有应用恢复问题的通用修复。
原稿另一条手工尝试的思路为读取:
CONTAINER_ID=$(docker inspect -f '{{.Id}}' cr)
NS_PATH=$(docker inspect -f '{{.NetworkSettings.SandboxKey}}' cr)
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' cr)
然后把 /proc/<PID>/ns/net 链接到 netns 目录。对尚未运行/已停止容器,PID 常是 0,此链接不会重建任何 namespace;符号链接也不会创建或恢复网络状态。原稿通过 cut 固定取路径段、再 ln 到不存在 PID 的方案不能作为可执行修复,因此这里不提供一个假称有效的恢复命令。应使用被当前运行时支持并验证过的恢复流程。
15. 手工制作镜像的进一步实践
15.1 主进程必须正确前台运行
容器生命周期由其主进程决定。应用自行 daemonize 后,如果启动它的 PID 1 随即退出,容器也会停止。后台运行并非容器内完全不允许,而是要有正确的主进程/监督机制负责生命周期、信号和退出状态。
Apache 的前台参数示例,二进制路径和启动环境应匹配镜像:
ENTRYPOINT ["httpd"]
CMD ["-D", "FOREGROUND"]
Nginx:
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
HAProxy 启动脚本可以使用:
#!/bin/bash
exec haproxy -db -f /etc/haproxy/haproxy.cfg
CMD ["run_haproxy.sh"]
脚本必须放在 PATH 中并可执行,或使用绝对路径。原稿用 tail -f /etc/hosts、top 等作为保活进程,这只能让容器留在 running,不能证明应用健康,也可能使应用退出后容器仍存活;生产服务应前台 exec 真正应用或使用明确的监督机制。
镜像生命周期为 build → tag → push/pull → run → save/load → 更新/淘汰。镜像更新需要重新构建、分发及替换容器,不会自动改变已有容器文件系统。
docker tag myimage:latest username/myimage:v1.0
docker push username/myimage:v1.0
docker pull username/myimage:v1.0
docker run -p 80:80 nginx
docker save -o backup.tar myimage:latest
docker load -i backup.tar
docker rmi myimage:latest
推送前需要合法账号和目标仓库权限,示例这里只转写命令。
15.2 commit 的名称与标签实验
commit 可用于运行或停止的容器,保存文件系统变化及镜像配置;不会保存进程内存、挂载卷或所有宿主机运行参数。-c/--change 可以修改受支持的镜像指令配置。
docker run -d --name loving_chaum nginx:latest
dcn loving_chaum
docker exec loving_chaum bash -c "echo 'Nginx Web Page by sswang' > /usr/share/nginx/html/index.html"
curl 172.17.0.2
原源容器 4fad98b2fa48,初始返回 Nginx Web Page by sswang。
不指定目标镜像名:
docker commit -a 'sswang' -m 'new image' loving_chaum
docker images
原结果:
sha256:81a1bc8dd2f69893c1f7f7c092424a2590b480069d0a6c9b2b3001117993e33e
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 81a1bc8dd2f6 44 seconds ago 192MB
指定名称、不写 tag:
docker exec loving_chaum bash -c "echo 'Nginx Web Page by sswang with commit-1' > /usr/share/nginx/html/index.html"
docker commit -a 'sswang' -m 'new image' loving_chaum mynginx
docker images
原结果:
sha256:0942a94177dd47854fdc1adc4744ae73f9ea2f63cf415b13ae6206ef073007b3
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx latest 0942a94177dd 4 seconds ago 192MB
<none> <none> 81a1bc8dd2f6 2 minutes ago 192MB
显式名称与版本:
docker exec loving_chaum bash -c "echo 'Nginx Web Page by sswang with commit-2' > /usr/share/nginx/html/index.html"
docker commit -a 'sswang' -m 'new image' loving_chaum mynginx:v2
docker images
sha256:1124dae0cf712ae4f8fb8adc3f92eb8ad2af5bc12abae313a1d8ca1d793833c1
REPOSITORY TAG IMAGE ID CREATED SIZE
mynginx v2 1124dae0cf71 4 seconds ago 192MB
mynginx latest 0942a94177dd About a minute ago 192MB
<none> <none> 81a1bc8dd2f6 4 minutes ago 192MB
这些提交都来自 loving_chaum 的当前状态;命名并不自动把容器切换为基于刚提交的新镜像。
15.3 运行三个版本
docker run --name mynginx1 -d 81a1bc8dd2f6
dcn mynginx1
curl 172.17.0.3
docker run --name mynginx2 -d mynginx
dcn mynginx2
curl 172.17.0.4
docker run --name mynginx3 -d mynginx:v2
dcn mynginx3
curl 172.17.0.5
原结果对应:
| 容器 | 引用 | 原 IP | 原响应 |
|---|---|---|---|
| mynginx1 | 无标签镜像 ID | 172.17.0.3 | Nginx Web Page by sswang |
| mynginx2 | mynginx,即 latest | 172.17.0.4 | Nginx Web Page by sswang with commit-1 |
| mynginx3 | mynginx:v2 | 172.17.0.5 | Nginx Web Page by sswang with commit-2 |
镜像即使没有 tag 仍可按 ID 使用。实际 IP 和 ID 应重新查询,不把原稿值当固定分配。
15.4 Ubuntu 中安装 Nginx 并提交
初始环境
清理上一组实验容器后:
docker images ubuntu
docker run -it -p 80 --name nginx_ubuntu ubuntu bash
-p 80 表示容器 80 发布到自动分配的宿主机端口,不是固定占用宿主机 80。原镜像 bf16bdcff9c9、78.1 MB。
容器内:
cat /etc/os-release
ls -l /etc/apt/sources.list
cat /etc/apt/sources.list
grep -v '^#' /etc/apt/sources.list.d/ubuntu.sources
原系统记录:
PRETTY_NAME="Ubuntu 24.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.2 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
原 sources.list 仅提示实际源已经迁移到 deb822 文件 sources.list.d/ubuntu.sources,原源内容:
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
更换软件源和安装
sed -i 's/archive\.ubuntu\.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/ubuntu.sources
sed -i 's/security\.ubuntu\.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/ubuntu.sources
apt update
apt install -y nginx
nginx -v
grep include /etc/nginx/nginx.conf
grep root /etc/nginx/sites-enabled/default | grep -v '#'
echo 'Nginx Web Page by sswang' > /var/www/html/index.nginx-debian.html
nginx -g 'daemon off;'
中科大镜像资料:https://mirrors.ustc.edu.cn/。如果需要用传统 sources.list 重写源,要先明确禁用/替换现有重复条目,不要无条件同时配置两套。
原 Nginx 版本 nginx/1.24.0 (Ubuntu),配置 include:
include /etc/nginx/modules-enabled/*.conf;
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
root /var/www/html;
容器常不包含 systemd/systemctl;安装软件后是否已启动由包维护脚本和容器策略决定。若已启动,应先确认进程和端口,避免重复启动 Nginx。
第一次提交
保持容器运行,在另一宿主机终端:
dps
dcn nginx_ubuntu
curl 172.17.0.2
docker commit -m 'ubuntu-nginx' -a 'sswang' nginx_ubuntu ubuntu_nginx:v1
docker run --name nginx -d ubuntu_nginx:v1 nginx -g 'daemon off;'
dcn nginx
curl 172.17.0.3
原发布端口为 0.0.0.0:32768->80/tcp,首页返回 Nginx Web Page by sswang。原提交 ID:
sha256:e9b18e63cd632ff0a509a03993ceef81f24474795d3f1e750ebe3e379e5868c8
这里必须指定 Nginx 命令的原因是源容器的默认命令原为 bash,交互中手动启动 nginx 不会自动变成镜像默认入口;不是 commit 一概不保存启动配置。
设置默认入口再提交
docker commit -a 'sswang' -m 'new image' \
-c 'ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]' \
-c 'CMD []' nginx_ubuntu ubuntu_nginx:v2
docker history ubuntu_nginx:v2
docker inspect --format '{{.Config.Entrypoint}}' ubuntu_nginx:v2
docker inspect ubuntu_nginx:v2 | jq '.[0].Config.Entrypoint'
docker run -d --name nginx-2 ubuntu_nginx:v2
dcn nginx-2
curl 172.17.0.5
显式清空 CMD,防止不需要的原默认参数继续作为入口参数;原稿只设置 ENTRYPOINT,其提交 ID 与此修正模板不要求相同。原历史记录:
74db7f31add6 2 minutes ago bash 65.8MB new image
bf16bdcff9c9 6 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:598ca... 78.1MB
<missing> 6 weeks ago /bin/sh -c #(nop) LABEL ... 0B
<missing> 6 weeks ago /bin/sh -c #(nop) LABEL ... 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ARG RELEASE 0B
原 ENTRYPOINT 查询结果:
["/usr/sbin/nginx", "-g", "daemon off;"]
history 适合观察历史和层大小,不应单靠它确认最终运行命令;使用 inspect 的 Config。原测试容器 nginx-2 返回同一首页,实际运行时二进制路径必须存在。
15.5 Rocky 中编译 Nginx 并提交
编译准备
docker images rockylinux
docker run -it -p 80 --name nginx_rocky rockylinux:9 bash
原基础镜像 9cc24f05f309、176 MB。容器内安装默认 Nginx 模块真正需要的依赖:
yum install -y gcc make pcre2-devel zlib-devel wget tar gzip
wget https://nginx.org/download/nginx-1.29.0.tar.gz
tar xf nginx-1.29.0.tar.gz
cd nginx-1.29.0
./configure --prefix=/data/server/nginx
make && make install
echo 'Nginx Web Page from Rocky' > /data/server/nginx/html/index.html
/data/server/nginx/sbin/nginx -g 'daemon off;'
原稿还装 gcc-c++、glibc-devel、pcre/pcre-devel、openssl-devel、systemd-devel、libxml2-devel、libxslt-devel、php-gd、gd-devel 等,未启用对应可选模块,且部分包依赖额外仓库,已改为本实验的必需集合。若另启用 SSL、XSLT、image_filter 等模块,应再加入对应依赖和 configure 选项。
宿主机测试:
dcn nginx_rocky
curl 172.17.0.2
原响应为 Nginx Web Page from Rocky。回到容器终端用 Ctrl+C 停止前台 Nginx,Shell 本身仍在运行,再从源码目录上一级清理:
cd /
rm -rf nginx-1.29.0*
yum autoremove
autoremove 是否删除依赖由包管理器决定,并不自动等于卸载全部编译环境;确认动态运行依赖仍满足。
提交前台启动镜像
宿主机:
docker commit -a 'sswang' -m 'new image' \
-c 'ENTRYPOINT ["/data/server/nginx/sbin/nginx", "-g", "daemon off;"]' \
-c 'CMD []' nginx_rocky rocky_nginx:v1
docker run -d --name nginx-3 rocky_nginx:v1
dcn nginx-3
curl 172.17.0.2
原提交 ID 779073b2ad8711bff9a6a210da7d2791785278eab61a7d6544671b7e53eaa22f,新容器 7ada6229909d。原稿在不同实验轮中给 nginx_rocky 和 nginx-3 都写了 172.17.0.2;同时存活的同网段容器不会正常共享同一地址,应使用 dcn 实际结果。
启动脚本与日志
原稿脚本后台启动 Nginx,再 tail access.log 来保活,文件大小 102 字节。这个模式不能让容器生命周期跟随 Nginx 故障,信号也可能不正确传递。下面修正为准备日志输出后 exec 前台主进程:
docker exec -it nginx_rocky bash
容器内 /start.sh:
#!/bin/bash
set -e
ln -sf /dev/stdout /data/server/nginx/logs/access.log
ln -sf /dev/stderr /data/server/nginx/logs/error.log
exec /data/server/nginx/sbin/nginx -g 'daemon off;'
chmod +x /start.sh
exit
宿主机:
docker commit -a 'sswang' -m 'new image' \
-c 'ENTRYPOINT ["/start.sh"]' -c 'CMD []' nginx_rocky rocky_nginx:v2
docker images rocky_nginx
docker run -d --name nginx-4 rocky_nginx:v2
dcn nginx-4
curl 172.17.0.4
docker logs nginx-4
原脚本版本镜像表,仅作为历史记录;修正后文件和元数据不同,ID/容量不会要求相同:
REPOSITORY TAG IMAGE ID CREATED SIZE
rocky_nginx v2 9d9c02097518 12 seconds ago 563MB
rocky_nginx v1 779073b2ad87 28 minutes ago 563MB
原 nginx-4 6ad9fa3b2010,IP 172.17.0.4,首页返回 Nginx Web Page from Rocky。原 tail 日志包含旧记录:
172.17.0.1 - - [15/Jul/2025:05:58:27 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/7.76.1"
172.17.0.1 - - [15/Jul/2025:06:28:24 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/7.76.1"
172.17.0.1 - - [15/Jul/2025:06:34:21 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/7.76.1"
修正脚本将新产生日志直接写入 stdout/stderr,不再用 tail 重放镜像中已有的访问日志。实验清理仍应只针对本组容器;dka 的作用见附录。
16. Dockerfile 扩展指令
16.1 SHELL
SHELL 更改后续 Shell 形式 RUN、CMD、ENTRYPOINT 使用的解释器及参数;不改变 exec 形式自己明确选择的可执行程序。它在 Bash 特性、错误传播和 Windows PowerShell 场景中有实际作用。
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
Windows 示例:
SHELL ["powershell", "-Command"]
镜像必须提供相应 Shell。显式 exec 形式不受默认 SHELL 影响:
SHELL ["/bin/bash", "-c"]
RUN echo "Hello" | cat -
RUN ["/bin/sh", "-c", "echo 'Hello' | cat -"]
可以在阶段中多次切换:
SHELL ["/bin/bash", "-c"]
RUN echo "Using bash"
SHELL ["/bin/sh", "-c"]
RUN echo "Using sh"
pipefail 使管道中任一环节非零时管道返回相应失败,而默认通常仅取最后一项状态;它本身不等于给任意脚本自动加 set -e。
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update && apt-get install -y curl | tee /tmp/packages.log
-c 表示后面是命令字符串。RUN 最终非零会使构建步骤失败,脚本内部若显式吞掉错误,pipefail 也不能神奇检测所有逻辑故障。
16.2 MAINTAINER
MAINTAINER 是已弃用的旧指令,推荐 LABEL。原稿称 Docker 20.10 已彻底移除并不准确;某些构建器仍为兼容接受它,但不应作为新写法。
MAINTAINER John Doe <john.doe@example.com>
现代写法:
LABEL maintainer="John Doe <john.doe@example.com>"
这里是镜像元数据指令示例,不是保留文档作者介绍。
16.3 HEALTHCHECK
HEALTHCHECK [选项] CMD <命令>
禁用继承的检查:
HEALTHCHECK NONE
| 选项 | 含义 |
|---|---|
--interval |
检查间隔,常见默认 30s |
--timeout |
单次检查超时,常见默认 30s |
--start-period |
启动宽限期,期间失败通常不累计为不健康;不是禁止开始检查的固定延迟 |
--retries |
连续失败到标记 unhealthy 的阈值,常见默认 3 |
健康状态通常为 starting、healthy、unhealthy。单机 Docker 不会仅因 unhealthy 自动按 restart 策略重启;restart 主要依据进程退出。Compose 可以用健康状态控制依赖就绪等;Kubernetes 使用它自己的 probes,不能把 Dockerfile HEALTHCHECK 当成 Kubernetes 自动采用的探针。
Nginx 示例要求镜像内提供 curl:
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
EXPOSE 80
curl -f 对许多 HTTP 4xx/5xx 失败,并不是“只要不等于 200 就失败”;若必须严格 200,应在专门脚本中比较状态码。
原 MySQL 进程可达性示例,变量由容器 Shell 展开:
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
CMD mysqladmin -u root -p"$MYSQL_ROOT_PASSWORD" ping || exit 1
mysqladmin ping 主要验证服务进程能响应,某些认证失败也可能返回服务器存活;它不等于数据库账号、表和业务查询均可用。需要业务就绪时使用具备最小权限的探针账号执行实际必要查询,并按真实凭证管理方式配置。
自定义脚本:
COPY healthcheck.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/healthcheck.sh
HEALTHCHECK --interval=1m --timeout=10s \
CMD /usr/local/bin/healthcheck.sh || exit 1
原稿没有给 healthcheck.sh 正文,需提供实际检测逻辑,不能创建空脚本冒充有效检查。
16.4 STOPSIGNAL
STOPSIGNAL SIGTERM
也可以按应用需要选择 SIGINT、SIGQUIT 等。最后一次有效声明决定镜像的停止信号;容器运行参数也可覆盖。docker stop 先发配置的停止信号,超时后通常再 SIGKILL,因此 STOPSIGNAL 本身不保证一定优雅退出。
| 信号 | 常见 Linux 编号 | 含义 |
|---|---|---|
| SIGTERM | 15 | 默认终止请求,应用可捕获并清理 |
| SIGINT | 2 | 常由 Ctrl+C 产生,应用可自行处理 |
| SIGQUIT | 3 | 默认处置通常终止并生成 core,但 Nginx 等应用可将它定义为优雅退出 |
| SIGKILL | 9 | 无法捕获/忽略的强制终止,不适合作为常规优雅关闭信号 |
| SIGTSTP | 常见为 20 | 作业控制暂停信号,编号可因架构不同,不适合作为普通服务停止策略 |
例如:
STOPSIGNAL SIGQUIT
适合配合正确前台运行的 Nginx。原稿 STOPSIGNAL 9 是强制终止语法示例,不应被误当作数据安全配置。
附录:课程快捷命令
本文使用的 dps、dcn、dka 是主教材自定义快捷命令,不是 Docker 自带子命令。仅需理解其作用时,可直接用原生命令替代。以下定义供同样的单 bridge 实验使用:
alias dps='docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"'
alias dcn='docker inspect --format "{{.NetworkSettings.Networks.bridge.IPAddress}}"'
alias dka='cs=$(docker ps -aq); [ -z "$cs" ] || docker rm -f $cs'
dcn 这里明确查询默认 bridge;host 网络没有独立容器 IP,自定义网络应按实际名称查询。dka 会强制删除当前 daemon 的全部容器,课程末尾的“清理环境”不应被误解成实际业务主机的常规无害操作。
评论