Docker 基础体系 · 第 60/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。

Docker Swarm 基础与边界:Service、Task、Overlay、Secret 和滚动更新

Docker Swarm 是 Docker Engine 内置的集群编排模式。它把多个 Docker Engine 组织成一个集群,由管理节点维护期望状态,由调度器把工作分配到节点,再由各节点上的 Agent 创建和监控容器。

Swarm 适合解释和实践以下问题:

  • 一个服务如何从“运行一个容器”变成“维持多个副本”;
  • 容器为什么会被重新创建,以及如何被调度到其他节点;
  • 不同节点上的容器如何通过 Overlay 网络通信;
  • 密码、证书等敏感配置如何传入容器;
  • 服务更新时如何逐批替换旧任务;
  • Swarm 与 docker compose up、镜像仓库、持久化状态和生产交付之间的边界在哪里。

本文以现代 Docker Engine、BuildKit 和 Compose 规范为背景,但示例明确针对 Linux 容器 Swarm 集群。Windows 容器、Windows 节点上的网络和 Secret 文件行为不能直接套用本文结论。


1. Swarm 的基本模型:集群、节点、服务和任务

1.1 Swarm 集群和节点角色

执行以下命令会把当前 Docker Engine 初始化为一个 Swarm:

docker swarm init --advertise-addr 10.0.0.11

成功后通常会输出一个用于加入其他节点的命令,例如:

docker swarm join --token SWMTKN-... 10.0.0.11:2377

其中:

  • 10.0.0.11 是其他节点能够访问的管理节点地址;
  • 2377/tcp 用于 Swarm 管理面通信;
  • 加入命令中的 token 用于节点认证,不应公开粘贴到公共日志中;
  • 初始化节点默认同时是 manager 和 worker。

在 Linux 防火墙和云安全组中,常见需要放通:

  • 2377/tcp:集群管理面;
  • 7946/tcp7946/udp:节点发现与控制通信;
  • 4789/udp:Overlay 数据面,默认 VXLAN 通常使用该端口。

实际端口和加密选项应以部署方式、Docker 版本和网络环境为准。只开放 2377 而忽略 Overlay 所需通信,可能导致节点已经成功加入,但跨节点容器无法通信。

查看节点:

docker node ls

典型输出:

ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS
abc... *                      manager-1  Ready     Active          Leader
def...                        worker-1   Ready     Active
ghi...                        worker-2   Ready     Active

Ready 表示节点当前可参与集群;Active 表示调度器可以向该节点放置任务;Leader 表示该 manager 当前是 Raft 共识组的领导者。

Swarm manager 负责:

  1. 保存集群状态;
  2. 选举 Raft leader;
  3. 接受服务创建、更新和删除请求;
  4. 调度任务;
  5. 把服务的期望状态传播给节点。

worker 上的 Docker Engine Agent 负责:

  1. 接收分配给本节点的任务;
  2. 创建、启动和停止容器;
  3. 把任务状态汇报给 manager;
  4. 持续尝试把本节点任务恢复到指定状态。

manager 也可以运行工作负载,但生产集群中经常使用:

docker node update --availability drain manager-1

使 manager 只管理集群而不接受新任务。drain 会把已经运行在该节点上的普通服务任务迁走,但不会让 manager 失去管理功能。

1.2 Raft 仲裁与管理面故障

多个 manager 使用 Raft 保存一致的集群状态。若 manager 数量为 NN,形成多数派所需的节点数为:

Q=N2+1Q = \left\lfloor \frac{N}{2} \right\rfloor + 1

例如:

  • 1 个 manager:需要 1 个;
  • 3 个 manager:需要 2 个;
  • 5 个 manager:需要 3 个。

如果 3 个 manager 中有 2 个同时失效,剩下的 1 个通常无法提交新的集群状态。此时可能无法执行创建服务、扩容或滚动更新,但已经在 worker 上运行的容器不一定立即停止。原因是:

  • 任务本身由 worker 上的 Docker Engine 运行;
  • 新的调度和状态提交依赖 manager 的 Raft 多数派。

