你有没有想过,当你打开一个AI绘画网站,输入提示词,几秒钟后一张精美的图片就出现在眼前——这背后其实是成千上万个“容器”在云端的服务器上协同工作。这些容器就像一个个小厨师,各有分工,有的负责图片生成,有的负责文本理解,有的负责模型加载。但问题来了:谁来安排这些“厨师”?谁告诉一个“厨师”该去哪儿找另一个“厨师”?当某个“厨师”突然生病请假,又有谁来补位? 在 Kubernetes(简称 K8s)的世界里,答案就是 Pod 和 Service。你完全可以把它想象成一家大型餐厅的后厨:Pod 是后厨里的一个个“工作小分队”,每个小分队自带灶台、食材和工具,随时准备开火;而 Service 则是前台的“领位员”,顾客只需要喊一声“我要点餐”,领位员就会优雅地把你带到正确的窗口,至于窗口后面是张厨师还是李厨师在掌勺,你根本不用关心。 这篇文章将成为你的后厨参观指南。我们会用小分队的视角看懂 Pod 的一生,用领位员的视角理解 Service 的服务发现与负载均衡,还会聊到只有 AI 工程师才懂的痛点——比如如何给“AI 大厨”限定食材预算(资源限制),如何给几十个同款小分队编号管理(标签选择器)。学完这篇文章,你不仅能看懂 K8s 的编排逻辑,更能亲手把自己的 AI 模型服务部署到一个高可用的集群上。

岗位关联

对 AI 从业者而言,K8s 是模型上线的“最后一公里”。掌握 Pod 与 Service,你就能独立把训练好的模型变成 7x24 小时稳定对外服务的产品,不再求人、不再担心服务宕机。

目录
  1. 可视化演示
  2. Pod概念
  3. Pod生命周期
  4. 资源限制配置
  5. Service概念
  6. 服务发现机制
  7. 负载均衡原理
  8. 标签选择器
  9. 端点管理机制
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示对称加密算法的多轮迭代变换过程,包括替换、置换、混淆和密钥加等操作,动态演示明文到密文的数据流。

本节配有交互动效(进入视口后自动加载,约 3KB)。

加密多轮变换 红色数据块表示明文经多轮变换逐步扩散为密文 每轮包含SB替换、SR行移位、MC列混淆、ARK密钥加四步 Ci = MC(SR(SB(Pi)) ⊕ Ki)

Pod概念

想象一下,一家米其林餐厅的后厨不只是一个个独立的灶台,而是每道招牌菜都有一个“专属小分队”:一位主厨、一位配菜师、一位摆盘师。他们共享同一张操作台、同一套刀具,配合默契地完成一道菜。在 Kubernetes 的世界里,这个“小分队”就是 Pod——它是 K8s 中最小、最基本的调度单元,是能独立创建、调度和运行的最小单位。

Pod 之所以是“小分队”而非“单个厨师”,是因为它能把多个容器(Container)打包在一起。这些容器共享同一个网络命名空间、同一个存储卷,意味着它们可以用 localhost 直接互通,就像小分队成员共用一台对讲机,不需要打电话。这种模式在 AI 工程中极其常见:主容器跑 TensorFlow Serving 模型推理,旁边的 sidecar 容器负责收集日志、监控指标,两者亲密无间地协作。

从技术实现上看,Pod 是对容器的“一层包装”。K8s 不直接操作单个容器,而是通过 Pod 统一管理。Pod 里的每个容器都被分配到同一个节点上,共享该节点的 IP 地址(Pod IP),端口也不能冲突——就像小分队共用一个工位,不能两个人同时坐在同一张椅子上。这种设计使得 Pod 成为天然的资源边界和故障边界。

1. 最小调度单元:Pod 是 K8s 中创建、调度和部署的最小原子单位,Node 节点只认 Pod。

Pod→Container1+Container2+⋯+Containern\text{Pod} \rightarrow \text{Container}_1 + \text{Container}_2 + \cdots + \text{Container}_n

2. 共享网络命名空间:Pod 内所有容器共享同一个 IP 和端口空间,用 localhost 即可互相访问。

IPPod=IPContainer1=IPContainer2=⋯\text{IP}_{\text{Pod}} = \text{IP}_{\text{Container}_1} = \text{IP}_{\text{Container}_2} = \cdots

3. 共享存储卷:Pod 级 Volume 挂载到每个容器,实现数据共享。

Volume↪{c1,c2,…,cn},∀i, pathi=shared_dir\text{Volume} \hookrightarrow \{c_1, c_2, \ldots, c_n\}, \quad \forall i, \ \text{path}_i = \text{shared\_dir}

一句话总结:Pod 是“共担风险、共享资源”的协作小分队,它是 K8s 世界的乐高积木——积木里的每个容器可以单独拼装,但积木本身才是搬动的最小单位。

Pod生命周期

小分队从组建到解散,会经历一系列阶段。K8s 为 Pod 设计了完整的生命周期管理,就像餐厅有一套人员管理流程:面试(Pending)→ 入职上岗(Running)→ 定期体检(健康检查)→ 离职交接(Terminating)。

