想象你是一家大型饭店的老板。后厨有几十位厨师,每天要处理几千份订单——如果每位客人都直接冲进后厨找某个特定厨师点菜,后厨瞬间就会乱成一锅粥。于是你做了三件事:把做同一道菜的厨师编成一组,围坐在同一张工作台旁共享厨具(Pod);在前台设一个总服务台,客人只需告诉服务员“来一份鱼香肉丝”,服务员把订单转给任何一位能做这道菜的厨师(Service);你还会定期巡视后厨,发现某道菜缺人就补上、多了就调走,推出新菜谱时分几批替换而不是一次性全换(Deployment)。 这就是Kubernetes(简称K8s)在做的事——它是云时代的“饭店运营总监”。K8s把应用程序打包成一个个标准的“餐桌”(Pod),用“前台”(Service)统一接待流量,用“主厨管理方案”(Deployment)保证应用永远有你想要的数量和版本。本篇文章将带你认识这几位核心角色,以及它们背后的配置管理(ConfigMap)、声明式API和协调循环工作原理。读完你会发现,K8s并不神秘,它不过是用一套聪明的“饭店管理哲学”,让千万台机器上的应用像一家运转有序的饭店一样井井有条。

岗位关联

掌握K8s核心概念是云原生工程师的入门门票。理解Pod、Service与Deployment这三个基本构件,你就能读懂生产环境的资源清单,迈出容器化应用上云的第一步。

目录
  1. 可视化演示
  2. Pod调度单元
  3. Service服务发现
  4. Deployment滚动更新
  5. ConfigMap配置
  6. 声明式API
  7. 协调循环
  8. 标签选择器
  9. 资源清单编写
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示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={c1,c2,…,cn},share:{netns,vol}\text{Pod} = \{c_1, c_2, \dots, c_n\}, \quad \text{share}: \{ \text{netns}, \text{vol} \}
这个式子表示:一个Pod由 n 个容器组成,且这些容器共享网络命名空间和存储卷。
  1. 容器集合Pod 中可以有多个容器,它们是这个逻辑单元的内部成员。
    Pod={c1,c2,…,cn}\text{Pod} = \{c_1, c_2, \dots, c_n\}
  2. 共享网络所有容器共享同一个网络命名空间,因此可以通过 localhost 互相通信。
    share:netns\text{share}: \text{netns}
  3. 共享存储所有容器可以挂载同一个存储卷,实现数据的共享与持久化。
    share:vol\text{share}: \text{vol}

Service服务发现

客人来饭店,不会直接冲进后厨找“今天做鱼香肉丝的师傅”——师傅可能轮休、可能换了人。客人只需要走到前台,说“一份鱼香肉丝”,前台自然会把订单转给任何一位能做的师傅。这个前台,就是Service。

Pod是有“生命”的:它可能因为故障被销毁重建,也可能因滚动更新被替换,每次重建IP地址都会变化。如果客户端直接记住Pod的IP,一旦Pod重建就失联了。Service提供了一个稳定的虚拟IP(ClusterIP)和DNS名字,作为流量的统一入口。它通过标签选择器(后面的章节详细介绍)找到当前所有符合条件的Pod,并维护一份实时更新的后端列表。

请求到达Service后,kube-proxy会将流量转发到列表中的某个Pod,默认采用轮询(round-robin)策略。这一层转发对客户端完全透明——客户端永远只跟Service通信,不必关心背后Pod的增删变化。就算某个Pod挂掉被重建,Service也会自动把它从列表中去掉,再把新Pod加进来。

只要前台还在,客人就永远能找到吃饭的地方——哪怕后厨换了一拨又一拨厨师。Service就是应用世界里永不关门的“前台”。

Service→selector{p1,p2,…,pn}→round-robinpi\text{Service} \xrightarrow{\text{selector}} \{p_1, p_2, \dots, p_n\} \xrightarrow{\text{round-robin}} p_i
这个式子表示:Service 通过标签选择器筛出后端 Pod 集合,再以轮询方式把请求转发给其中某一个 Pod。
  1. 选择器过滤通过标签选择器筛出当前所有可用的后端 Pod。
    Service→selector{p1,p2,…,pn}\text{Service} \xrightarrow{\text{selector}} \{p_1, p_2, \dots, p_n\}
  2. 稳定入口ClusterIP 与 DNS 名称是固定不变的,客户端永远访问这个地址。
    ClusterIP=stable\text{ClusterIP} = \text{stable}
  3. 轮询转发请求按顺序轮流分发到每个 Pod,实现负载均衡。
    →round-robinpi\xrightarrow{\text{round-robin}} p_i

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的滚动更新同样保证了应用升级不中断——这就是生产环境敢于频繁发布新版本的底气。

