深夜食堂的后厨永远在“换班”:有时两个灶台并成一个,有时一个灶台拆成两个,师傅们身上的工牌号还天天变。如果把 Kubernetes 想象成这家后厨,Pod 就是一个个灶台小组——几个厨师共享一台灶具、一块案板;Service 则是门口那台永远不变的点餐电话。顾客不会去记今天是谁掌勺,只会拨那个熟悉的号码,电话那头自会有人把订单派给任何一个能出菜的灶台。本文就用这个故事,讲清楚 Pod 是什么、Service 是什么、它们怎样通过“标签”和“排班表”互相配合,以及给外面客人留的几种“门”(ClusterIP、NodePort、LoadBalancer)到底怎么走。

岗位关联

对 AI 从业者来说,K8s 是 AI 工程化绕不开的部署层。真正理解 Pod 与 Service 的关系,你才能把训练好的模型稳定地暴露成可调用的在线服务,而不是每天被“服务又连不上了”折磨。

目录
  1. 可视化演示
  2. 1. 初识 Pod:一个灶台小组
  3. 2. Pod 与容器的关系:一个灶台,几个帮厨
  4. 3. 为什么必须要有 Service:别记灶台,记前台
  5. 4. Service 的工作原理:排班表是怎么生成的
  6. 5. 集群内的服务发现:一本自动更新的电话簿
  7. 6. 转发与负载均衡:总机接线员的派单逻辑
  8. 7. Service 的类型族谱:内部座机、取餐窗口与外卖平台
  9. 8. 排障实战:把排班表和灶台对起来
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示轮询、最少连接、加权轮询三种负载均衡算法的请求分发过程,拖动滑块调节请求频率观察分配差异。

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

轮询算法 请求按顺序依次分配到各服务器 next = (prev + 1) mod N 实现简单,但不考虑服务器实际负载

1. 初识 Pod:一个灶台小组

深夜食堂的后厨里,一个灶台小组通常由几个人组成:掌勺的、切配的、偶尔还有帮忙传菜的。他们共用同一个灶台、同一路水电,开工一起开工,收工一起收工。在 Kubernetes 里,这个“灶台小组”就是 Pod。它不是一个容器,而是若干个容器的集合,也是 K8s 眼里最小的调度单位。

你可以在一个 Pod 里放进一个 Nginx 容器,也可以放进两个容器:一个跑业务,一个在旁边打辅助。它们共享同一个 IP 地址、同一个网络命名空间,还能挂载同一块存储卷。这意味着 Pod 内任何容器都能用 localhost 互相访问,就像同一个小组成员能直接喊话,不需要额外对讲机。

为什么要强调“最小调度单元”?因为 K8s 不会单独调度一个容器,它只调度 Pod。Pod 创建后会被安排到某个节点(Node)上运行,节点相当于后厨里的某个楼层。Pod 的生命周期非常短暂:更新镜像、节点故障、手动删除,都会让旧 Pod 消失、新 Pod 出现。

回到比喻:顾客不需要知道后厨里有几个人、谁站在哪个位置,只需要知道“点了这桌菜”就行了。Pod 就是这样一个“桌”单位:里面的人共享一张桌子和一套餐具,对外只需要一个台号。

IP(c1)=IP(c2)=⋯=IP(cm)=IPPodIP(c_1) = IP(c_2) = \cdots = IP(c_m) = IP_{\text{Pod}}
Pod 内部的所有容器都共享同一个网络命名空间,所以它们的 IP 地址都等于 Pod 的 IP。
  1. 容器编号c_1, c_2, \dots, c_m 代表 Pod 内的 m 个容器,每个容器都是这个小组的一员。
    c1,c2,…,cmc_1, c_2, \dots, c_m
  2. 逐一取 IPIP(c_i) 表示第 i 个容器的 IP 地址。
    IP(ci)IP(c_i)
  3. 连等号等号说明这些容器的 IP 完全相同,因为它们共享同一个网络命名空间。
    IP(c1)=IP(c2)IP(c_1) = IP(c_2)
  4. Pod 的唯一地址IP_{\text{Pod}} 是 K8s 分配给整个 Pod 的地址,也是客户端访问这个小组的统一入口。
    IPPodIP_{\text{Pod}}

2. Pod 与容器的关系:一个灶台,几个帮厨

