命令敲下去,容器就起来了------中间的活儿是谁干的?这篇先把 Docker 的三件套(客户端、宿主机、镜像仓库)拆开,看一条
docker run怎么在这三层之间走完三步;再给出单机部署的六步顺序,以及把客户端单独挪到另一台机器的 C/S 分离做法。
本文概要:
| 项目 | 内容 |
|---|---|
| 文章主题 | Docker 的 C/S 架构(Client / Host / Registry 三件套与一次 run 的完整链路)、版本演进、单机部署六步、C/S 分离部署 |
| 所属类别 | 云计算 → 容器技术 → Docker 基础 |
| 知识关键字 | dockerd · docker run · Images · Containers · Registry · daemon.json · -H tcp:// |
主线是这样的:先搞清楚「谁在干活」(架构),再确认「装哪个版本」(版本),然后按顺序把它装起来(部署)。最后把客户端拆出去做远程管理(C/S 分离)。
架构那段看着抽象,却决定了后面所有的排错方向。命令报错到底是客户端的问题、守护进程的问题,还是仓库连不上,答案都在这一层。
一、三件套:客户端、宿主机、镜像仓库
Docker 用的是 C/S(客户端/服务端)架构,整体由三部分组成:
- Client 客户端------你敲命令的那个终端;
- Docker Host 宿主机------跑守护进程、存镜像、跑容器的那台机器;
- Registry 镜像仓库------镜像的源头。
客户端与守护进程之间通过 Socket 通信,所有容器操作都是这么发出去的。
1.1 客户端:只发指令,不动手
Client 是用户交互入口,本身不直接创建容器,只把指令发给 Docker daemon。三条最常见的命令正好对应三个阶段:
| 命令 | 发给 daemon 之后做什么 |
|---|---|
docker build |
读 Dockerfile,把构建结果存成本地镜像 |
docker pull |
去 Registry 把镜像拉到本地 Images |
docker run |
基于本地镜像创建并启动容器 |
理解这一点很关键:客户端和服务端可以不在同一台机器上 。docker 命令后面加 -H 就能指向远端 daemon,这也是第四节 C/S 分离的理论基础。
1.2 宿主机:真正干活的地方
宿主机上跑的核心组件是 dockerd(Docker daemon),后台常驻进程,是整个架构的服务端。镜像构建、拉取、容器创建、启停、销毁,全是它做的。宿主机里只有两类资源:
Images 镜像------只读模板,可以理解成容器的安装包,里面是应用程序、依赖库、配置文件。
- 可以通过
docker build本地构建,也可以docker pull从仓库下载; - 镜像分层存储,多个镜像可以共享底层,节约磁盘;
- 镜像本身只读,不能直接运行,必须先实例化成容器。
Containers 容器------镜像的运行实例,一个轻量级沙箱。
docker run时,daemon 读本地镜像,在镜像之上增加一层可读写层,生成容器;- 一台宿主机可以同时跑多个容器,彼此隔离、互不可见;
- 容器本质上是宿主机上受 Namespace 与 Cgroups 管控的进程;
- 容器内产生的数据写在读写层,镜像本身不会被修改;容器删除后读写层数据默认丢失。
⚠️ 注意 :最后这条是新手最容易踩的坑------在容器里改了配置、存了数据,
docker rm之后全没了,还以为是镜像坏了。要持久化必须挂 volume 或绑定宿主机目录,不能指望容器的读写层。
1.3 Registry:镜像的源头
Registry 是集中存放镜像的地方。仓库注册服务器里存着很多类镜像,每类镜像又用不同的**标签(tag)**区分版本。比如 Ubuntu 这个分类下,就有 24.04、22.04、20.04 这些 tag。
按公开与否分两类:
- 公开仓库:Docker Hub 最大,镜像数量庞大;国内不少云厂商也提供了镜像源,访问更稳定;
- 私有仓库 :在本地网络里自建一个只能自己访问的仓库,常见方案是 Harbor 或官方的
registry:2。
镜像做好之后 docker push 传上去,别的机器 docker pull 拉下来,镜像的分发就完成了。
1.4 一次 run 的完整链路

