本文系统梳理云计算 Lab 4 的核心知识------容器编排(Container Orchestration),从 Docker Compose 单机多容器管理出发,逐步深入到 Docker Swarm 分布式集群编排,覆盖反向代理、服务扩缩、滚动更新、节点排空、Routing Mesh、Stack 声明式部署、自愈机制等关键技术,并升华到云原生编排生态(Kubernetes/ECS/AKS/GKE)的工程视角。
一、为什么需要编排:从单容器到分布式系统
1.1 编排的本质
在云计算语境下,**编排(Orchestration)**指的是自动协调和管理多种服务、应用与基础设施资源,使它们高效协同工作的过程。它本质上是把"手动运维"转换为"声明式自动化":
- 手动运维时代:管理员逐台机器配置网络、存储、应用、监控
- 编排时代:声明"我想要 3 个 web 实例运行在 2 个节点上",系统自动完成调度、健康检查、故障恢复
编排平台负责的核心任务包括:
| 任务 | 含义 |
|---|---|
| Deployment(部署) | 自动拉镜像、起容器、配置网络 |
| Scaling(扩缩) | 根据需要增加或减少实例数量 |
| Monitoring(监控) | 持续检查容器/节点健康状态 |
| Recovery(恢复) | 容器/节点故障时自动重建 |
| Service Discovery(服务发现) | 让服务之间通过名称互相找到 |
| Load Balancing(负载均衡) | 把流量均匀分发到多个副本 |
| Rolling Update(滚动更新) | 不停机升级版本 |
编排的最终目的:降低人为失误、提高系统可用性、简化复杂云环境管理。
1.2 编排在云计算栈中的位置
┌─────────────────────────────────────┐
│ 应用层(业务代码) │
├─────────────────────────────────────┤
│ 编排层(Compose / Swarm / K8s) │ ← 本实验聚焦
├─────────────────────────────────────┤
│ 容器运行时(Docker / containerd) │
├─────────────────────────────────────┤
│ 操作系统(Linux Kernel) │
├─────────────────────────────────────┤
│ 虚拟化层(VirtualBox / KVM) │
├─────────────────────────────────────┤
│ 物理硬件(CPU / 内存 / 网络) │
└─────────────────────────────────────┘
编排层是云原生时代的"操作系统"------把集群当成单台虚拟主机调度。
二、实验环境准备
2.1 虚拟机拓扑
实验使用 3 台 Debian VM,配置在同一个 VirtualBox NAT Network 中:
| 主机名 | 角色 | 示例 IP |
|---|---|---|
| manager1 | Swarm Manager(管理节点) | 10.0.2.15 |
| worker1 | Swarm Worker(工作节点) | 10.0.2.3 |
| worker2 | Swarm Worker(工作节点) | 10.0.2.x |
2.2 修改主机名
bash
sudo nano /etc/hostname
# 写入新主机名后保存
sudo reboot
2.3 验证 Docker 运行
bash
sudo systemctl status docker
sudo systemctl start docker
⚠️ 三台机器都必须装好 Docker Engine 且服务已启动。
三、Docker Compose:单机多容器编排
Docker Compose 是面向单机的声明式多容器管理工具,使用 YAML 描述应用拓扑。
3.1 第一个 Compose 项目
创建目录结构:
compose_orchestration/
└── compose.yaml
compose.yaml:
yaml
services:
welcome:
image: docker/welcome-to-docker
container_name: welcome
ports:
- "8080:80"
volumes:
- volume_1:/usr/share/nginx/html
alpine:
image: alpine
container_name: alpine
tty: true # 保持交互模式
volumes:
volume_1:
external: true
⚠️ 缩进规范:使用两个空格,不要用 Tab。这是 YAML 的硬性要求。
关键字段解读:
services:定义所有服务image:使用的镜像container_name:固定容器名(注意:与 scaling 互斥)ports:端口映射"宿主机:容器"volumes:数据卷挂载tty: true:等价于docker run -t,分配伪终端保持容器存活external: true:告诉 Compose 这个 volume 不由它创建,引用一个已存在的外部卷
3.2 启动与生命周期管理
bash
# 后台启动(detach 模式)
docker compose up -d
# 列出所有 Compose 项目
docker compose ls
# 列出当前项目的容器(需在 compose.yaml 所在目录)
docker compose ps
# 也可指定项目名
docker compose -p <COMPOSE_NAME> ps
# 停止服务(容器保留)
docker compose stop
# 列出所有服务(包括已停止)
docker compose ps -a
# 停止并清理资源(删除容器、网络)
docker compose down
访问验证:
- 浏览器访问
http://localhost:8080查看 welcome-to-docker 页面 - 进入 alpine 容器:
docker exec -it alpine /bin/sh
3.3 服务扩缩(Scaling)
要让一个服务运行多个实例,必须解决两个冲突:
- 容器名冲突 :
container_name是唯一的,多实例时必须去掉 - 端口冲突 :宿主机端口只能被一个容器绑定,多实例不能写
"8080:80"
修改后的 compose.yaml:
yaml
services:
welcome:
image: docker/welcome-to-docker
ports:
- "80" # 宿主机端口由 Docker 随机分配
volumes:
- volume_1:/usr/share/nginx/html
alpine:
image: alpine
tty: true
volumes:
volume_1:
external: true
扩缩命令:
bash
# 启动项目
docker compose up -d
# 扩到 3 个 alpine 实例
docker compose up -d --scale alpine=3
# 扩到 4 个 welcome 实例(注意会把 alpine 缩回 1)
docker compose up -d --scale welcome=4
# 同时指定多个服务的实例数
docker compose up -d --scale welcome=3 --scale alpine=2
⚠️ 常见坑 :--scale 只对本次命令有效。如果你下次只指定 welcome 的数量,其他服务会被重置为默认值 1。生产场景推荐用 deploy.replicas 字段固化。
默认端口探查:
bash
docker compose ps
输出中 PORTS 列形如 0.0.0.0:32768->80/tcp,宿主机端口从 32768 起随机分配。这就是著名的"临时端口范围"(ephemeral port range)。
3.4 反向代理与负载均衡
问题:每个 welcome 实例都暴露在不同的随机端口,用户体验极差,且无法做统一的入口控制。
解决 :引入**反向代理(Reverse Proxy)**作为单一入口,由代理负责把请求分发到后端实例。本节选用 nginx:alpine。
3.4.1 项目结构
compose_reverse_proxy/
├── compose.yaml
├── nginx.conf
└── index.html
3.4.2 compose.yaml
yaml
services:
proxy:
image: nginx:alpine
depends_on:
- web
ports:
- "80:80" # 宿主机 80 ↔ 容器 80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # 只读挂载
web:
image: nginx:alpine
ports:
- "80" # 内部端口,宿主机随机
volumes:
- ./index.html:/usr/share/nginx/html/index.html
关键字段:
depends_on:声明 proxy 依赖 web,启动顺序保证:ro后缀:容器内只读挂载(避免容器篡改配置)
3.4.3 nginx.conf
nginx
events {}
http {
upstream backend {
server web:80;
}
server {
location / {
proxy_pass http://backend;
}
}
}
核心机制:
upstream backend { server web:80; }:定义后端服务池,地址写服务名web,而不是某个 IP- Docker 内置 DNS 会把
web解析为该服务所有副本的 IP 列表 - Nginx 默认采用 Round Robin(轮询) 策略在这些 IP 之间分发请求
3.4.4 index.html
html
<html>Hello world</html>
3.4.5 启动并扩容
bash
docker compose up --scale web=3
访问 http://localhost 多次刷新,日志会显示 web-1、web-2、web-3 轮换响应。
3.4.6 涉及的 Docker 关键概念(实验问题 4)
| 概念 | 作用 |
|---|---|
| Service(服务) | Compose 中以名字标识的一组相同容器 |
| Bridge network(桥接网络) | Compose 自动为项目创建的内部网络 |
| Internal DNS(内部 DNS) | Docker 守护进程的服务发现机制 |
| Service discovery(服务发现) | 通过服务名访问,自动解析到所有副本 |
| Load balancing(负载均衡) | Nginx 在多个解析出的 IP 之间轮询 |
| Volume mount(数据卷挂载) | 配置/页面热更新 |
| Port publish(端口发布) | 把容器端口暴露到宿主机 |
| depends_on(依赖关系) | 启动顺序编排 |
3.4.7 让页面显示来自哪个容器(实验任务 5)
一种简洁方案:把 hostname 写进首页。修改 compose.yaml:
yaml
web:
image: nginx:alpine
ports:
- "80"
command: /bin/sh -c "echo \"<h1>Served by $$(hostname)</h1>\" > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"
刷新页面,每次显示的容器 hostname 不同,可直观看到负载均衡的效果。
3.4.8 架构示意
┌──── 内部 Docker network ────┐
│ │
[ Internet ] ──80──▶│ nginx Proxy ──┬─▶ web-1:80 │
│ (load bal) ├─▶ web-2:80 │
│ └─▶ web-3:80 │
└──────────────────────────────┘
四、Docker Swarm:跨主机的分布式编排
Compose 解决了单机多容器,但生产环境需要把负载分散到多台机器。Docker Swarm 应运而生------它把多台 Docker 主机组成一个集群,让用户像操作单机一样操作整个集群。
4.1 Swarm 核心概念深度解读
4.1.1 What is a Swarm?
Swarm(蜂群)是 Docker 内置的编排系统,把一组 Docker Engine 联合成一个虚拟主机。它能够:
- 调度工作负载到不同节点
- 维持期望状态(Desired State)
- 自动处理故障转移(Failover)
4.1.2 核心组件矩阵
| 概念 | 比喻 | 职责 |
|---|---|---|
| Node | 机器 | 集群中的成员(物理机或虚拟机) |
| Manager Node | 大脑 | 接收 API 请求、调度、状态管理、Leader 选举 |
| Worker Node | 肌肉 | 执行 Manager 分配的任务 |
| Service | 期望应用定义 | "运行 3 副本的 nginx" |
| Task | 运行中的容器 | Service 的一个副本实例 |
| Stack | 完整应用 | 多个 Service 组成的应用(前端+后端+DB) |
| Overlay Network | 通信层 | 跨节点的容器虚拟网络 |
| Routing Mesh | 入口路由 | 让任意节点都能接收并路由流量 |
| Desired State | 目标状态 | 用户声明,Swarm 持续逼近 |
4.1.3 Manager 与 Worker 的本质区别
Manager Node:
- 维护集群状态(用 Raft 共识算法保证多 Manager 一致性)
- 调度容器(决定哪个 Task 跑在哪个 Node)
- 处理 API 请求(
docker service create等命令只能在 Manager 上执行) - 默认也运行 Task(除非通过 drain 排除)
💡 Raft 拓展:Raft 是一种基于 Leader 选举的共识算法,要求大多数节点存活才能继续工作。Swarm 推荐部署奇数个 Manager(3、5、7),可分别容忍 1、2、3 个 Manager 故障。
Worker Node:
- 只接受 Manager 分配的任务并执行
- 不参与决策、不存储集群状态
- 通过加入令牌(join token)认证
4.1.4 Service vs Container:思维范式的转变
这是从 Docker 进入云原生的最重要认知跃迁:
-
Container:单个运行实例,短暂、易消失
-
Service :对应用的声明式定义------"我想要什么",而不是"怎么做"
传统命令式: docker run / docker stop / docker rm(手动管理)
↓
云原生声明式: "我要 3 个副本一直运行" → Swarm 自动维持
4.1.5 Task 的不可变性(Immutability)
Task 是 Service 的最小调度单位,且是**不可变(immutable)**的:
- 一个 Task 一旦失败,Swarm 不会"修复"它,而是创建一个全新的 Task 替换
- 这种设计避免了"修改运行态导致漂移"的问题,与不可变基础设施(Immutable Infrastructure)理念一致
4.1.6 总览架构图
┌─────────────────────┐
│ Service: 3 nginx │
│ (declared on mgr) │
└──────────┬──────────┘
│ 调度
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ nginx.1 │ │ nginx.2 │ │ nginx.3 │
│ (Task) │ │ (Task) │ │ (Task) │
└──────────┘ └──────────┘ └──────────┘
manager1 worker1 worker2
五、Swarm 实战:从零搭建集群
5.1 初始化 Manager
在 manager1 执行:
bash
sudo docker swarm init --advertise-addr <MANAGER1_IP>
--advertise-addr 告诉其他节点通过哪个 IP 找到 Manager(多网卡环境必须显式指定)。
输出会给出加入命令:
docker swarm join --token SWMTKN-1-xxx 10.0.2.15:2377
⚠️ 端口规划:
| 端口 | 协议 | 用途 |
|---|---|---|
| 2377 | TCP | 集群管理通信 |
| 7946 | TCP/UDP | 节点间通信(gossip) |
| 4789 | UDP | overlay 网络数据平面(VXLAN) |
查看集群状态
bash
docker info # 看到 Swarm: active 段
docker node ls # 列出节点(仅 Manager 可执行)
docker node ls 输出中 * 标记当前所在节点;MANAGER STATUS 列为 Leader 表示该节点是 Raft 集群的 Leader。
5.2 加入 Worker 节点
在 worker1、worker2 上分别执行:
bash
sudo docker swarm join --token SWMTKN-1-xxx 10.0.2.15:2377
如果忘记 token,在 manager 上:
bash
sudo docker swarm join-token worker # 工作节点的加入命令
sudo docker swarm join-token manager # 管理节点的加入命令
回到 manager 验证:
bash
sudo docker node ls
应看到 3 个节点全部 Ready + Active,其中 manager1 标记 Leader。
5.3 部署第一个 Service
bash
sudo docker service create \
--replicas 1 \
--name welcome \
-p 8080:80 \
docker/welcome-to-docker
参数解读:
| 参数 | 含义 |
|---|---|
--replicas 1 |
期望状态:1 个副本 |
--name welcome |
Service 名 |
-p 8080:80 |
宿主机 8080 ↔ 容器 80(经过 Routing Mesh) |
查看:
bash
sudo docker service ls # 所有 Service
sudo docker service ps welcome # 该 Service 的所有 Task
docker service ps 中的 DESIRED STATE 与 CURRENT STATE 反映了"声明式编排"的精髓:用户只写期望,系统持续逼近。
访问验证:
bash
curl http://<ANY_NODE_IP>:8080
注意:任何节点的 IP 都能访问,这是 Routing Mesh 的功劳(详见后文)。
5.4 检查 Service 详情
bash
sudo docker service inspect --pretty welcome
--pretty 显示可读格式;去掉则输出原始 JSON,适合脚本处理。
定位 Task 所在节点:
bash
sudo docker service ps welcome
到对应节点上:
bash
sudo docker ps # 看具体容器
5.5 服务扩缩
bash
sudo docker service scale welcome=5
Swarm 会自动 在 3 个节点间均衡分配 5 个 Task。再次 docker service ps welcome 可以看到任务分布在 manager1、worker1、worker2 上。
默认调度策略:Spread(均匀分散),尽量让每个节点的 Task 数接近。
5.6 删除 Service
bash
sudo docker service rm welcome
sudo docker service inspect welcome # 应返回 no such service
容器需要几秒清理,可在各节点 docker ps 观察。
六、镜像升级实战:制作新版本
实验通过 docker commit 演示版本创建(虽然是 bad practice,仅用于教学):
bash
# 1. 起一个 welcome 容器
docker run -d -p 8080:80 --name welcome docker/welcome-to-docker
# 2. 进入容器修改首页
sudo docker exec -it welcome /bin/sh
echo "Version 2.0 of welcome-to-docker." > /usr/share/nginx/html/index.html
exit
# 3. commit 为新镜像
docker commit -m "Version 2.0 of welcome-to-docker." -a "Alice" welcome docker/welcome-to-docker:new
# 4. 清理临时容器
docker stop welcome && docker rm welcome
# 5. 给旧版打 old 标签
docker tag docker/welcome-to-docker:latest docker/welcome-to-docker:old
# 6. 删除 latest 标签
docker rmi docker/welcome-to-docker:latest
⚠️ 为什么 commit 是反模式?
| 问题 | 说明 |
|---|---|
| 不可复现 | 没有源代码描述如何构建 |
| 不可审计 | 镜像内部的修改无法追踪 |
| 与 GitOps 冲突 | 镜像应由 CI 流水线从 Dockerfile 构建 |
| 容器膨胀 | 中间状态被一并打包 |
✅ 正确做法:Dockerfile
update_welcome/
├── Dockerfile
└── index.html
index.html:
Version 2.0 of welcome-to-docker.
Dockerfile:
dockerfile
# 基础镜像
FROM docker/welcome-to-docker:old
# 覆盖默认首页
COPY index.html /usr/share/nginx/html/index.html
# 暴露 80 端口
EXPOSE 80
构建:
bash
docker build -t docker/welcome-to-docker:new .
镜像分发:私有 Registry
实验只在 manager 上构建了镜像,worker 节点没有!生产中要通过 Docker Registry 分发:
bash
# 起一个本地 registry
docker run -d -p 5000:5000 --name registry registry:2
# 重新打标签
docker tag docker/welcome-to-docker:new <REGISTRY_HOST>:5000/welcome-to-docker:new
# 推送
docker push <REGISTRY_HOST>:5000/welcome-to-docker:new
# 在 worker 节点上拉取
docker pull <REGISTRY_HOST>:5000/welcome-to-docker:new
💡 在 Swarm 中部署 Service 时,如果指定的镜像在 Manager 节点能访问 Registry,Worker 节点会被指令去 Registry 拉取,无需手动同步。
七、滚动更新(Rolling Update):零停机升级
7.1 部署可升级的 Service
bash
sudo docker service create \
--replicas 3 \
--name welcome \
-p 8080:80 \
--update-delay 10s \
docker/welcome-to-docker:old
--update-delay 10s:每个 Task 更新成功后,等 10 秒再更新下一个。
7.2 触发滚动更新
bash
sudo docker service update --image docker/welcome-to-docker:new welcome
7.3 滚动更新的工作流程
┌──────────────┐
│ Stop Task #1 │ ← 优雅停止旧版本
└──────┬───────┘
▼
┌──────────────┐
│ Pull & Start │ ← 拉新镜像启动新版
│ New Task │
└──────┬───────┘
▼
┌──────────────┐
│ Wait Delay │ ← --update-delay 间隔
└──────┬───────┘
▼
┌──────────────┐
│ Next Task │ ← 循环
└──────────────┘
关键策略参数:
| 参数 | 含义 |
|---|---|
--update-parallelism |
同时更新的 Task 数(默认 1) |
--update-delay |
两批更新之间的等待时间 |
--update-failure-action |
单 Task 失败行为:pause / continue / rollback |
--update-max-failure-ratio |
允许的失败比例 |
--update-monitor |
健康监控窗口(默认 5s) |
--update-order |
start-first / stop-first(先起新版还是先停旧版) |
观察更新过程:
bash
sudo docker service ps welcome
可看到旧 Task 状态变为 Shutdown,新 Task 处于 Running,呈现"新老混合"的中间态。
7.4 滚动回滚
bash
sudo docker service rollback welcome
或者初始定义时配置自动回滚:
bash
--rollback-failure-action pause
--rollback-parallelism 1
滚动更新的核心价值:让升级成为业务可承受的常规操作,而不是惊心动魄的运维事件。
八、节点排空(Drain):优雅维护
8.1 Node 的三种 Availability
| 状态 | 含义 |
|---|---|
| Active | 正常调度,可接收新 Task |
| Pause | 不接收新 Task,已有的保留 |
| Drain | 不接收新 Task,且现有 Task 被驱逐到其他节点 |
8.2 Drain 实操
bash
sudo docker node update --availability drain worker1
观察:
bash
sudo docker node ls
# worker1 的 AVAILABILITY 变成 Drain
sudo docker service ps welcome
# worker1 上的 Task 状态 Shutdown,其他节点出现新 Task
8.3 恢复节点
bash
sudo docker node update --availability active worker1
被恢复的节点可在以下时机重新接收任务:
- 后续的 Service 扩容
- 滚动更新
- 其他节点 drain 时
- 现有 Task 故障重调度
8.4 Drain 的典型应用场景
- 计划维护:OS 升级、内核 patch、硬件维护
- 资源回收:把某节点的负载转移走以便下线
- 故障隔离:怀疑某节点有问题,先 drain 再排查
⚠️ Drain 只影响 Swarm 调度的 Task,不会动 docker run 创建的独立容器。
九、Routing Mesh:分布式入口路由
9.1 核心机制
Routing Mesh 是 Swarm 最酷的特性之一:任何节点的发布端口都能接收请求,即使该节点上根本没有运行对应 Service 的 Task。
工作原理:
- Swarm 在所有节点的内核创建一个名为
ingress的 overlay 网络 - 节点收到流量后,通过 IPVS(Linux 内核负载均衡)路由到运行 Task 的节点
- 整个过程对客户端完全透明
9.2 新语法发布端口
bash
docker service create \
--name welcome \
--publish published=8080,target=80 \
--replicas 2 \
docker/welcome-to-docker:old
| 字段 | 含义 |
|---|---|
published=8080 |
Routing Mesh 上对外暴露的端口 |
target=80 |
容器内监听端口 |
对比旧语法 -p 8080:80,新语法可读性更好、支持更多选项(如 mode=host 跳过 Mesh)。
9.3 给已有 Service 加端口
bash
docker service update \
--publish-add published=8080,target=80 \
welcome
9.4 实验:感受 Routing Mesh 的"魔法"
bash
# 创建 3 副本 service
docker service create \
--replicas 3 \
--name welcome \
--publish published=8080,target=80 \
--update-delay 10s \
docker/welcome-to-docker:old
# 把 worker1 排空(worker1 上不再有任何 welcome Task)
docker node update --availability drain worker1
# 但是!访问 worker1 的 IP:8080 仍然能看到页面
curl http://<WORKER1_IP>:8080
✨ 这正是 Routing Mesh 的核心价值:负载均衡器(如 HAProxy)只需配置所有节点 IP,无需关心哪个节点真正运行了 Task。
9.5 与外部 LB 配合的拓扑
[ HAProxy / 云 LB ]
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
node1:8080 node2:8080 node3:8080
┌────────┐ ┌────────┐ ┌────────┐
│ ingress│ │ ingress│ │ ingress│
│ mesh │◀─────▶│ mesh │◀───────▶│ mesh │
└────────┘ └────────┘ └────────┘
│ │ │
web-1 web-2 web-3
外部 LB 只做"哪台机器活着就发给它"的健康检查,应用层负载均衡完全交给 Mesh。
十、Stack:声明式部署的最佳实践
10.1 命令式 vs 声明式
之前用的 docker service create ... 是命令式 ------临时、不可追溯、难以版本控制。生产环境应使用 Stack:用 YAML 描述整个应用,一键部署。
10.2 Stack 文件示例
stack.yml:
yaml
services:
welcome:
image: docker/welcome-to-docker:old
deploy:
replicas: 3
update_config:
delay: 10s
parallelism: 1
order: start-first
restart_policy:
condition: on-failure
max_attempts: 3
placement:
constraints:
- node.role == worker
ports:
- "8080:80"
新增的 deploy 块(仅在 Swarm 模式下生效):
| 字段 | 含义 |
|---|---|
replicas |
期望副本数 |
update_config |
滚动更新策略 |
restart_policy |
重启策略 |
placement.constraints |
调度约束(节点角色、标签等) |
resources |
CPU/内存限制与预留 |
mode |
replicated 或 global |
10.3 部署与管理
bash
# 部署
docker stack deploy -c stack.yml welcome_stack
# 查看所有 Stack
docker stack ls
# 查看 Stack 内的 Service
docker stack services welcome_stack
# 查看所有 Task
docker stack ps welcome_stack
# 移除 Stack
docker stack rm welcome_stack
10.4 Stack 在云原生中的对应概念
| Swarm | Kubernetes | 含义 |
|---|---|---|
| Stack(stack.yml) | Application(Helm Chart / Kustomize) | 完整应用 |
| Service | Deployment + Service | 一组同质副本 |
| Task | Pod | 运行实例 |
| Manager Raft | etcd | 集群状态存储 |
| Routing Mesh | kube-proxy / Service | 服务暴露 |
| Overlay Network | CNI(Calico、Flannel) | 跨节点网络 |
这种映射是为什么"先学 Swarm 再上 K8s"是一条平滑的认知路径。
十一、弹性与自愈(Resilience & Self-Healing)
11.1 Desired State Reconciliation
Swarm 的核心承诺:You declare, I maintain(你声明,我维持)。
┌─────────────────────────────┐
│ Declared State (用户期望) │
│ "3 replicas of welcome" │
└─────────────┬───────────────┘
│ 持续比对
▼
┌─────────────────────────────┐
│ Actual State (实际运行) │
│ watching all nodes / tasks │
└─────────────┬───────────────┘
│
┌──────────┴──────────┐
│ 一致? │
▼ ▼
[Yes] [No → 修复]
├ 重建容器
├ 重新调度到其他节点
└ 重新拉取镜像
11.2 实验:手动杀掉容器
bash
# 找一个运行中的 welcome 容器
docker ps
# 强制删除
docker rm -f <CONTAINER_ID>
# 观察 Swarm 的反应
docker stack ps welcome_stack
可以看到一个 Task 进入 Shutdown/Failed,新的 Task 几秒内被创建。
11.3 实验:关掉一台 VM
直接 shutdown -h now 关掉 worker1:
bash
# 在 manager 上观察
docker node ls # worker1 STATUS 变为 Down
docker service ps welcome # worker1 上的 Task 重新调度到其他节点
只要至少有一个节点存活,服务就持续可用。
11.4 自愈的边界
自愈不是万能的:
- ❌ Manager Quorum 丢失(多数 Manager 宕机)→ 集群进入只读
- ❌ 共享存储故障 → 持久化数据丢失
- ❌ 镜像 Registry 不可达 → 新 Task 无法拉镜像
- ❌ Service 配置本身就错(如端口冲突)→ Task 反复重启
生产建议:
- 部署 3 或 5 个 Manager(容忍 1 或 2 个故障)
- 持久化数据用云存储或分布式存储(Ceph、GlusterFS)
- 镜像 Registry 做 HA
- 设置
restart_policy.max_attempts避免崩溃循环
十二、Practice:三个综合场景设计
实验要求设计三个综合应用编排概念的场景。下面给出参考方案:
场景一:可扩展博客平台
架构:Nginx(反向代理) + WordPress(应用) × N + MySQL(数据库)
stack.yml:
yaml
services:
proxy:
image: nginx:alpine
ports:
- "80:80"
configs:
- source: nginx_conf
target: /etc/nginx/nginx.conf
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
depends_on:
- wordpress
wordpress:
image: wordpress:latest
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wp
WORDPRESS_DB_PASSWORD: wp_pass
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root_pass
MYSQL_DATABASE: wordpress
MYSQL_USER: wp
MYSQL_PASSWORD: wp_pass
volumes:
- db_data:/var/lib/mysql
deploy:
replicas: 1
placement:
constraints: [node.labels.storage == ssd]
volumes:
db_data:
configs:
nginx_conf:
file: ./nginx.conf
设计要点:
- Nginx 作为统一入口,避免暴露多个端口
- WordPress 副本数动态扩缩
- 数据库仅 1 副本,绑定到带 SSD 标签的节点
- 用 Docker
configs管理 Nginx 配置(Swarm 原生特性)
场景二:流量突发型 API 服务(负载均衡 + 自动扩缩)
架构:HAProxy + Node.js API × N
yaml
services:
api:
image: myorg/node-api:1.0
deploy:
replicas: 5
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
restart_policy:
condition: on-failure
networks:
- backend
haproxy:
image: haproxy:alpine
ports:
- "80:80"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
deploy:
replicas: 2
networks:
- backend
networks:
backend:
driver: overlay
关键演示:
- 用
ab或wrk压测,观察 Routing Mesh 自动分发流量 - 手动
docker service scale api=10演示扩容效果 - 杀死随机容器,验证自愈
场景三:分布式跨节点部署 + 滚动更新
架构:3 节点集群同时跑 web + cache + worker
yaml
services:
web:
image: myorg/web:v1
ports:
- "80:80"
deploy:
mode: global # 每个节点恰好 1 个副本
update_config:
parallelism: 1
delay: 30s
order: start-first
cache:
image: redis:alpine
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
worker:
image: myorg/worker:v1
deploy:
replicas: 6
placement:
preferences:
- spread: node.id # 均匀分散
演示要点:
mode: global让每个节点都有一个 web 副本(典型场景:日志收集、监控 Agent)- 滚动更新时
order: start-first保证总有副本可用 placement.preferences.spread演示软调度策略
十三、横向对比:Compose vs Swarm vs Kubernetes
| 维度 | Docker Compose | Docker Swarm | Kubernetes |
|---|---|---|---|
| 主要场景 | 开发、测试 | 中小型生产 | 大规模生产 |
| 单机/多机 | 单机 | 多机 | 多机 |
| 学习曲线 | 低 | 中 | 高 |
| 自愈 | 有限(restart) | ✅ | ✅ |
| 滚动更新 | ❌ | ✅ | ✅(更细粒度) |
| 服务发现 | DNS(项目内) | DNS + VIP | DNS + Service |
| 负载均衡 | 需外部组件(如 Nginx) | 内置 Routing Mesh | kube-proxy + Service |
| 持久化卷 | local | local + 插件 | PV/PVC 丰富生态 |
| 网络策略 | ❌ | 有限 | NetworkPolicy |
| 自动扩缩 | ❌ | 手动 | HPA / VPA / Cluster Autoscaler |
| 生态规模 | 小 | 中 | 极大 |
| 配置复杂度 | 低 | 中 | 高 |
选型建议:
- 个人项目 / CI 测试 → Compose
- 公司内部工具 / 中小型 SaaS → Swarm(运维简单)
- 大型互联网产品 / 多团队协作 → Kubernetes
十四、云原生视角的升华
14.1 编排是云原生的"操作系统"
云原生应用的核心特征:
- 微服务化:每个服务独立部署、独立伸缩
- 容器化封装:镜像即应用
- 动态管理:编排平台调度
- DevOps:CI/CD 自动化
- 可观测性:日志、指标、链路追踪
编排平台是把这些特征落地的基础设施。Swarm 是入门,Kubernetes 是工业标准,云厂商封装的 ECS/AKS/GKE 是托管版。
14.2 与公有云编排服务的对应
| 概念 | AWS | Azure | GCP |
|---|---|---|---|
| 托管 K8s | EKS | AKS | GKE |
| 容器服务 | ECS / Fargate | Container Instances | Cloud Run |
| 镜像仓库 | ECR | ACR | GCR / Artifact Registry |
| 服务网格 | App Mesh | Open Service Mesh | Anthos Service Mesh |
| Serverless 容器 | Fargate | Container Apps | Cloud Run |
学会 Swarm 的核心思想后,过渡到这些托管服务只是"配置语法的差异"。
14.3 编排引发的工程范式变化
- Pet vs Cattle(宠物 vs 牲畜):不再给单台服务器起名字,机器是可替换的
- Immutable Infrastructure(不可变基础设施):变更靠重建,不靠 SSH 修改
- GitOps:Git 仓库是唯一可信来源,编排平台同步状态
- Observability First(可观测优先):日志、指标、链路追踪三位一体
- SRE 模型:可靠性目标量化(SLI/SLO/SLA),自动化优于人工
14.4 编排平台不能解决的问题
诚实地讲,编排不是银弹:
- ❌ 不能让设计糟糕的应用变好
- ❌ 不能消除分布式系统固有的复杂性(一致性、可观测性、网络分区)
- ❌ 不能取代良好的测试和监控
- ❌ 不能自动优化数据库 schema 设计
编排平台只让"做正确的事"变得更容易,但"什么是正确的事"还需工程师判断。
十五、实验问题答案汇总
Q1:Scaling 场景中默认自动分配的端口范围?
Linux 系统的 ephemeral port range(临时端口),通常是 32768-60999(可通过 cat /proc/sys/net/ipv4/ip_local_port_range 查询)。Docker 从此范围中为容器随机选择一个空闲端口绑定。
Q2:反向代理场景中涉及的 Docker 概念?
完整清单:
- Service (Compose 服务):
proxy和web - Bridge Network:Compose 自动创建的项目内网络
- Internal DNS:Docker daemon 内置的 DNS,把服务名解析为副本 IP 列表
- Service Discovery:通过名字访问,无需关心 IP
- Load Balancing:Nginx upstream 在多副本间轮询
- Volume Mount(bind mount):把宿主机文件挂到容器
- Port Publishing:宿主机 80 ↔ 容器 80
- depends_on:声明启动依赖
十六、结语
本实验沿着 Compose → Swarm → 云原生 的路径,完整呈现了容器编排的核心概念:
✅ 用 Compose 完成单机多容器声明式管理
✅ 用 Nginx 反向代理理解负载均衡的"内部网络 + 服务发现 + LB 算法"三要素
✅ 用 Swarm 把单机思维扩展到分布式集群
✅ 用 Service 与 Task 的二分理解声明式编排
✅ 用滚动更新与 Drain 体会零停机运维
✅ 用 Routing Mesh 感受云原生的"任意入口"理念
✅ 用 Stack 走向 Infrastructure as Code
✅ 用自愈机制理解 Desired State Reconciliation
容器编排不是技术终点,而是云原生工程思维的起点------从命令式到声明式、从宠物到牲畜、从单机到集群、从手工到自动。理解了这些原则,无论后续转向 Kubernetes、Service Mesh,还是 Serverless 容器,都只是同一思想的不同表达。
🌟 真正的编排能力,不在于背熟命令,而在于以"系统的眼光"去看待应用的部署、运行与演进。
参考资料
- Docker 官方文档:https://docs.docker.com/engine/swarm/
- Docker Compose 规范:https://docs.docker.com/compose/compose-file/
- Raft 共识算法:https://raft.github.io/
- The Twelve-Factor App:https://12factor.net/
- Kubernetes vs Docker Swarm:https://kubernetes.io/docs/concepts/
完成于云计算 Lab 4 实验后,欢迎在评论区交流讨论。如果对你有帮助,点赞收藏支持一下!