Pod 的状态流转是理解 K8s 的第一步。当你提交一个 Pod 定义,它会先进入 Pending 状态(等待被分配到节点),调度器(Scheduler)像人事经理一样挑选合适的节点,之后 Pod 变为 Running(容器已启动)。运行过程中若节点故障或容器崩溃,Pod 会进入 Failed 或 Unknown,控制器会像猎头一样立即“再找一个”新 Pod 顶上。

健康检查是生命周期中的“定期体检”。K8s 提供两类探针:存活探针(livenessProbe)判断容器是否“活着”,如果死了就重启它;就绪探针(readinessProbe)判断容器是否“能干活”,如果没就绪就从 Service 的负载池中摘除,不分配新请求。这两者就像餐厅的“是否在岗”和“是否能接单”两个指标——在岗不代表能立刻接单,可能还在收拾灶台。

优雅终止是生命周期的最后一环,也是最体现 K8s 细腻的地方。当 Pod 需要被删除时,K8s 会先发送 SIGTERM 信号,等待容器执行清理逻辑(比如保存模型状态、断开数据库连接),这一过程叫 preStop 钩子;若超过宽限期(默认 30 秒)仍未退出,才会发送 SIGKILL 强制杀死。这就像一个即将离职的厨师要把灶台收拾干净、向团队交接清楚,而不是被保安直接架出去。

1. 状态流转:Pod 从创建到销毁经历 Pending → Running → Succeeded / Failed。

Pending→调度成功Running→正常退出Succeeded / →异常Failed\text{Pending} \xrightarrow{\text{调度成功}} \text{Running} \xrightarrow{\text{正常退出}} \text{Succeeded} \ / \ \xrightarrow{\text{异常}} \text{Failed}

2. 存活探针:livenessProbe 定期探测,失败则 Kubelet 重启容器。