以 docker run nginx 为例,三步是:
- 用户在 Client 执行
docker run nginx,请求发给 Docker daemon; - daemon 发现本地没有 nginx 镜像,访问 Registry 下载,存进本机 Images;
- daemon 读取本地 nginx 镜像,创建容器需要的网络、存储等资源,运行 Container。
本地已经有这个镜像时,第二步直接跳过------这也是「第二次起容器明显更快」的原因。
二、版本怎么认:三个阶段三种编号
Docker 的版本号换过两次格式,看到不同写法不用怀疑,是历史原因:
| 阶段 | 时间 | 格式 | 关键版本 |
|---|---|---|---|
| 语义化 1.x | 2013 -- 2017.02 | 主.次.补丁,如 1.13.1 |
0.1(2013.06 首发,基于 LXC)、1.0(2014.06 生产可用,Docker Hub 上线)、1.12(内置 Swarm、引入 healthcheck)、1.13(1.x 最后一版,命令行重构) |
| 日历版本 CE/EE | 2017.03 -- 2022.06 | YY.MM.Patch,分社区版 CE 与企业版 EE |
17.03(首个日历版,runc 落地)、17.05(支持多阶段构建)、18.09(底层全面用 containerd)、20.10(经典稳定版,支持 cgroup v2 与 rootless,存量环境大量在用)、22.06(日历版终点,Compose V1 废弃) |
| 回归语义化 | 2023.02 至今 | 主.次.补丁 |
23.0(默认启用 BuildKit)、24.0 / 25.0(生产稳定推荐)、29.x(最新,清理大量老旧接口) |
选版本的经验:
- 新装环境选 24.x / 25.x,稳且资料多;
- 老环境看到
20.10.x不要奇怪,那是过去几年最广泛部署的一版; - 29.x 起全新安装默认使用 containerd image store ,存储路径从
/var/lib/docker变成/var/lib/containerd,脚本里写死路径的要注意。
三、单机部署:六步走完

