Day 08 - Service 与集群内服务发现

📘 Day 08:Service 与集群内服务发现

🎯 今日目标

  • 理解 kube-proxy 如何实现 Service 流量转发
  • 创建四种类型 Service 并验证
  • 理解 Endpoint 与 Service 的关系
  • 掌握 CoreDNS 域名格式和 DNS 策略
  • 能通过 Pod DNS 名称实现服务间通信

🧠 理论精讲(30 分钟)

Service 解决了什么问题

Pod 是短暂的(IP 会变),Service 提供稳定的访问入口

1
2
3
Pod-A (IP: 10.244.1.5) ─┐
Pod-B (IP: 10.244.2.3) ─┤──→ Service (ClusterIP: 10.96.0.1) ──→ 客户端
Pod-C (IP: 10.244.1.9) ─┘

无论后端 Pod 如何变化,Service 的 ClusterIP 不变。

kube-proxy 工作模式

模式 原理 性能
iptables 通过 iptables 规则随机转发 规则数多时性能下降
IPVS 内核级负载均衡 高性能,支持多种调度算法

四种 Service 类型

类型 访问范围 典型场景
ClusterIP 集群内部 内部服务间通信
NodePort 节点 IP + 端口 开发调试、简单外部访问
LoadBalancer 外部 LB 分发 生产环境外部入口
ExternalName DNS CNAME 外部服务映射

CoreDNS 域名格式

1
<service-name>.<namespace>.svc.cluster.local
1
2
3
# 示例
api-server.default.svc.cluster.local
redis-cache.production.svc.cluster.local

🔧 动手实操(120 分钟)

练习 8.1:ClusterIP Service 基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. 创建后端 Deployment
kubectl create deploy backend --image=nginx:alpine --replicas=3

# 2. 暴露为 ClusterIP Service
kubectl expose deploy backend --port=80 --target-port=80

# 3. 查看 Service
kubectl get svc backend
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# backend ClusterIP 10.96.xxx.xxx <none> 80/TCP 10s

# 4. 查看 Endpoint
kubectl get endpoints backend
# NAME ENDPOINTS AGE
# backend 10.244.1.2:80,10.244.2.3:80,10.244.1.4:80 10s

# 5. 用临时 Pod 测试访问
kubectl run test-client --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend
# 输出:nginx 默认页面

# 6. 测试完整的 FQDN
kubectl run test-dns --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend.default.svc.cluster.local

练习 8.2:NodePort Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 创建 NodePort Service
kubectl expose deploy backend --type=NodePort --port=80 --name=backend-np

# 2. 查看分配的端口
kubectl get svc backend-np
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# backend-np NodePort 10.96.xxx.xxx <none> 80:30080/TCP 5s

# NodePort 范围:30000-32767

# 3. 通过节点 IP 访问
# 获取任一节点 IP
kubectl get nodes -o wide
# 在集群外浏览器或 curl 访问:
curl http://<任意节点IP>:30080

# 4. 验证 NodePort 也在 ClusterIP 上可用
kubectl run test-np --image=busybox:1.36 --rm -it --restart=Never -- \
wget -q -O- http://backend-np

# 5. 清理
kubectl delete svc backend-np

练习 8.3:ExternalName Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 将外部域名映射为集群内部 Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: database.example.com
EOF

# 验证
kubectl get svc external-db
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# external-db ExternalName <none> database.example.com <none> 5s

# 集群内 Pod 访问 external-db 会解析到 database.example.com
kubectl run dns-check --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup external-db

# 清理
kubectl delete svc external-db

练习 8.4:Headless Service + 直接 Pod DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 创建 Headless Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: headless-svc
spec:
clusterIP: None
selector:
app: backend
ports:
- port: 80
EOF

# 2. 验证 ClusterIP 为空
kubectl get svc headless-svc
# CLUSTER-IP: None

# 3. DNS 查询 Headless Service
kubectl run dns-hl --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup headless-svc
# 返回所有后端 Pod 的 IP(而不是一个 ClusterIP)

# 4. 清理
kubectl delete svc headless-svc

练习 8.5:Service 负载均衡验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 创建一个能区分 Pod 的后端
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: lb-test
spec:
replicas: 3
selector:
matchLabels:
app: lb-test
template:
metadata:
labels:
app: lb-test
spec:
containers:
- name: http
image: busybox:1.36
command:
- sh
- -c
- |
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from $(hostname)" > /tmp/response
while true; do nc -l -p 8080 < /tmp/response; done
ports:
- containerPort: 8080
EOF

kubectl expose deploy lb-test --port=8080 --target-port=8080

# 2. 多次请求验证负载均衡效果
kubectl run lb-client --image=busybox:1.36 --rm -it --restart=Never -- \
sh -c 'for i in $(seq 1 10); do wget -q -O- http://lb-test:8080; echo; done'
# 输出应分布在不同 Pod

# 3. 检查 kube-proxy 模式
kubectl logs -n kube-system -l k8s-app=kube-proxy | grep "Using"

