
1. 为什么还在聊Swarm它解决的痛点和适合的人群先说个背景。我接触Docker比较早大概在2016年前后就开始在测试环境里用容器跑应用了。那时候最深的感受是单机玩Docker很爽docker run一行命令环境隔离、依赖打包全解决。但一旦应用要上生产要搞多台机器问题就来了——几十个容器分散在不同宿主机上谁在跑哪个容器容器挂了怎么自动拉起服务之间怎么互相发现流量怎么分发到不同机器上的容器这些其实都是编排Orchestration问题。Docker Swarm就是Docker官方给出的解法。它把多台Docker宿主机抽象成一个逻辑上的集群你向集群下发一个服务定义Swarm负责调度、容灾、服务发现和负载均衡。简单说Swarm的目标是让一组服务器用起来像一台更大的服务器。可能有人会说现在Kubernetes这么火谁还用Swarm我自己的态度是工具没有绝对的好坏只有适不适合。Swarm的优势在于跟Docker原生API完全兼容学习曲线平缓部署轻量小规模集群比如三五台到十几台机器完全够用。它跟Docker Compose的契合度也很高很多Compose文件稍作调整就能直接作为Swarm服务发布。对于中小团队、边缘节点、内网自建场景Swarm往往比K8s更省心。这篇内容我会从原理层面把Swarm的关键机制讲清楚再给出一套可以从零开始部署到生产环境的完整路径包括初始化集群、节点管理、服务发布、滚动更新、存储、安全和常见故障排查。适合正在用Docker、想提升多机编排能力或者想对比Swarm和K8s再做选型的读者。2. Swarm的核心机制节点、服务和通信协议2.1 节点角色划分Manager和Worker并不是简单的主从一个Swarm集群里的节点分成两类管理节点Manager和工作节点Worker。大多数入门资料只会告诉你Manager管调度Worker跑容器这个说法没有错但不够准确。Manager节点的核心职责是维护集群的期望状态Desired State。你通过命令行下发一个服务副本数为3的指令Manager会把这个期望状态写入集群的共识存储然后由调度器决定这3个副本分别跑在哪些节点上并持续监控实际状态是否与期望一致。一旦某个副本挂了调度器会在其他可用节点上重新创建副本让集群自动恢复到期望状态。Worker节点相对简单只负责执行Manager下发的任务Task比如启动一个容器、停止一个容器。但它同样会周期性向Manager汇报心跳和节点状态。这里有个容易忽略的细节新加入集群的节点如果不特别指定默认是Worker角色如果你想让它成为Manager需要在join命令里显式加上--advertise-addr并声明管理角色。生产环境里我比较推荐至少部署3个Manager节点。原因后面讲RAFT协议的时候会详细解释这里先记住一个结论Manager数量奇数且不少于3个是Swarm集群高可用的基础。2.2 共识存储与RAFT协议3个Manager为什么是底线Swarm的集群元数据节点列表、服务配置、密钥、网络定义等存放在一个分布式的共识存储里底层用的是Raft协议。我把Raft简化理解成多个节点共同维护一份状态日志每次状态变更都需要大多数节点Quorum确认才会被采纳。假设一个集群有3个ManagerQuorum就是2。任何一条变更指令比如扩副本必须至少2个Manager写入成功才算有效。这样即使其中1个Manager宕机集群依然可以正常对外服务。反过来如果只有2个ManagerQuorum也是2任何一个Manager宕机导致Quorum不足整个集群的编排功能就会冻结——新的扩缩容、服务更新全部不可用正在跑的容器不受影响但你无法管理它。所以我一直建议要么用1个Manager测试环境够了要么直接用3个Manager生产环境。2个Manager是最尴尬的配置等于花了多一倍的机器可靠性并没有实质提升。5个Manager当然更好但除非集群规模特别大否则3个已经足够。2.3 任务调度与服务抽象服务、任务和容器的层级关系Swarm里有一个清晰的层级关系服务Service定义了一个应用的期望状态比如镜像版本、副本数、网络、端口映射服务每一次实际执行会生成若干任务Task每个任务最终落实到一台宿主机上的一个容器实例。理解这个层级非常重要因为它决定了你排障时的思路。服务是逻辑概念任务是一次调度单元容器是最终运行的实体。你用docker service ls看到的是服务列表用docker service ps service看到的是任务状态用docker ps看到的是分布在各个节点上的具体容器。排查问题的时候要从服务期望状态出发逐层往下看到实际运行的容器才能定位异常发生在哪一层。另外Swarm的服务分为两种模式replicated按副本数调度和global每个节点跑一个实例。前者适合无状态应用比如Web服务后者适合需要覆盖每个节点的组件比如日志采集代理、监控Exporter。很多人在创建服务时不注意这个参数导致明明想每个节点跑一个采集器结果调度成了只在个别节点上跑固定副本数。2.4 服务发现与负载均衡VIP和DNS轮询容器在Swarm集群里被调度到哪台机器对调用方是透明的。这背后靠的是Swarm内置的服务发现和负载均衡机制。每个服务在创建时会被分配一个虚拟IPVIP。当客户端访问这个VIP时流量会被转发到该服务对应的某个容器实例上。Swarm内置的DNS模块会解析服务名默认返回VIP这个VIP本身由Ingress网络中的负载均衡组件分发请求。对应用来说它只需要知道服务名和端口不需要关心实例去哪里了。除了VIP模式Swarm还支持DNS轮询模式--endpoint-mode dnsrr。这种模式不创建VIPDNS查询直接返回所有副本的IP列表由客户端自己做负载均衡。适用于需要客户端侧负载均衡策略的场景比如某些自定义协议或需要会话保持的业务。默认建议用VIP模式因为对应用最透明但在压测时要注意VIP所在节点的流量转发能力是否成为瓶颈。3. 集群初始化与节点管理的实用细节3.1 初始化管理节点一条命令和一串参数在选好一台机器作为第一个管理节点后初始化命令是docker swarm init --advertise-addr 192.168.1.10其中--advertise-addr要填当前节点上其他节点可以访问到的IP。这个参数我踩过坑如果机器上有多个网卡比如有内网IP也有外网IP不指定的话Swarm可能选错IP导致其他节点无法加入。所以生产环境务必手动指定不要偷懒。初始化成功后命令行会输出两段提示一段是管理节点加入时需要的token另一段是工作节点加入时需要的token。这两段token要妥善保存因为后续扩容节点或者管理节点丢失需要重新加入时都离不开它们。3.2 加入节点与找回Token的完整操作工作节点加入集群的命令格式是docker swarm join --token WORKER_TOKEN 192.168.1.10:2377如果当时没保存token也不用紧张可以在管理节点上重新查询docker swarm join-token worker docker swarm join-token manager这个命令不只会显示token最后还会自动附带一个完整的join命令直接复制到新节点执行即可。节点加入完成后在管理节点上执行docker node ls可以查看所有节点状态。我建议关注Availability和Manager Status两列。Availability有Active、Pause、Drain三种状态Active代表正常参与调度Pause代表不接收新任务但保留现有容器Drain代表把已有容器迁移走并停止接收新任务。生产环境对某台机器做硬件维护前先docker node update --availability drain node让Swarm把容器平滑迁移到其他节点再停机器是非常稳妥的操作。3.3 节点标签让小众调度需求变得可控Swarm支持给节点打标签Label这在需要把特定服务固定到特定机器时非常有用。比如数据库容器需要跑在SSD机器上或者GPU服务需要调度到带GPU的节点docker node update --label-add ssdtrue node-03 docker service create --constraint node.labels.ssdtrue --name mysql mysql:8.0节点标签配合约束Constraint基本能满足大部分场景化调度的需求。注意标签是加在节点上不是加在守护进程上所以不需要重启Docker。4. 生产级服务部署Compose文件、滚动更新与数据持久化4.1 从Compose到Stack转换的成本低到超出预期Docker Compose在单机环境下用得非常普遍。Swarm几乎无缝兼容Compose文件通过docker stack deploy把一套Compose定义为集群中的服务栈。比如你有一个application.yml写好的Compose文件部署到Swarm的命令docker stack deploy -c docker-compose.yml myappCompose文件里的大多数字段都会生效但也有一些字段在Swarm模式下会被忽略最典型的是depends_on和container_name。前者在Swarm里没有意义因为服务间的依赖由编排系统处理你不需要也不可能控制不同节点上的容器启动顺序后者在集群模式下本来就无法保证唯一性。改造Compose文件时遇到这两个字段直接删掉即可不要试图强行保留。另外建议在Compose文件里显式声明deploy配置块。虽然不写也能部署但你会失去对调度、更新策略的控制。生产环境至少要把replicas、update_config和restart_policy配置上。4.2 滚动更新与回滚变更生产服务时最常用的两个流程滚动更新Rolling Update是Swarm生产实践里使用频率最高的能力之一。默认情况下docker service update一次只更新一个副本而且没有延迟。如果是跨大版本的镜像更新我强烈建议设置更新并行度和间隔docker service update --image myapp:v2.0 --update-parallelism 2 --update-delay 10s myapp这样一次最多同时更新2个副本每批间隔10秒给健康检查和监控留出观察时间。如果新版本有问题可以在更新过程中中止然后一键回滚docker service rollback myapp这里有个非常实用的经验回滚命令会恢复到上一次的服务定义包括镜像版本、环境变量、挂载配置等。所以在准备更新前应该保证当前服务定义已经保存成一份可复用的Compose文件否则回滚恢复的是内存里的旧状态而不是你预留在仓库里的配置两者可能偏差。4.3 持久化存储的几种姿势与适用场景容器本身是无状态的但很多应用数据库、消息队列必须有状态。Swarm里持久化存储主要有三种方式。第一种是volume由Docker管理生命周期适合跨节点共享的场景配合NFS驱动可以做到多副本共享同一份数据。第二种是bind mount把宿主机的目录直接挂载进容器适合对性能不敏感但需要直接访问宿主机文件的场景。第三种是tmpfs数据存内存适合缓存类数据容器重启数据清空。生产环境给MySQL这类单副本数据库做数据持久化我一般用volume加NFS后端如果只是单节点上的有状态服务直接bind mount即可。注意不要给数据库服务配多副本加共享存储除非你额外做了主从同步否则两个Pod同时写同一个数据目录数据损坏只是时间问题。5. 网络、安全与高可用的生产关键配置5.1 Ingress网络与端口发布的工作逻辑Swarm集群默认会创建一个ingress网络负责对外发布服务端口。docker service create --publish 80:80会把宿主机的80端口映射到服务内部80端口而且这个映射在集群的每个节点上都生效。也就是说你访问任意一个集群成员的80端口流量都会被送到Ingress网络再路由到实际运行该服务副本的容器上。这个机制有一个容易被忽略的特性即使某个节点上没有运行任何该服务的副本它依然会监听80端口并转发请求。这意味着你可以在集群前面挂一台负载均衡器直接把请求打到任意节点上。但要注意Ingress网络的转发会经过VIP和负载均衡组件带来额外的性能开销。在高吞吐场景下可以考虑用host模式网络代替但那样会失去Swarm原生的负载均衡能力需要自己在前层再解决分发问题。5.2 管理面加密与应用流量的安全加固Swarm默认会对管理节点之间的通信比如Raft协议、节点心跳做TLS加密这是自动开启的不需要额外配置。但服务之间的应用流量默认是明文在Overlay网络内传输的。如果你的业务数据敏感可以在应用层做TLS/SSL加密也可以接受Overlay网络内部通信的信任模型——通常内网环境下这是可接受的。服务间如果需要对端加密通信Swarm也支持通过指定网络开启加密docker network create --driver overlay --opt encrypted secure-net但这个加密会消耗额外的CPU资源而且只对使用该overlay网络的服务间通信生效。我建议网络加密选配而非默认开启先评估威胁模型再决定。5.3 密钥管理与配置注入不要把密码写进镜像很多团队部署应用时习惯把数据库密码、第三方API Key写进环境变量其实这不是好习惯。Swarm内置了Secret机制可以把敏感信息以加密方式存储在服务运行时以文件形式挂载到容器内指定路径。echo MyDBPassword | docker secret create db_pass - docker service create --secret db_pass --name myapp myapp:latest容器内可以通过/run/secrets/db_pass读取。相比环境变量Secret不会暴露在docker service inspect的明文输出里。同理非敏感配置可以用docker config管理。这两个机制用好了能从流程上避免密码随着镜像到处传播的问题。5.4 高可用架构的合理形态3个Manager加奇数建议总结一下我推荐的生产部署形态3个Manager节点加若干Worker节点。单机Swarm只适合测试因为管理面和业务面同时挂在同一台机器上任何一个故障都会导致整集群不可用。3个Manager配合专用的Worker节点跑业务容器才能在硬件故障、网络抖动、机房维护等场景下保持可用。6. 从Swarm到K8s何时迁移以及迁移思路6.1 Swarm与K8s的定位差异官方早已宣布在Docker Engine中集成Swarm的原生编排功能并会继续支持产品形态上Swarm还是Docker的原生组成部分生命周期维护持续进行。Kubernetes则在容器编排生态中占据主流地位具备更丰富的扩展能力和生态集成。两者的选择我一句话概括业务复杂度低、团队规模小、Docker技能栈成熟Swarm会很舒服如果业务已经走到需要自定义CRD、复杂权限控制、大规模多集群治理的地步K8s必然是更顺手的工具。6.2 迁移路径从Compose到K8s的过渡考虑到Swarm和Compose的天然血缘从Swarm迁往K8s的第一步通常是保留Compose文件把它改造成K8s可识别的Deployment、Service等资源清单。也可以借助一些转换工具自动生成基础K8s清单但效果不一定完美仍需人工检查。真正的难点其实不在文件格式转换而在于思维方式从声明服务加副本数到管理标签、选择器、探针、Ingress、Namespace的转变需要团队投入数周的学习和适应。6.3 Swarm仍然值得选择的具体场景我目前仍然在这样一些场景里用Swarm内部工具类服务监控看板、CI Runner、边缘节点的轻量应用分发、临时活动业务的快速搭建。这类场景追求的是快速部署、低运维成本Swarm的轻量特性非常契合。如果你的业务在这些范围之内完全可以放心使用不必被K8s的生态声势裹挟着迁移。7. 日常运维排障指南我实际遇到的高频问题7.1 节点状态为Down排查链路生产环境最常遇到的问题就是节点显示Down。我的排查路径通常是这样先用docker node ls确认是哪个节点Down然后登到对应机器上查看Docker守护进程状态和日志。多数情况下是机器重启后Docker服务没有自动启动少数情况是防火墙阻塞了2377端口的集群通信。特别提醒在管理面监听端口被云平台安全组拦截时节点间RAFT心跳会中断但没有内行的话很容易误判成应用层故障。7.2 服务长时间处于Starting状态服务副本一直处于Starting通常是镜像拉取失败、资源不足或健康检查未通过。先docker service ps service看事件记录Swarm的编排日志比容器日志更有价值它记录了一次调度从分配到启动失败的全链路状态。再配合journalctl -u docker检查该节点的Docker守护进程日志。遇到镜像拉取慢或失败优先考虑配置镜像加速器或者在每个预置节点上提前docker pull好需要的镜像。7.3 服务间网络不通overlay网络的坑Swarm服务之间访问不通排除应用配置因素后最常见的是容器跑在不同的overlay网络上或者网络被误删。一条高效的判断命令是docker exec 容器名 getent hosts 服务名如果在容器内解析不到服务名优先检查是否所有服务都附加到了同一个overlay网络以及该网络是否处于正常状态。7.4 单个节点上容器路由状态异常有时候某个worker节点上的容器无法访问外部网络但其他节点正常这种故障往往跟iptables规则、内核转发参数ip_forward有关。重启节点后没有恢复可以逐项检查这些系统级配置。遇到网络健康检查频繁失败时不要先重启服务而是确认服务绑定的端口是否有冲突、监听地址是否正确。8. 生产落地复盘一次多机集群部署的完整记录最后分享一个完整的落地案例。之前帮朋友团队搭过一套四节点的Swarm集群3个Manager1个Worker用于内部的自建PAAS式应用交付平台。前期我们做了容量评估CPU、内存和存储确定Manager节点不需要太高规格但要保证稳定的网络连接。集群初始化后把所有基础设施组件监控、日志采集、CI Runner通过global模式部署业务应用按需求用replicated模式部署节点打标签后做了约束调度还顺手把Secret机制用于数据库密码管理。整个上线过程最耗时的部分其实是应用改造——把原本依赖单机路径的配置全部改成了通过服务名访问才让容器在不同节点上自由调度。滚动更新上线时我们设置了并行更新数为1更新间隔30秒确保每次只有一个实例切换业务无感知。我个人的体会是Swarm的生产落地最大的成本不在工具本身而在应用的容器化适配程度。如果项目从一开始就以12要素为设计原则Swarm部署几乎是零成本的事。9. 最后的小技巧设计一套可靠的备份告警预案日志和监控层面建议在集群外单独部署监控避免集群挂了监控也没了的循环。定时备份集群元数据的key-value存储Raft日志里的全部配置信息同样很重要方案上可以定期用docker swarm update跨管理面输出配置快照也可以依赖基础设施层备份来兜底。别因为Swarm部署简单就忽略了这些比部署本身更花时间的工作。Swarm简化了编排但生产系统的稳定性终究靠的是每一步的细心和冗余设计。