云计算实验笔记(四):容器编排(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 实验后,欢迎在评论区交流讨论。如果对你有帮助,点赞收藏支持一下!

相关推荐
kukubuzai1 小时前
Docker Note
linux·运维·docker
惜年_night1 小时前
Docker部署05-GitLab的CI-CD发布
ci/cd·docker·gitlab
自小吃多2 小时前
某志步进电机驱动器故障排查标准流程
笔记
大貔貅喝啤酒2 小时前
pip 国内镜像源大全【测试 / 自动化开发常备】
运维·自动化·pip·国内镜像源
hj2862512 小时前
Linux网络基础一
linux·运维
杨某不才2 小时前
内网环境下,使用Docker安装Elasticsearch分词器插件
elasticsearch·docker·jenkins
zhangrelay2 小时前
后智能时代智能体推演预测娱乐文-节选-
笔记·学习·娱乐
小碗羊肉2 小时前
【Agent笔记 | 第六篇】Agent关键组件
笔记·agent
云计算磊哥@2 小时前
运维开发宝典023-WEB网站服务
运维·前端·运维开发