因此,“管理面不可用”和“业务容器立即不可用”是两个不同故障。恢复 manager 多数派后,集群才能继续正常收敛。不要在不了解 Raft 数据状态的情况下随意执行破坏性的集群重建操作。


2. Service 和 Task:期望状态如何变成容器

2.1 Service 是期望状态

Swarm 中的 Service 是一组具有相同规格的任务,以及对这组任务的期望状态描述。

例如:

docker service create \
  --name web \
  --replicas 3 \
  --publish published=8080,target=80 \
  nginx:1.27

这条命令表达的不是“启动一个 nginx 容器”,而是:

创建名为 web 的服务,使它长期保持 3 个使用 nginx:1.27 镜像、监听容器 80 端口并发布宿主机 8080 端口的任务。

查看服务:

docker service ls

可能得到:

ID             NAME   MODE         REPLICAS   IMAGE
p3...          web    replicated   3/3        nginx:1.27

3/3 的含义是当前运行中的任务数为 3,期望任务数也是 3。它不是三个“固定容器名”,而是三个任务槽位。

服务可以用声明式方式描述:

services:
  web:
    image: nginx:1.27
    deploy:
      replicas: 3
      restart_policy:
        condition: on-failure

在单机 Compose 中执行:

docker compose up

不会启动 Swarm Service;它启动的是当前 Docker Engine 上的普通 Compose 容器。只有使用 Swarm 部署命令时,deploy 相关配置才进入 Swarm 服务模型:

docker stack deploy -c compose.yaml demo

因此,Compose 文件格式相同,不等于运行模型相同。

2.2 Task 是 Service 的一次任务实例

Task 是 Service 期望运行的一个具体实例。它通常对应一个被 Swarm 管理的容器,但任务本身还包含:

  • 所属 Service;
  • 任务槽位;
  • 目标节点;
  • 镜像和命令;
  • 网络、挂载、Secret 等配置;
  • 当前状态;
  • 失败原因和重启历史。

查看任务:

docker service ps web

可能输出:

ID             NAME      IMAGE       NODE       DESIRED STATE   CURRENT STATE
a1...          web.1     nginx:1.27  worker-1   Running         Running 2 minutes ago
b2...          web.2     nginx:1.27  worker-2   Running         Running 2 minutes ago
c3...          web.3     nginx:1.27  worker-1   Running         Running 2 minutes ago

如果 web.2 所在节点故障,Swarm 不会把同一个容器“搬到”另一个节点。容器不能在节点间迁移。调度器会创建一个新的任务,例如:

d4...          web.2     nginx:1.27  worker-1   Running         Running 10 seconds ago
b2...          web.2     nginx:1.27  worker-2   Shutdown         Rejected 1 minute ago

这里仍然是任务槽位 web.2,但任务 ID 已经变化。任务是不可变的执行记录;失败后通常创建替代任务,而不是修改旧任务使其复活。

可以把 Service 的基本收敛过程写成:

期望副本数当前可运行副本数\text{期望副本数} - \text{当前可运行副本数}

当差值为正,调度器尝试创建任务;当差值为负,调度器停止多余任务。实际调度还必须满足节点资源、标签、约束、端口和卷等条件。

例如,期望副本数为 3,但只有两个节点允许运行该服务,且每个节点最多只能容纳一个副本,那么结果可能长期为 2/3。这不是 Swarm 忽略了配置,而是约束条件使期望状态暂时不可满足。

2.3 调度约束、偏好和资源边界

可以用节点标签限制任务位置:

docker node update --label-add disk=ssd worker-1

服务使用约束:

docker service create \
  --name db \
  --constraint 'node.labels.disk == ssd' \
  --mount type=volume,source=dbdata,target=/var/lib/postgresql/data \
  postgres:16