# 4. 清理
kubectl delete deploy backend lb-test
kubectl delete svc backend lb-test

🐛 排错练习(30 分钟)

场景 1:Service 无法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 排查清单
# 1. Service 的 selector 是否匹配 Pod 的 labels?
kubectl get svc <svc-name> -o jsonpath='{.spec.selector}'
kubectl get pod -l <key>=<value>

# 2. Endpoint 是否为空?
kubectl get endpoints <svc-name>
# 如果 ENDPOINTS 为空,说明 selector 不匹配

# 3. targetPort 是否正确?
kubectl get svc <svc-name> -o jsonpath='{.spec.ports[*].targetPort}'
kubectl get pod -l app=<name> -o jsonpath='{.spec.containers[*].ports}'

# 4. 网络策略是否阻止?
kubectl get networkpolicies

场景 2:CoreDNS 解析失败

1
2
3
4
5
6
7
8
9
10
11
# 测试 DNS 解析
kubectl run dns-debug --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default

# 检查 CoreDNS Pod
kubectl get pods -n kube-system -l k8s-app=kube-dns

# 检查 CoreDNS 日志
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=20

# 重启 CoreDNS
kubectl rollout restart deploy/coredns -n kube-system

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 35 分钟

题目:多层服务暴露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
【操作要求】

1. 创建 Deployment app-v1(3 副本,nginx:alpine),暴露为 ClusterIP Service app-svc
2. 创建 Deployment app-v2(2 副本,httpd:alpine),暴露为 ClusterIP Service app-v2-svc
3. 在 app-v1 的 nginx 中配置反向代理到 app-v2-svc(通过 Service 名称)
4. 创建 NodePort Service app-gateway,暴露 app-svc 到节点 31080 端口
5. 验证:
- 集群内 curl http://app-svc → 返回 nginx 页面
- 集群内 curl http://app-v2-svc → 返回 httpd 页面
- 节点 IP:31080 → 返回 nginx 页面
- CoreDNS 解析 app-svc 和 app-v2-svc 均正常
6. 输出所有 Service 的 Endpoint 信息

【评分标准】
- Service 创建正确(30 分)
- 集群内通信正常(25 分)
- NodePort 访问正常(20 分)
- DNS 解析正常(15 分)
- Endpoint 正确(10 分)

📋 命令速查

命令 功能 注解
kubectl get svc 列出所有 Service TYPE 列显示 ClusterIP/NodePort/LoadBalancer/ExternalName
kubectl get svc -o wide Service + 选择器/端口 确认 Selector 和暴露的端口
kubectl get endpoints 查看 Service 后端端点 为空说明 Selector 没匹配到 Running Pod
kubectl describe svc <name> Service 详细信息 查看 SessionAffinity、Endpoints、Events
kubectl expose deploy <name> --port=80 --target-port=8080 快速创建 Service 暴露 Deployment 默认创建 ClusterIP 类型
kubectl expose deploy <name> --type=NodePort --port=80 --target-port=8080 创建 NodePort Service 节点 IP:30000-32767 可外部访问
kubectl expose deploy <name> --type=LoadBalancer --port=80 创建 LoadBalancer Service 云厂商分配外部 LB IP(学习环境会一直 Pending)
kubectl run test --image=busybox --rm -it -- wget -O- http://<svc-name> 临时 Pod 测试 Service 可达性 --rm 退出即删除,网络调试首选
kubectl exec <pod> -- nslookup <svc-name> 验证 CoreDNS 解析 解析失败说明 CoreDNS 故障或 SVC 不存在
kubectl exec <pod> -- nslookup <svc-name>.<ns>.svc.cluster.local 验证 FQDN 解析 全限定域名格式:<svc>.<namespace>.svc.cluster.local
kubectl exec <pod> -- curl -s <svc-name>.<ns>.svc.cluster.local:<port> 通过 FQDN 访问服务 跨命名空间通信必须用 FQDN
kubectl get pods -l app=<label> 按标签查 Pod 验证 Service Selector 是否匹配到正确的 Pod
kubectl patch svc <name> -p '{"spec":{"sessionAffinity":"ClientIP"}}' 修改会话亲和性 同一客户端 IP 始终路由到同一 Pod
kubectl create svc clusterip <name> --tcp=80:8080 --dry-run=client -o yaml 生成 ClusterIP Service YAML --tcp 指定 <port>:<targetPort>
kubectl create svc nodeport <name> --tcp=80:8080 --node-port=30080 --dry-run=client -o yaml 生成 NodePort Service YAML 指定固定 NodePort 而非随机端口
kubectl -n kube-system logs -l k8s-app=kube-dns 查看 CoreDNS 日志 DNS 解析故障时首要排查

📚 参考来源

来源 链接 / 说明
Kubernetes 官方:Service https://kubernetes.io/docs/concepts/services-networking/service/
Kubernetes 官方:DNS 与服务发现 https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
Kubernetes 官方:Service 调试 https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/
Kubernetes 官方:EndpointSlice https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
CoreDNS 官方文档 https://coredns.io/manual/toc/