一个灶台小组里,掌勺师傅负责做菜,洗菜师傅负责备料。两人配合才能出菜,但他们共享同一套水源和排气管道。K8s 里最常见的组合是“主容器 + Sidecar 容器”:主容器运行核心业务,比如一个推理模型服务;Sidecar 容器在旁边打辅助,比如收集日志、刷新模型文件、转发指标。

为什么要硬塞进同一个 Pod,而不是分开部署?因为这两个容器有“强协作”关系:它们必须同生共死、共享同一份存储卷,并且通信延迟要极低。用 localhost 直接访问比走 Service 更快,也避免了额外的网络开销。典型例子是:主容器负责推理,Sidecar 容器从远端仓库拉取最新模型写入共享卷,主容器发现文件更新后自动加载。

但同一个 Pod 里的容器共享网络端口空间,所以它们监听端口不能冲突。你可以这样理解:同一个灶台上只有一个出气口,两个锅不能同时霸占同一个出气口。

用比喻来说,Pod 是一个“灶台编制”:主厨和帮厨一起排班,共享一口灶和一块案板。正因为这种强绑定,Pod 内的多容器协作才能像同一个人一样默契。

Pod={ cmain,csidecar,… }共享 IP 与共享 Volume\text{Pod} = \{\, c_{\text{main}}, c_{\text{sidecar}}, \dots \,\}\quad \text{共享 IP 与共享 Volume}
Pod 是多个容器的组合体,这些容器共享网络地址和存储卷,形成强绑定的协作单元。
  1. 主容器c_{\text{main}} 是承担主要业务的容器,比如跑模型的进程。
    cmainc_{\text{main}}
  2. 辅助容器c_{\text{sidecar}} 是辅助容器,负责日志、监控、模型预热等周边工作。
    csidecarc_{\text{sidecar}}
  3. 集合与共享花括号表示这些容器属于同一个 Pod,并且共享 IP、共享 Volume。
    共享 IP 与共享 Volume\text{共享 IP 与共享 Volume}

3. 为什么必须要有 Service:别记灶台,记前台

假如顾客每次点餐都要记住“今晚是 3 号灶台的张师傅在做”,那明天张师傅休息,顾客就找不到人了。Pod 也是这样:它的 IP 地址不固定,Pod 重建、滚动更新、扩容缩容,都会让 IP 发生变化。如果客户端把 Pod IP 写死在配置里,Pod 一换,服务立刻断掉。

这正是 Service 存在的意义。Service 是集群内的一个“前台电话”,拥有一个稳定不变的虚拟 IP(ClusterIP)。无论背后有多少个 Pod、它们换过多少轮,前端永远只拨这个固定号码,由 Service 把请求转给真正在工作的 Pod。

在时间维度上,后端 Pod 的 IP 集合是不断变化的。上午可能是 [10.244.1.5, 10.244.1.6],下午滚动更新后就变成了 [10.244.2.8, 10.244.2.9]。客户端完全感知不到这种变化,因为 Service 的地址始终没变。

所以,记灶台没有用,记前台才有用。Service 就是那台永远不变的点餐电话:顾客只记号码,后厨随便换人。

B(t)={podi∣podi runs at time t}andB(t1)≠B(t2)B(t) = \{ \text{pod}_i \mid \text{pod}_i \text{ runs at time } t \} \quad \text{and} \quad B(t_1) \neq B(t_2)
后端可用 Pod 的集合会随时间变化,Service 就是把这种变化挡在门外的“稳定入口”。
  1. 后端集合B(t) 表示在 t 时刻正在运行的后端 Pod 集合。
    B(t)B(t)
  2. 集合定义竖线右边的条件限定,只有“正在运行”的 Pod 才会被算进集合。
    {podi∣podi runs at time t}\{ \text{pod}_i \mid \text{pod}_i \text{ runs at time } t \}
  3. 集合变化t_1 和t_2是两次观察时刻,不等号说明 Pod 会漂移、IP 会改变。
    B(t1)≠B(t2)B(t_1) \neq B(t_2)

4. Service 的工作原理:排班表是怎么生成的

Service 是怎么知道该把电话转给哪个灶台的?靠的是“标签”和“排班表”。K8s 给每个 Pod 贴标签(Label),比如 app=nginx、tier=backend。Service 定义自己的选择器(Selector),只选择标签完全匹配的后端 Pod。这个匹配过程,就像厨师长在排班表上圈出“今天能做鱼香肉丝的人”。