约束是硬条件。若没有满足 disk=ssd 的节点,任务会处于 PendingRejected 等非运行状态,而不是自动忽略约束。

资源限制和预留也会影响调度:

services:
  api:
    image: example/api:2025-01-01
    deploy:
      replicas: 4
      resources:
        reservations:
          cpus: "0.50"
          memory: 256M
        limits:
          cpus: "1.00"
          memory: 512M

reservations 用于调度判断,表示任务至少需要多少资源;limits 是运行时上限。若节点可分配资源不足,任务不会被安排到该节点。实际内存不足时,Linux 内核仍可能触发 OOM,导致任务退出并被重启。


3. Overlay 网络:跨节点容器如何通信

3.1 Overlay 的作用和范围

普通 bridge 网络只存在于单个 Docker Engine。若 worker-1 上的容器要访问 worker-2 上的容器,就需要一种跨 Docker 主机的虚拟网络,Swarm 使用 Overlay 网络

创建网络:

docker network create \
  --driver overlay \
  --attachable \
  app-net

--attachable 允许普通容器使用该网络;没有该选项时,主要供 Swarm Service 使用。

在 Stack 文件中,网络通常写成:

networks:
  app-net:
    driver: overlay

服务加入该网络后,Swarm 会为不同节点上的任务建立逻辑上的二层网络。Linux Docker 的常见实现使用 VXLAN 封装,但 VXLAN 属于实现细节,不能把某个内核接口表现当作应用层协议保证。

Overlay 中至少存在两类通信:

  1. 控制面:传播网络成员、端点和服务发现信息;
  2. 数据面:真正承载容器之间的业务数据包。

可以使用加密数据面:

docker network create \
  --driver overlay \
  --opt encrypted \
  secure-net

encrypted 通常会增加 CPU 和网络开销。它不是对应用协议的替代,也不能解决容器内应用自身的认证授权问题。是否启用应结合节点间网络是否可信、性能和合规要求验证。

3.2 服务发现:VIP 与 DNSRR

默认情况下,Swarm Service 使用 VIP(Virtual IP)模式。服务名解析到一个虚拟 IP,访问该 VIP 时由 Docker 的服务发现和负载均衡逻辑把连接转发到任务。

例如:

docker service create \
  --name api \
  --network app-net \
  --replicas 3 \
  example/api:2025-01-01

同一 Overlay 网络中的其他服务可以使用:

http://api:8080

而不是查找具体任务 IP。

也可以使用 DNSRR:

services:
  api:
    image: example/api:2025-01-01
    networks:
      - app-net
    deploy:
      endpoint_mode: dnsrr

DNSRR(DNS round-robin)返回多个任务地址,由客户端选择目标。它适合客户端本身支持多地址发现的场景,但不能假设所有客户端都会正确地轮询、剔除失效地址或重新解析 DNS。VIP 模式通常对普通 HTTP 客户端更透明。

服务名解析只在加入同一网络的容器之间成立。宿主机直接执行:

curl http://api:8080

通常不能解析 Swarm 内部服务名;这是容器网络命名空间与宿主机 DNS 范围不同造成的。

3.3 发布端口与 Routing Mesh

创建服务时:

docker service create \
  --name web \
  --replicas 3 \
  --publish published=8080,target=80 \
  nginx:1.27

这里:

  • published=8080 是对外暴露的端口;
  • target=80 是容器内服务监听的端口。

默认发布模式是 ingress,也就是 Routing Mesh。访问任意一个 Swarm 节点的 8080,流量都可能被转发到集群中某个健康任务,而不要求该节点本地正好运行该任务。

客户端
  │
  ▼
任意 Swarm 节点:8080
  │  Routing Mesh / ingress
  ▼
某个 web task:80

这不代表应用获得了一个通用、高级的生产负载均衡器。它解决的是 Docker 层面的端口入口和任务转发,仍需考虑:

  • 长连接和连接分布;
  • 客户端源地址是否被保留;
  • TLS 终止位置;
  • 应用层健康检查;
  • 外部负载均衡器和 DNS;
  • 节点防火墙与可达性。