环境前提:CentOS 7 最小化安装,容器运行时用 runc,管理工具是 Docker Engine。Docker 支持几乎所有 Linux 发行版,也支持 macOS 与 Windows。
3.1 先解决「从哪装」:本地源
最小化安装的系统没有外网访问的稳定性保障,第一件事是把安装光盘挂成 yum 源:
bash
[root@docker ~]# mkdir /dvd
[root@docker ~]# cp /etc/fstab /etc/fstab.bak
[root@docker ~]# echo '/dev/sr0 /dvd iso9660 defaults 0 0' >> /etc/fstab
[root@docker ~]# mount /dvd
mount: /dev/sr0 写保护,将以只读方式挂载
# 看不到内容时先检查虚拟机是否关联了 ISO
[root@docker ~]# ls /dvd
CentOS_BuildTag EULA images LiveOS repodata RPM-GPG-KEY-CentOS-7
[root@docker ~]# mkdir /etc/yum.repos.d/bak
[root@docker ~]# mv /etc/yum.repos.d/*repo /etc/yum.repos.d/bak
[root@docker ~]# vim /etc/yum.repos.d/dvd.repo
[dvd]
name=local repo from dvd
baseurl=file:///dvd
gpgcheck=0
[root@docker ~]# yum clean all && yum makecache
元数据缓存已建立
报 mount: 在 /dev/sr0 上找不到媒体 就是虚拟机没挂 ISO 文件,先把光驱关联上再 mount。
3.2 防火墙不能关
这一步和很多教程相反:把 firewalld 开着,因为容器的端口映射依赖它的 NAT 功能。
bash
[root@docker ~]# systemctl enable firewalld.service --now
[root@docker ~]# firewall-cmd --set-default-zone=trusted
默认区设为 trusted 是实验环境的省事做法(放行一切);生产环境应当精细放行,但不能直接 systemctl stop firewalld ------关掉之后 -p 80:80 的映射会静默失效,容器在跑但外面访问不到。
⚠️ 注意 :这是本篇第一个容易走错的地方。网上大量「装 Docker 先关防火墙」的说法来自 CentOS 6 时代;现在的 Docker 会自己往 firewalld 的
DOCKER链里插规则,把 firewalld 停掉反而让规则链失效。
3.3 配 Docker 仓库
系统源里没有 docker-ce,得单独加一个源:
bash
[root@docker ~]# cat > /etc/yum.repos.d/docker-ce.repo <<'EOF'
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.huaweicloud.com/docker-ce/linux/centos/7/$basearch/stable
enabled=1
gpgcheck=0
EOF
$basearch 会自动展开成 x86_64,不用手改。gpgcheck=0 是为了跳过 GPG 校验,内网环境可以接受;对公网源建议改成 1 并导入官方 key。
📌 想完全离线部署,就把 rpm 包全部下载下来自己造一个本地源------目录结构、元数据生成、客户端配置的完整做法在《自定义 yum 软件源》那篇里,这里不重复。
3.4 装并启动
bash
[root@docker ~]# yum install -y docker-ce
[root@docker ~]# systemctl enable docker.service --now
[root@docker ~]# docker --version
Docker version 26.1.3, build b72abbb
3.5 配镜像加速(不配基本拉不动)
直连 docker.io 在国内基本不可用:要么超时,要么几 KB 每秒。这一步配华为云的镜像加速器,再把自建仓库和第三方加速站讲清楚。这三类东西常被写成一段,结果谁也搞不清哪个字段管哪件事。
3.5.1 取华为云 SWR 的加速地址
- 登录华为云容器镜像服务 SWR 控制台;
- 左侧导航点「镜像加速器」;
- 复制页面上的加速器地址 ,形如
https://abcdef123456.mirror.swr.myhuaweicloud.com。
地址里那串字符是账号标识,每个账号一个,不能照抄别人的。目前开放加速器的局点:华南-广州、华北-北京四、华东-上海一、华东-上海二、华东二、华北-乌兰察布一、西南-贵阳一。
3.5.2 写进 daemon.json
bash
[root@docker ~]# mkdir -p /etc/docker
[root@docker ~]# tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://abcdef123456.mirror.swr.myhuaweicloud.com"]
}
EOF
[root@docker ~]# systemctl daemon-reload
[root@docker ~]# systemctl restart docker
[root@docker ~]# docker info | grep -A1 "Registry Mirrors"
Registry Mirrors:
https://abcdef123456.mirror.swr.myhuaweicloud.com/
docker info 里出现这一行才算生效。没出现就是没读到配置,两个原因最常见:daemon.json 不在 /etc/docker/ 下;或者 /etc/sysconfig/docker、/etc/default/docker 里也写了 registry-mirrors,两处冲突时 daemon 直接起不来。
3.5.3 加速器的边界
| 项 | 官方口径 |
|---|---|
| 能加速什么 | 只覆盖 Docker Hub 上的常用开源镜像,不保证每个镜像、每个 tag 都能拉到 |
| 给谁用 | 面向华为云上容器产品与个人开发场景;生产环境建议把镜像同步到 SWR 私有仓库,再按私有地址拉 |
| 客户端版本 | Docker 18.06 ~ 28.5 |
这一段不是免责声明,是排错依据。拉 nginx 很快、拉某个冷门镜像还是慢,属于覆盖范围问题,不是配置写错了。
3.5.4 三类「加速」别混着写
registry-mirrors 和 insecure-registries 是两个完全不同的字段。把私有仓库塞进 registry-mirrors,是最常见的一种误解。
| 类型 | 例子 | 本质上是什么 | 写进哪个字段 |
|---|---|---|---|
| 云厂商加速器 | 华为云 SWR 加速地址 | 替你访问 Docker Hub 的代理 | registry-mirrors |
| 第三方公共站 | docker.m.daocloud.io、docker.1ms.run |
同上,稳定性自负 | registry-mirrors,排后面兜底 |
| 自建 Harbor | http://hub.zhb.cloud |
自己的私有仓库,存自己的镜像 | insecure-registries(只开 HTTP 时才需要) |
三条容易误会的地方:
-
registry-mirrors只对docker.io生效。 想加速gcr.io、quay.io、registry.k8s.io的镜像,加速站帮不上忙,得先把镜像同步到自己的仓库。 -
自建 Harbor 不是「公共镜像加速站」。 它不替你去 Docker Hub 拉东西,写进
registry-mirrors不会生效;拉里面的镜像要写全名,并且先登录:bash[root@docker ~]# docker login hub.zhb.cloud [root@docker ~]# docker pull hub.zhb.cloud/library/nginx:1.25 -
HTTP 地址必须同时进
insecure-registries。 否则 daemon 拒绝明文连接,报http: server gave HTTP response to HTTPS client。
实验环境里常见的组合是这样,两个字段各管一段:
bash
[root@docker ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://abcdef123456.mirror.swr.myhuaweicloud.com"],
"insecure-registries": ["hub.zhb.cloud"]
}
写完必须 systemctl restart docker。 daemon.json 只在 daemon 启动时读一次,改完不重启等于没改------这是本篇第二个常见卡点。
3.6 验收:两个容器才算过
第一个容器用 hello-world。它只做一件事:把整条链路走一遍------客户端联系 daemon、daemon 拉镜像、daemon 起容器、输出回传。
bash
[root@docker ~]# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
看到 Hello from Docker! 说明客户端、守护进程、镜像仓库三段都通了。
第二个容器用 httpd,验证端口映射:
bash
[root@docker ~]# docker run -d -p 80:80 httpd
817f24cca3514568f9b7a7cac6f183734077fb74caf4c62502acd2a4d0d29520
[root@docker ~]# ip -br a
ens33 UP 10.1.8.102/24
docker0 UP 172.17.0.1/16
veth7c57cce@if8 UP
[root@docker ~]# curl 10.1.8.102
<html><body><h1>It works!</h1></body></html>
docker0 上出现 172.17.0.1/16 是正常的------那是 Docker 默认网桥,容器从这段地址里分 IP。真正要确认的是:浏览器或 curl 能访问宿主机 IP 的 80 端口。
⚠️ 注意 :
-p 80:80的前后顺序是「宿主机端口 : 容器端口」,写反了不会报错但访问不到。宿主机 80 被占用时换成-p 8080:80,访问时用 8080。
四、C/S 分离:把客户端挪到另一台机器

