想象你是一家大型饭店的老板。后厨有几十位厨师,每天要处理几千份订单——如果每位客人都直接冲进后厨找某个特定厨师点菜,后厨瞬间就会乱成一锅粥。于是你做了三件事:把做同一道菜的厨师编成一组,围坐在同一张工作台旁共享厨具(Pod);在前台设一个总服务台,客人只需告诉服务员“来一份鱼香肉丝”,服务员把订单转给任何一位能做这道菜的厨师(Service);你还会定期巡视后厨,发现某道菜缺人就补上、多了就调走,推出新菜谱时分几批替换而不是一次性全换(Deployment)。 这就是Kubernetes(简称K8s)在做的事——它是云时代的“饭店运营总监”。K8s把应用程序打包成一个个标准的“餐桌”(Pod),用“前台”(Service)统一接待流量,用“主厨管理方案”(Deployment)保证应用永远有你想要的数量和版本。本篇文章将带你认识这几位核心角色,以及它们背后的配置管理(ConfigMap)、声明式API和协调循环工作原理。读完你会发现,K8s并不神秘,它不过是用一套聪明的“饭店管理哲学”,让千万台机器上的应用像一家运转有序的饭店一样井井有条。
掌握K8s核心概念是云原生工程师的入门门票。理解Pod、Service与Deployment这三个基本构件,你就能读懂生产环境的资源清单,迈出容器化应用上云的第一步。
可视化演示
动态演示K8s核心概念的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
K8s核心概念:Pod最 Pod最小调度单元与容器组模型 Service服务发现与负载均 核心机制可视化 K8s核心概念整体架构与关键组件
Pod调度单元
想象一家饭店的后厨。做宫保鸡丁需要配菜、掌勺、装盘三位师傅密切配合。老板干脆让他们坐在同一张工作台旁,共享同一套刀具和食材。这样一来,三人配合默契,调度起来也方便——要上就一起上,要撤就一起撤。这张工作台,就是K8s里的Pod。
在Kubernetes中,Pod是最小、最基本的调度单元,而不是容器。一个Pod内可以包含一个或多个容器,这些容器共享同一个网络命名空间(IP、端口)和存储卷。K8s调度器以Pod为单位决定把任务放到哪台机器上,而不是单独调度某个容器。这意味着Pod内的容器可以通过 localhost 互相访问,也能共享同一个数据卷读写文件。
Pod里的容器生命周期完全绑定:任何容器异常退出,整个Pod都会被K8s感知并重建(通常由上层Deployment完成)。这也是为什么生产实践中,推荐把“关系紧密、需要同生共死”的进程放进一个Pod,比如日志采集边车(sidecar)和应用主容器。日志边车跟着应用一起启停,天然保证日志不丢失。
就像同一桌的师傅必须同进同出,Pod里的容器也共享生命周期。K8s不会单独拆散“一桌人”——这保证了协作型应用要么一起启动,要么一起消失,不给“半桌菜”的尴尬局面留机会。
- 容器集合Pod 中可以有多个容器,它们是这个逻辑单元的内部成员。
- 共享网络所有容器共享同一个网络命名空间,因此可以通过 localhost 互相通信。
- 共享存储所有容器可以挂载同一个存储卷,实现数据的共享与持久化。
Service服务发现
客人来饭店,不会直接冲进后厨找“今天做鱼香肉丝的师傅”——师傅可能轮休、可能换了人。客人只需要走到前台,说“一份鱼香肉丝”,前台自然会把订单转给任何一位能做的师傅。这个前台,就是Service。
Pod是有“生命”的:它可能因为故障被销毁重建,也可能因滚动更新被替换,每次重建IP地址都会变化。如果客户端直接记住Pod的IP,一旦Pod重建就失联了。Service提供了一个稳定的虚拟IP(ClusterIP)和DNS名字,作为流量的统一入口。它通过标签选择器(后面的章节详细介绍)找到当前所有符合条件的Pod,并维护一份实时更新的后端列表。
请求到达Service后,kube-proxy会将流量转发到列表中的某个Pod,默认采用轮询(round-robin)策略。这一层转发对客户端完全透明——客户端永远只跟Service通信,不必关心背后Pod的增删变化。就算某个Pod挂掉被重建,Service也会自动把它从列表中去掉,再把新Pod加进来。
只要前台还在,客人就永远能找到吃饭的地方——哪怕后厨换了一拨又一拨厨师。Service就是应用世界里永不关门的“前台”。
- 选择器过滤通过标签选择器筛出当前所有可用的后端 Pod。
- 稳定入口ClusterIP 与 DNS 名称是固定不变的,客户端永远访问这个地址。
- 轮询转发请求按顺序轮流分发到每个 Pod,实现负载均衡。
Deployment滚动更新
饭店主厨手里有一本“期望菜单”:此刻后厨必须保证有5位能做招牌菜的师傅在岗。他每隔几分钟巡视一圈:人少了就打电话叫临时工,人多了就安排休息。如果明天要换新菜谱,他不会一次性把所有菜换掉(万一客人不满意就全砸了),而是先让两位师傅试做新菜,客人反响好再逐步推广。
Deployment是K8s里管理无状态应用的“主厨”。你在YAML里声明期望的副本数(replicas)和容器镜像版本,Deployment通过底层的ReplicaSet保证这个数量始终满足。当需要升级镜像版本时,Deployment不会“一刀切”地同时替换所有Pod,而是按滚动更新策略逐批替换,确保服务在升级过程中始终可用。
滚动更新有两个关键参数:maxSurge(更新时允许超出期望副本数的个数)和 maxUnavailable(更新时允许最多几个Pod暂时不可用)。比如期望5个副本、maxSurge=1、maxUnavailable=1,那么更新过程中集群里Pod总数最多6个,且最少有4个保持可用。当新的Pod就绪后,旧的Pod才被销毁,如此反复直至全部替换完毕。若新版本发布了严重故障,kubectl rollout undo 还能快速回滚到上一个稳定版本。
主厨“分批换菜”的思路,保证了饭店任何时候都有菜可卖。Deployment的滚动更新同样保证了应用升级不中断——这就是生产环境敢于频繁发布新版本的底气。
- 期望副本数用户声明的应用正常运行所需的 Pod 数量。
- maxSurge更新时允许超出期望副本数的最大数量,用于先启动新 Pod。
- maxUnavailable更新时允许暂时不可用的最大 Pod 数量,保证服务不中断。
- 收敛条件当就绪副本数达到期望副本数,滚动更新宣告完成。
ConfigMap配置
每道菜的配方都写在后厨墙上的菜谱里,而不是刻在厨师脑子里。今天客人普遍口味偏淡,老板改写菜谱里“盐3克”为“盐1克”,所有厨师照着新菜谱做菜就行——不需要重新培训任何一个厨师。菜谱,就是ConfigMap。
应用的配置(数据库地址、超时时间、功能开关)如果写死在容器镜像里,每次改配置都要重新构建镜像、重新发布,效率极低。ConfigMap把配置从镜像中剥离出来,以键值对的形式独立存储。Pod可以像“看菜谱”一样读取这些配置,镜像本身则保持通用不变。
ConfigMap有两种主流注入方式:一是通过环境变量注入,Pod启动时把配置值变成容器内的环境变量;二是把ConfigMap挂载成文件,容器运行时读取配置文件。二者的更新行为不同:环境变量注入是一次性的,修改ConfigMap后需要重建Pod才生效;文件挂载则会随ConfigMap更新而动态刷新(有秒级延迟)。
菜谱可以随时改,但已经出锅的菜不会自动变咸变淡。理解了这句,你就明白了为什么环境变量方式不能“热更新”——菜已经端上桌了。
- 键值对存储配置以 key-value 的形式保存在 ConfigMap 中。
- 环境变量注入Pod 启动时,将 ConfigMap 的某个键映射为容器的环境变量。
- 文件挂载把 ConfigMap 整体或部分挂载为容器内的只读文件。
声明式API
你去饭店点菜,只需对服务员说“鱼香肉丝,少辣”,把目标描述清楚就够了,至于后厨是先切肉还是先热油,你完全不用操心。这就是声明式。反过来,如果你站在后厨边上喊“先放油!再下肉!再加酱!”,那就是命令式——你说一步,他做一步。
Kubernetes的API设计是声明式的:用户提供一个YAML文件,描述“我想要什么”(比如“3个运行nginx的Pod”),而不是“请依次执行哪些命令”。kubectl apply 就是声明式操作——把清单提交给API Server,然后由系统自行协调落地。
声明式的核心价值在于可重复和可自愈。同一个YAML文件提交一百次,结果完全一致(幂等);而且系统会持续对比“清单描述的状态”和“集群当前状态”,一旦现实偏离了声明,控制器会自动把它拉回来。命令式操作则很难做到这一点——你手动创建的Pod挂了就没了,没有人替你负责重建。
你只负责说“想吃什么”,后厨保证“菜必上桌”——声明式API把用户从繁琐的操作步骤中解放出来,让“要什么”和“怎么实现”彻底解耦。
- 提交清单用户通过 YAML 文件描述期望状态。
- API 校验存储API Server 校验清单合法性后写入 etcd。
- 监听与协调控制器监听资源变化,发现偏差就执行协调动作。
协调循环
主厨的例行工作是什么?不是炒菜,而是“巡视”。他手里拿着期望菜单,每隔几分钟就在后厨转一圈:数数实际有多少师傅在岗。少三个,立刻打电话叫人来;多两个,安排休息。这个“看→比→调→再看”的无限循环,就是K8s控制器的灵魂——协调循环。
几乎所有K8s控制器(ReplicaSet、Deployment、DaemonSet等)的内部都是一个协调循环:读取期望状态(用户声明的replicas)、拉取实际状态(当前Pod列表)、计算差距、执行调整(创建/删除Pod),然后继续循环。这个循环没有“终点”,它永远在运行——这正是系统“自愈”能力的来源。
协调循环依赖两个底层组件:etcd持久化保存期望状态(YAML里写的“理想”),API Server暴露当前状态(集群里实际的“现实”)。控制器通过List-Watch机制订阅变化:先一次性拉取全量状态,再持续监听增量事件,保证任何偏差都能被及时发现。即使没有任何事件发生,部分控制器也会周期性地重新执行协调,防止“静默漂移”。
主厨永远不偷懒,K8s的控制器也永远不松懈。正是这永不停歇的巡视,让“期望”和“现实”的差距被时刻钉死——这就是云原生系统“自愈”的真相。
- 读取期望从声明式清单中获得目标状态。
- 观察实际从 API Server 汇总当前资源状态。
- 计算差值Δ 大于 0 说明缺少资源需要补建,小于 0 说明多余需要删除,等于 0 则无需动作。
- 持续循环一轮协调结束后立即开始下一轮,永不停歇。
标签选择器
外卖订单上会有“少辣”“免葱”“多加香菜”这样的备注。后厨接单后,根据备注把订单分给相应的师傅:“少辣”给能把辣度调低的师傅。备注就是标签,筛选备注的过程就是选择器匹配。
标签(Label)是挂在K8s资源上的键值对,比如 app=frontend、env=production。它不承载业务逻辑,只用于“分类和标记”。选择器(Selector)则是一套匹配规则,用来筛选出所有满足条件的资源。在K8s中,Service用选择器找Pod,Deployment用选择器管Pod,两个资源之间的关联关系完全由标签和选择器建立——这是K8s解耦设计的关键。
选择器支持两种匹配方式:相等匹配(=、==、!=)和集合匹配(in、notin、exists)。一个选择器可以包含多个条件,多个条件之间是“与”关系——资源必须满足所有条件才被选中。比如 selector: app=frontend, env=prod 同时满足两个标签的Pod才会被Service纳入后端列表。这种松散耦合让不同团队可以独立管理自己的资源,只要标签规范一致,就能互相发现。
备注贴得好,后厨不慌乱。一套设计合理的标签体系,能让Service、Deployment、监控、日志等十几种资源各取所需、互不干扰——这是K8s世界里最重要的“组织纪律”。
- 定义标签给资源贴上键值对标签,如 app=frontend。
- 声明选择器在资源清单中写出匹配规则。
- 与关系组合所有条件同时满足才算命中,缺一不可。
资源清单编写
在K8s里“点菜”,靠的是一张标准的“点菜单”。菜单必须写清楚四个部分:菜系(是川菜还是粤菜)、菜名(宫保鸡丁)、桌号(坐哪桌)、具体要求(少辣多葱)。这张菜单,就是资源清单(Manifest)。
无论Pod、Service还是Deployment,在K8s里都以YAML清单的形式描述。所有资源清单都包含四个核心字段:apiVersion(API版本)、kind(资源类型)、metadata(元数据:名称、标签、命名空间)、spec(规格:期望状态的详细描述)。掌握这四个字段,你就能读懂任何一份K8s清单。
以Deployment为例:apiVersion 是 apps/v1,kind 是 Deployment,metadata 里写名字和标签,spec 里写副本数、选择器和Pod模板。kubectl apply -f 提交清单后,API Server校验合法性、写入etcd,随后控制器开始协调。可以说,写清单就是“与K8s签订契约”——你许诺目标,K8s许诺执行。
菜单写清楚,后厨才好干活。资源清单写清楚,K8s才能精确执行——这就是“一切皆声明、声明即契约”的云原生世界观。
---
- apiVersion指定所使用的 API 组与版本,决定清单由谁来解析。
- kind 字段声明资源类型,是 Pod、Service 还是 Deployment。
- metadata包含资源名称、标签、命名空间等标识信息。
- spec 内容期望状态的核心描述,比如副本数、镜像、端口等。
AI 应用场景
微服务架构的流量入口治理
阶段:应用部署与发布
解决的问题:微服务多副本部署后 Pod IP 频繁漂移,传统方式靠人工维护 Nginx 配置文件做反向代理,Pod 每次重建都要手动更新,极易出错且无法自动恢复。
具体案例:某电商平台将订单服务通过 Deployment 部署 3 个副本,并创建一个 Service 暴露稳定 ClusterIP。前端应用永远只调用 Service 的固定地址,后端 Pod 无论怎么扩缩容、重建,流量都能通过标签选择器自动找到可用实例。发布时采用滚动更新,先上 1 个新版本 Pod 验证,再逐步替换其余副本,全程无感知。
多环境配置一键切换
阶段:配置管理
解决的问题:开发、测试、生产三套环境的数据库地址、日志级别、功能开关不同。传统方式是为每个环境构建不同镜像,镜像数量爆炸且容易混淆。
具体案例:某互联网金融公司把三套环境的配置分别存为 3 个 ConfigMap(dev-config、test-config、prod-config),同一份 Deployment 清单配合不同的 ConfigMap 名称即可部署到不同环境。配置要改时只需更新对应 ConfigMap 并滚动重启一次 Deployment(或采用文件挂载实现热更新),镜像保持一致,再也不用为环境差异维护多版本镜像。
无中断发布与快速回滚
阶段:发布与变更
解决的问题:应用版本升级时,停机发布会导致用户流失;新版本出问题后,手工回滚耗时且容易遗漏数据迁移步骤。
具体案例:某在线教育公司发布 v2 课堂录像转码服务,Deployment 滚动更新第一批 Pod 后监控发现内存泄漏,立即执行 kubectl rollout undo deployment/transcoder,Deployment 自动将副本回滚到 v1 版本,整个过程约 2 分钟,服务零中断。这套机制让团队敢于“小步快跑”频繁发布,同时拥有安全降落伞。
Python 演示
class Pod: """模拟一个 Pod 实例""" def __init__(self, name): self.name = name self.status = "Running" class ReplicaSetController: """模拟 K8s 控制器: 让实际 Pod 数量向期望值收敛""" def __init__(self, desired): self.desired = desired self.pods = [] self.gc_count = 0 def reconcile(self): """协调循环: 对比期望与实际, 补齐或删除 Pod""" self.gc_count += 1 current = len(self.pods) if current < self.desired: need = self.desired - current for i in range(need): self.pods.append(Pod("pod-%d" % (self.gc_count * 10 + i))) print("补齐: 当前 %d 个 Pod -> 期望 %d 个" % (current, self.desired)) elif current > self.desired: for _ in range(current - self.desired): self.pods.pop() print("缩容: 当前 %d 个 Pod -> 期望 %d 个" % (current, self.desired)) else: print("稳定: 当前 %d 个 Pod 与期望一致" % current) class Service: """模拟 Service 的轮询负载均衡""" def __init__(self, pods): self.pods = pods self.qq_index = 0 def request(self): """模拟一次外部请求经 Service 转发到某个 Pod""" if not self.pods: return "503: 无可用 Pod" pod = self.pods[self.qq_index % len(self.pods)] self.qq_index += 1 return "200: 请求已转发到 %s" % pod.name def main(): ctl = ReplicaSetController(3) ctl.reconcile() svc = Service(ctl.pods) for i in range(4): print("外部请求 %d -> %s" % (i + 1, svc.request())) ctl.desired = 2 ctl.reconcile() if __name__ == "__main__": main()
ctl = ReplicaSetController(3)创建控制器并声明期望 3 个 Pod,对应 YAML 里的 spec.replicas。def reconcile(self)协调循环的核心方法:对比当前 Pod 数量与期望值,多删少补。self.gc_count计数器,用于生成不重复的 Pod 名称,呼应控制器每次协调都会产生新资源。svc = Service(ctl.pods)把当前 Pod 列表交给 Service,模拟 kube-proxy 维护后端转发列表。svc.request()每次请求按self.qq_index % len(self.pods)轮询选择 Pod,实现负载均衡。ctl.desired = 2修改期望副本数后再次 reconcile,模拟扩容或缩容场景。
常见误区
误区“既然 Pod 能放多个容器,那我干脆把一个应用的所有组件都塞进一个 Pod 里,这样既省资源又好管理!”
正解Pod 内多个容器应遵循“高内聚”原则——它们必须共享网络和存储才能协同工作,比如主容器加日志采集边车。如果把互相独立、不需要同生共死的服务硬塞一起,一个容器异常会让整桌“团灭”,扩缩容也没法独立进行。就像你不会把炒菜师傅和洗碗工安排在同一张桌子——他们没有必须共享的厨具,强行绑在一起反而互相拖累。
为何常见:看到 Pod“可以”多容器,容易把“可以”理解成“应该”,忽略了 Pod 设计的本意是让“必须紧密协作”的进程共享生命周期。
误区“Service 把请求转发给 Pod,所以 Service 的 IP 就是某个 Pod 的 IP 吧?只要我用 Service 的 IP,就等于直连某个 Pod。”
正解Service 的 ClusterIP 是一个虚拟 IP,不代表任何真实 Pod。它由 kube-proxy 维护转发规则,流量落在哪个 Pod 由负载均衡策略决定。Pod 自己有独立的 IP,且 IP 会随 Pod 重建而变化。就像前台电话是总机,拨进去后转给哪个分机,你根本不知道也不需要知道分机号。
为何常见:从“Service 指向 Pod”的直觉出发,容易把虚拟 IP 和真实 Pod IP 混为一谈,忽略了 Service 作为抽象入口层的存在。
误区“我把 ConfigMap 里的数据库地址改了,容器里的环境变量应该立刻变成新地址吧?”
正解如果配置是通过环境变量注入的,修改 ConfigMap 不会影响已经启动的容器——环境变量只在进程启动时读取一次,相当于菜已经出锅,改菜谱不会让桌上的菜变味。只有通过“卷挂载”方式把 ConfigMap 挂成文件,修改后才会在容器内动态更新(存在秒级延迟)。
为何常见:把“配置中心”的语义套用到 K8s 的 ConfigMap 上,忽略了注入方式对更新行为的影响——以为改了源头,所有下游都会实时联动。
误区“Deployment 升级版本时,为了快点完成,应该把老 Pod 全部删掉,再一次性建一批新 Pod。”
正解Deployment 的滚动更新是“逐批替换”:先启动一个(或几个)新 Pod,等它就绪后再删掉一个旧 Pod,如此反复。maxUnavailable 参数保证任何时刻最少有指定数量的 Pod 可用,从而让服务在升级期间不中断。就像饭店换新菜单,不会先把所有旧菜倒掉再开始做新菜,而是先试做、再逐步推广。
为何常见:把“更新”类比成本地部署时“停旧上新”的一刀切操作,忽略了生产环境对可用性的硬性要求。
面试题
Q1. K8s 中最小的调度单元是什么?
- A.Pod 容器组← 正确答案
- B.单个容器
- C.物理服务器
- D.虚拟网络
答案:A。K8s 的最小调度单元是 Pod,而不是容器。Pod 内可包含多个容器,它们共享网络和存储,调度器以 Pod 为单位决定任务落点,就像饭店以“一桌人”为单位安排座位。
Q2. Service 在 K8s 中的核心作用是什么?
- A.提供稳定访问入口← 正确答案
- B.负责镜像构建
- C.执行容器调度
- D.管理密钥分发
答案:A。Service 提供稳定的虚拟 IP 和 DNS 名字作为流量的统一入口,屏蔽后端 Pod IP 的频繁变化。它不像前台那样只接电话,而是帮客人把订单准确转给任何一位能做的厨师。
Q3. 下列哪个资源负责管理 Pod 的滚动更新?
- A.Service
- B.Deployment← 正确答案
- C.ConfigMap
- D.Namespace
答案:B。Deployment 负责声明期望副本数并执行滚动更新,通过 maxSurge 和 maxUnavailable 控制替换节奏。Service 只负责流量转发,ConfigMap 负责配置管理,Namespace 负责资源隔离。
Q4. Pod 内多个容器之间的关系是?
- A.网络完全隔离
- B.共享网络和存储← 正确答案
- C.各自独立 IP
- D.必须只有一个容器
答案:B。Pod 内所有容器共享同一个网络命名空间和存储卷,可以通过 localhost 互相通信,也能共享数据卷。这正是“一桌人共享道具和食材”的模型——它们是紧密协作的整体。
Q5. 标签选择器(Selector)的主要用途是什么?
- A.过滤筛选资源← 正确答案
- B.加密传输数据
- C.自动分配 IP
- D.压缩镜像体积
答案:A。标签选择器通过键值对匹配规则筛选资源,建立 Service 与 Pod、Deployment 与 Pod 之间的关联。就像外卖备注“少辣”让订单被分到正确的师傅手里,它只做筛选,不参与其他逻辑。
Q6. 控制器协调循环的核心动作是?
- A.只观察实际状态
- B.对比期望和实际← 正确答案
- C.只在创建时执行
- D.完全依赖人工干预
答案:B。协调循环的每一步都是“读取期望状态、观察实际状态、计算差值 Δ、执行调整”,然后不断重复。它不是一次性动作,而是像主厨巡视那样无限循环,让现实不断向期望收敛。
Q7. 滚动更新中 maxUnavailable 参数的含义是?
- A.允许同时不可用数量← 正确答案
- B.允许同时新增数量
- C.Pod 最小副本数
- D.更新完成阈值
答案:A。maxUnavailable 表示更新过程中最多允许几个 Pod 暂时不可用,用于保证服务不中断。比如期望 5 副本、maxUnavailable=1,则任何时刻至少 4 个 Pod 保持可用;maxSurge 才是允许额外新增的数量。
Q8. 声明式 API 与命令式 API 的本质区别是?
- A.描述期望目标态← 正确答案
- B.描述具体操作步
- C.强调先后的执行序
- D.依赖交互式命令
答案:A。声明式 API 的核心是“描述想要什么”,而不是“怎么一步步做”。kubectl apply 提交 YAML 后由系统自动协调达成状态,可重复且可自愈;命令式则像一步一步指挥后厨做菜,挂了没人负责重建。
Q9. K8s 资源清单的 metadata 字段可以包含哪些内容?
- A.资源名称← 正确答案
- B.标签选择器
- C.期望副本数
- D.命名空间← 正确答案
答案:AD。metadata 存放资源的“身份信息”——名称和命名空间,同时还可以包含标签(Label)供选择器匹配。标签选择器和期望副本数都属于 spec 字段的内容,API Version 则是独立的顶层字段。
Q10. 以下关于 ConfigMap 的说法正确的是?
- A.配置从镜像分离← 正确答案
- B.环境变量注入即刻生效
- C.可作为文件挂载← 正确答案
- D.只能保存加密数据
答案:AC。ConfigMap 把配置从镜像中剥离,支持环境变量注入和文件挂载两种方式。通过环境变量注入的配置在容器启动时读取一次,修改后不会即刻生效;它也不提供加密能力,敏感数据应使用 Secret。