Rready=Rdesired+maxSurge−maxUnavailableR_{\text{ready}} = R_{\text{desired}} + \text{maxSurge} - \text{maxUnavailable}
这个式子描述滚动更新过程中的容量约束:就绪副本数由期望副本数、允许超出数和允许不可用数共同决定。
  1. 期望副本数用户声明的应用正常运行所需的 Pod 数量。
    RdesiredR_{\text{desired}}
  2. maxSurge更新时允许超出期望副本数的最大数量,用于先启动新 Pod。
    maxSurge\text{maxSurge}
  3. maxUnavailable更新时允许暂时不可用的最大 Pod 数量,保证服务不中断。
    maxUnavailable\text{maxUnavailable}
  4. 收敛条件当就绪副本数达到期望副本数,滚动更新宣告完成。
    Rready→RdesiredR_{\text{ready}} \to R_{\text{desired}}

ConfigMap配置

每道菜的配方都写在后厨墙上的菜谱里,而不是刻在厨师脑子里。今天客人普遍口味偏淡,老板改写菜谱里“盐3克”为“盐1克”,所有厨师照着新菜谱做菜就行——不需要重新培训任何一个厨师。菜谱,就是ConfigMap。

应用的配置(数据库地址、超时时间、功能开关)如果写死在容器镜像里,每次改配置都要重新构建镜像、重新发布,效率极低。ConfigMap把配置从镜像中剥离出来,以键值对的形式独立存储。Pod可以像“看菜谱”一样读取这些配置,镜像本身则保持通用不变。

ConfigMap有两种主流注入方式:一是通过环境变量注入,Pod启动时把配置值变成容器内的环境变量;二是把ConfigMap挂载成文件,容器运行时读取配置文件。二者的更新行为不同:环境变量注入是一次性的,修改ConfigMap后需要重建Pod才生效;文件挂载则会随ConfigMap更新而动态刷新(有秒级延迟)。

菜谱可以随时改,但已经出锅的菜不会自动变咸变淡。理解了这句,你就明白了为什么环境变量方式不能“热更新”——菜已经端上桌了。

Eenv=ConfigMap[k],file=ConfigMap[k]E_{\text{env}} = \text{ConfigMap}[k], \quad \text{file} = \text{ConfigMap}[k]
这个式子表示:ConfigMap 中的键值对既可以通过环境变量注入,也可以作为文件挂载到容器中。
  1. 键值对存储配置以 key-value 的形式保存在 ConfigMap 中。
    ConfigMap={k→v}\text{ConfigMap} = \{k \to v\}
  2. 环境变量注入Pod 启动时,将 ConfigMap 的某个键映射为容器的环境变量。
    Eenv=ConfigMap[k]E_{\text{env}} = \text{ConfigMap}[k]
  3. 文件挂载把 ConfigMap 整体或部分挂载为容器内的只读文件。
    file=ConfigMap[k]\text{file} = \text{ConfigMap}[k]

声明式API

你去饭店点菜,只需对服务员说“鱼香肉丝,少辣”,把目标描述清楚就够了,至于后厨是先切肉还是先热油,你完全不用操心。这就是声明式。反过来,如果你站在后厨边上喊“先放油!再下肉!再加酱!”,那就是命令式——你说一步,他做一步。

Kubernetes的API设计是声明式的:用户提供一个YAML文件,描述“我想要什么”(比如“3个运行nginx的Pod”),而不是“请依次执行哪些命令”。kubectl apply 就是声明式操作——把清单提交给API Server,然后由系统自行协调落地。

声明式的核心价值在于可重复和可自愈。同一个YAML文件提交一百次,结果完全一致(幂等);而且系统会持续对比“清单描述的状态”和“集群当前状态”,一旦现实偏离了声明,控制器会自动把它拉回来。命令式操作则很难做到这一点——你手动创建的Pod挂了就没了,没有人替你负责重建。

你只负责说“想吃什么”,后厨保证“菜必上桌”——声明式API把用户从繁琐的操作步骤中解放出来,让“要什么”和“怎么实现”彻底解耦。

APIServer:manifest↦statedesired,watch→reconcile\text{APIServer}: \text{manifest} \mapsto \text{state}_{\text{desired}}, \quad \text{watch} \to \text{reconcile}
这个式子表示:API Server 接收用户提交的资源清单并将其转化为期望状态,随后控制器通过监听机制驱动集群持续协调。
  1. 提交清单用户通过 YAML 文件描述期望状态。
    manifest\text{manifest}
  2. API 校验存储API Server 校验清单合法性后写入 etcd。
    APIServer:manifest↦statedesired\text{APIServer}: \text{manifest} \mapsto \text{state}_{\text{desired}}
  3. 监听与协调控制器监听资源变化,发现偏差就执行协调动作。
    watch→reconcile\text{watch} \to \text{reconcile}

协调循环

