Service - 服务发现

Service - 服务发现

为什么需要 Service

Pod 的 IP 地址是动态变化的,会因为以下原因改变:

  • Pod 重启(新 IP)
  • Pod 扩缩容(增减 Pod)
  • Pod 调度到不同节点
  • 滚动更新(创建新 Pod,删除旧 Pod)

问题:如何稳定地访问一组 Pod?

答案:使用 Service!

Service 是什么

Service 是 Kubernetes 中的抽象概念,定义了一组 Pod 的访问策略。

核心功能:

  • 服务发现:通过固定的 DNS 名称访问
  • 负载均衡:自动分发流量到多个 Pod
  • 稳定入口:提供不变的 ClusterIP

Service 类型

Kubernetes 提供四种 Service 类型:

1. ClusterIP(默认)

只在集群内部可访问,是最常用的类型。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80          # Service 端口
    targetPort: 80    # Pod 端口
kubectl apply -f service.yaml

# 查看 Service
kubectl get svc nginx-service

# 输出
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.96.123.45    <none>        80/TCP    1m

访问方式

# 在集群内访问
curl http://10.96.123.45

# 通过 DNS 访问(推荐)
curl http://nginx-service
curl http://nginx-service.default.svc.cluster.local

2. NodePort

在每个节点上暴露一个端口,可从集群外访问。

apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30080  # 可选,不指定则自动分配 30000-32767
kubectl get svc nginx-nodeport

# 输出
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx-nodeport   NodePort   10.96.123.46   <none>        80:30080/TCP   1m

访问方式

# 通过任意节点 IP + NodePort 访问
curl http://<NodeIP>:30080

# Minikube 可以直接获取 URL
minikube service nginx-nodeport --url

3. LoadBalancer

使用云服务商的负载均衡器(AWS ELB、GCP Load Balancer 等)。

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
kubectl get svc nginx-lb

# 输出(云环境)
NAME       TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
nginx-lb   LoadBalancer   10.96.123.47   203.0.113.10     80:31234/TCP   1m

访问方式

# 通过 EXTERNAL-IP 访问
curl http://203.0.113.10

4. ExternalName

将服务映射到外部 DNS 名称。

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: mysql.example.com

集群内访问 external-db 会被重定向到 mysql.example.com

创建 Service

方法一:YAML 配置

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
  labels:
    app: webapp
spec:
  type: ClusterIP
  selector:
    app: webapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443

方法二:命令行创建

# 为 Deployment 创建 Service
kubectl expose deployment webapp --port=80 --target-port=8080

# 创建 NodePort Service
kubectl expose deployment webapp --type=NodePort --port=80

# 创建 LoadBalancer Service
kubectl expose deployment webapp --type=LoadBalancer --port=80

Service 工作原理

标签选择器

Service 通过标签选择器找到对应的 Pod:

# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp      # Pod 标签
  template:
    metadata:
      labels:
        app: webapp    # Pod 标签
    spec:
      containers:
      - name: webapp
        image: nginx

---
# Service
apiVersion: v1
kind: Service
metadata:
  name: webapp-service
spec:
  selector:
    app: webapp        # 匹配 Pod 标签
  ports:
  - port: 80
    targetPort: 80

Endpoints

Service 会自动创建 Endpoints 对象,记录所有匹配的 Pod IP。

# 查看 Endpoints
kubectl get endpoints webapp-service

# 输出
NAME             ENDPOINTS                           AGE
webapp-service   10.244.0.5:80,10.244.0.6:80,...    1m

# 详细信息
kubectl describe endpoints webapp-service

服务发现

DNS 解析

Kubernetes 提供内置 DNS 服务(CoreDNS),自动为 Service 创建 DNS 记录。

DNS 格式

<service-name>.<namespace>.svc.cluster.local

示例

# 同命名空间内访问
curl http://nginx-service

# 跨命名空间访问
curl http://nginx-service.default

# 完整域名
curl http://nginx-service.default.svc.cluster.local

环境变量