单机装完,把客户端拆出去的意义在于:管理入口和运行环境分离。运维在自己的机器上敲命令,镜像和容器都在服务端的机房机器上。
4.1 server 端:把 2375 打开
两条路,改的文件不同、效果一样:方式一直接改单元文件 ,图快,适合一次性实验;方式二 drop-in 覆盖 + daemon.json,不怕升级,适合长期维护的机器------这台机器最后用的是方式二。两种写法都写在这里,别只抄一半。
方式一:直接编辑单元文件
bash
[root@docker ~]# yum install -y docker-ce
[root@docker ~]# vim /usr/lib/systemd/system/docker.service
# 在 ExecStart 行末尾追加 -H tcp://0.0.0.0:2375
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
[root@docker ~]# systemctl daemon-reload
[root@docker ~]# systemctl enable docker.service --now
# 验证端口起来了
[root@docker ~]# lsof -i :2375
dockerd 2565 root 3u IPv6 76021 0t0 TCP *:2375 (LISTEN)
三个参数各自的含义:-H fd:// 是 systemd 的 socket 激活方式,保留它;-H tcp://0.0.0.0:2375 是本次新加的 TCP 监听;--containerd=/run/containerd/containerd.sock 指定 containerd 的 socket 路径,容器的生命周期实际由 containerd 接管。
三个细节都要做到位:改的是 docker.service、改完必须 daemon-reload、fd:// 那一项不能删。只加新参数不动原来的,两个地址会同时监听。
问题是这个文件属于 docker-ce 包,yum update docker-ce 一次就装回原样,2375 悄悄消失。
方式二:drop-in 覆盖 + daemon.json(实际采用)
不碰发行版的文件,另开一份只写自己那几行的覆盖:
bash
[root@docker ~]# mkdir -p /etc/systemd/system/docker.service.d
[root@docker ~]# vim /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock
监听地址不写在命令行里,交给 daemon.json:
bash
[root@docker ~]# cat /etc/docker/daemon.json
{
"hosts": [
"unix:///var/run/docker.sock",
"tcp://0.0.0.0:2375"
]
}
[root@docker ~]# systemctl daemon-reload
[root@docker ~]# systemctl restart docker
[root@docker ~]# lsof -i :2375
dockerd 2565 root 3u IPv6 76021 0t0 TCP *:2375 (LISTEN)
两处不能省:
ExecStart=那行空着不是笔误。 systemd 里同一指令第二次赋值是追加,先赋空值才能把原值整体清掉;少了它,两个ExecStart拼在一起,daemon 直接起不来。- override 里不能再出现
-H。 命令行有-H、daemon.json又有hosts,dockerd 会拒绝启动,报the following directives are specified both as a flag and in the configuration file: hosts。原来的-H fd://也一并去掉------它和unix:///var/run/docker.sock是同一个地址的两种说法,改由hosts统一接管后,本机docker命令照常能用。
| 对照项 | 方式一:改单元文件 | 方式二:drop-in + daemon.json |
|---|---|---|
| 监听地址写在哪 | ExecStart 的 -H 参数 |
daemon.json 的 hosts |
yum update docker-ce 之后 |
被覆盖,2375 丢失 | 不受影响 |
与 daemon.json 的 hosts |
冲突,daemon 起不来 | 不冲突 |
| 适合 | 一次性实验 | 长期维护的机器 |
📌
systemctl edit --force docker.service打开的其实就是override.conf这个文件,用它写和用vim写等价。改完用systemctl cat docker看合并后的最终结果,比对着两份文件猜更直接。
4.2 client 端:只装 CLI
客户端机器不需要 dockerd ,装一个 docker-ce-cli 就够:
bash
[root@docker-cli ~]# yum install -y docker-ce-cli
[root@docker-cli ~]# docker -H 10.1.8.102 run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
4.3 验证:镜像确实在服务端
bash
[root@docker-cli ~]# docker -H 10.1.8.102 images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 0a30f4c29d25 2 weeks ago 138MB
hello-world latest bf756fb1ae65 11 months ago 13.3kB
客户端本机 docker images 是空的------镜像和容器全都存在 server 端,客户端只是发指令。这一条能验证,就说明 C/S 分离真的成了。
⚠️ 注意 :
2375是明文、无认证的端口,能连上的人等于拿到服务端的 root 。只在隔离的实验网段开,别在公网机器上开。生产环境要用2376加 TLS 双向证书,或者干脆走 SSH 隧道。
五、Windows 与 macOS:Docker Desktop
非 Linux 桌面不用装 docker-ce,装 Docker Desktop 即可。它把 daemon、CLI、K8s 打包在同一个安装包里。
Windows 版的 Docker Desktop 需要跑在两种架构之上:
- WSL 2(Windows Subsystem for Linux)------默认方式,轻量,文件性能好;
- Hyper-V------旧方案,需要开启 Hyper-V 功能。
装完在 PowerShell 里执行 docker version 能同时看到 Client 与 Server 两段信息,就说明后端已经起来了。
⚠️ 注意 :Docker Desktop 在 Windows 上是跑在 WSL 2 的一个 Linux 发行版里的,不是原生 Windows 容器。想跑 Windows 容器得切到 Windows containers 模式,两者不能同时开。装之前先确认虚拟化已在 BIOS 中开启,否则 WSL 2 起不来。
六、部署排错速查
| 现象 | 原因 | 处理 |
|---|---|---|
Unable to find a match: docker-ce |
没配 docker-ce.repo,或 $basearch 展开后路径不对 |
检查 /etc/yum.repos.d/docker-ce.repo 的 baseurl |
Cannot connect to the Docker daemon |
服务没起,或当前用户不在 docker 组 |
systemctl status docker;普通用户加组后重新登录 |
docker pull 卡在 Pulling fs layer |
没配镜像加速,或加速站失效 | 改 daemon.json 的 registry-mirrors 后 restart |
| 容器起来了但 80 端口访问不到 | firewalld 被停掉,NAT 链失效 | systemctl enable --now firewalld |
docker -H ... 报 connection refused |
server 端没监听 2375 | lsof -i :2375 确认,检查 service 文件与 daemon-reload |
改了 daemon.json 没效果 |
daemon 只在启动时读一次 | systemctl restart docker |
📚 参考资料(官方文档)
- Docker Engine 安装(CentOS):https://docs.docker.com/engine/install/centos/
- Docker 架构与核心组件:https://docs.docker.com/get-started/docker-overview/
- daemon 配置文件 daemon.json:https://docs.docker.com/reference/cli/dockerd/
- 用 systemd 配置 dockerd 启动参数:https://docs.docker.com/engine/daemon/
- Docker Desktop for Windows 安装:https://docs.docker.com/desktop/setup/install/windows-install/
附:术语英文全称速查表
| 缩写 | 英文全称 | 中文 |
|---|---|---|
| C/S | Client / Server | 客户端 / 服务端 |
| daemon | --- | 守护进程(常驻后台的服务程序) |
| CLI | Command Line Interface | 命令行界面 |
| CE / EE | Community Edition / Enterprise Edition | 社区版 / 企业版 |
| WSL | Windows Subsystem for Linux | Windows 的 Linux 子系统 |
| NAT | Network Address Translation | 网络地址转换 |
把这篇压成一句话:客户端发指令,守护进程干活,仓库供货。 部署时六步顺序不能乱------源、防火墙、仓库、安装、加速、验收;验收要两个容器都过(
hello-world验链路、httpd验端口)。C/S 分离只是把第一层的客户端搬到另一台机器,服务的、存的都还在 server 上;而 2375 那个端口,实验能开、生产不能开。