每当 Pod 创建或销毁,K8s 都会自动更新 Service 的 Endpoints(端点列表)。Endpoints 就是一张动态排班表,记录当前所有匹配且就绪的 Pod IP 和端口。请求到达 Service 的虚拟 IP 后,K8s 里的转发组件会根据这张表把流量发往后端。

Service 还要做端口映射。客户端访问 ClusterIP:Port,Service 把流量转发到后端的 PodIP:targetPort。这意味着前端端口和后端端口可以不一样,就像前台电话是 8008,但转接后实际响的是灶台边的分机 8080。

排班表是自动生成的,不用人工维护。这也是 Service 能应对 Pod 频繁变化的底气:前台电话永远打得通,后端是谁随时在换。

Backends(Svc)={ pod∣labels(pod)⊇selector(Svc) }\text{Backends}(Svc) = \{\, \text{pod} \mid \text{labels}(\text{pod}) \supseteq \text{selector}(Svc) \,\}
Service 通过标签选择器圈定后端 Pod 范围,凡是标签集合包含 selector 全部键值对的 Pod,都会进入候选名单。
  1. 后端集合\text{Backends}(Svc) 是这个 Service 当前能找到的全部后端 Pod。
    Backends(Svc)\text{Backends}(Svc)
  2. 标签函数\text{labels}(\text{pod}) 是 Pod 身上所有标签构成的集合。
    labels(pod)\text{labels}(\text{pod})
  3. 包含关系\supseteq 表示 Pod 的标签集合必须完全包含 selector 要求的键值对,一个都不能少。
    labels(pod)⊇selector(Svc)\text{labels}(\text{pod}) \supseteq \text{selector}(Svc)

5. 集群内的服务发现:一本自动更新的电话簿

顾客怎么知道前台的电话号码?用电话簿。K8s 集群里也有一本自动更新的“电话簿”,它就是 CoreDNS。Pod 不需要记住 Service 的 IP,只需要记住 Service 的名字。

每个 Service 都有一个完整域名(FQDN),格式为服务名.命名空间.svc.cluster.local。如果两个对象在同一个命名空间里,直接用服务名就能访问;跨命名空间则必须写完整域名。比如 AI 服务部署在 ml 命名空间,另一个服务想访问它,就要写 my-model.ml.svc.cluster.local。

除了 DNS,K8s 还会给 Pod 注入环境变量来辅助服务发现。但在 Pod 创建后新增的 Service 不会出现在环境变量里,所以生产环境中更推荐用 DNS。这本电话簿会自动同步所有 Service 的地址变化,不需要人工修改任何配置文件。

有了这本电话簿,服务之间互相调用时只需要“打名字”,不用关心 IP 是否又变了。

FQDN:svc-name.namespace.svc.cluster.local↦ClusterIP\text{FQDN}: \text{svc-name.namespace.svc.cluster.local} \mapsto \text{ClusterIP}
服务名加命名空间构成完整域名,DNS 把它解析成 Service 的虚拟 ClusterIP。
  1. 服务名\text{svc-name} 是 Service 的名字,相当于电话簿里的联系人姓名。
    svc-name\text{svc-name}
  2. 命名空间\text{namespace} 表示服务所在命名空间,避免不同环境重名冲突。
    namespace\text{namespace}
  3. 固定后缀\text{svc.cluster.local} 是集群内部域名后缀,表示这是集群内服务而不是外网域名。
    svc.cluster.local\text{svc.cluster.local}
  4. 解析动作\mapsto 表示 DNS 把完整域名映射成 ClusterIP。
    FQDN↦ClusterIP\text{FQDN} \mapsto \text{ClusterIP}

6. 转发与负载均衡:总机接线员的派单逻辑

Service 是一个虚拟概念,真正干活的是每个节点上的 kube-proxy。它把 Service 的虚拟 IP 转成一组后端 Pod IP,再写入节点的转发规则。最常见的实现有两种:iptables 模式和 IPVS 模式。

iptables 模式像一张巨大的规则表,每来一个连接就按概率匹配规则,把请求转到某个后端 Pod。IPVS 模式则更像专业的负载均衡器,支持轮询(round-robin)、最少连接(least-connection)、加权轮询等多种调度算法,性能更好,规则也更清晰。