Pod 创建时,Kubernetes 会注入 Service 相关的环境变量:

# 进入 Pod
kubectl exec -it <pod-name> -- env | grep SERVICE

# 输出
NGINX_SERVICE_SERVICE_HOST=10.96.123.45
NGINX_SERVICE_SERVICE_PORT=80
NGINX_SERVICE_PORT=tcp://10.96.123.45:80

注意:只有 Pod 创建时已存在的 Service 才会注入环境变量。

会话亲和性

默认情况下,Service 随机分发请求。可以配置会话亲和性:

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
spec:
  selector:
    app: webapp
  sessionAffinity: ClientIP  # 基于客户端 IP 的会话保持
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800  # 3 小时
  ports:
  - port: 80
    targetPort: 8080

Headless Service

不需要负载均衡和单独的 ClusterIP,直接返回所有 Pod IP。

apiVersion: v1
kind: Service
metadata:
  name: webapp-headless
spec:
  clusterIP: None  # 关键:设置为 None
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 8080

用途:

  • StatefulSet 中的 Pod 身份识别
  • 自己实现负载均衡逻辑
  • 服务网格(Service Mesh)
# DNS 查询返回所有 Pod IP
nslookup webapp-headless

# 输出
Name:   webapp-headless.default.svc.cluster.local
Address: 10.244.0.5
Address: 10.244.0.6
Address: 10.244.0.7

多端口 Service

一个 Service 可以暴露多个端口:

apiVersion: v1
kind: Service
metadata:
  name: webapp-service
spec:
  selector:
    app: webapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443
  - name: metrics
    protocol: TCP
    port: 9090
    targetPort: 9090

实战示例

完整的 Deployment + Service

# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: nginx:1.21
        ports:
        - containerPort: 80

---
# ClusterIP Service
apiVersion: v1
kind: Service
metadata:
  name: webapp-clusterip
spec:
  type: ClusterIP
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 80

---
# NodePort Service
apiVersion: v1
kind: Service
metadata:
  name: webapp-nodeport
spec:
  type: NodePort
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

测试服务

# 应用配置
kubectl apply -f webapp.yaml

# 查看资源
kubectl get deployment,svc,pod

# 测试 ClusterIP(集群内)
kubectl run test-pod --image=busybox --rm -it -- sh
wget -O- http://webapp-clusterip

# 测试 NodePort(集群外)
curl http://<NodeIP>:30080

# Minikube
minikube service webapp-nodeport

常用命令

# 创建 Service
kubectl expose deployment <name> --port=80
kubectl apply -f service.yaml

# 查看 Service
kubectl get svc
kubectl get svc <name>
kubectl describe svc <name>

# 查看 Endpoints
kubectl get endpoints <name>

# 查看 Service 详情
kubectl get svc <name> -o yaml

# 删除 Service
kubectl delete svc <name>

# 端口转发(本地测试)
kubectl port-forward svc/<name> 8080:80

最佳实践

1. 使用有意义的名称

metadata:
  name: user-api-service    # 好
  # name: service1          # 不好

2. 明确指定端口名称

ports:
- name: http              # 明确
  port: 80
- name: grpc              # 明确
  port: 9090

3. 生产环境使用 ClusterIP + Ingress

不要直接使用 NodePort 暴露服务到外网,而是:

外部流量 → Ingress → Service (ClusterIP) → Pods

4. 健康检查配合 Service

readinessProbe:
  httpGet:
    path: /health
    port: 8080

只有就绪的 Pod 才会被加入 Service 的 Endpoints。

小结

Service 是 Kubernetes 中实现服务发现和负载均衡的核心资源:

  • ClusterIP:集群内部访问(最常用)
  • NodePort:通过节点端口访问
  • LoadBalancer:使用云负载均衡器
  • ExternalName:映射外部服务

关键概念:

  • 通过标签选择器关联 Pod
  • 自动创建 DNS 记录
  • 自动更新 Endpoints
  • 内置负载均衡

下一章我们将学习 ConfigMap 和 Secret,实现配置管理。