如果使用 host 发布模式:

services:
  web:
    image: nginx:1.27
    ports:
      - target: 80
        published: 8080
        protocol: tcp
        mode: host

请求只会进入运行该任务的节点。对于同一节点上的服务任务,端口还会发生冲突;因此 host 模式更接近“每个节点直接监听端口”,不等价于 ingress 模式。


4. Secret:敏感配置如何进入任务

4.1 Secret 的存储和生命周期

Docker Secret 用于密码、API Token、私钥等敏感数据。创建 Secret 前,当前 Engine 必须处于 Swarm 模式:

printf 'correct horse battery staple\n' | \
  docker secret create db_password -

查看 Secret:

docker secret ls

Secret 的内容不会直接出现在普通 docker service inspect 输出中。Swarm manager 将 Secret 纳入集群状态,并通过加密的管理通道发送给被授权的任务。Linux Swarm 中,任务通常在容器内通过内存文件系统暴露 Secret,而不是通过环境变量传入。

创建 Secret 后不能原地修改。若密码需要更换,应创建新名称:

printf 'new-password\n' | docker secret create db_password_v2 -

然后更新服务,使其使用新 Secret。旧 Secret 只有在不再被任何服务使用后才应删除。

4.2 Secret 在容器内的表现

Stack 文件示例:

services:
  app:
    image: example/app:2025-01-01
    secrets:
      - source: db_password_v2
        target: db_password
        uid: "1000"
        gid: "1000"
        mode: 0400

secrets:
  db_password_v2:
    external: true

部署前必须先创建名为 db_password_v2 的 Swarm Secret:

docker stack deploy -c stack.yaml demo

容器内通常可见:

/run/secrets/db_password

应用应读取文件内容,而不是读取名为 DB_PASSWORD 的环境变量。例如:

cat /run/secrets/db_password

会显示密码内容,因此在生产环境不要把这个命令写入日志或诊断脚本。

target 决定容器内文件名;uidgidmode 决定 Linux 文件的所有者和权限。若应用以非 root 用户运行,却没有读取权限,典型表现是:

open /run/secrets/db_password: permission denied

Secret 能减少密码出现在环境变量、命令行和 Compose 文件中的机会,但它不是应用级密钥管理的全部方案:

  • 进程只要有权限读取文件,就能获得明文;
  • 应用日志可能意外打印 Secret;
  • Secret 被加载到应用内存后,Docker 无法控制应用如何使用它;
  • 需要轮换时必须设计兼容旧值和新值的更新顺序。

4.3 Secret 与 Compose 的边界

下面的命令:

docker compose up -d

使用的是单机 Compose 生命周期。Compose 也支持 secrets 字段,但其具体实现取决于本地 Compose 和平台;它不自动获得 Swarm manager 的 Raft Secret 语义。

下面的命令:

docker stack deploy -c stack.yaml demo

才使用 Swarm Secret 和 Service 模型。Stack 文件中的 deploy、滚动更新、副本数等配置,也主要在这里生效。


5. 用 Stack 完成一个可运行的服务部署

先准备一个 Secret:

printf 'demo-password\n' | docker secret create demo_db_password -

准备 stack.yaml

services:
  web:
    image: nginx:1.27
    networks:
      - app-net
    ports:
      - target: 80
        published: 8080
        protocol: tcp
        mode: ingress
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
        monitor: 30s
        max_failure_ratio: 0
      rollback_config:
        parallelism: 1
        order: stop-first
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 60s
    secrets:
      - source: demo_db_password
        target: db_password
        mode: 0400

networks:
  app-net:
    driver: overlay

secrets:
  demo_db_password:
    external: true

部署:

docker stack deploy -c stack.yaml demo

Stack 会给资源增加名前缀,因此 Service 名称通常是:

demo_web

检查部署:

