KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

02 · Service — keel 龙骨

类型:主题概念 · 应用课对应:02 章 · 清单解剖

类型:主题概念 · 应用课对应:02 章 · 清单解剖

这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。

一、它为什么会出现

一个具体的错位。你的服务跑得好好的,把其中一个 Pod 删掉——它按上一页说的方式回来了,
但地址变了:

被删掉的那个   web-565bc6668-t8bp4   10.244.0.6
回来的那个     web-565bc6668-c9g8d   10.244.0.8

这件事在集群里不是意外,是常态:滚动更新、自愈、扩容都在换 Pod,每次都会带来新地址、淘汰旧地址。
所以"用地址去连接"在集群里根本不成立——你写下去的那一刻就开始失效了。

它面对的需求是:在底下一层不断换的情况下,往上提供一层不换的。

二、它是什么

一句话:Service 是一个不随 Pod 生灭而变的固定入口,加一份"哪些 Pod 收我的流量"的名单。

这两半要分开记,因为它们一个不动、一个一直在动:

它不是这三样东西——混淆会带来误判:

三、它长什么样

它自己写出来的样子,只有两处是它独有的:

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:            # ← 哪些 Pod 收我的流量(跟 Pod 上的标签对上)
    app: web
  ports:               # ← 对外的端口 / 转发到容器里的哪个端口
    - port: 80
      targetPort: 80

交上去之后,两条命令分别读出它那两半(Kubernetes v1.37.0):

$ kubectl get svc
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.96.94.104   <none>        80/TCP    1s

$ kubectl get endpoints web
NAME   ENDPOINTS                     AGE
web    10.244.0.7:80,10.244.0.8:80   2s

第一行是不动的那一半(CLUSTER-IP),第二行是会变的那一半(ENDPOINTS)。
把 kubectl get pods -o wide 里那两个 IP 拿来一比,会发现它们和 ENDPOINTS 逐个对得上——
名单不是凭空来的,是从 Pod 身上收来的。

它还有一件只在集群内部看得见的事:那个名字能直接被解析,你不需要知道任何地址:

$ kubectl exec <集群里的某个 Pod> -- wget -qO- http://web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

$ kubectl exec <集群里的某个 Pod> -- nslookup web
Name:	web.default.svc.cluster.local

四、它怎么工作

从声明到能用,五步:

① 声明   你写 selector("带这些标签的 Pod 收我的流量")
② 找齐   有一段循环持续找出所有符合这个条件的 Pod
③ 记名单 把它们的地址写进这个 Service 的成员名单——这就是 get endpoints 读到的东西
④ 建入口 把"这个名字 / 这个虚拟地址"接到那份名单上(在集群内部生效)
⑤ 重来   名单里的任何变化(换掉一个、加一个、减一个)都只是让 ②③ 再跑一遍

第 ④ 步是"虚拟"这个词的来处:CLUSTER-IP 不是一个真接在哪块网卡上的地址,
它是每个节点上的一套转发规则。所以它才能做到"换成员不影响入口"——
入口这一侧从来就没打算指向某个具体的东西。

第 ⑤ 步解释了那个最反直觉的现象:名单可以是空的,而且空着不算错。
"符合这个条件的 Pod 现在有 0 个"是一个合法状态——可能只是还没有 Pod 起来。
所以这件事永远不会在提交时报错,只会在名单上表现出来。

五、它会带出哪些概念

六、回到应用课

应用课里的那句话 现在你知道它为什么成立
02 章:"Service 不创建 Pod,只『选择』 Pod" 它只有"选择 + 记名单 + 建入口"三个动作,没有任何一步会造出 Pod;名单的来源是别人造好的东西
02 章:"Service 的 selector 匹配不到任何 Pod 时,K8s 不会报错" 因为第 ⑤ 步里"名单为空"是一个合法的中间状态,不是错误。失败被推到了"访问时",而不是"提交时"
02 章:"Service.spec.selector ……能,随时改,立刻生效" 它改的是第 ① 步的输入。改完 ②③ 立刻重跑,名单就换了——入口那一半完全没动,所以不会有任何东西需要重建
02 章:"为什么加前缀之后服务还能正常工作(selector 和标签都没动,匹配关系不变)" 因为它连接两边的依据是标签,不是名字。名字变了不影响匹配关系,所以这一半本来就不该被碰

这一页改变了你的哪个判断:以后看到"服务访问不通",第一件事不是去查 Pod 起没起来,
而是先去比对那份名单——get endpoints 是不是空的、名单里的地址是不是你预期的那些 Pod。
入口和成员是两半,症状一样、位置完全不同。

自测题

  1. 为什么 CLUSTER-IP 可以在 Pod 被换掉之后保持不变,而 Pod 自己的地址不行?
  2. "名单是空的"为什么不算一个错误状态?这个性质如何解释那类"apply 成功但服务不可用"的故障?

进入 keel 阅读