---
url: /courses/kubernetes-cluster/08-service-discovery-access/index.md
---
# 第八节 Service、服务发现与外部访问

::: tip 本课目标
为 `yunfan-web` 建立稳定的 ClusterIP 与 DNS 名称，核对 selector、EndpointSlice 和就绪 Pod；通过 NodePort 与 kind 端口映射从本机访问，并比较 Headless、ExternalName、会话保持和 port-forward 的边界。
:::

## 8.1 接单：Pod IP 会变，客户端应该连接谁

Deployment 替换 Pod 后，Pod 名称与 IP 都可能变化。把客户端直接绑定到 Pod IP，会让每次扩缩容和自愈都变成地址变更。

Service 为一组后端提供稳定入口；控制平面根据 selector 生成 EndpointSlice，集群 DNS 再为 Service 建立名称。

固定排错顺序：

```text
Service 字段 → selector → EndpointSlice → Ready Pod → 网络与 DNS
```

新版 Kubernetes 应优先检查 EndpointSlice；旧的 Endpoints API 从 Kubernetes v1.33 起已弃用，不作为本课主要证据。

## 8.2 ClusterIP：先建立集群内稳定入口

Service selector 必须匹配第 6 课 Deployment 的 `app.kubernetes.io/name: yunfan-web`。EndpointSlice 是保存后端地址和就绪条件的当前 API。

执行：

```bash
kubectl apply -f manifests/08-service/yunfan-web-services.yaml
kubectl get service -n yunfan-shop
kubectl get endpointslice -n yunfan-shop \
  -l kubernetes.io/service-name=yunfan-web \
  -o wide
kubectl get pod -n yunfan-shop \
  -l app.kubernetes.io/name=yunfan-web \
  -o wide
```

EndpointSlice 中就绪地址数量应与可用 Web Pod 对应。`sessionAffinity: ClientIP` 让同一客户端 IP 的连接尽量保持同一后端，不等于用户登录会话已可靠持久。

## 8.3 DNS：用名称而不是虚拟 IP

创建一次性诊断 Pod：

```bash
kubectl run netcheck \
  -n yunfan-shop \
  --image=busybox:1.36.1 \
  --restart=Never \
  --command -- sleep 3600

kubectl wait -n yunfan-shop --for=condition=Ready pod/netcheck --timeout=120s
kubectl exec -n yunfan-shop netcheck -- nslookup yunfan-web
kubectl exec -n yunfan-shop netcheck -- wget -qO- http://yunfan-web
```

同命名空间可使用短名称 `yunfan-web`；跨命名空间应至少写 `yunfan-web.yunfan-shop`，完整名称是 `yunfan-web.yunfan-shop.svc.cluster.local`。

::: tip DNS 通过能证明什么
它证明诊断 Pod 能访问 CoreDNS，CoreDNS 能生成 Service 记录，并且 Service 可把 HTTP 请求送到后端。它不能证明外部访问或公网 DNS。
:::

## 8.4 NodePort 与 kind 端口映射

第 2 课已经把宿主机 `127.0.0.1:18080` 映射到节点容器 `30080`；本课 NodePort 也固定为 `30080`，完整链路才一致。

从个人电脑执行：

```bash
curl -i http://127.0.0.1:18080
```

若没有保留该端口映射，使用临时 port-forward：

```bash
kubectl port-forward -n yunfan-shop service/yunfan-web 18080:80
```

另开终端访问 `http://127.0.0.1:18080`，完成后按 `Ctrl+C` 停止。port-forward 依赖当前终端连接，适合诊断，不是生产入口。

::: warning 外部访问边界
NodePort 加 kind 映射只适合本机实验。LoadBalancer 需要云提供商或本地控制器。`externalIPs` 从 Kubernetes v1.36 起已弃用，本课只说明原理，不创建依赖该字段的新方案。
:::

## 8.5 Headless 与 ExternalName

Headless Service 把 `clusterIP` 设为完整值 **None**，不分配虚拟 IP，也不由 kube-proxy 做负载均衡；DNS 直接返回后端地址。ExternalName 只返回 CNAME，不创建代理和 EndpointSlice。

验证 DNS 记录：

```bash
kubectl apply -f manifests/08-service/discovery-variants.yaml
kubectl exec -n yunfan-shop netcheck -- nslookup yunfan-web-headless
kubectl exec -n yunfan-shop netcheck -- nslookup public-docs
```

ExternalName 对 HTTP `Host` 与 TLS 证书可能产生不匹配，本课不把 DNS CNAME 成功解释为应用协议一定可用。

## 8.6 故障演练：selector 不匹配

临时把 ClusterIP Service selector 改成不存在的值：

```bash
kubectl patch service yunfan-web \
  -n yunfan-shop \
  --type=merge \
  -p '{"spec":{"selector":{"app.kubernetes.io/name":"wrong-web"}}}'

kubectl get service yunfan-web -n yunfan-shop -o yaml
kubectl get endpointslice -n yunfan-shop \
  -l kubernetes.io/service-name=yunfan-web
```

DNS 仍可解析 Service 名称，但 EndpointSlice 没有可用后端，HTTP 请求失败。恢复方式：

```bash
kubectl apply -f manifests/08-service/yunfan-web-services.yaml
kubectl get endpointslice -n yunfan-shop \
  -l kubernetes.io/service-name=yunfan-web
```

这证明“Service 存在”和“Service 有后端”是两个验收点。

## 8.7 排错矩阵

| 现象 | 首查 | 常见根因 |
| --- | --- | --- |
| Service 名称无法解析 | CoreDNS、Pod resolv.conf | DNS 组件或命名空间错误 |
| 能解析但连接拒绝 | EndpointSlice、targetPort | 后端为空或端口错误 |
| EndpointSlice 为空 | Service selector、Pod labels | 标签不匹配 |
| 集群内可达、本机不可达 | NodePort、extraPortMappings | 端口链不一致 |
| port-forward 成功、NodePort 失败 | 节点映射 | kind 配置未预留端口 |
| ExternalName 解析但 HTTPS 失败 | CNAME、Host、证书名 | 应用协议主机名不匹配 |

## 8.8 交付

1. 两份 Service YAML。
2. 一张内部访问截图：Service、EndpointSlice、DNS 和 Pod 内 wget。
3. 一张外部访问截图：本机 curl 与端口链说明。
4. 一段 selector 故障的“空端点—恢复”记录。

{{reflection-checkpoint:service-discovery-check}}

清理诊断 Pod，保留 Service 与 Web Deployment：

```bash
kubectl delete pod netcheck -n yunfan-shop
```

## 本章小测

{{assessment:service-discovery}}

## 本章小结

* Service 用 selector 关联后端，EndpointSlice 保存当前地址与就绪条件。
* DNS 提供稳定名称，ClusterIP 提供集群内虚拟入口。
* kind 外部访问需要宿主机映射、NodePort 与 targetPort 逐跳一致。
* Headless 通过 DNS 返回后端地址，ExternalName 只创建 CNAME。
* 排错顺序固定为 Service、selector、EndpointSlice、Pod、网络/DNS。