docker stack services demo
docker stack ps demo
docker service ps demo_web

从任意能访问 Swarm 节点的机器测试:

curl http://10.0.0.11:8080

若返回 nginx 的欢迎页面,说明至少完成了以下链路:

  1. manager 接受 Stack 配置;
  2. Service 产生 3 个副本任务;
  3. scheduler 把任务分配给可用节点;
  4. 节点拉取 nginx:1.27
  5. 容器加入 Overlay 网络;
  6. ingress 端口接收 8080
  7. Routing Mesh 把请求送到某个任务的 80 端口;
  8. Secret 被挂载到任务容器的 /run/secrets/db_password

docker stack deploy 默认不会替你构建应用镜像。若文件中写了:

build: .

不能把它当作集群中可靠的构建步骤。常见流程是使用 BuildKit 在 CI 中构建并推送镜像:

docker buildx build \
  --platform linux/amd64 \
  -t registry.example.com/example/api:2025-03-08 \
  --push .

然后在 Stack 文件中引用该镜像。所有可能运行任务的节点都必须能访问镜像仓库,或者镜像已经预置在节点上。多架构镜像还必须包含目标节点架构,否则任务可能在某些节点上因镜像平台不匹配而失败。


6. 滚动更新:Service 如何替换旧任务

6.1 更新不是修改容器,而是创建新任务

执行:

docker service update \
  --image nginx:1.27.1 \
  demo_web

Swarm 不会把已经运行的容器中的镜像文件替换掉。它会修改 Service 的期望规格,然后按更新策略逐步:

  1. 选出需要更新的旧任务;
  2. 创建或启动新规格任务;
  3. 等待新任务达到可接受状态;
  4. 停止旧任务;
  5. 继续下一批任务。

因此,任务 ID 和容器 ID 会变化。应用必须把容器视为可替换实例,而不能把本地容器文件系统当作永久资产。

6.2 parallelismdelayorder

假设服务有 5 个副本:

web.1 web.2 web.3 web.4 web.5

设置:

update_config:
  parallelism: 2
  delay: 20s
  order: stop-first

更新过程可以抽象为:

初始:旧旧旧旧旧

批次 1:
停止旧任务 1、2
启动新任务 1、2
等待 monitor / 状态检查
得到:新新旧旧旧

等待 20 秒

批次 2:
停止旧任务 3、4
启动新任务 3、4
得到:新新新新旧

等待 20 秒

批次 3:
停止旧任务 5
启动新任务 5
结束:新新新新新

parallelism 表示每批处理多少个任务。它不是“同时最多运行多少个容器”的精确保证,因为 start-first 会使新旧任务短暂并存。

order 有两个常见值:

  • stop-first:先停止旧任务,再启动新任务;
  • start-first:先启动新任务,再停止旧任务。

start-first 可以缩短因旧任务停止带来的服务空窗,但需要额外资源,并且会受到端口、卷和应用启动逻辑的限制。例如:

  • 单节点只有 1 GB 可用内存,旧任务占用 700 MB,新任务无法并存;
  • 使用 host 模式发布同一个端口,新任务无法在同一节点绑定该端口;
  • 使用同一个独占本地卷,两个任务不能同时挂载;
  • 应用不支持两个实例同时处理同一份状态。

因此,start-first 不是无条件的零停机保证。

6.3 更新成功和失败如何判定

可以配置:

update_config:
  monitor: 30s
  failure_action: rollback
  max_failure_ratio: 0

含义是:

  • monitor: 30s:更新任务后观察一段时间;
  • failure_action: rollback:更新失败时自动回滚;
  • max_failure_ratio: 0:允许的失败比例为 0。

失败可能来自:

  • 镜像拉取失败;
  • 容器无法启动;
  • 端口或挂载冲突;
  • 节点不满足约束;
  • 进程在观察窗口内退出;
  • 健康检查失败并导致任务不健康。

健康检查必须由镜像或服务配置定义。例如:

services:
  api:
    image: example/api:2025-03-08
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 3s
      retries: 3

健康检查只能说明容器内部探针是否成功,不能自动验证外部依赖、业务数据一致性或真实用户请求是否正常。一个能返回 200/health,并不等于数据库迁移已经兼容、支付接口可用或消息消费没有积压。

查看更新状态:

docker service inspect \
  --pretty \
  demo_web

以及:

docker service ps demo_web --no-trunc

如果任务失败,--no-trunc 往往能看到更完整的错误原因,例如镜像不存在、端口冲突或健康检查失败。


7. 回滚:恢复 Service 规格,而不是恢复容器现场

手动回滚:

docker service rollback demo_web

回滚的是 Service 的上一版本规格,例如上一版本的:

  • 镜像;
  • 环境变量;
  • 网络;
  • Secret;
  • 挂载;
  • 更新配置。

它不是虚拟机快照,也不会恢复新版本任务在外部数据库中已经执行的写入。若新版本已经完成不可逆的数据迁移,单纯回滚应用镜像可能仍然无法工作。

Stack 文件可以配置回滚策略:

deploy:
  rollback_config:
    parallelism: 1
    delay: 5s
    order: stop-first

典型的自动失败路径是:

旧版本服务
   │
   ▼
更新第 1 批任务
   │
   ├── 新任务启动并保持正常
   │       └── 继续下一批
   │
   └── 新任务失败超过阈值
           │
           ▼
       failure_action=rollback
           │
           ▼
       按 rollback_config 恢复旧规格

回滚同样需要旧镜像可拉取、节点可用、端口和卷条件满足。若旧镜像只存在于已经损坏的本地缓存中,或旧版本依赖的 Secret 已被删除,回滚也可能失败。


8. 副本、端口和共享状态的边界

8.1 副本只复制无状态计算

将服务扩容为 5 个副本:

docker service scale demo_web=5

只会增加 5 个相同的任务实例。它不会自动:

  • 复制数据库数据;
  • 共享本地文件;
  • 让进程内存中的 Session 互通;
  • 让本地上传目录在所有节点可见;
  • 让定时任务自动去重。

如果应用把 Session 存在进程内存中,请求被转发到不同副本时可能表现为“随机掉线”。解决方案通常是外部 Session 存储、无状态 Token,或者经过明确验证的会话粘性方案。

8.2 本地 Volume 不是集群共享存储

下面的挂载:

volumes:
  - type: volume
    source: appdata
    target: /var/lib/app

如果使用默认 local volume driver,它通常只存在于任务所在的那个 Docker 节点。任务从 worker-1 重调度到 worker-2 后,两个节点上的 appdata 可能是两个不同的数据目录。

因此,下面的因果链是危险的:

任务故障
  → 新任务被调度到另一节点
  → 自动挂载同名 local volume
  → 误以为拿到了原数据

实际上,新节点可能得到一个空目录。需要持久化和跨节点迁移时,应使用外部存储、支持集群的 Volume Driver,或把有状态系统放在适合其一致性模型的专用平台上。数据库的复制、故障转移和备份也不能由 Swarm 副本数替代。

8.3 定时任务和唯一消费者

把一个定时任务服务设置为多个副本,通常会导致每个副本都执行同一任务:

deploy:
  replicas: 3

Swarm 的副本控制只保证有 3 个任务,不保证“每个时间点只有一个任务执行某个业务动作”。唯一执行者需要分布式锁、队列消费者竞争、数据库约束或专用调度机制。


9. 常见失败表现与诊断路径

9.1 服务长期处于 0/3

先看任务:

docker service ps demo_api --no-trunc

再看节点:

docker node ls

常见原因包括:

  • 节点处于 Drain
  • 约束没有匹配节点;
  • CPU 或内存 reservation 不满足;
  • 镜像仓库不可达;
  • 私有仓库认证缺失;
  • 端口已被占用;
  • Secret 或 Config 不存在;
  • 节点架构与镜像不匹配。