无论哪种模式,目的都是把流量均匀地分发到后端多个 Pod。如果后端有 3 个 Pod,权重相同,那么每个 Pod 大约承担三分之一的请求。正因为多副本 + Service 负载均衡,模型服务才能扛住突发流量:流量大了就多扩几个 Pod,流量小了就缩回去,前台不用改任何配置。

接线员不关心哪个灶台做了什么菜,只关心排班表上还有谁能接单。只要排班表里有多个灶台,订单就会自动摊开,谁也不累着。

P(request→podi)=wi∑j=1nwjP(\text{request} \to \text{pod}_i) = \dfrac{w_i}{\sum_{j=1}^{n} w_j}
请求被分到第 i 个 Pod 的概率等于该 Pod 的权重占所有后端总权重的比例。默认权重相同时,就是 1/n。
  1. 目标概率P(\text{request} \to \text{pod}_i) 表示一个请求被派给第 i 个 Pod 的概率。
    P(request→podi)P(\text{request} \to \text{pod}_i)
  2. 单个权重w_i 是第 i 个 Pod 的权重,默认情况下所有 Pod 权重为 1。
    wiw_i
  3. 总权重\sum_{j=1}^{n} w_j 把 n 个后端 Pod 的权重全部加起来,作为分母。
    ∑j=1nwj\sum_{j=1}^{n} w_j
  4. 比例分配整体公式表示按权重比例派单,权重越高被选中的概率越大。
    wi∑j=1nwj\dfrac{w_i}{\sum_{j=1}^{n} w_j}

7. Service 的类型族谱:内部座机、取餐窗口与外卖平台

Service 按暴露方式分成几种类型,对应这家餐厅不同的对外渠道。

  • ClusterIP:默认类型,只在集群内部可达。就像餐厅的内部座机,只供餐厅员工互相联络,外面顾客打不进来。
  • NodePort:在每个节点上开放一个固定端口(默认范围 30000-32767),外部通过 节点IP:NodePort 就能访问。相当于餐厅在临街墙上开了一个取餐窗口,窗口号码每个分店都一样。
  • LoadBalancer:由云厂商提供负载均衡器,把公网流量分发到各节点的 NodePort。相当于上了外卖平台,顾客只记外卖平台的链接,平台自动把订单派到最近的门店。
  • Headless Service:不分配 ClusterIP,DNS 直接返回后端 Pod 的 IP 列表。适合有状态应用(比如数据库)需要直连某个 Pod 的场景。相当于老客户不要前台转接,直接要后厨地址上门自提。

无论哪种类型,底层都有一个稳定的“入口”思想:变化被挡在里面,稳定暴露在外面。

不同场景选不同的门:内部服务用 ClusterIP,灰度联调用 NodePort,面向 C 端上线用 LoadBalancer,有状态数据库用 Headless。

LoadBalancer:80→NodeIP:NodePort→ClusterIP:Port→PodIP:targetPort\text{LoadBalancer}:80 \rightarrow \text{NodeIP}:\text{NodePort} \rightarrow \text{ClusterIP}:\text{Port} \rightarrow \text{PodIP}:\text{targetPort}
外部流量按这条链路逐层进入集群:云负载均衡 → 节点端口 → 虚拟 ClusterIP → 后端 Pod 端口。
  1. 第一跳\text{LoadBalancer}:80 是云平台提供的公网入口,用户只访问 80 端口。
    LoadBalancer:80\text{LoadBalancer}:80
  2. 第二跳\text{NodeIP}:\text{NodePort} 表示负载均衡器把流量转发到某个节点的指定端口。
    NodeIP:NodePort\text{NodeIP}:\text{NodePort}
  3. 第三跳\text{ClusterIP}:\text{Port} 表示进入集群后到达 Service 的虚拟 IP 和端口。
    ClusterIP:Port\text{ClusterIP}:\text{Port}
  4. 最终跳\text{PodIP}:\text{targetPort} 是后端 Pod 实际监听的端口,到这里请求真正被业务进程处理。
    PodIP:targetPort\text{PodIP}:\text{targetPort}

8. 排障实战:把排班表和灶台对起来

线上服务突然 502 了,怎么查?核心思路就是“对账”:看看排班表(Endpoints)和实际灶台(Pod)是否对得上。

