K8s 学习笔记(四):服务发现与网络

写在前面

本文是 K8s 学习笔记系列的第四篇,介绍 K8s 的服务发现和网络机制:Service、Ingress 和 DNS。前置知识:工作负载管理(第三篇)。


一、Service 概述

Pod 的 IP 是不固定的(重启后会变),Service 提供稳定的访问入口。

1
客户端 → Service(固定 IP + 固定 DNS)→ Pod(通过标签选择器自动转发)

1.1 Service 工作原理

1
2
3
4
1. Service 通过 selector 匹配 Pod 的标签
2. kube-proxy 在每个节点上配置转发规则
3. 客户端访问 Service IP,流量被转发到后端 Pod
4. Pod 变化时,控制器自动更新后端列表(EndpointSlice)

二、Service 类型

2.1 ClusterIP(默认)

集群内部访问,外部不可达。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: ClusterIP              # 默认值,可省略
  selector:
    app: web-app
  ports:
  - port: 80                   # Service 端口
    targetPort: 8080            # Pod 端口
1
2
3
4
集群内访问方式:
- Service 名称:http://web-app:80
- Service IP:http://10.100.200.50:80
- 完整 DNS:http://web-app.default.svc.cluster.local:80

2.2 NodePort

通过节点端口暴露服务,外部可以通过 节点IP:端口 访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
  - port: 80                   # Service 端口
    targetPort: 8080            # Pod 端口
    nodePort: 30080             # 节点端口(30000-32767,可省略自动分配)
1
2
外部访问方式:
- http://<任意节点IP>:30080

NodePort 适合测试和小规模使用,生产环境一般用 Ingress 或 LoadBalancer。

2.3 LoadBalancer

云厂商提供的负载均衡器(AWS ELB、GCP LB 等)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
  - port: 80
    targetPort: 8080

本地环境(minikube)没有真正的 LoadBalancer。可以用 minikube tunnel 模拟。

2.4 ExternalName

通过 DNS CNAME 把 Service 名称映射到外部 DNS 名称,不创建 EndpointSlice,也不提供代理或端口转换。客户端协议还要能正确处理别名后的主机名,例如 TLS 证书校验。

1
2
3
4
5
6
7
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com   # 外部数据库地址
1
2
集群内访问:mysql://external-db:3306
实际解析到:mysql://db.example.com:3306

2.5 选型对比

四种 Service 类型怎么选?一张表 + 决策顺序:

类型访问范围典型场景生产?
ClusterIP集群内微服务互调(绝大多数 Service)✓ 默认
NodePort节点 IP:端口测试、临时暴露、自建集群△ 一般不直接
LoadBalancer集群外部由云平台或负载均衡器实现提供入口,是否公网取决于实现与配置✓ 视环境而定
ExternalName集群内 → 外部 DNS把外部服务(数据库/API)包成本地 Service
1
2
3
4
5
6
7
8
决策顺序(从简到繁):
  ① 集群内调用             → ClusterIP(95% 的 Service)
  ② 把外部服务当本地用     → ExternalName(统一入口,便于迁移)
  ③ 单服务直接对公网       → LoadBalancer(云上)
  ④ 测试 / 自建集群暴露    → NodePort

  公网入口要"一个 IP 对多服务 + 域名路由 + TLS"
    → 别堆多个 LoadBalancer(贵),用 Ingress(见第五章)+ 一个 LB
1
2
3
4
一句口诀:
  内部调用 ClusterIP,外部映射 ExternalName
  云上直暴 LoadBalancer,测试调试 NodePort
  公网多服务,Ingress 来收口

三、Headless Service

不分配 ClusterIP,直接返回后端 Pod 的 IP。常配合 StatefulSet 使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None              # Headless
  selector:
    app: mysql
  ports:
  - port: 3306
1
2
3
4
DNS 解析结果:
mysql-0.mysql.default.svc.cluster.local → Pod-0 的 IP
mysql-1.mysql.default.svc.cluster.local → Pod-1 的 IP
mysql-2.mysql.default.svc.cluster.local → Pod-2 的 IP

StatefulSet 的每个 Pod 有固定的 DNS 名称,适合需要知道具体连接哪个实例的场景(如 MySQL 主从)。


四、EndpointSlice

EndpointSlice 记录 Service 的后端地址,是当前 Service 发现和转发使用的主要 API。旧的 Endpoints API 从 Kubernetes 1.33 起已弃用,不应再作为新配置的首选。

1
2
3
4
5
# 查看 Service 对应的 EndpointSlice
kubectl get endpointslice \
  -l kubernetes.io/service-name=web-app

# Service 没有 selector 时,控制器不会自动创建 EndpointSlice