不要只看 docker service ls 的副本数字;真正的失败原因通常在任务历史中。

9.2 跨节点服务名能解析,但请求失败

这说明 DNS 服务发现可能工作正常,但不代表数据面完整。应依次检查:

docker network inspect app-net
docker service inspect demo_api
docker service ps demo_api

然后验证:

  1. 两个服务是否加入同一个 Overlay 网络;
  2. 应用是否监听 0.0.0.0 而不是只监听 127.0.0.1
  3. 应用监听端口是否与 target 一致;
  4. 节点之间的 79464789 是否被防火墙阻断;
  5. 容器内部是否存在额外的应用层认证或 TLS 配置。

应用只监听 127.0.0.1:8080 时,即使 Docker 已正确发布端口,其他容器也无法通过容器 IP 访问它,因为该进程只接受本地回环接口连接。

9.3 更新卡住或自动回滚

检查:

docker service ps demo_web --no-trunc
docker service inspect --pretty demo_web

重点寻找:

  • Rejected:调度、端口、挂载或约束问题;
  • Failed:进程退出、探针失败或启动错误;
  • Preparing 长时间不变:镜像拉取、节点或仓库问题;
  • 新任务运行但业务失败:健康检查过于简单,或外部依赖不兼容。

若更新使用 start-first,还要检查是否需要额外资源。若使用 host 端口,检查新旧任务是否无法同时绑定同一端口。


10. 镜像、标签与可复现交付

使用可变标签:

docker service update --image example/api:latest demo_api

会带来不确定性:

  • 不同节点拉取时可能得到不同时间推送的内容;
  • latest 无法表达发布版本;
  • 回滚时难以确认原始镜像;
  • 仓库认证或镜像清理可能使旧版本无法重新拉取。

更可控的方式是使用不可变版本标签,最好记录镜像 digest:

example/api@sha256:...

镜像 digest 指向具体内容,同一 digest 具有更强的可重复性。但仍需保留镜像仓库中的内容,不能因为 Service 记录了 digest 就假设仓库永远可用。

docker stack deploy 的交付链通常是:

源码提交
  → CI 使用 BuildKit 构建镜像
  → 推送带版本的镜像
  → 运行测试和安全检查
  → 部署 Stack
  → 观察 Service、Task、日志和业务指标
  → 必要时回滚 Service

Swarm 负责运行时编排,不会自动替代 CI、镜像仓库、数据库迁移工具、灰度控制、容量评估和运行手册。


11. Swarm 能保证什么,不能保证什么

能够保证的核心性质

在 manager 多数派可用、节点和镜像满足条件时,Swarm 会持续尝试使实际状态接近期望状态:

  • 维持指定数量的任务;
  • 在节点失效后重新调度任务;
  • 按策略执行服务更新;
  • 在 Overlay 网络中提供服务发现;
  • 将授权的 Secret 传递给对应任务;
  • 保存 Service、Task 和节点关系的集群状态。

不能自动保证的性质

Swarm 不自动保证:

  • 应用接口一定正确;
  • 数据库写入不会丢失;
  • 本地 Volume 会跨节点复制;
  • 多副本之间状态自动一致;
  • 更新一定零停机;
  • 回滚一定能撤销数据库迁移;
  • 多个副本中的定时任务只执行一次;
  • 单个健康检查成功就代表完整业务可用;
  • 所有节点都能拉取私有镜像;
  • 运行中的容器可以无损迁移到另一个节点。

理解这个边界的关键是区分三种状态:

  1. Service 期望状态:例如副本数为 3、镜像为某个版本;
  2. Task 运行状态:某个具体任务是否已启动、失败或被替换;
  3. 业务状态:数据库、队列、文件、外部 API 和用户会话是否正确。

Swarm 主要负责前两者。第三者必须由应用架构、存储系统、交付流程和运维验证共同负责。


系列导航与关联阅读

官方资料

本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。