先用 kubectl get svc 查看 Service 的 ClusterIP 和端口,再用 kubectl get endpoints 查看这个 Service 的后端列表。如果 endpoints 列表为空,说明没有 Pod 匹配 selector,或者匹配到的 Pod 都没就绪。接下来检查 Deployment 的标签和 Service 的 selector 是否拼写一致,比如 Service 要求 app=nginx,Pod 上却写成了 app=nginix,那永远匹配不上。

还要检查端口对照:Service 的 port 是前端开放端口,targetPort 是容器内实际端口。如果容器监听 8080,Service 的 targetPort 写成了 80,请求就会转发失败。最后可以用 kubectl describe svc 和 kubectl logs 配合查看事件与报错。

排障的本质就是不断回到那本排班表:排班表空 → 看标签;排班表有名字但电话打不通 → 看端口和 kube-proxy。把表和灶台对齐,问题往往就浮出水面了。

Nendpoints=Nready podsN_{\text{endpoints}} = N_{\text{ready pods}}
只有当 Endpoints 的数量等于真正就绪的后端 Pod 数量时,才说明 Service 的排班表是健康的。
  1. 排班表人数N_{\text{endpoints}} 是 Service 当前 endpoints 列表里登记的后端数量。
    NendpointsN_{\text{endpoints}}
  2. 就绪灶台数N_{\text{ready pods}} 是经过就绪探针检查、真正能接单的 Pod 数量。
    Nready podsN_{\text{ready pods}}
  3. 等号判断两边相等说明排班表准确;如果 endpoints 少于 ready pods,说明匹配规则或探针配置出了问题。
    Nendpoints=Nready podsN_{\text{endpoints}} = N_{\text{ready pods}}

AI 应用场景

AI 推理服务的稳定暴露

阶段:模型部署与在线推理

解决的问题:训练好的模型要部署成在线 API,但推理 Pod 会因发布升级、弹性扩缩容而频繁变化。如果直接给调用方 IP 地址,Pod 一换就断连,大模型服务根本无法稳定上线。

具体案例:某 AI 团队部署 vLLM 推理服务,启动 8 个推理 Pod,前面挂一个 ClusterIP 类型的 Service。调用方只需访问 Service 的固定域名,Pod 扩容到 16 个或缩到 4 个时调用方无感知。kube-proxy 按 IPVS 轮询把请求分发到不同 GPU Pod,单卡压力均衡,服务可用性明显提升。

模型蓝绿发布与金丝雀验证

阶段:模型版本迭代与灰度上线

解决的问题:新模型上线时不敢直接切全部流量,需要先让一小部分请求打到新版本观察效果。

具体案例:新老模型分别部署成两个 Deployment,标签设为 version=v1 和 version=v2。先让 Service 的 selector 只匹配 version=v1,稳定后把 selector 改成 version=v2,实现蓝绿切换。更精细的做法是把 Service 做两套,用网关按权重把流量同时打到两个版本,形成金丝雀发布。这比直接删 Pod 重建要安全得多,也方便一键回滚。

Sidecar 模式提前加载大模型权重

阶段:模型启动与资源准备

解决的问题:大模型权重动辄几十 GB,每次 Pod 冷启动都要等很久。如果所有容器都等模型下载完才启动,白白浪费 GPU 资源。

具体案例:主容器启动推理服务,Sidecar 容器负责从对象存储拉取模型权重到共享 Volume。主容器通过文件就绪信号感知模型到位后自动加载。Pod 内共享 IP 和 Volume 的特性让两个容器协作零额外开销,GPU 显存被充分预热,冷启动时间从十几分钟降到几十秒。

Python 演示

