你有没有想过,当你打开一个AI绘画网站,输入提示词,几秒钟后一张精美的图片就出现在眼前——这背后其实是成千上万个“容器”在云端的服务器上协同工作。这些容器就像一个个小厨师,各有分工,有的负责图片生成,有的负责文本理解,有的负责模型加载。但问题来了:谁来安排这些“厨师”?谁告诉一个“厨师”该去哪儿找另一个“厨师”?当某个“厨师”突然生病请假,又有谁来补位? 在 Kubernetes(简称 K8s)的世界里,答案就是 Pod 和 Service。你完全可以把它想象成一家大型餐厅的后厨:Pod 是后厨里的一个个“工作小分队”,每个小分队自带灶台、食材和工具,随时准备开火;而 Service 则是前台的“领位员”,顾客只需要喊一声“我要点餐”,领位员就会优雅地把你带到正确的窗口,至于窗口后面是张厨师还是李厨师在掌勺,你根本不用关心。 这篇文章将成为你的后厨参观指南。我们会用小分队的视角看懂 Pod 的一生,用领位员的视角理解 Service 的服务发现与负载均衡,还会聊到只有 AI 工程师才懂的痛点——比如如何给“AI 大厨”限定食材预算(资源限制),如何给几十个同款小分队编号管理(标签选择器)。学完这篇文章,你不仅能看懂 K8s 的编排逻辑,更能亲手把自己的 AI 模型服务部署到一个高可用的集群上。
对 AI 从业者而言,K8s 是模型上线的“最后一公里”。掌握 Pod 与 Service,你就能独立把训练好的模型变成 7x24 小时稳定对外服务的产品,不再求人、不再担心服务宕机。
可视化演示
展示对称加密算法的多轮迭代变换过程,包括替换、置换、混淆和密钥加等操作,动态演示明文到密文的数据流。
本节配有交互动效(进入视口后自动加载,约 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。
2. 共享网络命名空间:Pod 内所有容器共享同一个 IP 和端口空间,用 localhost 即可互相访问。
3. 共享存储卷:Pod 级 Volume 挂载到每个容器,实现数据共享。
一句话总结: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。
2. 存活探针:livenessProbe 定期探测,失败则 Kubelet 重启容器。
3. 就绪探针:readinessProbe 失败时从 Service Endpoints 中摘除该 Pod。
4. 优雅终止:先 SIGTERM,等待 preStop 和 terminationGracePeriodSeconds,超时后 SIGKILL。
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 总和。
2. CPU 限流(throttle):当容器 CPU 使用 > limit 时,CFS 配额限制其调度时间片。
3. 内存驱逐(OOM):超过 memory limit 触发内核 OOM Killer,按 oom_score 优先杀死。
4. 节点驱逐策略:磁盘或内存压力下,Kubelet 按 QoS(服务质量)等级,从 BestEffort 到 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。
2. Selector 匹配:选择器基于标签筛选后端 Pod,形成后端集合 S。
3. 代理规则(iptables):kube-proxy 为 Service IP 写入 DNAT 规则。
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 和端口写为环境变量。
2. DNS 解析:Pod 内应用通过 Service 的 DNS 名称解析得到 ClusterIP。
3. 服务端自动同步:kube-controller-manager 监控 Service 与 Endpoints 变化。
服务发现机制是 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 以相等的概率被选中。
2. iptables 随机匹配:kube-proxy 为每一条 DNAT 规则设置匹配概率。
3. 会话保持:基于客户端 IP 哈希选择固定后端。
负载均衡是 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. 等值选择:相等匹配,基于键的精确值。
2. 不等值选择:排除特定值。
3. 集合选择(in/notin):基于值列表的成员判断。
标签选择器是 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 事件。
2. 就绪过滤:只有 ready=True 的 Pod 才被写入 Endpoints。
3. 滚动更新协调:新版 Pod 就绪后加入,旧版 Pod 摘除后终止。
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 演示
"""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()
class Pod:定义 Pod 类,模拟小分队,记录名称、标签、资源请求和就绪状态。self.ready = False初始状态不可用,代表还没通过就绪探针。def match(self, pod):标签选择器核心逻辑,逐项检查 Pod 的标签是否满足 Service 的所有匹配条件。self.backends = [p for p in pods if self.match(p) and p.ready]Endpoints 同步:精确模拟 K8s 的做法,只有“标签匹配 + 就绪”的 Pod 才进入后端列表。idx = request_id % len(self.backends)轮询策略:用取模运算实现请求的循环分配。Pod("ai-model-v1-abc", {...})创建带标签的 Pod,模拟给 Pod 打上版本标签。svc = Service("chatbot-service", ...)创建 Service,指定选择器筛选 v1 版本的 Pod。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 是什么?
- A.一种容器运行时工具
- B.一个或多个容器的组合的最小调度单元← 正确答案
- C.一组集群节点的统称
- D.一种镜像构建服务
答案:B。Pod 是 K8s 中最小的调度单元,它包含一个或多个容器,共享网络和存储。你可以把 Pod 想象成一个小分队,容器是队里的成员。选项 A 错在把 Pod 当成了类似 Docker 的运行时工具;C 错在把 Pod 当成了节点集合;D 错在混淆了镜像构建与调度编排。
Q2. 同一个 Pod 内的多个容器之间,可以通过什么方式直接通信?
- 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 的主要作用是什么?
- A.存储应用数据
- B.为 Pod 提供稳定的访问入口← 正确答案
- C.自动部署容器镜像
- D.监控集群节点健康
答案:B。Service 是一个稳定的虚拟 IP + DNS 入口,后端对接一组动态变化的 Pod,类似餐厅门口的固定招牌。它不存储数据(那是 Volume 的功能),不部署镜像(那是 Deployment 的工作),也不负责监控节点(那是 kubelet 和监控系统做的)。
Q4. 下列哪种探针失败时会将 Pod 从 Service 的 Endpoints 中摘除?
- A.存活探针
- B.就绪探针← 正确答案
- C.启动探针
- D.全部探针都会
答案:B。就绪探针(readinessProbe)专门回答“是否准备好接收流量”这个问题,失败就摘除,通过才恢复,相当于领位员看到你还在擦桌子就不安排客人。存活探针失败会重启容器,启动探针则只在启动期间生效,它们都不直接影响 Endpoints 中的成员关系。
Q5. 关于 Pod 的 requests 和 limits,下列说法正确的是?
- A.requests 是资源上限,limits 是资源下限
- B.requests 是调度依据,limits 是运行约束← 正确答案
- C.requests 只对 CPU 生效,limits 只对内存生效
- D.两者都可以不设,设置越多性能越好
答案:B。requests 决定 Pod 被调度到哪个节点(节点必须能提供这么多资源),limits 则限制容器运行时的资源使用上限——requests 是“保底饭票”,limits 是“餐费上限”。设置越多不会提升性能,反而可能导致调度失败或 OOM,必须基于实际压测数据设定。
Q6. K8s 中实现服务发现的方式有哪些?
- A.环境变量注入← 正确答案
- B.DNS 解析服务名← 正确答案
- C.Service 自动创建新 Pod
- D.手动修改客户端 IP 配置
答案:AB。服务发现就是让 Pod 找到 Service 背后真实 IP 的“自动通讯录”,K8s 提供两种方式——环境变量注入(纸质通讯录,只对先创建的 Service 有效)和 DNS(内线电话系统,实时更新)。Service 本身不会自动创建 Pod(那是控制器的事),手动改 IP 则完全违背自动化的初衷。
Q7. Service 的负载均衡通过什么机制实现?
- 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)选择器?
- A.app=chatbot
- B.version!=v1
- C.env in (prod, staging)← 正确答案
- D.name="model"
答案:C。env in (prod, staging) 是典型的集合选择器——判断键的值是否在给定集合中。A 是等值选择,B 是不等值选择,D 也是等值选择。集合选择器更适合多值筛选场景,类似“在多个品牌中任选其一”。
Q9. 关于 Endpoints 与滚动更新,说法正确的是?
- A.新版本 Pod 就绪后自动加入 Endpoints← 正确答案
- B.旧版本 Pod 从 Endpoints 移除后才终止← 正确答案
- C.Endpoints 需要工程师手动添加与删除
- D.就绪探针失败会延迟新 Pod 加入流量← 正确答案
答案:ABD。EndpointSliceController 自动监听 Pod 事件:新 Pod 就绪后自动加入,旧 Pod 将在终止前先被摘除(保证零中断),就绪探针未通过则不会加入。这一切完全自动化,不需要人工干预——所以 C 说需要手动管理是错的。
Q10. K8s 中 Pod 被 OOM(Killed) 的根本原因是什么?
- A.CPU limits 设置过高
- B.容器内存使用超过 memory limits 触发内核 OOM Killer← 正确答案
- C.存储卷容量不足
- D.Pod 副本数量过多
答案:B。OOMKilled 是容器内存实际使用量超过 memory limit 后,Linux 内核 OOM Killer 主动发起 SIGKILL 杀掉进程导致的。这就像餐费超过报销上限直接被停卡。CPU 超限只会被限流(throttle),不会杀容器;存储卷不足和副本过多则与 OOM 无直接因果关系。