手动 EndpointSlice 示例(为无 selector 的 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
# Service(不指定 selector)
apiVersion: v1
kind: Service
metadata:
  name: external-api
spec:
  ports:
  - port: 80
    targetPort: 80
---
# 手动指定 EndpointSlice
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: external-api-1
  labels:
    kubernetes.io/service-name: external-api
    endpointslice.kubernetes.io/managed-by: example.com/manual
addressType: IPv4
ports:
- name: http
  protocol: TCP
  port: 80
endpoints:
- addresses:
  - "192.168.1.100"

五、Ingress

Ingress 是 Kubernetes 中声明 HTTP/HTTPS 路由的稳定 API,实际转发必须由另行安装的 Ingress Controller 实现。Ingress API 已冻结,Kubernetes 项目建议新设计优先评估仍在演进的 Gateway API;现有 Ingress 不会因此立即失效。

5.1 为什么需要 Ingress

1
2
3
4
5
6
7
8
没有 Ingress:
  每个 Service 都需要 NodePort/LoadBalancer
  端口管理混乱,无法按域名路由

有 Ingress:
  只需要一个入口(LoadBalancer 或 NodePort)
  按域名和路径路由到不同 Service
  支持 TLS 证书

5.2 Ingress 示例

 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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  ingressClassName: example     # 替换为集群实际安装的 IngressClass
  rules:
  - host: app.example.com       # 域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-app       # 转发到这个 Service
            port:
              number: 80

  - host: api.example.com       # 另一个域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80

5.3 路径路由

 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
rules:
- host: app.example.com
  http:
    paths:
    - path: /api
      pathType: Prefix
      backend:
        service:
          name: api-service
          port:
            number: 80
    - path: /static
      pathType: Prefix
      backend:
        service:
          name: static-service
          port:
            number: 80
    - path: /
      pathType: Prefix
      backend:
        service:
          name: web-app
          port:
            number: 80

5.4 TLS 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
spec:
  tls:
  - hosts:
    - app.example.com
    secretName: tls-secret      # 包含 TLS 证书的 Secret
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-app
            port:
              number: 80

5.5 安装 Ingress Controller

1
2
3
4
5
# minikube 启用 Ingress 插件
minikube addons enable ingress

# 验证 Ingress Controller
kubectl get pods -n ingress-nginx

上述命令只用于 minikube 自带插件的学习环境。社区 ingress-nginx 项目已于 2026 年 3 月退役,不再获得错误修复或安全更新;生产环境应选择仍受维护的 Gateway API 实现或其他 Ingress Controller,并按其文档配置 ingressClassName


六、DNS

K8s 集群内置 DNS 服务(CoreDNS),Pod 自动配置 DNS 解析。

6.1 DNS 命名规则

1
2
3
4
<service-name>                          # 同命名空间
<service-name>.<namespace>              # 跨命名空间
<service-name>.<namespace>.svc          # 完整写法
<service-name>.<namespace>.svc.cluster.local  # 完整 FQDN

6.2 跨命名空间访问

1
2
3
4
5
# 在 dev 命名空间访问 prod 命名空间的 Service
mysql://mysql.prod.svc.cluster.local:3306

# 简写
mysql://mysql.prod:3306

6.3 DNS 调试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 启动调试 Pod
kubectl run debug --image=busybox --rm -it --restart=Never -- sh

# 在调试 Pod 中
nslookup web-app                          # 同命名空间
nslookup web-app.default.svc.cluster.local # 完整域名
nslookup web-app.prod                      # 跨命名空间

# 查看Pod 的 DNS 配置
cat /etc/resolv.conf

七、网络策略

NetworkPolicy 控制 Pod 之间的网络访问(默认全部互通)。

7.1 网络策略示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-policy
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: api-server        # 策略应用到这些 Pod
  policyTypes:
  - Ingress                  # 入站规则
  ingress:
  - from:
    - podSelector:            # 只允许这些 Pod 访问
        matchLabels:
          app: web-app
    ports:
    - port: 8080
      protocol: TCP

7.2 常见策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 拒绝所有入站(默认拒绝)
spec:
  podSelector: {}
  policyTypes:
  - Ingress

# 允许同命名空间访问
spec:
  podSelector: {}
  ingress:
  - from:
    - podSelector: {}

# 允许指定命名空间访问
spec:
  podSelector: {}
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          env: dev

NetworkPolicy 只有在集群网络实现支持并启用相应策略能力时才会生效。minikube 是否支持取决于所选网络插件和启动配置,不能仅凭 API 对象创建成功判断策略已经执行。


八、开发者视角:服务接入

8.1 微服务间的调用方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package example

import "net/http"

func checkServices(client *http.Client) error {
    // 不要硬编码 Pod IP;通过 Service DNS 名称访问。
    userResp, err := client.Get("http://user-service:8080/api/users")
    if err != nil {
        return err
    }
    userResp.Body.Close()

    orderResp, err := client.Get("http://order-service.prod.svc.cluster.local:8080/api/orders")
    if err != nil {
        return err
    }
    orderResp.Body.Close()
    return nil
}

示例由调用方传入配置了超时的 http.Client;生产代码还应检查 HTTP 状态码,并按幂等性设计重试。

8.2 服务接入检查清单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
1. Service selector 是否匹配 Pod  labels
    kubectl get endpointslice -l kubernetes.io/service-name=<service> 检查是否有后端

2. 端口是否正确?
    Service `port` 要转到应用实际监听的 `targetPort``containerPort` 主要用于声明和命名,本身不会让进程监听端口

3. Pod 是否 Ready
    readinessProbe 没通过的 Pod 不会接收流量

4. 跨命名空间是否用了完整 DNS
     <service>.<namespace> 格式

5. 外部访问是否配置了 Ingress
    检查 Ingress 规则和域名解析

九、小结

本文学习了 K8s 的服务发现与网络:

  • Service 四种类型(ClusterIP、NodePort、LoadBalancer、ExternalName)
  • Headless Service 和 StatefulSet 的配合
  • EndpointSlice 和外部服务后端
  • Ingress HTTP 路由和 TLS
  • DNS 命名规则和跨命名空间访问
  • NetworkPolicy 网络策略
  • 开发者视角的服务接入

下一篇将学习配置与存储:ConfigMap、Secret、PV/PVC。

参考资料

Licensed under CC BY-NC-SA 4.0
最后更新于 Thursday, August 20, 2026