python51 行
import random  class Pod:    def __init__(self, name, labels):        self.name = name        self.labels = labels        self.requests = 0  class Service:    def __init__(self, name, selector):        self.name = name        self.selector = selector        self.endpoints = []     def sync(self, all_pods):        self.endpoints = [            pod for pod in all_pods            if self.selector.items() <= pod.labels.items()        ]     def dispatch(self, policy="rr"):        if not self.endpoints:            return None        if policy == "rr":            pod = self.endpoints.pop(0)            self.endpoints.append(pod)        elif policy == "random":            pod = random.choice(self.endpoints)        pod.requests += 1        return pod  def main():    pods = [        Pod("pod-a", {"app": "nginx", "tier": "backend"}),        Pod("pod-b", {"app": "nginx", "tier": "backend"}),        Pod("pod-c", {"app": "redis", "tier": "backend"}),    ]    svc = Service("nginx-svc", {"app": "nginx"})    svc.sync(pods)    print("endpoints:", [p.name for p in svc.endpoints])    for _ in range(6):        p = svc.dispatch("rr")        print("next ->", p.name)    print("load:", {p.name: p.requests for p in svc.endpoints})  if __name__ == "__main__":    main()
目的:用 Python 模拟 Kubernetes 里 Service 的工作流程:按 selector 筛选后端 Pod,再用轮询策略分发请求并统计负载。
  1. class Service:定义 Service 对象,保存 selector 和匹配到的 endpoints。
  2. if self.selector.items() <= pod.labels.items():用字典子集判断实现标签匹配,只有包含 selector 全部键值对的 Pod 才入选。
  3. self.endpoints = [pod for pod in all_pods ...]同步排班表,相当于 kubectl get endpoints。
  4. pod = self.endpoints.pop(0)轮询派单:弹出队首 Pod。
  5. self.endpoints.append(pod)把刚用过的 Pod 放到队尾,形成循环队列。
  6. pod.requests += 1给 Pod 的请求计数,用来观察负载是否均匀。

常见误区

误区Pod 创建后我就把它当成一台固定服务器,把它的 IP 写进配置文件,上线后一直用这个地址访问。

正解Pod 是临时工,不是正式员工。它可能因节点故障、发布更新、扩缩容随时被销毁重建,IP 也会随之改变。正确做法是把 Service 当成稳定前台,客户端只访问 Service 的域名或 ClusterIP,后端 Pod 怎么换都无所谓。

为何常见:刚学 K8s 时容易沿用“服务器 = 固定 IP”的旧认知,忽略了 Pod 的临时性设计。

误区Service 应该像 Pod 一样有个容器在跑,我想 `kubectl exec` 进 Service 里看看它转发到了哪里。

正解Service 不是 Pod,也没有对应容器。它本质上是 API 对象加上 kube-proxy 写入的转发规则。想看后端列表应该用 kubectl get endpoints,想看转发规则应该看节点的 iptables 或 IPVS 配置。

为何常见:人们习惯把“服务”理解为一个常驻进程,而 K8s 的 Service 更多是一种抽象声明。

误区Service 创建好了,我直接在自己的电脑上 curl 它的 ClusterIP,应该能访问吧?

正解ClusterIP 是集群内部的虚拟 IP,只在集群内可达。外部访问需要 NodePort 把端口暴露到节点上,或用 LoadBalancer 接云负载均衡器。就像餐厅内部座机只能在店里打,外卖必须走取餐窗口。

为何常见:看到"IP"就默认它是公网可达的,忽略了 K8s 内部网络与外部网络的边界。

误区Pod 里有两个容器,都监听 8080 端口也没关系,反正它们是两个不同的容器。

正解Pod 内所有容器共享同一个网络命名空间,同一个 IP 和端口空间。两个容器如果都监听 8080 就会端口冲突,启动失败。哪怕它们需要协作,也要规划不同的端口,就像同一个灶台上的两个锅不能同时霸占同一个气口。

为何常见:容易把容器想象成互相隔离的虚拟机,忽略了同一个 Pod 内强共享的特性。

误区Service 创建成功、ClusterIP 也分配了,那请求就一定没问题,报错了肯定是网络插件的问题。

正解Service 本身不保证后端可用。如果 selector 写错、Pod 标签不匹配,或者后端 Pod 没通过就绪探针,Endpoints 列表会是空的,请求永远打不到任何 Pod。排障首先要看 kubectl get endpoints,确认排班表里真的有名字。

为何常见:先入为主地认为“入口存在 = 服务可用”,实际上入口背后还需要 selector、endpoints、就绪探针整条链路都健康。

面试题

Q1. Pod 与容器最准确的关系是?

单选题 · easy

  • A.容器上层抽象,可共享网络← 正确答案
  • B.Pod 与容器严格一一对应
  • C.容器运行在 Pod 的外部
  • D.Pod 是容器的另一个名字

答案:A。Pod 是多个容器组成的集合,容器之间共享网络命名空间和存储卷,是 K8s 的最小调度单元。B、C、D 都歪曲了 Pod 与容器的关系,就像把灶台小组和单个厨师混为一谈。

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

单选题 · easy

  • A.为动态变化的 Pod 提供稳定入口← 正确答案
  • B.让 Pod 拥有更大磁盘空间
  • C.代替容器运行业务进程
  • D.把 Pod 编译成二进制文件