liveness={success→保持现状failure×n→restart_container\text{liveness} = \begin{cases} \text{success} & \rightarrow \text{保持现状} \\ \text{failure} \times n & \rightarrow \text{restart\_container} \end{cases}

3. 就绪探针:readinessProbe 失败时从 Service Endpoints 中摘除该 Pod。

ready(Pod)=false  ⟹  Pod∉EndpointsService\text{ready}(\text{Pod}) = \text{false} \implies \text{Pod} \notin \text{Endpoints}_{\text{Service}}

4. 优雅终止:先 SIGTERM,等待 preStop 和 terminationGracePeriodSeconds,超时后 SIGKILL。

SIGTERM→grace_periodSIGKILL,grace_period=30s (默认)\text{SIGTERM} \xrightarrow{\text{grace\_period}} \text{SIGKILL}, \quad \text{grace\_period} = 30\text{s (默认)}

Pod 的生命周期像弹簧一样充满韧性:崩溃了会被拉起,不健康了会被摘除,离职了会体面交接。正是这套流程,让 AI 服务能在无人值守的情况下 7x24 小时稳定运行。

资源限制配置

餐厅给每个小分队分配食材预算是有限度的——既不能饿着他们干活(没食材做不出菜),也不能让他们无限取材(把整个仓储搬空)。Pod 的资源管理正是这个逻辑:requests(请求量)和 limits(上限)是每个 Pod 都要申报的两项“预算”。

requests 是 Pod 运行所需的最低资源保证,类似员工的“基本工资承诺”。调度器在决定把 Pod 放到哪台节点时,会检查节点的剩余资源是否满足 requests 之和——就像人事安排工位时先确认“这个工位至少能放下一个灶台”。它保证 Pod 至少能得到这么多资源。如果节点资源不足,Pod 会一直停留在 Pending 状态,相当于“没位子,等着”。

limits 则是 Pod 能使用的资源硬性上限,类似“年度报销额度”,超过就自费。对于 CPU,一旦达到 limit,容器会被限流(throttled),相当于给你空调但只能开制冷模式不能开制热。对于内存,情况更嚴酷:超过 limit 会触发 OOM(Out Of Memory)Kill,相当于预算超支直接岗位取消——容器被强制杀死。这就是 AI 工程师最怕看到的 OOMKilled 状态。

这两个参数直接决定了调度与驱逐机制。Kubernetes 依据 requests 做调度决策(把 Pod 放哪),依据 limits 做运行约束(控制它吃多少)。当节点资源吃紧时,Kubelet 会优先驱逐使用量超过 requests 的 Pod——就像餐厅临时裁员,先裁那些拿得多干得少的。

1. 调度决策依据 requests:调度器选择节点时要求节点可分配资源 ≥ Pod 所有容器的 requests 总和。

allocatable(N)−∑Pod∈Nrequests(Pod)≥requests(new Pod)\text{allocatable}(N) - \sum_{\text{Pod} \in N} \text{requests}(\text{Pod}) \geq \text{requests}(\text{new Pod})

2. CPU 限流(throttle):当容器 CPU 使用 > limit 时,CFS 配额限制其调度时间片。

quota_period=cpu_limit×periodcpu_capacity,throttled_time↑\text{quota\_period} = \frac{\text{cpu\_limit} \times \text{period}}{\text{cpu\_capacity}}, \quad \text{throttled\_time} \uparrow

3. 内存驱逐(OOM):超过 memory limit 触发内核 OOM Killer,按 oom_score 优先杀死。

usagemem>limitmem  ⟹  SIGKILL,reason=OOMKilled\text{usage}_{\text{mem}} > \text{limit}_{\text{mem}} \implies \text{SIGKILL}, \quad \text{reason} = \text{OOMKilled}

4. 节点驱逐策略:磁盘或内存压力下,Kubelet 按 QoS(服务质量)等级,从 BestEffort 到 Guaranteed 依次驱逐。

Besteffort≻Burstable≻Guaranteed\text{Besteffort} \succ \text{Burstable} \succ \text{Guaranteed}

资源限制就像给 AI Pod 立下的食堂规矩:requests 是“保底饭票”,limits 是“餐费上限”。优秀的 AI 工程师会仔细评估模型推理的峰值内存,既不让 Pod 饿到 OOM,也不让资源浪费——毕竟云上每一毫核 CPU 都是钱。

Service概念

小分队可以随时来随时走,但顾客可不想每次来都发现招牌菜换了窗口。Kubernetes 需要一个“永远的招牌”——这就是 Service。Service 是一个稳定的访问入口,它像前台的领位员一样,无论后面真正接待你的是哪张桌子、哪位厨师,你只需要记住“这家餐厅的招牌菜名”即可。

Service 的核心思想是将业务逻辑与具体 Pod 实例解耦。在 K8s 中,Pod 的 IP 是“朝生暮死”的——Pod 被删除重建后 IP 就变了。如果 AI 客户端直接记录 Pod IP,那每次模型服务重启后客户端就找不到北了。Service 拥有一个稳定的虚拟 IP(ClusterIP),专门解决这个问题。

从类型上看,最常用的是 ClusterIP 类型,它在集群内部提供一个虚拟 IP,只有集群内能访问。它通过 kube-proxy 组件维护 iptables 规则,将这些规则翻译成“流量路由表”。当数据包到达 Service IP 时,iptables 会把它转发到某个真实的 Pod IP——这就是四层(L4)转发,它发生在 TCP/UDP 层,只认 IP 和端口号,不关心你的请求是 HTTP 还是 HTTPS。

Service 的定义非常优雅:它包含一个 selector(选择器),像一个“工作岗位描述”,凡是标签匹配的小分队都算这个 Service 的下属。Service 创建时,K8s 的控制器会自动找出所有符合条件的 Pod,并把它们的 IP 加入后端的转发列表。这个列表就是 Endpoints 对象——后续章节会详细展开。

1. 稳定虚拟 IP:Service 创建时,API Server 分配一个 ClusterIP。

ClusterIPsvc=virtual_ip,固定不变,直至删除\text{ClusterIP}_{\text{svc}} = \text{virtual\_ip}, \quad \text{固定不变,直至删除}

2. Selector 匹配:选择器基于标签筛选后端 Pod,形成后端集合 S。

S={Podi∣label(Podi)⊨selector(Service)}\mathcal{S} = \{ \text{Pod}_i \mid \text{label}(\text{Pod}_i) \models \text{selector}(\text{Service}) \}

3. 代理规则(iptables):kube-proxy 为 Service IP 写入 DNAT 规则。

DNAT:ClusterIP:portsvc⟶PodIPi:portpod,i 轮询选择\text{DNAT}: \quad \text{ClusterIP} : \text{port}_{\text{svc}} \longrightarrow \text{PodIP}_{i} : \text{port}_{\text{pod}}, \quad i \text{ 轮询选择}

Service 就像餐厅门口那块永不更换的招牌灯箱——招牌后面哪怕换了三批厨子、换了四个灶台,顾客永远只需要认准这块招牌。AI 工程师只需要记住模型服务的 Service 名,就能永远访问到最新版本的模型。

服务发现机制

顾客来了,但领位员怎么知道该把顾客带到哪张桌子?如果招牌菜从“三号窗口”搬到了“七号窗口”,领位员怎么得到消息?在 K8s 中,这个“内部通讯录”就是服务发现机制——Pod 查询 Service 背后真实 IP 的方式,主要有两种:环境变量和 DNS。

环境变量是最朴素的方式,相当于入职时发给你的纸质通讯录。当 Pod 创建时,Kubelet 会把集群中已存在的 Service 信息注入为其环境变量,比如 MY_SERVICE_SERVICE_HOST 和 MY_SERVICE_SERVICE_PORT。这就像新员工入职时拿到一份打印的通讯录,但问题很严重:纸质的通讯录不会自动更新——如果 Service 在你之后才创建,你永远得不到它的信息。所以这种方式只适用于“先有 Service 后有 Pod”的场景。

DNS 是更主流的方式,相当于企业内部的内线电话系统。K8s 集群内置 DNS 服务(如 CoreDNS),Pod 中任何程序都可以通过“名字”访问 Service,比如 my-model-service 这个 Service 的完整域名是 my-model-service.default.svc.cluster.local——其中 default 是命名空间(namespace),svc 表示这是 Service,cluster.local 是集群域名后缀。DNS 服务器会实时跟踪 Service 的变化,自动更新解析记录,这比纸质通讯录先进多了。

服务发现带来的最大好处是IP 自动更新。当 Service 后端的 Pod 被删掉重建(IP 变更),DNS 和 Endpoints 会自动同步,应用层完全无感知。就像通讯录换成了云端联系人——有人改了手机号,你的通讯录自动更新,不需要手动同步。

1. 环境变量注入:Pod 启动时,Kubelet 将 Service 的 IP 和端口写为环境变量。

env[{SVC}SERVICE_HOST]=ClusterIPsvc\text{env}[\{SVC\}_SERVICE\_HOST] = \text{ClusterIP}_{\text{svc}}

env[{SVC}SERVICE_PORT]=portsvc\text{env}[\{SVC\}_SERVICE\_PORT] = \text{port}_{\text{svc}}

2. DNS 解析:Pod 内应用通过 Service 的 DNS 名称解析得到 ClusterIP。

resolve(svc_name.namespace.svc.cluster.local)→ClusterIPsvc\text{resolve}(\text{svc\_name}.\text{namespace}.svc.\text{cluster.local}) \rightarrow \text{ClusterIP}_{\text{svc}}

3. 服务端自动同步:kube-controller-manager 监控 Service 与 Endpoints 变化。

ΔService→watchΔEndpoints→eventΔDNS 记录\Delta \text{Service} \xrightarrow{\text{watch}} \Delta \text{Endpoints} \xrightarrow{\text{event}} \Delta \text{DNS 记录}

服务发现机制是 AI 应用上云的“自动寻址”能力。当你的模型服务从 1 个副本扩容到 50 个副本,客户端代码不需要任何修改——它只需要记住那个“领位员的名字”,领位员会自动分配最新鲜的“座位”。

负载均衡原理

餐厅有 10 张餐桌,领位员怎么决定把顾客安排到哪张桌子?最简单的策略是“轮流叫号”——1 号桌、2 号桌……循环往复,这就是 轮询(Round-Robin) 负载均衡算法。K8s 的 Service 默认就对后端 Pod 做轮询转发。

负载均衡是 Service 最重要的流量分发功能。当流量到达 Service 的 ClusterIP 时,kube-proxy 通过 iptables 规则链完成转发。具体机制是:K8s 为每个 Service 创建一条 iptables 链,链上有若干条规则,每条规则对应一个后端 Pod,并有匹配概率。概率由 --probability 参数控制,本质上是按权重随机选择——权重相等时就是轮询效果。

不过这种默认方式并不完美。比如一个请求量很大的 AI 推理服务,如果每个请求都被随机分配到任意 Pod,可能会导致某些 Pod 忙的忙死,闲的闲死。K8s 还支持 会话保持(SessionAffinity)——把同一个客户端的请求固定转发给同一个 Pod,就像餐厅领位员记住熟客的习惯喜好,老主顾来了总坐同一个位置。这可以通过设置 sessionAffinity: ClientIP 实现,对于需要维持张量状态的推理服务非常有用。

负载均衡的深层价值在于 弹性伸缩配合。当某个 Pod 因为内存不足被 OOM Kill 重建后,iptables 规则会自动更新(通过 Endpoints 同步),把新 Pod 的 IP 加入转发池。扩容同理:从 2 个 Pod 扩到 5 个 Pod,规则链自动增加 3 条,新增的 Pod 立刻被纳入轮询队列。

1. 轮询策略:每个后端 Pod 以相等的概率被选中。

P(转发到 Podi)=1n,n=∣S∣,∑i=1n=1P(\text{转发到 Pod}_i) = \frac{1}{n}, \quad n = |\mathcal{S}|, \quad \sum_{i=1}^{n} = 1

2. iptables 随机匹配:kube-proxy 为每一条 DNAT 规则设置匹配概率。

rulei: −−match statistic −−mode random −−probability pi\text{rule}_i: \ --match \ statistic \ --mode \ random \ --probability \ p_i

3. 会话保持:基于客户端 IP 哈希选择固定后端。

backendi=hash(src_IP) mod n,∀请求同源→同一 backend\text{backend}_i = \text{hash}(\text{src\_IP}) \bmod n, \quad \forall \text{请求同源} \rightarrow \text{同一 backend}

负载均衡是 Post 时代的“自动排班表”。它让几十个 AI 模型副本有条不紊地分担流量——有的忙完先休息(缩容),有的加班加点(扩容),而领位员始终冷静地“叫号”,不让任何一个服务实例被流量打垮。

标签选择器

K8s 星系里到处都是 Pod、Service、Deployment 等各种对象,如何精准地管理它们?答案就是标签(Label)——为云中万物打的“身份证标签”。标签是键值对(key-value)结构,如 app=ai-model、tier=backend、version=v2,像超市货架上的分类标签:既有“薯片区”也有“饮料区”。

标签选择器(Label Selector)则是“在超市找商品”的筛选逻辑。K8s 支持两种选择运算。等值选择(Equality-based)是最简单的形式:app=ai-model 表示只选标签 app 的值恰好是 ai-model 的对象,类似于 Excel 的筛选等于;还可写成 app!=web 表示排除不等于。这种选择器精确但局限,适合小规模过滤。

集合选择(Set-based)更灵活:env in (prod, staging) 会选择标签 env 的值为 prod 或 staging 的对象;tier notin (frontend) 则排除值为 frontend 的对象。这就好比超市导购按“品牌 A 或品牌 B”帮你找饮料——扩大了搜索范围。Service 的 Selector 就是通过标签选择器精确锁定它要代理的 Pod 集合。

标签是 K8s 的“关系型数据库索引”——通过标签能优雅地实现复杂的对象关联。例如给 AI 模型推理服务的 Pod 打上 version=v1,升级时新 Pod 打 version=v2,通过修改 Service 的 selector 从 v1 切到 v2,就能实现金丝雀发布(百分之几流量先打到新版本)。这就是工程上常见的标签驱动的发布策略。

1. 等值选择:相等匹配,基于键的精确值。

selector={key=value}  ⟺  labelobj(key)=value\text{selector} = \{ \text{key} = \text{value} \} \iff \text{label}_{\text{obj}}(\text{key}) = \text{value}

2. 不等值选择:排除特定值。

key≠value  ⟺  labelobj(key)≠value\text{key} \neq \text{value} \iff \text{label}_{\text{obj}}(\text{key}) \neq \text{value}

3. 集合选择(in/notin):基于值列表的成员判断。

key∈{v1,v2,…}  ⟺  labelobj(key)∈{v1,v2,…}\text{key} \in \{v_1, v_2, \ldots\} \iff \text{label}_{\text{obj}}(\text{key}) \in \{v_1, v_2, \ldots\}

key∉{v1,v2,…}  ⟺  labelobj(key)∉{v1,v2,…}\text{key} \notin \{v_1, v_2, \ldots\} \iff \text{label}_{\text{obj}}(\text{key}) \notin \{v_1, v_2, \ldots\}

标签选择器是 K8s 的“搜索引擎”。有了它,你可以像使用数据库查询一样,在成千上万个 Pod 中瞬间锁定目标,实现资源的精准管理——这是运维 AI 模型集群的一双“火眼金睛”。

端点管理机制

领位员手里的“座位登记表”必须实时准确:哪张桌空着、哪张桌客人刚离开、哪张桌正在翻台——这就是 Endpoints 对象。在 K8s 中,Endpoints 是一个独立 API 对象,记录了 Service 背后所有就绪的 Pod IP 和端口列表,是 Service 转发流量的最终“发货单”。

自动同步机制是 Endpoints 的灵魂。kube-controller-manager 中的 EndpointSliceController 持续监听 Pod 和 Service 的变化。当新 Pod 启动并变为就绪状态时,控制器自动把它加入 Endpoints 列表;当 Pod 被删除或健康检查失败时,控制器自动把它移除。这种自动同步保证了“领位员的信息永远是最新鲜的”——不需要人工修改任何配置。

就绪状态过滤是 Endpoints 的核心逻辑。一个 Pod 即使处于 Running 状态,如果它的 readinessProbe 尚未通过,就不会被加入 Endpoints——相当于“人在工位但还没准备好接客”。只有就绪的 Pod 才是 Service 的有效后端。这个机制在滚动更新中尤为重要:新版本 Pod 只有通过就绪检查后才开始接收流量,而旧版本 Pod 在终止前会被先从 Endpoints 中摘除,从而实现零中断的发布——这在 AI 模型升级时至关重要。

端点管理机制是 Service 高可用的最终保障。无论 Pod 怎么变,只要 Endpoints 列表正确,Service 的流量转发就不会出错。在实际 AI 工程中,你可以通过 kubectl get endpoints 查看模型服务当前有哪些 Pod 在承接流量,诊断“为什么请求没有打到新 Pod”等疑难杂症。

1. 自动同步:EndpointSliceController 通过 Watch 机制监听 Pod / Service 事件。

Watch(Pod,Service)→eventUpdate(Endpoints)\text{Watch}(\text{Pod}, \text{Service}) \xrightarrow{\text{event}} \text{Update}(\text{Endpoints})

2. 就绪过滤:只有 ready=True 的 Pod 才被写入 Endpoints。

Endpoints={Podi∣ready(Podi)=true}\text{Endpoints} = \{ \text{Pod}_i \mid \text{ready}(\text{Pod}_i) = \text{true} \}

3. 滚动更新协调:新版 Pod 就绪后加入,旧版 Pod 摘除后终止。

Podnew→readyEndpoints+ Podnew;Podold→removeEndpoints−Podold\text{Pod}_{\text{new}} \xrightarrow{\text{ready}} \text{Endpoints} + \ \text{Pod}_\text{new}; \quad \text{Pod}_{\text{old}} \xrightarrow{\text{remove}} \text{Endpoints} - \text{Pod}_\text{old}

Endpoints 是 K8s 系统中那个“不显眼但绝对不能出错”的总账本。它让 Service 的领位员永远拿着最新版座位图——干净、准确、随时可查。

AI 应用场景

场景名 1:AI 模型推理服务的高可用部署

阶段:模型部署与上线

解决的问题:将训练好的模型(如 BERT、ResNet)部署为在线推理服务时,单点部署会因 Pod 崩溃、节点故障导致服务中断,且无法应对突发流量洪峰。

具体案例:某智能客服公司要将 GPT 类模型部署为 RESTful API 服务。工程师创建了一个 Deployment 管理 3 个 Pod 副本,每个 Pod 内跑模型推理容器。再创建一个 Service(ClusterIP),通过标签选择器 app=chatbot 将三个副本纳入负载均衡池。当流量突增时,HPA(水平自动伸缩)根据 CPU 指标将副本扩展至 10 个,新 Pod 自动通过就绪探针检测后加入 Service 的 Endpoints 列表,整个过程无需人工干预。

场景名 2:AI 模型版本迭代的金丝雀发布

阶段:模型迭代与升级

解决的问题:模型从 v1 升级到 v2 时,一次性切换风险极大——如果新模型效果差或有 bug,会导致全部线上业务受影响。

具体案例:推荐系统团队在 Service 前新增一个入口网关,为旧 Pod 打标签 version=v1,新 Pod 打 version=v2。先创建一个 selector 为 app=recsys AND version=v1 的 Service 承接全部流量,再创建 selector 为 app=recsys AND version=v2 的新 Service,通过网关配置将 5% 流量切到 v2,验证指标正常后再逐步增加权重,最后全量切换并删除旧 Service。整个过程零宕机,用户无感知。

Python 演示

python81 行
"""K8s Pod 与 Service 核心机制模拟器用 Python 对象模拟 Pod 生命周期、标签选择和 Service 负载均衡。"""import timeimport random  class Pod:    """模拟一个 K8s Pod(小分队)"""     def __init__(self, name, labels, cpu_request=0.5, mem_request=512):        self.name = name        self.labels = labels        self.cpu_request = cpu_request        self.mem_request = mem_request        self.ready = False        self.status = "Pending"     def start(self):        """模拟 Pod 启动与就绪探针通过"""        self.status = "Running"        time.sleep(0.1)        self.ready = True        print(f"[Pod] {self.name} 启动完成,就绪状态={self.ready}")  class Service:    """模拟 K8s Service:标签选择 + 轮询负载均衡"""     def __init__(self, name, selector):        self.name = name        self.selector = selector        self.backends = []     def match(self, pod):        """标签选择器匹配(等值选择)"""        for key, value in self.selector.items():            if pod.labels.get(key) != value:                return False        return True     def update_endpoints(self, pods):        """模拟 Endpoints 自动同步(只保留就绪 Pod)"""        self.backends = [p for p in pods if self.match(p) and p.ready]        print(f"[Service] {self.name} 同步 Endpoints: "              f"{[p.name for p in self.backends]}")     def forward(self, request_id):        """轮询负载均衡:按 % len(backends) 转发"""        if not self.backends:            print(f"[Service] 无可用后端,请求 {request_id} 失败!")            return        # 轮询选择        idx = request_id % len(self.backends)        target = self.backends[idx]        print(f"[Service] 请求 {request_id} -> "              f"{target.name} (轮询位置 {idx})")  def main():    # 创建 3 个模型推理 Pod    pods = [        Pod("ai-model-v1-abc", {"app": "chatbot", "version": "v1"}),        Pod("ai-model-v1-def", {"app": "chatbot", "version": "v1"}),        Pod("ai-model-v2-ghi", {"app": "chatbot", "version": "v2"}),    ]    for pod in pods:        pod.start()     # Service 只选 v1 版本的 Pod    svc = Service("chatbot-service", {"app": "chatbot", "version": "v1"})    svc.update_endpoints(pods)     # 模拟 6 个请求的轮询转发    for i in range(6):        svc.forward(i)  if __name__ == "__main__":    main()
目的:用 Python 模拟 K8s 中 Pod 的就绪状态、Service 的标签选择器匹配和 Endpoints 同步,以及轮询负载均衡的核心机制。
  1. class Pod:定义 Pod 类,模拟小分队,记录名称、标签、资源请求和就绪状态。
  2. self.ready = False初始状态不可用,代表还没通过就绪探针。
  3. def match(self, pod):标签选择器核心逻辑,逐项检查 Pod 的标签是否满足 Service 的所有匹配条件。
  4. self.backends = [p for p in pods if self.match(p) and p.ready]Endpoints 同步:精确模拟 K8s 的做法,只有“标签匹配 + 就绪”的 Pod 才进入后端列表。
  5. idx = request_id % len(self.backends)轮询策略:用取模运算实现请求的循环分配。
  6. Pod("ai-model-v1-abc", {...})创建带标签的 Pod,模拟给 Pod 打上版本标签。
  7. svc = Service("chatbot-service", ...)创建 Service,指定选择器筛选 v1 版本的 Pod。
  8. svc.update_endpoints(pods)模拟 Endpoints 控制器的自动同步动作。

常见误区

误区我一直以为 Service 是用来给外部用户访问集群内服务的,内部的 Pod 之间通信不需要 Service,直接写对方 Pod 的 IP 就行。

正解实际上 Service 主要是为集群内部的稳定服务发现和负载均衡设计的!ClusterIP 类型的 Service 只有集群内部可以访问(外部需要通过 NodePort 或 Ingress 暴露)。而且正因为 Pod 的 IP 是“短命”的(随时可能重建),内部服务之间才更需要通过 Service 的稳定 DNS 名访问,而不是直接记忆 IP。想象一下,后厨小分队之间沟通,应该打到固定的分机号(Service),而不是记某个厨师的手机号(Pod IP)——人走了号码就废了。

为何常见:大家接触 K8s 时最先看到的是“将应用暴露给外部”的场景(部署一个网站),自然把 Service 等同于“外部入口”。实际上 Service 最主要的价值在集群内部的南北东西流量的统一治理。

误区我配置好了 Service 和 Pod,如果 Pod 挂了我是不是就不用管了?Service 会自动创建一个新 Pod 顶上吧?

正解Service 本身不具备自动恢复能力。它只有一个“领位员”的职责——只知道把请求分给谁,不知道“人没了要立刻招聘”。真正负责“创建新 Pod 顶上”的是Deployment、StatefulSet 等控制器(Controller)。Service 只负责:Pod 挂掉后把它从 Endpoints 列表摘除,但当 Deployment 创建了新 Pod,Service 会自动将其加回后端。正确的架构应该是:Deployment 管“生死”,Service 管“路由”,两者配合才形成自愈能力。

为何常见:把 Service 和 Deployment 当成了一个东西。在 K8s 中每个对象职责单一,理解“谁管生命周期、谁管流量分发”是入门关键。

误区我的 AI 模型推理特别吃内存,为了避免 OOM,我把 memory limit 设为 100Gi,这样容器就不会被杀了吧?

正解恰恰相反!limits 设置过高反而更危险。当节点内存不足时,Kubelet 会进行节点级驱逐(node pressure eviction),而请求量使用率超过 requests 的 Pod 会优先被驱逐。如果你的 Pod requests 只写了 1Gi 但实际用了 20Gi,节点内存紧张时调度器会认为你占用“超预算资源”,第一个拿你开刀。正确做法是:requests 和 limits 都要基于实际压测数据设置,requests 描述“正常水位”,limits 设置“安全红线”,两者差距不要过大。就像你申报差旅费:申报上限太高,公司会怀疑你铺张浪费,审批排期反而更严格。

为何常见:把 limits 当作“性能升级”手段,实际上 limits 是约束而非优化参数。资源调优靠的是合理配置 requests/limits 比例和选择合适的节点规格。

误区我听说 K8s 里服务要用 Service 来访问,那我一个 Pod 里有两个容器,它们是不是也要创建 Service 才能互相调用?

正解完全不需要!同一个 Pod 内的多个容器共享同一个网络命名空间(shared network namespace),它们可以通过 localhost 或 127.0.0.1 直接通信,甚至共享同一个端口空间(注意端口不能冲突)。Service 的目的是跨 Pod、跨节点稳定访问。想象一下:小分队里两个成员面对面交流当然不用通过前台总机——直接递个纸条(localhost)就行了;但跨团队沟通才需要拨总机号(Service)。

为何常见:刚学 K8s 时容易把“容器间”和“Pod 间”两个层级混为一谈,没有意识到 Pod 内部容器的“家人关系”和 Pod 之间“同事关系”的差异。

误区探针就是检查容器正不正常吗?那我写一个探针就行了吧,就绪和存活有什么区别?

正解区别巨大!存活探针(livenessProbe) 回答“容器是否活着”——探测失败会杀容器并重启,就像发现厨师突然倒地了,马上送医;就绪探针(readinessProbe) 回答“是否准备好接收流量”——探测失败不会杀容器、重启,而是从 Service 的 Endpoints 中摘除,像厨师还在但正在擦灶台,先不给他安排客人。你的模型服务加载 10GB 模型需要 3 分钟,这个阶段存活探针很容易误判(进程启动但没 hang),就绪探针才是调节“上菜时机”的关键。两者配合使用才是完整方案。

为何常见:初学阶段容易用一个探针“包打天下”,遇到“加载期被杀”或“已死还在接流量”的问题时才意识到二者缺一不可。

面试题

Q1. 在 Kubernetes 中,Pod 是什么?

单选题 · easy

  • A.一种容器运行时工具
  • B.一个或多个容器的组合的最小调度单元← 正确答案
  • C.一组集群节点的统称
  • D.一种镜像构建服务

答案:B。Pod 是 K8s 中最小的调度单元,它包含一个或多个容器,共享网络和存储。你可以把 Pod 想象成一个小分队,容器是队里的成员。选项 A 错在把 Pod 当成了类似 Docker 的运行时工具;C 错在把 Pod 当成了节点集合;D 错在混淆了镜像构建与调度编排。

Q2. 同一个 Pod 内的多个容器之间,可以通过什么方式直接通信?

单选题 · mid

  • A.通过 Service 的 ClusterIP
  • B.通过 NodePort
  • C.通过 localhost← 正确答案
  • D.通过 Ingress 域名

答案:C。Pod 内的容器共享同一个网络命名空间和 IP 地址,所以直接用 localhost 或 127.0.0.1 就能访问彼此,就像小分队成员面对面说话不需要打电话。Service 的 ClusterIP、NodePort 和 Ingress 都是跨 Pod、跨节点的访问方式,对于 Pod 内部通信来说是多余且不生效的。

Q3. K8s 中 Service 的主要作用是什么?

单选题 · easy

  • A.存储应用数据
  • B.为 Pod 提供稳定的访问入口← 正确答案
  • C.自动部署容器镜像
  • D.监控集群节点健康

答案:B。Service 是一个稳定的虚拟 IP + DNS 入口,后端对接一组动态变化的 Pod,类似餐厅门口的固定招牌。它不存储数据(那是 Volume 的功能),不部署镜像(那是 Deployment 的工作),也不负责监控节点(那是 kubelet 和监控系统做的)。

Q4. 下列哪种探针失败时会将 Pod 从 Service 的 Endpoints 中摘除?

单选题 · mid

  • A.存活探针
  • B.就绪探针← 正确答案
  • C.启动探针
  • D.全部探针都会

答案:B。就绪探针(readinessProbe)专门回答“是否准备好接收流量”这个问题,失败就摘除,通过才恢复,相当于领位员看到你还在擦桌子就不安排客人。存活探针失败会重启容器,启动探针则只在启动期间生效,它们都不直接影响 Endpoints 中的成员关系。

Q5. 关于 Pod 的 requests 和 limits,下列说法正确的是?

单选题 · mid

  • A.requests 是资源上限,limits 是资源下限
  • B.requests 是调度依据,limits 是运行约束← 正确答案
  • C.requests 只对 CPU 生效,limits 只对内存生效
  • D.两者都可以不设,设置越多性能越好

答案:B。requests 决定 Pod 被调度到哪个节点(节点必须能提供这么多资源),limits 则限制容器运行时的资源使用上限——requests 是“保底饭票”,limits 是“餐费上限”。设置越多不会提升性能,反而可能导致调度失败或 OOM,必须基于实际压测数据设定。

Q6. K8s 中实现服务发现的方式有哪些?

多选题 · mid

  • A.环境变量注入← 正确答案
  • B.DNS 解析服务名← 正确答案
  • C.Service 自动创建新 Pod
  • D.手动修改客户端 IP 配置

答案:AB。服务发现就是让 Pod 找到 Service 背后真实 IP 的“自动通讯录”,K8s 提供两种方式——环境变量注入(纸质通讯录,只对先创建的 Service 有效)和 DNS(内线电话系统,实时更新)。Service 本身不会自动创建 Pod(那是控制器的事),手动改 IP 则完全违背自动化的初衷。

Q7. Service 的负载均衡通过什么机制实现?

多选题 · hard

  • A.kube-proxy 写入 iptables 规则← 正确答案
  • B.通过轮询或随机概率 DNAT 转发到后端 Pod← 正确答案
  • C.应用层 HTTP 代理解析请求内容
  • D.Endpoints 同步保证后端列表准确← 正确答案

答案:ABD。Service 的负载均衡是在内核态实现的:kube-proxy 根据 Service 信息写入 iptables/ipvs 规则,数据包到达 ClusterIP 后按概率或轮询规则做 DNAT 转发,而 Endpoints 的同步保证了后端列表永远准确。这是四层(L4)转发,不解析 HTTP 内容——所以 C(应用层代理)是错的,那是 Ingress 或 Service Mesh 的职责。

Q8. 下列哪个表达式属于 K8s 集合(Set-based)选择器?

单选题 · easy

  • A.app=chatbot
  • B.version!=v1
  • C.env in (prod, staging)← 正确答案
  • D.name="model"

答案:C。env in (prod, staging) 是典型的集合选择器——判断键的值是否在给定集合中。A 是等值选择,B 是不等值选择,D 也是等值选择。集合选择器更适合多值筛选场景,类似“在多个品牌中任选其一”。

Q9. 关于 Endpoints 与滚动更新,说法正确的是?

多选题 · hard

  • A.新版本 Pod 就绪后自动加入 Endpoints← 正确答案
  • B.旧版本 Pod 从 Endpoints 移除后才终止← 正确答案
  • C.Endpoints 需要工程师手动添加与删除
  • D.就绪探针失败会延迟新 Pod 加入流量← 正确答案

答案:ABD。EndpointSliceController 自动监听 Pod 事件:新 Pod 就绪后自动加入,旧 Pod 将在终止前先被摘除(保证零中断),就绪探针未通过则不会加入。这一切完全自动化,不需要人工干预——所以 C 说需要手动管理是错的。

Q10. K8s 中 Pod 被 OOM(Killed) 的根本原因是什么?

单选题 · hard

  • A.CPU limits 设置过高
  • B.容器内存使用超过 memory limits 触发内核 OOM Killer← 正确答案
  • C.存储卷容量不足
  • D.Pod 副本数量过多

答案:B。OOMKilled 是容器内存实际使用量超过 memory limit 后,Linux 内核 OOM Killer 主动发起 SIGKILL 杀掉进程导致的。这就像餐费超过报销上限直接被停卡。CPU 超限只会被限流(throttle),不会杀容器;存储卷不足和副本过多则与 OOM 无直接因果关系。