ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

容器编排器之战复盘:Kubernetes胜出的底层逻辑

容器编排器之战复盘:Kubernetes胜出的底层逻辑 做基础设施这些年我经常被问到同一个问题容器编排器到底应该怎么选每次聊这个话题我都会把一句观察放在最前面容器编排器之战严格意义上是一场“还没拉开大幕就结束了”的战斗。Docker Swarm有Docker原生光环Mesos和Marathon有大规模调度经验Nomad靠极简和稳定硬撑可这些选项最后基本都被Kubernetes收编。表面上看是开源社区的赢家通吃实际上从Kubernetes把“声明式API加控制器”这套架构放到台面上的那一刻起比赛规则就已经变了。这篇文章不打算给你复述完整的时间线而是想用这些年我在基础设施领域做选型、排障和平台设计的实际经验把这场战役的底层逻辑讲清楚。无论你是正在纠结学习方向的后端工程师还是要为团队选型的架构师这篇文章都能给你一个不太一样的复盘视角。1. 起点容器解决了“装法”但“管法”才是刚需1.1 Docker的红利很快就用完了2013年前后Docker刚火起来的时候大家最兴奋的是“一次构建到处运行”。镜像这个东西太方便了代码、运行时、依赖全打进一个包开发机跑什么样服务器跑什么样基本可以保持一致。我到现在还记得第一次用docker run时的感觉确实有点魔法。但等到真把容器放进生产环境问题就变了。生产环境不是一台机器而是几十台机器、几十个服务、随时可能宕机的节点。你把服务容器化了接下来还有一连串问题这个容器应该放在哪台机器上机器的CPU、内存够不够容器起来了要怎么被发现它崩了以后谁来把它重新拉起来发新版本的时候怎么做到不中断服务这些都不是“把一个应用装进容器”能解决的。小规模的时候可以靠脚本硬扛比如写一个Shell脚本SSH到每台机器上拉镜像、重启容器。但当你从5个节点扩展到20个节点从10个服务扩展到50个服务脚本就会变成一堆没人敢改的意大利面。这时候你需要的不是一个容器运行工具而是一个集群管理系统。容器编排器这个赛道就是在这样的背景下被推上舞台的。1.2 编排器真正要解决的是调度、发现和生命周期三件硬事先说调度。容器的调度不是“找个机器跑起来”这么简单。你需要考虑每台机器的资源水位要设置反亲和性避免同一服务的副本挤在同一台机器上要在机器维护时先驱逐上面的容器还要考虑某些有状态服务只能跑在特定节点上。这些规则如果都靠人来记迟早出事。编排器要做的第一件事就是把“容器放哪”这个决策自动化。再说服务发现。容器一旦被调度到某台机器它的IP和端口可能随时变化。客户端不能记录一个写死的IP而是需要访问一个稳定的虚拟地址再让编排器负责把流量转发到当前存活的容器后面。DNS和负载均衡在这个环节几乎是标配没有服务发现容器再多也是孤岛。最后是生命周期。进程起来不等于服务健康服务健康不等于可以对外提供流量。编排器必须定义探活机制区分“活得好好的”和“刚起来但还不能接流量”然后根据探活结果做重启、滚动更新、扩容缩容。这三点单独拆开每一项都不算难难的是把它们做进一套统一的系统里并且还能在故障时自动收敛到期望状态。1.3 Kubernetes生来就没打算只定义容器Kubernetes刚开源的时候很多人以为它只是一个“更复杂的容器调度器”。但如果你看过它早期的架构设计会发现它从一开始就站得更高。Kubernetes源自Google内部Borg和Omega的复杂经验但它在开源时做了一个很关键的抽象Pod、Label、Service、Controller、Namespace。这些概念全都不绑定某个具体容器运行时也不绑定某个具体网络方案。社区给它的定位不是“容器的管理器”而是“分布式系统的基础设施层”。这就是Kubernetes和其他编排器最本质的分野Docker Swarm把编排做成容器命令的延伸Mesos把调度做成资源Offer协议而Kubernetes想把“容器”变成整个集群计算模型中的一个普通对象然后在这个对象之上建立一套通用的控制逻辑。它虽然晚于Docker进入市场但它的目标从一开始就是给整个编排之战定规则而不是参与一场“谁的容器管得更好”的局部竞争。2. 同台竞技的选手Swarm、Mesos、Nomad和Kubernetes的真实差距2.1 Docker Swarm原生优势很强却把编排做成了容器命令的延伸Docker Swarm最被人称道的一点就是“原生”。Swarm Mode直接集成在Docker Engine里你不需要额外部署一套控制面集群也不需要装一个独立的管理服务。一条docker swarm init就能把一个节点变成管理节点再执行docker swarm join就能让其他节点加进来。当时我搭建测试环境的时候感觉确实是爽所有命令都是熟悉的Docker风格学习成本极低。但问题恰恰出在这个“熟悉”上。Swarm的所有抽象都附着在容器和Docker宿主机上服务就是一组容器任务就是容器的副本节点就是Docker守护进程所在的机器。这种模型对于小规模的“容器化项目”够用可当你想做更复杂的平台能力时会发现它很难延展。权限模型简单、资源模型粗糙、没有丰富的API对象更谈不上让第三方基于它做二次开发。这里有个很现实的商业矛盾Docker Inc更赚钱的业务是帮助开发者解决“打包和运行”问题还是帮助运维解决“大规模集群管理”问题从结果看Docker在容器运行时上的成功太耀眼反而让它很难下定决心把自己从“开发者工具”变成“企业平台”。Swarm强在“离容器近”输也输在“只离容器近”。2.2 Mesos与Marathon调度功底很硬给开发者用的接口却断代了Mesos在容器编排之前就已经是做大规模调度的老兵了。它的核心思路是把整个数据中心看成一台巨大的计算机由Mesos Master向各个Framework提供资源OfferFramework根据自己的需求决定接受还是拒绝。这种“资源分配”和“任务调度”分开的架构对有状态的数据计算任务很友好。我在调研阶段也认真搭过Mesos加Marathon的环境单独看调度能力它绝对是那一代里最能打的。Marathon负责长期运行任务可以配合Marathon LB做服务发现也能通过健康检查实现容器重启。但问题在于这套体系对普通开发团队太不友好。你不仅要理解Offer、Framework、Executor这套概念还要自己维护Zookeeper、Mesos Master、Agent集群还要处理Marathon和Mesos之间的状态同步。上了生产之后排障链路又深又长。最尴尬的是Marathon始终只是Mesos生态里的一个长服务框架它没有把自己的API提升为一个统一的“平台层”。对于想在上面做产品、做平台的公司来说扩展的难度比Swarm还要高。Mesos的技术深度没有变成社区宽度反而成了普通团队望而却步的门槛。2.3 HashiCorp Nomad极简派代表但生态输在了可扩展面上HashiCorp Nomad是另一个很有意思的选手。它的定位是通用调度器不仅支持调度Docker容器还支持裸进程、Java虚拟机、QEMU虚拟机等多种负载。部署一个Nomad集群只需要下载一个二进制Server节点和Client节点自己就能跑起来配合Consul做服务发现、Vault做密钥管理整个组合拳在“极简运维”这个方向上做得很有代表性。我一直认为Nomad是一个被低估的系统它解决了很多真实问题几百个节点的批量任务调度不需要那么重不需要那么多概念就能稳定跑起来。它的公平调度和装箱策略设计得很实用故障恢复也很可靠。但它没有赢下这场“容器编排器之战”的核心原因在于它的扩展点太少了。Nomad的Job模型很干净可它没有提供一个像Kubernetes CRD那样的机制让第三方把一个自定义的“业务对象”变成调度系统的合法概念。你很难在上面写一个Operator很难模拟“Deployment管理ReplicaSet、ReplicaSet管理Pod”这种分层控制。简单是美德但在一个需要大量周边工具和平台化能力的时代简单也意味着别人没办法站到你的肩膀上盖高楼。2.4 Kubernetes它不是某个功能赢了是“整个平台”的宽度赢了Kubernetes的难度从一开始就是出了名的。它有一堆概念Pod、Service、Deployment、StatefulSet、Ingress、PV/PVC、Namespace、RBAC每一个都不是“看一眼就会”的程度。早期我和很多同行聊都会觉得这东西太重了是不是给大厂准备的后来才慢慢看懂重是因为它要承载的东西多。Kubernetes要承载的是整个分布式系统的管理逻辑。它把“容器怎么放”这个底层问题封装起来然后向上提供一套统一的资源模型。Service解决服务发现Controller解决期望状态HPA解决水平伸缩RBAC解决权限Namespace解决隔离边界CSI/CNI/CRI把存储、网络、运行时全部插件化。这些东西单独拿出来都不稀奇但组合在一起就构成了一台真正意义上的“集群操作系统”。更要命的是Kubernetes不是“自己把一切都做好”而是把接口开放出来让所有人都能参与构建。这种平台的宽度让它在编排器之战里根本不需要全面碾压每个单项。它只要让生态里最有活力的人愿意往它身上靠就已经赢了。这里可以做一个比较直观的对比维度Docker SwarmMesos MarathonHashiCorp NomadKubernetes主要抽象Service / TaskTask / FrameworkJob / AllocationPod / Deployment / Service / CRD控制面内嵌Raft状态存储Mesos Master ZookeeperNomad Serverkube-apiserver etcd服务发现内置DNS与LBMarathon LB / Mesos-DNSConsul集成Service / Endpoints / Ingress扩展方式单一Docker API自定义FrameworkTask DriverCRD / Admission / CSI / CNI / CRI上手门槛低高中中高生态纵深弱偏大数据有限但有口碑极宽这张表看下来你会发现Kubernetes在“生态纵深”这栏几乎是断档的领先。而这恰恰是最终决定这场战斗走向的关键。3. Kubernetes真正赢在几个“看不见的基本功”上3.1 控制面与数据面分离让集群成为可编程基础设施Kubernetes的架构核心是控制面与数据面的分离。控制面由kube-apiserver、controller-manager、scheduler和etcd组成负责保存集群的期望状态并持续调整数据面由每台节点上的kubelet和kube-proxy组成负责真正把容器跑起来并维护网络规则。这让整个集群变成一个“可编程的基础设施”。因为你所有操作都是走kube-apiserver这个唯一入口所以无论谁来调用、什么时候调用、用什么版本的工具调用看到的都是一致的API。这与Swarm那种围绕Docker守护进程做扩展的模式有本质区别。etcd在里面承担的是“事实数据库”的角色。控制平面的关键状态全部落到etcd里控制面组件本身可以重启、切换、甚至多副本部署。只要etcd不丢集群的期望状态就不会丢剩下的所有控制器都会努力把现实状态推向期望状态。这对运维来说意味着一个很踏实的心智模型我可以面向API编程不用关心具体是哪台机器在干活。3.2 声明式API不是会写YAML而是“描述期望状态后交给控制器收敛”很多人把Kubernetes的声明式API理解成“写YAML”这是个片面的认识。声明式API真正厉害的地方在于它把“操作步骤”换成了“期望状态”。比如你创建一个Deployment告诉集群“我要3个nginx副本”之后这个Deployment对象会一直被控制器监视。某个Pod因为节点宕机丢了ReplicaSet控制器发现当前副本数小于期望值就会自动创建新Pod。整个过程不需要任何人的介入不需要执行重跑脚本也不需要“绑定到某台节点”。你写下的不是docker run这种“每次执行一步”的命令序列而是一个长期有效的契约。这里可以放一个最基础的例子apiVersion: apps/v1 kind: Deployment metadata: name: blog spec: replicas: 3 selector: matchLabels: app: blog template: metadata: labels: app: blog spec: containers: - name: blog image: nginx:stable这段YAML的语义不是“现在帮我启动3个nginx”而是“这个集群里必须一直有3个nginx在跑”。如果节点故障导致副本数变成2控制循环会自动补回一个。如果你想扩容只需要把replicas改成5然后提交。这种模型天然适合自动化也天然适合GitOps代码仓库里躺着的是环境的期望状态应用它剩下的交给控制器。3.3 控制器模式让每个组件各管一摊逻辑可以无限拆分Kubernetes内部最常见的模式就是控制器循环每个控制器负责一种资源的收敛观察当前状态比对期望状态执行差异操作再写回状态。Deployment控制器负责创建和更新ReplicaSetReplicaSet控制器负责管理Pod副本kubelet负责把Pod最终变成容器。这种模式的奇妙之处在于它不是一套“越大越全”的中央调度系统而是由一组可拆分、可组合的控制器构成。你想做自动扩缩容就加一个HorizontalPodAutoscaler控制器你想处理自定义的备份对象就写一个Operator。控制逻辑不是焊死在某个引擎里而是可以像业务代码一样被扩展、被替换。这也是为什么后来会出现那么多“Operator模式”的软件数据库集群、消息队列、日志采集器都能以自定义资源的形式挂在Kubernetes里。每加一个新的控制器Kubernetes就多一种能力。Swarm和Nomad没有这种模式它们的架构决定了自己只能是一个“用完即走”的调度器而不是一个能长出无数子系统的平台。3.4 原生留给外部的接口决定了生态的爆炸式增长Kubernetes不只是开放了几个插件目录它对整个生态开放的是一整套标准接口。容器运行时层有CRI网络层有CNI存储层有CSI。这三层接口让不同的厂商可以各自实现自己的最佳方案上层API完全不用变。跑在同一个集群里的Pod可以随时换一个CNI插件来该网络模式可以挂一个不同厂商的存储卷这些都不会破坏Deployment和Service的声明。更关键的是CRD和Admission Webhook。CRD允许你把任何业务对象变成集群API的一部分Admission Webhook允许你在资源创建过程中插入校验和修改逻辑。正是因为这些扩展点云厂商可以把自己家的负载均衡器、存储、数据库做成Kubernetes资源ISV可以把复杂软件做成一个kind: MongoCluster平台团队可以把公司内部的发布流程做成一个Operator。这已经远远超出“容器编排器”的范畴了。Kubernetes实际上成为一个“面向基础设施的API系统”。你在上面做的每一层创新都不需要修改Kubernetes本身只需要用一个控制器去监听自定义资源就够了。生态会像滚雪球一样越来越大因为每个人都能在它的基础上做自己的东西而且彼此不冲突。4. 几次险些翻盘的节点为什么都没撑到下一集4.1 Docker 1.12发布Swarm Mode最像样的一次反击2016年Docker 1.12正式把Swarm Mode内置进了Docker引擎这是我对这场战斗印象最深的一次“反击”。当时开一个集群真的快只需要在管理节点上执行docker swarm init --advertise-addr IP然后让其他节点执行docker swarm join几秒钟就能把一个多节点集群拉起来。通过docker service create --replicas 3 nginx可以创建带副本数的服务通过docker service update --image nginx:1.21可以做滚动更新整套体验面向“已经会Docker的人”极其友好。我当时一度觉得如果团队规模不是特别大Swarm完全够用没必要上那么重的Kubernetes。但站在后来的视角看Swarm Mode只是把Swarm变成了一层更顺滑的Docker命令扩展它没有试图成为一个真正的平台。它的API对象基本还是围着服务和任务转没有给第三方开发平台逻辑留下足够空间。社区的注意力很快转向了Kubernetes一方面是因为Kubernetes已经形成了更大的讨论规模另一方面是因为大家发现Swarm能解决“部署容器”但解决不了“在容器之上建立一套自己的平台抽象”这件事。4.2 Mesos的硬核底色挡不住普通团队Mesos的调度能力在当时绝对是最硬核的但“硬核”在技术博弈中是个双刃剑。它面向的是能接受复杂底层概念的大规模基础设施团队不是普通业务研发。你可以在Mesos上做出非常精确的资源分配可如果你只是想顺手把一组Web服务编排起来看到Offer和Framework的定义可能就直接关掉了文档。而且Mesos生态里真正被用户反复验证过的编排组件是Marathon但Marathon的功能边界一直很窄。它把长期运行的容器调度起来了可服务发现要单独配Marathon LB日志收集要自己搭配置中心要自己接存储方案要自己想办法。这些“周边能力”在现代平台里都属于基本盘但在Marathon上全部需要拼凑。更遗憾的是Kubernetes后来通过自己的生态把很多数据平台也“收编”了。Spark、Flink、Kafka这些系统都可以通过Operator跑在Kubernetes上Mesos在大数据领域曾经的优势被一点点削弱。硬核底子仍然是底子但一个没有足够多“中间层组件”和开发者入口的硬核是留不住普通从业者的。4.3 Rancher从自己造编排器到全面转向Kubernetes是最真实的站队信号早期Rancher有一个自己的容器管理平台叫Cattle想法是用一套可视化的UI让你管容器集群同时它也支持调度多个环境。我在2016年就试过Rancher 1.x它确实比Swarm更好看、更好操作对当时的容器环境管理是个很重要的补充。但后来的事情大家都知道了Rancher 2.0没有继续押注Cattle而是全面转向做Kubernetes管理平台变成了很多人装机必经的交付Portal。这个转向比任何技术文档都更说明问题连一个自己做了一套容器编排器的公司都认为把精力花在Kubernetes之上更划算。因为在Kubernetes上做平台不用再维护一套自己的控制面也不用教育用户一套全新概念可以直接借用已经成熟的API和社区生态。这类商业层面的站队信号对技术生态的走向影响极大。开源项目可以靠激情活一阵子但想让生态里的公司持续投入就必须让人看到“站在你上游能赚到钱或者省下钱”。Kubernetes提供了这个确定性其他编排器没给出来。4.4 Docker在运行时层面让步阶段性的终局就此确定到Docker自己也宣布支持Kubernetes的时候这场编排器之战基本就没有悬念了。Docker企业版后来可以把Kubernetes和Swarm同时作为编排后端可选这相当于官方盖章Swarm不再是Docker唯一推荐的编排方案。再往后就是容器运行时层面的整合。containerd作为一个中立的容器运行时管理组件进入CNCFCRI标准让Kubernetes能支持各种容器运行时。Docker真正的核心资产“容器镜像和运行时能力”被保留了下来而“调度编排”的部分则交到了Kubernetes手里。从那时起整个行业讨论的话题从“用哪个编排器”彻底变成了“怎么管理Kubernetes集群”“怎么做多云发布”“怎么做多集群治理”。战场转移了原来那些选手自然不可能再打下去。5. 复盘之后我把这套经验用在了选型决策里5.1 看一个基础设施项目先看它允许别人做什么而不是它自己做什么经历过这场战役后我评估任何基础设施技术都会换一种问法它自己是不是一个“厉害的功能集合”还是一个“可以被别人扩展的平台”Kubernetes的功能集合其实一开始并不完整很多能力都是后来通过控制器和插件补上去的。但它允许别人做东西这让它的边界远大于官方代码仓库本身的边界。Swarm的官方功能体验很顺可你想在Swarm上建立一个自定义资源的控制器几乎没有路径。Nomad的Job调度很稳定但你想做一层自己的Operator没有标准对象可用。工具能做什么决定它的起点别人能基于它做什么决定它的上限。5.2 运维成本不比“部署多重”比“出问题时知识密度有多高”很多人觉得Kubernetes运维成本高因为它组件多、概念多。这个判断有一定的道理但我更愿意把它拆解成另一种成本知识密度。当你的系统遇到一个不常见的问题时你有多大概率从公共资料、社区问答、同事的既有经验里找到答案用Swarm的人可能不多遇到一个特定版本的Bug可能翻遍Issue都找不到解法用Mesos的排障链路可能需要内部专家才能看懂用Kubernetes虽然复杂度高可它的社区规模决定了绝大多数错误信息都有前人踩过Google一搜基本能定位到大概方向。我不是说Kubernetes的报错不劝退而是说在真实的运维世界里“踩过同一个坑的人够多”本身就是一种巨大的抗风险能力。5.3 先问“业务是不是真的需要编排”再问“选哪一套”如果因为Kubernetes赢了这场战争就认为所有场景都必须上Kubernetes那也是一种矫枉过正。我个人的经验是先回到业务本身你到底有多少节点服务之间是不是有频繁的扩缩容有没有自动化发布和故障自愈的硬需求如果只有三五台机器跑的是几个固定服务那么用Docker Compose、systemd甚至直接裸进程管理可能都比Kubernetes更合适。如果确实需要一个调度系统但主要是批量任务Nomad至今也还值得考虑。但如果你的目标是在容器之上搭建一个让多个团队都能自助交付的平台那Kubernetes几乎就是那个绕不开的底座。5.4 不要在“技术是不是最先进”上浪费感情要看它是否成为标准这场容器编排器之战最值得咀嚼的一点是胜者不一定是考卷上分数最高的学生而是那个让大部分考生都愿意一起答题的考场。Kubernetes的赢不是因为它把所有细节都设计得完美而是因为它给出了足够标准的API、足够统一的扩展方式、足够稳定的生态协作模型。我现在做技术选型不会因为某个工具简单就认定它好也不会因为某个项目复杂就劝退。我会把时间线拉长问自己三个问题它是否定义了足够通用的抽象它是否给生态留了足够的扩展点我的团队在外面修问题的时候能不能靠公共知识解决大部分麻烦把这三个问题回答清楚很多技术之争根本不需要等到血战就会有答案。
返回列表