答案:A。Pod 会频繁漂移,IP 不稳定,Service 提供一个固定的虚拟 IP 和负载均衡入口。它不是存储服务,也不是业务进程,更不是编译器。

Q3. ClusterIP 类型的 Service 特点是什么?

单选题 · mid

  • A.仅集群内部可访问的虚拟 IP← 正确答案
  • B.只能在集群外部访问的固定 IP
  • C.直接指向某一个 Pod 的物理 IP
  • D.由云厂商分配的弹性公网 IP

答案:A。ClusterIP 是 K8s 在集群内部为 Service 分配的虚拟 IP,默认只能被集群内的 Pod 访问。外部访问需要 NodePort 或 LoadBalancer 进一步暴露。

Q4. Service 通过什么字段匹配后端 Pod?

单选题 · mid

  • A.selector 与 Pod 标签匹配← 正确答案
  • B.以容器镜像名称来匹配
  • C.通过启动时间顺序匹配
  • D.依据节点位置标签匹配

答案:A。Service 用 label selector 选择 Pod,要求 Pod 的标签完整包含 selector 定义的键值对。镜像名、启动顺序、节点位置都不是 Service 的匹配依据。

Q5. 下面哪些属于 Service 的类型?

多选题 · mid

  • A.ClusterIP 类型← 正确答案
  • B.NodePort 类型← 正确答案
  • C.LoadBalancer 类型← 正确答案
  • D.VolumeMount 类型

答案:ABC。ClusterIP、NodePort、LoadBalancer、ExternalName 是 Service 的常见类型。VolumeMount 是容器挂载卷的配置字段,不是 Service 类型。

Q6. 当 Service 的后端 Pod 全部不可用时,kubectl get endpoints 会显示什么?

单选题 · hard

  • A.endpoints 列表为空← 正确答案
  • B.自动生成一个默认 Pod
  • C.显示上一次的 Pod 地址
  • D.报错并删除 Service 对象

答案:A。Endpoints 是 Service 的后端排班表,一旦没有匹配且就绪的 Pod,列表就是空的。Service 本身不会被删除,也不会自动创建 Pod。

Q7. 关于 Headless Service 的正确说法有哪些?

多选题 · mid

  • A.不分配 ClusterIP← 正确答案
  • B.DNS 返回后端 Pod IP← 正确答案
  • C.适合有状态应用直连← 正确答案
  • D.只能在集群外部使用

答案:ABC。Headless Service 没有虚拟 ClusterIP,DNS 查询直接返回后端各个 Pod 的 IP 列表,常用于数据库等有状态应用。它不是用来对外暴露 的,D 说法错误。

Q8. kube-proxy 主要承担什么职责?

单选题 · hard

  • A.把 Service 规则写入转发链← 正确答案
  • B.管理 Pod 的健康检查探针
  • C.为容器编译业务程序代码
  • D.存储所有 Pod 的日志文件

答案:A。kube-proxy 在节点上维护 iptables 或 IPVS 转发规则,让发往 ClusterIP 的流量能到达后端 Pod。健康检查是 kubelet 的职责,编译和日志存储都与 kube-proxy 无关。

Q9. 以下哪些原因可能导致 Service 无法访问?

多选题 · mid

  • A.selector 与 Pod 标签不一致← 正确答案
  • B.Pod 处于 Pending 未就绪← 正确答案
  • C.NodePort 端口必须是偶数
  • D.Service 名必须以数字开头

答案:AB。selector 不匹配会导致 Endpoints 为空,Pod 未就绪也不会被纳入排班表,因此 A、B 会引发访问失败。NodePort 并不要求偶数,Service 名称也不需要以数字开头。

Q10. Service 的 selector 匹配到 3 个 Pod,但其中有 1 个未通过就绪探针,Endpoints 中会出现几个地址?

单选题 · extreme

  • A.只显示两个就绪 Pod 地址← 正确答案
  • B.三个地址全部显示出来
  • C.地址数量随机无法判断
  • D.endpoints 显示为空列表

答案:A。Endpoints 只登记通过就绪检查的后端 Pod。未就绪的 Pod 即使标签匹配,也不会被写进排班表,所以只剩两个就绪地址。这也是为什么“标签匹配”和“探针就绪”必须同时满足。