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

🎯 今日目标

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

🧠 理论精讲(30 分钟)

Service 解决了什么问题

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

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 分发生产环境外部入口
ExternalNameDNS CNAME外部服务映射

CoreDNS 域名格式

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

🔧 动手实操(120 分钟)

练习 8.1:ClusterIP Service 基础

# 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. 创建 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

# 将外部域名映射为集群内部 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. 创建 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. 创建一个能区分 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. 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 解析失败

# 测试 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. 创建 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列出所有 ServiceTYPE 列显示 ClusterIP/NodePort/LoadBalancer/ExternalName
kubectl get svc -o wideService + 选择器/端口确认 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 官方:Servicehttps://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 官方:EndpointSlicehttps://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
CoreDNS 官方文档https://coredns.io/manual/toc/