主厨的例行工作是什么?不是炒菜,而是“巡视”。他手里拿着期望菜单,每隔几分钟就在后厨转一圈:数数实际有多少师傅在岗。少三个,立刻打电话叫人来;多两个,安排休息。这个“看→比→调→再看”的无限循环,就是K8s控制器的灵魂——协调循环。

几乎所有K8s控制器(ReplicaSet、Deployment、DaemonSet等)的内部都是一个协调循环:读取期望状态(用户声明的replicas)、拉取实际状态(当前Pod列表)、计算差距、执行调整(创建/删除Pod),然后继续循环。这个循环没有“终点”,它永远在运行——这正是系统“自愈”能力的来源。

协调循环依赖两个底层组件:etcd持久化保存期望状态(YAML里写的“理想”),API Server暴露当前状态(集群里实际的“现实”)。控制器通过List-Watch机制订阅变化:先一次性拉取全量状态,再持续监听增量事件,保证任何偏差都能被及时发现。即使没有任何事件发生,部分控制器也会周期性地重新执行协调,防止“静默漂移”。

主厨永远不偷懒,K8s的控制器也永远不松懈。正是这永不停歇的巡视,让“期望”和“现实”的差距被时刻钉死——这就是云原生系统“自愈”的真相。

Δ=Sdesired−Sactual,action={createΔ>0deleteΔ<0noopΔ=0\Delta = S_{\text{desired}} - S_{\text{actual}}, \quad \text{action} = \begin{cases} \text{create} & \Delta > 0 \\ \text{delete} & \Delta < 0 \\ \text{noop} & \Delta = 0 \end{cases}
这个式子表示:控制器计算期望状态与实际状态的差值 Δ,根据正负决定创建、删除或保持现状。
  1. 读取期望从声明式清单中获得目标状态。
    SdesiredS_{\text{desired}}
  2. 观察实际从 API Server 汇总当前资源状态。
    SactualS_{\text{actual}}
  3. 计算差值Δ 大于 0 说明缺少资源需要补建,小于 0 说明多余需要删除,等于 0 则无需动作。
    Δ=Sdesired−Sactual\Delta = S_{\text{desired}} - S_{\text{actual}}
  4. 持续循环一轮协调结束后立即开始下一轮,永不停歇。
    reconcile→watch→reconcile\text{reconcile} \to \text{watch} \to \text{reconcile}

标签选择器

外卖订单上会有“少辣”“免葱”“多加香菜”这样的备注。后厨接单后,根据备注把订单分给相应的师傅:“少辣”给能把辣度调低的师傅。备注就是标签,筛选备注的过程就是选择器匹配。

标签(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世界里最重要的“组织纪律”。

match(p)=(k1=v1)∧(k2=v2)∧⋯∧(kn=vn)\text{match}(p) = (k_1 = v_1) \land (k_2 = v_2) \land \cdots \land (k_n = v_n)
这个式子表示:一个资源要被选中,必须同时满足选择器中所有键值对条件。
  1. 定义标签给资源贴上键值对标签,如 app=frontend。
    k=vk = v
  2. 声明选择器在资源清单中写出匹配规则。
    selector:{k1=v1,k2=v2}\text{selector}: \{k_1=v_1, k_2=v_2\}
  3. 与关系组合所有条件同时满足才算命中,缺一不可。
    ⋀i=1n(ki=vi)\bigwedge_{i=1}^{n} (k_i = v_i)

资源清单编写

在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才能精确执行——这就是“一切皆声明、声明即契约”的云原生世界观。

---

Manifest={apiVersion,kind,metadata,spec}\text{Manifest} = \{ \text{apiVersion}, \text{kind}, \text{metadata}, \text{spec} \}
这个式子表示:一份完整的资源清单由 API 版本、资源类型、元数据和规格四部分组成。
  1. apiVersion指定所使用的 API 组与版本,决定清单由谁来解析。
    apiVersion\text{apiVersion}
  2. kind 字段声明资源类型,是 Pod、Service 还是 Deployment。
    kind\text{kind}
  3. metadata包含资源名称、标签、命名空间等标识信息。
    metadata\text{metadata}
  4. spec 内容期望状态的核心描述,比如副本数、镜像、端口等。
    spec\text{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 演示

python61 行
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()
目的:用 Python 模拟 K8s 控制器的协调循环和 Service 的轮询负载均衡,演示“期望状态驱动实际状态收敛”的核心机制。
  1. ctl = ReplicaSetController(3)创建控制器并声明期望 3 个 Pod,对应 YAML 里的 spec.replicas。
  2. def reconcile(self)协调循环的核心方法:对比当前 Pod 数量与期望值,多删少补。
  3. self.gc_count计数器,用于生成不重复的 Pod 名称,呼应控制器每次协调都会产生新资源。
  4. svc = Service(ctl.pods)把当前 Pod 列表交给 Service,模拟 kube-proxy 维护后端转发列表。
  5. svc.request()每次请求按 self.qq_index % len(self.pods) 轮询选择 Pod,实现负载均衡。
  6. 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 中最小的调度单元是什么?

单选题 · easy

  • A.Pod 容器组← 正确答案
  • B.单个容器
  • C.物理服务器
  • D.虚拟网络

答案:A。K8s 的最小调度单元是 Pod,而不是容器。Pod 内可包含多个容器,它们共享网络和存储,调度器以 Pod 为单位决定任务落点,就像饭店以“一桌人”为单位安排座位。

Q2. Service 在 K8s 中的核心作用是什么?

单选题 · easy

  • A.提供稳定访问入口← 正确答案
  • B.负责镜像构建
  • C.执行容器调度
  • D.管理密钥分发

答案:A。Service 提供稳定的虚拟 IP 和 DNS 名字作为流量的统一入口,屏蔽后端 Pod IP 的频繁变化。它不像前台那样只接电话,而是帮客人把订单准确转给任何一位能做的厨师。

Q3. 下列哪个资源负责管理 Pod 的滚动更新?

单选题 · mid

  • A.Service
  • B.Deployment← 正确答案
  • C.ConfigMap
  • D.Namespace

答案:B。Deployment 负责声明期望副本数并执行滚动更新,通过 maxSurge 和 maxUnavailable 控制替换节奏。Service 只负责流量转发,ConfigMap 负责配置管理,Namespace 负责资源隔离。

Q4. Pod 内多个容器之间的关系是?

单选题 · mid

  • A.网络完全隔离
  • B.共享网络和存储← 正确答案
  • C.各自独立 IP
  • D.必须只有一个容器

答案:B。Pod 内所有容器共享同一个网络命名空间和存储卷,可以通过 localhost 互相通信,也能共享数据卷。这正是“一桌人共享道具和食材”的模型——它们是紧密协作的整体。

Q5. 标签选择器(Selector)的主要用途是什么?

单选题 · mid

  • A.过滤筛选资源← 正确答案
  • B.加密传输数据
  • C.自动分配 IP
  • D.压缩镜像体积

答案:A。标签选择器通过键值对匹配规则筛选资源,建立 Service 与 Pod、Deployment 与 Pod 之间的关联。就像外卖备注“少辣”让订单被分到正确的师傅手里,它只做筛选,不参与其他逻辑。

Q6. 控制器协调循环的核心动作是?

单选题 · mid

  • A.只观察实际状态
  • B.对比期望和实际← 正确答案
  • C.只在创建时执行
  • D.完全依赖人工干预

答案:B。协调循环的每一步都是“读取期望状态、观察实际状态、计算差值 Δ、执行调整”,然后不断重复。它不是一次性动作,而是像主厨巡视那样无限循环,让现实不断向期望收敛。

Q7. 滚动更新中 maxUnavailable 参数的含义是?

单选题 · hard

  • A.允许同时不可用数量← 正确答案
  • B.允许同时新增数量
  • C.Pod 最小副本数
  • D.更新完成阈值

答案:A。maxUnavailable 表示更新过程中最多允许几个 Pod 暂时不可用,用于保证服务不中断。比如期望 5 副本、maxUnavailable=1,则任何时刻至少 4 个 Pod 保持可用;maxSurge 才是允许额外新增的数量。

Q8. 声明式 API 与命令式 API 的本质区别是?

单选题 · hard

  • A.描述期望目标态← 正确答案
  • B.描述具体操作步
  • C.强调先后的执行序
  • D.依赖交互式命令

答案:A。声明式 API 的核心是“描述想要什么”,而不是“怎么一步步做”。kubectl apply 提交 YAML 后由系统自动协调达成状态,可重复且可自愈;命令式则像一步一步指挥后厨做菜,挂了没人负责重建。

Q9. K8s 资源清单的 metadata 字段可以包含哪些内容?

多选题 · mid

  • A.资源名称← 正确答案
  • B.标签选择器
  • C.期望副本数
  • D.命名空间← 正确答案

答案:AD。metadata 存放资源的“身份信息”——名称和命名空间,同时还可以包含标签(Label)供选择器匹配。标签选择器和期望副本数都属于 spec 字段的内容,API Version 则是独立的顶层字段。

Q10. 以下关于 ConfigMap 的说法正确的是?

多选题 · hard

  • A.配置从镜像分离← 正确答案
  • B.环境变量注入即刻生效
  • C.可作为文件挂载← 正确答案
  • D.只能保存加密数据

答案:AC。ConfigMap 把配置从镜像中剥离,支持环境变量注入和文件挂载两种方式。通过环境变量注入的配置在容器启动时读取一次,修改后不会即刻生效;它也不提供加密能力,敏感数据应使用 Secret。