1. 为什么我还在用 Docker Swarm 而不是 K8s先把结论摆在前面这套 Swarm 集群我用了快两年跑着十几个生产服务日均处理几十万次请求中间除了两次硬件故障触发自动迁移没有因为编排层本身挂过。这不是说 Swarm 比 K8s 强而是想说——选型这件事合适比先进重要得多。我最初接触容器编排是从 K8s 开始的文档啃了一大堆集群也搭起来了但真正要维护的时候发现问题来了团队里没人能完全吃透那一堆 CRD、Operator、Ingress Controller 的组合拳出问题排查链路太长。后来一个内部项目需要快速上线规模不大——三台机器、七八个服务、日请求量也就几万——我抱着试试看的心态用了 Swarm结果一用就是两年。Swarm 的核心价值在于它是 Docker 原生自带的编排能力不需要额外装一堆组件学习曲线几乎是平的。你只要会写docker-compose.yml稍微改几个字段就能变成docker stack deploy的部署文件。对于中小规模、服务数量在几十个以内、团队没有专职运维的场景Swarm 的投入产出比高得离谱。这篇文章我会把整套部署过程拆开讲清楚从集群初始化、节点管理、网络规划、存储方案到服务编排、滚动更新、监控告警再到实际踩过的坑和排查思路。适合那些手上有几台服务器、想把服务容器化但不想被 K8s 复杂度劝退的开发者也适合已经用了 Swarm 但想看看有没有更优实践的朋友。提醒一句Swarm 不是银弹。如果你的服务规模到了几百个微服务、需要精细的流量治理和自动扩缩容策略那还是老老实实上 K8s。但如果你的场景是十几台机器跑几十个服务Swarm 能让你少掉很多头发。2. 集群规划与初始化动手之前先想清楚这几件事2.1 节点角色怎么分管理节点到底要几个Swarm 的节点分两种角色Manager管理节点和Worker工作节点。Manager 负责集群状态维护、任务调度、API 响应Worker 只负责跑容器。这里有个关键点很多人会忽略Manager 节点默认也可以跑任务但生产环境我强烈建议把 Manager 设为只管理不干活。为什么因为 Manager 节点要维护 Raft 一致性协议节点之间需要频繁通信同步状态。如果 Manager 上还跑着吃 CPU 和内存的业务容器一旦负载高了Raft 心跳超时集群可能触发重新选举严重的时候整个集群会短暂不可用。我踩过这个坑——早期把 Manager 和 Worker 混在一起某次一个 Java 服务内存泄漏把节点压垮连带集群管理都出了问题。Manager 节点数量有个硬性要求必须是奇数推荐 3 个或 5 个。原因是 Raft 协议需要多数派Quorum才能完成写操作。3 个 Manager 能容忍 1 个挂掉5 个能容忍 2 个挂掉。2 个 Manager 反而比 1 个更脆弱因为任何一台挂了都凑不齐多数派。我现在的配置是 3 个 Manager N 个 WorkerManager 配置不高2C4G 足够Worker 根据业务负载来定。节点规划建议用表格理清楚节点角色数量配置建议职责是否跑业务Manager32C4G集群管理、调度、API否Worker按需按业务定运行容器任务是ManagerWorker不推荐-混合是2.2 网络规划Overlay 网络的坑比你想的多Swarm 内置了 Overlay 网络让不同节点上的容器能像在同一台机器上一样通信。听起来很美好但实际用起来有几个必须提前规划的点。第一Overlay 网络会带来额外的封装开销。容器间的跨节点通信会经过 VXLAN 封装MTU 会从默认的 1500 降到 1450 左右。如果你的应用对 MTU 敏感比如某些老旧的 RPC 框架可能会出现大包丢失、连接卡顿的问题。我的做法是能走 host 网络的就走 host必须跨节点通信的才用 Overlay。第二Overlay 网络的加密选项要慎用。--opt encrypted会启用 IPSec 加密安全性提升但性能下降明显实测吞吐量会掉 30% 以上。内网环境如果本身已经隔离得比较好没必要开这个。第三服务发现依赖 DNS 轮询。Swarm 内置的 DNS 会把服务名解析到所有副本的 IP客户端拿到的是轮询结果。这意味着它没有健康检查感知——如果某个副本挂了但还没被调度器剔除DNS 可能还会返回它的 IP。所以应用层最好自己做重试和熔断。我一般会创建两个网络一个frontend给对外服务用一个backend给内部服务通信用。这样既能隔离流量又方便后面做网络策略。# 创建 overlay 网络 docker network create \ --driver overlay \ --subnet 10.0.10.0/24 \ --attachable \ backend docker network create \ --driver overlay \ --subnet 10.0.20.0/24 \ --attachable \ frontend--attachable这个参数很关键加上之后单机的普通容器也能接入这个网络方便调试。2.3 存储方案数据持久化到底怎么选这是 Swarm 相比 K8s 最弱的一环。K8s 有 PV/PVC/StorageClass 一整套抽象Swarm 只有volume和bind mount两种而且默认不支持跨节点共享存储。我的实践经验是分场景处理无状态服务直接用 volume 或者不持久化随便调度到哪台机器都行。有状态但单副本的服务比如某些定时任务用节点标签约束把服务固定到特定节点然后用 bind mount 挂本地盘。需要跨节点共享数据的服务比如多个副本要读同一份文件必须自己搭 NFS 或者用云厂商的共享存储然后所有节点都挂载同一个目录再用 bind mount 引用。节点标签约束的用法# 给节点打标签 docker node update --label-add storagessd node-01 # 在 stack 文件中约束 deploy: placement: constraints: - node.labels.storage ssd注意Swarm 的 volume 默认是 local driver数据只存在容器所在节点。如果服务重新调度到别的节点数据就丢了。有状态服务一定要配合约束使用或者上共享存储。3. 核心部署实操从零到跑起第一个服务3.1 初始化集群与加入节点在选定的第一台 Manager 上执行docker swarm init --advertise-addr 10.0.0.1--advertise-addr必须指定否则多网卡机器上 Swarm 可能选错网卡导致其他节点连不上。执行完会输出加入集群的命令分 Manager 和 Worker 两种 token。在另外两台 Manager 上执行docker swarm join --token SWMTKN-xxx 10.0.0.1:2377然后回到第一台机器把新加入的 Manager 提升为管理角色其实用 manager token 加入的自动就是 Manager。Worker 节点用 worker token 加入即可。验证集群状态docker node ls应该能看到所有节点Manager 节点前面有Leader或Reachable标记。3.2 编写 stack 文件比 compose 多了什么Swarm 的部署文件本质上是 compose 文件的超集多了deploy这一层。我拿一个典型的 Web 服务举例version: 3.8 services: web: image: nginx:1.25-alpine ports: - 80:80 networks: - frontend deploy: replicas: 3 update_config: parallelism: 1 delay: 10s order: start-first restart_policy: condition: on-failure max_attempts: 3 resources: limits: cpus: 0.5 memory: 256M placement: constraints: - node.role worker api: image: myapp/api:v1.2.0 networks: - backend environment: - DB_HOSTdb deploy: replicas: 2 resources: limits: cpus: 1.0 memory: 512M networks: frontend: external: true backend: external: true几个关键字段解释一下update_config.order: start-first是滚动更新的策略。默认是stop-first先停旧容器再起新的会有短暂的服务中断。改成start-first会先起新容器健康后再停旧的实现零停机。但要注意端口冲突——如果服务占用了固定 host 端口start-first会因为端口被占而失败这时候只能用stop-first。resources.limits是硬限制超过会被 OOM Kill 或者 CPU 被限流。Swarm 不支持 requestsK8s 有所以调度时不会预留资源这点要注意——可能出现多个重负载服务被调度到同一节点的情况。placement.constraints用来控制调度位置。node.role worker确保业务不会跑到 Manager 上。3.3 部署与更新命令背后的逻辑部署docker stack deploy -c stack.yml myapp更新服务改了镜像版本后docker stack deploy -c stack.yml myappSwarm 会自动对比差异只更新变化的服务。查看更新进度docker service ps myapp_web --no-trunc会看到新旧任务的交替过程。如果更新卡住了可以回滚docker service rollback myapp_web实操心得滚动更新时parallelism不要设太大。我一般设 1最多 2。设太大如果新版本有问题会瞬间大面积故障。delay给 10-30 秒留出健康检查的时间窗口。3.4 健康检查配置别让坏容器继续接流量Swarm 支持 HEALTHCHECK但和 K8s 的 liveness/readiness 探针不一样——Swarm 的健康检查不会自动重启不健康的容器也不会把它从负载均衡里摘除。它只是标记状态需要配合restart_policy或者外部工具使用。healthcheck: test: [CMD, curl, -f, http://localhost:8080/health] interval: 30s timeout: 5s retries: 3 start_period: 40sstart_period很重要给应用启动留出时间避免刚启动就被判定为不健康。我见过有人没设这个Java 应用启动要 30 秒结果健康检查 10 秒就失败容器被反复重启。4. 监控、日志与故障排查实录4.1 监控方案Prometheus 怎么接进来Swarm 本身没有内置监控需要自己搭。我的方案是cAdvisor Node Exporter Prometheus Grafana。cAdvisor 负责采集容器指标但它有个问题在 Swarm 模式下cAdvisor 只能看到本节点的容器。所以需要在每个节点都部署一个 cAdvisor用global模式cadvisor: image: gcr.io/cadvisor/cadvisor:latest volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro deploy: mode: global resources: limits: memory: 256Mmode: global表示每个节点都跑一个副本这是 Swarm 特有的模式非常适合监控代理这类服务。Prometheus 配置里用dns_sd_configs自动发现 Swarm 服务scrape_configs: - job_name: cadvisor dns_sd_configs: - names: - tasks.cadvisor type: A port: 8080tasks.cadvisor是 Swarm 内置的 DNS 记录会返回所有副本的 IP。4.2 日志收集别再用 docker logs 了docker logs在单机时代够用但集群环境下你要一台台机器去查效率极低。我的做法是接入Loki Promtail轻量且和 Grafana 集成好。Promtail 同样用global模式部署采集/var/lib/docker/containers下的日志文件。配置里加上 Swarm 的元数据标签这样在 Grafana 里可以按服务名、节点、任务 ID 过滤。踩坑记录Docker 默认的 json-file 日志驱动如果不限制大小日志会把磁盘撑爆。一定要在/etc/docker/daemon.json里配置{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }改完要重启 Docker 才生效而且只对新容器生效老容器需要重建。4.3 常见问题速查表现象可能原因排查命令解决思路服务一直 Pending资源不足或约束不满足docker service ps --no-trunc检查节点资源、约束条件容器反复重启健康检查失败或 OOMdocker inspect container调整健康检查参数或内存限制跨节点通信失败Overlay 网络问题docker network inspect检查 MTU、防火墙 4789/7946 端口更新卡住不回滚更新策略配置问题docker service inspect手动 rollback 或调整 parallelismManager 失联Raft 心跳超时docker node ls检查网络延迟、Manager 负载DNS 解析失败服务名拼写或网络隔离docker exec c nslookup svc确认服务在同一网络4.4 独家避坑技巧技巧一用docker node promote/demote做 Manager 维护。需要重启 Manager 时先把它 demote 成 Worker等维护完再 promote 回来避免影响 Raft 多数派。技巧二服务名不要用下划线。Swarm 的 DNS 解析对下划线支持不好用中划线更稳妥。技巧三定期清理未使用的镜像和 volume。Swarm 不会自动清理旧镜像时间长了磁盘会满。写个定时任务docker system prune -af --filter until168h技巧四docker stack deploy是幂等的可以放心重复执行。但要注意它会覆盖手动改的配置所以所有变更都要落到 stack 文件里别手动docker service update。技巧五节点下线前先 drain。docker node update --availability drain node-01会把该节点上的任务迁移到其他节点避免服务中断。5. 这套方案后续还能怎么扩展跑通基础部署之后我陆续加了一些东西让整套体系更完善。一是加了 Traefik 做反向代理和自动 HTTPS通过监听 Docker socket 自动发现服务打上 label 就能暴露比手动配 Nginx 省事很多。二是接入了 Watchtower 做镜像自动更新配合 CI 推送新镜像后自动滚动更新适合开发环境。三是用 Portainer 做可视化管理虽然命令行够用但有个 Web 界面看集群状态确实方便尤其是给团队其他人用的时候。如果规模继续增长下一步可以考虑把有状态服务拆出去用独立数据库Swarm 只跑无状态应用这样调度会简单很多。再往上如果真到了 Swarm 撑不住的量级迁移到 K8s 也不是从零开始——compose 文件转 K8s manifest 有现成工具网络和存储的概念也有对应关系过渡成本比想象中低。我个人在实际操作中的体会是编排工具的选择应该服务于团队的实际能力而不是反过来让团队去追工具。Swarm 让我把精力放在业务上而不是基础设施上这两年的时间省下来够我多迭代好几个版本了。
阅读完成 · 觉得有帮助?