云计算实验笔记(四):容器编排(Container Orchestration)

本文系统梳理云计算 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)

要让一个服务运行多个实例,必须解决两个冲突:

  1. 容器名冲突container_name 是唯一的,多实例时必须去掉
  2. 端口冲突 :宿主机端口只能被一个容器绑定,多实例不能写 "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;
    }
  }
}

核心机制

  1. upstream backend { server web:80; }:定义后端服务池,地址写服务名 web,而不是某个 IP
  2. Docker 内置 DNS 会把 web 解析为该服务所有副本的 IP 列表
  3. 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-1web-2web-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 STATECURRENT 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。

工作原理:

  1. Swarm 在所有节点的内核创建一个名为 ingress 的 overlay 网络
  2. 节点收到流量后,通过 IPVS(Linux 内核负载均衡)路由到运行 Task 的节点
  3. 整个过程对客户端完全透明

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

关键演示

  • abwrk 压测,观察 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 编排是云原生的"操作系统"

云原生应用的核心特征:

  1. 微服务化:每个服务独立部署、独立伸缩
  2. 容器化封装:镜像即应用
  3. 动态管理:编排平台调度
  4. DevOps:CI/CD 自动化
  5. 可观测性:日志、指标、链路追踪

编排平台是把这些特征落地的基础设施。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 概念?

完整清单:

  1. Service (Compose 服务):proxyweb
  2. Bridge Network:Compose 自动创建的项目内网络
  3. Internal DNS:Docker daemon 内置的 DNS,把服务名解析为副本 IP 列表
  4. Service Discovery:通过名字访问,无需关心 IP
  5. Load Balancing:Nginx upstream 在多副本间轮询
  6. Volume Mount(bind mount):把宿主机文件挂到容器
  7. Port Publishing:宿主机 80 ↔ 容器 80
  8. depends_on:声明启动依赖

十六、结语

本实验沿着 Compose → Swarm → 云原生 的路径,完整呈现了容器编排的核心概念:

✅ 用 Compose 完成单机多容器声明式管理

✅ 用 Nginx 反向代理理解负载均衡的"内部网络 + 服务发现 + LB 算法"三要素

✅ 用 Swarm 把单机思维扩展到分布式集群

✅ 用 Service 与 Task 的二分理解声明式编排

✅ 用滚动更新与 Drain 体会零停机运维

✅ 用 Routing Mesh 感受云原生的"任意入口"理念

✅ 用 Stack 走向 Infrastructure as Code

✅ 用自愈机制理解 Desired State Reconciliation

容器编排不是技术终点,而是云原生工程思维的起点------从命令式到声明式、从宠物到牲畜、从单机到集群、从手工到自动。理解了这些原则,无论后续转向 Kubernetes、Service Mesh,还是 Serverless 容器,都只是同一思想的不同表达。

🌟 真正的编排能力,不在于背熟命令,而在于以"系统的眼光"去看待应用的部署、运行与演进。


参考资料


完成于云计算 Lab 4 实验后,欢迎在评论区交流讨论。如果对你有帮助,点赞收藏支持一下!

相关推荐
谢白羽2 分钟前
SGLang模型加载过程笔记
笔记·llm·论文·agent·vllm·大模型部署·sglang
BugShare3 分钟前
告别来回切换,Navop:一站式整合数据库、SSH、终端与 AI 的开发运维工作台
运维·数据库·ssh
奈斯先生Vector15 分钟前
从“能调用”到“可替换”:Coze 多模型 API 编排层设计指南
linux·运维·人工智能·ubuntu·平面·ios
AAA代码批发商22 分钟前
Days 32 Linux 多线程:线程分离属性、多进程 vs 多线程、线程同步互斥锁、信号量
linux·笔记
技术不支持24 分钟前
wsl 离线安装 Debian 11, Debian 12等指定版本
运维·debian
Databuff26 分钟前
使用Pinpoint作分布式链路跟踪系统
运维·分布式·运维开发·开源软件
李少兄29 分钟前
Linux 服务器从零部署 kkFileView 完全指南(Docker 方案)
linux·服务器·docker
小张同学a.30 分钟前
OpenStack 私有云实战 3—— Neutron 网络服务与 Dashboard 控制台部署
linux·运维·服务器·openstack
2301_旺仔35 分钟前
【Docker 镜像仓库】
docker·云原生·仓库
味悲36 分钟前
HAProxy 负载均衡实战:从环境搭建到七层/四层/HTTPS 全解析
运维·https·负载均衡