Kubernetes 入门教程之五

大纲

Kubernetes 核心技术

Service

Service 的概念

Service 是 Kubernetes 的核心概念之一。通过创建 Service,可以为一组具备相同功能的 Pod 提供统一的访问入口(即暴露服务),并将请求流量负载均衡地分发到后端各个 Pod 上。Pod 与 Service 之间是通过 Label(标签)和 Label Selector(标签选择器)建立关联关系的。值得一提的是,在 Kubernetes 中,Service + EndpointController 的配合实现了类似注册中心的功能:当 Pod 创建后,Kubernetes 控制器会自动将其加入对应 Service 的 Endpoints 列表,并通过 Readiness Probe(就绪探针)动态更新,确保只有可用的 Pod 接收流量。Pod 可以通过 DNS 直接访问 Service 名称(如 <service-name>.<namespace>.svc.cluster.local),而流量则由 kube-proxy 负责负载均衡。整体上,Service 提供了 DNS 名称 + 虚拟 IP(ClusterIP)的抽象,真正存储服务实例信息的是 EndpointSlice,整个过程完全自动化,Pod 无需显式注册,从而实现了透明的服务注册与发现。

提示

Kubernetes 中的 Service + EndpointController 机制,提供了与注册中心类似的自动注册和服务发现能力,但 Pod 无需显式注册,且数据由 Kubernetes 控制平面自动维护,主要面向集群内部的服务(Pod)注册与发现。

Service 的作用

  • 服务发现(Service Discovery)

    • Pod 间互相通信的唯一入口:
      • 为一组 Pod 提供一个固定的访问入口,解决 Pod IP 动态变化的问题。
    • DNS 服务发现:
      • 集群内部 Pod 可通过 <service-name>.<namespace>.svc.cluster.local 访问目标服务,无需感知 Pod 的 IP。
    • 自动注册与维护:
      • Pod 创建或销毁时,Kubernetes 会自动更新 Service 对应的 EndpointSlice,应用无需显式注册。
  • 负载均衡(Load Balancing)

    • 集群内负载均衡:
      • kube-proxy 会自动将访问 Service 的流量分发到后端多个 Pod。
    • 负载策略:
      • 默认采用轮询(Round Robin)。
      • 结合 SessionAffinity 可以实现会话保持。
    • Service 类型扩展:
      • ClusterIP:仅集群内访问,内部负载均衡。
      • NodePort:通过每个节点固定端口暴露服务。
      • LoadBalancer:集成云厂商的外部负载均衡器。
  • 对外暴露服务

    • 集群外访问能力:
      • NodePort:通过节点 IP + 端口访问。
      • LoadBalancer:借助云厂商负载均衡对外暴露。
      • ExternalName:将集群内部访问映射到外部域名。
  • 解耦应用与底层 Pod

    • 稳定访问地址:
      • 应用通过 Service 名称访问后端服务,不依赖具体 Pod IP。
    • 支持滚动升级:
      • Pod 替换过程中,Service 始终提供不变的入口,保障请求不中断。
    • 简化业务逻辑:
      • 业务代码不需要实现服务注册、心跳检测、路由等逻辑。
  • 健康检查与流量控制

    • 与 Readiness Probe(就绪探针)结合:
      • 只将健康的 Pod 添加到 EndpointSlice,自动摘除异常 Pod。
    • 支持蓝绿发布 / 灰度发布:
      • 结合标签选择器(selector),灵活管理流量转发目标。
  • 服务注册中心的替代方案

    • 自动化注册与发现:
      • 无需像 Zookeeper / Eureka 那样主动注册,Pod 生命周期事件由 Kubernetes 控制器接管。
    • 真实的 “注册表”:
      • Pod 实例信息存储在 EndpointSlice 中,Service 只是抽象层,负责提供 DNS 和虚拟 IP(ClusterIP)。

Service 的类型

五大类型

在 Kubernetes 中,Service 有以下几种类型:

  • ClusterIP
    • 概述
      • ClusterIP 是 Service 的默认类型。
    • 作用:
      • 为一组 Pod 提供一个集群内部虚拟 IP,只能通过集群内部的 Pod 或 Service 访问。
    • 使用场景:
      • 内部微服务之间通信。
      • 数据库、内部 API 等只在集群内部访问的服务。
    • 配置示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      apiVersion: v1
      kind: Service
      metadata:
      name: <service-name> # Service 的名称
      spec:
      type: ClusterIP # Service 类型为 ClusterIP,集群内部可访问
      selector:
      app: my-app # 选择标签为 app=my-app 的 Pod 作为后端
      ports:
      - port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
      targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
    • 访问方式:
      • 外部访问:
        • ClusterIP 类型的 Service 默认无法被外部访问,它只在集群内部有效。
        • 如果需要外部访问,则必须通过 Ingress、LoadBalancer 或 NodePort 将流量引入集群,再访问集群内的 Pod 或 Service。
      • 集群内部访问:
        • 通过虚拟 IP(ClusterIP)访问:
          • 集群内 Pod 可以直接访问 Service 的 ClusterIP:http://<clusterIp>:<port>
        • 通过 DNS 名称(域名)访问:
          • Kubernetes 会自动为 Service 创建 DNS 名称(域名)。
          • Pod 内部可以通过这个 DNS 名称(域名)访问 Service:http://<service-name>.<namespace>.svc.cluster.local:<port>
          • Service 的默认命名空间(namespace)是 default,可以通过 kubectl get svc -A 命令查看。
          • kube-proxy 会将流量转发到对应 Pod 的 targetPort

  • NodePort
    • 概述:
      • 将 Service 暴露在每个集群节点的固定端口上。
    • 作用:
      • 外部流量可以通过集群节点的 IP 和 NodePort 访问到集群内的 Pod。
    • 端口范围:
      • 默认 30000 ~ 32767
    • 使用场景:
      • 测试环境或临时访问集群服务。
      • 没有 Ingress 或云负载均衡器时,简单暴露服务。
    • 配置示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      apiVersion: v1
      kind: Service
      metadata:
      name: <service-name> # Service 的名称
      spec:
      type: NodePort # Service 类型为 NodePort,可通过节点 IP 访问
      selector:
      app: my-app # 选择标签为 app=my-app 的 Pod 作为后端
      ports:
      - port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
      targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
      nodePort: 30080 # 映射到物理机的端口号,默认范围 30000 - 32767
    • 访问方式:
      • 外部访问:
        • 通过任意一个集群节点的 IP 和 nodePort 访问集群内的 Pod:http://<nodeIP>:<nodePort>
      • 集群内部访问:
        • Kubernetes 会自动为 Service 创建 DNS 名称(域名)。
        • 集群内部 Pod 可以通过这个 DNS 名称(域名)访问 Service:http://<service-name>.<namespace>.svc.cluster.local:<port>
        • Service 的默认命名空间(namespace)是 default,可以通过 kubectl get svc -A 命令查看。
        • kube-proxy 会将流量路由到对应 Pod 的 targetPort

  • LoadBalancer
    • 概述:
      • 依赖云厂商的负载均衡器,将外部流量分发到集群。
    • 作用:
      • 自动向云平台申请一个外部负载均衡器(如 AWS ELB、阿里云 SLB)。
    • 特点:
      • 会自动分配到一个公网 IP。
      • 负载均衡器将流量转发到后端 NodePort。
    • 使用场景:
      • 云环境生产集群中,外部流量访问的标准方式。
      • 对外提供 API 网关、Web 服务、支付网关等服务。
    • 配置示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      apiVersion: v1
      kind: Service
      metadata:
      name: <service-name> # Service 的名称
      spec:
      type: LoadBalancer # Service 类型为 LoadBalancer,自动申请外部负载均衡器(LB)
      selector:
      app: my-app # 选择标签为 app=my-app 的 Pod 作为后端
      ports:
      - port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
      targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
    • 访问方式:
      • 外部访问:
        • 外部通过云负载均衡器的公网 IP 或域名访问:
          1
          2
          3
          4
          5
          # 基于公网 IP 访问
          curl http://<loadbalancer-ip>:80

          # 基于域名访问
          curl http://<service-name>.example.com
        • <LoadBalancer-IP> 由云平台自动分配。
        • 可绑定自定义域名,通过 DNS 解析访问。
      • 集群内部访问:
        • Kubernetes 会自动为 Service 创建 DNS 名称(域名)。
        • 集群内部 Pod 可以通过这个 DNS 名称(域名)访问 Service:http://<service-name>.<namespace>.svc.cluster.local:<port>
        • Service 的默认命名空间(namespace)是 default,可以通过 kubectl get svc -A 命令查看。
        • kube-proxy 会将流量路由到对应 Pod 的 targetPort

  • ExternalName
    • 概述:
      • 通过 DNS 将 Service 名称映射到集群外部服务域名,不做流量代理。
    • 作用:
      • 通过 Kubernetes 内部的 DNS 把 Service 映射为外部域名,Pod 通过访问 Service 名称即可访问外部服务。
    • 特点:
      • 不会创建虚拟 IP(ClusterIP)。
      • 只是一个 DNS CNAME 解析,流量不经过 Kubernetes 负载均衡或代理(kube-proxy)。
    • 使用场景:
      • 访问外部数据库、外部 API 服务等。
    • 配置示例:
      1
      2
      3
      4
      5
      6
      7
      apiVersion: v1
      kind: Service
      metadata:
      name: external-service # Service 的名称
      spec:
      type: ExternalName # Service 类型为 ExternalName,将 Service 名称映射到外部域名
      externalName: db.example.com # 集群内部访问 Service 时,DNS 解析到的外部域名
    • 访问方式:
      • 外部访问:
        • ExternalName 类型的 Service 本身不提供外部访问入口,它只是 Kubernetes 内部的 DNS 映射,外部无法通过 Service 名称访问集群内的 Pod 或 Service。
        • 如果需要外部访问,则必须通过 Ingress、LoadBalancer 或 NodePort 将流量引入集群,再访问集群内的 Pod 或 Service。
      • 集群内部访问:
        • Kubernetes 会自动为 Service 创建 DNS 名称(域名)。
        • 集群内部 Pod 可以通过这个 DNS 名称(域名)访问 Service:http://<service-name>.<namespace>.svc.cluster.local,实际上会解析到 db.example.com
        • Service 的默认命名空间(namespace)是 default,可以通过 kubectl get svc -A 命令查看。

  • None
    • 概述:
      • 无虚拟 IP(ClusterIP)的 Service(Headless Service - 无头服务),是一种没有 ClusterIP 的特殊 Service 类型。
      • 用于暴露 K8s 集群内 Pod 的真实 IP 和 DNS 名称(域名),而不是通过一个统一的虚拟 IP(ClusterIP)进行负载均衡,即 K8s 不会做流量负载均衡。
    • 作用:
      • 不需要虚拟 IP(ClusterIP)和负载均衡时使用。
      • 客户端可以直接感知 Pod 的 IP,实现自定义的负载均衡或服务发现。
    • 定义方式:
      • 在 Service 配置中设置:clusterIP: None
    • 使用场景:
      • 部署有状态服务(StatefulSet),如 MySQL、ZooKeeper、Kafka 等。
      • 客户端需要自己实现负载均衡或服务发现的场景。
    • 配置示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      apiVersion: v1
      kind: Service
      metadata:
      name: headless-service # Service 的名称
      spec:
      clusterIP: None # 设置为 None 表示无虚拟 IP(ClusterIP),直接返回 Pod 的 IP
      selector:
      app: my-app # 选择标签为 app=my-app 的 Pod 作为后端
      ports:
      - port: 80 # Service 对外暴露的端口,客户端访问时使用
      targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
    • 访问方式:
      • 外部访问:
        • 由于 None 类型的 Service 不提供虚拟 IP,Kubernetes 不做负载均衡,因此外部不能直接访问 Service。
        • 如果外部需要访问 None 类型的 Service,可以使用以下方式实现:
          • 通过 Pod 的 Node IP + 容器端口(Pod 暴露端口需要通过 NodePort 或其他方式实现)。
          • 或者借助 Ingress 或 LoadBalancer 将流量引入集群,再由客户端自行选择 Pod。
      • 集群内部访问:
        • 通过 DNS 名称(域名)访问:
          • Kubernetes 会自动为 Service 创建 DNS 名称(域名)。
          • Service 的默认命名空间(namespace)是 default,可以通过 kubectl get svc -A 命令查看。
          • 第一种 DNS 访问方式:
            • 集群内部 Pod,可以通过 Service 的 DNS 名称(域名)查询所有匹配 Pod 的 IP,域名格式:<service-name>.<namespace>.svc.cluster.local
              1
              2
              3
              4
              nslookup mysql.default.svc.cluster.local
              10.244.1.5
              10.244.1.6
              10.244.1.7
          • 第二种 DNS 访问方式(StatefulSet 专用访问)
            • ClusterIP 为 None 的 Service,每个 Pod 都有固定的 DNS 名称(域名),适用于数据库或分布式系统访问(比如 MySQL、ZooKeeper),保证客户端可以稳定访问指定 Pod。
            • 集群内部 Pod,可以通过 Pod 的 DNS 名称(域名)直接访问指定的 Pod,域名格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local
              1
              2
              3
              nslookup mysql-statefulset-0.mysql.default.svc.cluster.local

              telnet mysql-statefulset-0.mysql.default.svc.cluster.local 3306
        • 直接访问 Pod 的 IP:
          • 客户端可根据自定义的负载均衡策略(轮询、随机、哈希等)直接访问 Pod 的 targetPort
            1
            2
            curl http://10.244.1.5:8080
            curl http://10.244.1.6:8080

类型访问范围是否需要 kube-proxy 是否有虚拟 IP(ClusterIP)典型场景
ClusterIP 仅集群内部✅ 是✅ 有微服务内部通信
NodePort 外部可访问,通过集群节点的 IP✅ 是✅ 有简单对外暴露服务
LoadBalancer 外部可访问,通过 LB 公网 IP✅ 是✅ 有生产外部访问
ExternalName 集群内部访问外部域名❌ 否❌ 无外部服务映射
None 集群内部直接返回 Pod 的 IP✅ 是❌ 无有状态服务访问(如 MySQL、ZooKeeper)
网络测试

特别注意

ClusterIP 类型的 Service 只能在 Kubernetes 集群内部访问,如果在集群外部机器(比如直接在集群的 Master 节点)上,通过 Service 的 DNS 名称直接访问 Pod(比如 http://nginx.default.svc.cluster.local:80),肯定是无法访问成功的。

在 Kubernetes 中,创建一个 Nginx 的 Pod,使用 ClusterIP 类型的 Service 来暴露服务

  • 创建一个 Nginx 的 Deployment 和 Service
1
2
3
4
5
# 创建 Nginx
kubectl create deployment nginx --image=nginx

# 暴露 Nginx 的端口(Service 的默认类型是 ClusterIP,可以通过 --type 参数指定类型)
kubectl expose deployment nginx --port=80 --target-port=80
  • 查看 Pod 列表
1
kubectl get pods -o wide
1
2
NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
nginx-6799fc88d8-dkltf 1/1 Running 0 12m 10.244.2.2 k8s-node3 <none> <none>
  • 查看 Service 列表
1
kubectl get svc -A
1
2
3
4
NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 42d
default nginx ClusterIP 10.0.0.231 <none> 80/TCP 12s
kube-system kube-dns ClusterIP 10.0.0.2 <none> 53/UDP,53/TCP,9153/TCP 25m

在 Kubernetes 集群内部,通过 Service 的 DNS 名称(域名)访问 Nginx 的 Pod

  • 创建一个临时 Pod,并进入 Pod 内部的交互式 Shell
1
2
# 添加 --rm 参数,为了在 Shell 中执行 exit 命令退出后自动销毁 Pod
kubectl run test-pod --image=busybox:1.35 --restart=Never -it --rm -- sh
  • 在临时 Pod 的内部,通过 Service 的 DNS 名称(域名)访问 Nginx 的 Pod(必须保证临时 Pod 与 Service 处于同一个命名空间),域名格式:<service-name>.<namespace>.svc.cluster.local
1
nslookup nginx.default.svc.cluster.local
1
2
3
4
5
Server:		10.0.0.2
Address: 10.0.0.2:53

Name: nginx.default.svc.cluster.local
Address: 10.0.0.231
1
2
# 或者通过 Service 的 DNS 名称(域名)访问 Nginx 的首页面
wget -qO- http://nginx.default.svc.cluster.local:80
  • 如果 nslookup 或者 wget 工具无法通过 Service 的 DNS 名称(域名)来访问 Nginx 的 Pod,建议重点检查 CoreDNS(Kubernetes 官方提供的 DNS 服务)是否可以正常运行(也可能是没有安装 CoreDNS
1
2
# 查看 CoreDNS 的运行状态
kubectl get pod -n kube-system -l k8s-app=kube-dns
  • 预期输出以下内容,如果 CoreDNS 的 STATUS 不是 Running 或 Pod 不存在,则说明 CoreDNS 服务没有正常运行
1
2
3
NAME                       READY   STATUS    RESTARTS   AGE
coredns-6b9bb479b9-g6t6f 1/1 Running 0 34m
coredns-6b9bb479b9-lfd7j 1/1 Running 0 34m

Kubernetes 删除 Deployment 和 Service

  • 如果需要删除上面创建的 Deployment 和 Service,可以执行以下命令
1
2
3
4
5
# 删除Service
kubectl delete service nginx

# 删除Deployment
kubectl delete deployment nginx

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
apiVersion: v1
kind: Service
metadata:
name: nginx-service # Service 的名称
namespace: production # 所属命名空间,例如生产环境
labels:
app: nginx # 标签,标识该服务属于哪个应用
tier: frontend # 层级标签,例如前端、后端
annotations:
description: "Nginx Web Service for production" # 业务描述信息
spec:
selector: # 匹配后端 Pod
app: nginx # 必须与 Pod 的 labels 匹配
type: NodePort # Service 类型:ClusterIP / NodePort / LoadBalancer / ExternalName
clusterIP: 10.0.0.15 # 集群内部 IP(可选,默认自动分配)
sessionAffinity: None # 会话亲和性,可选值:None / ClientIP
ports:
- name: http # 端口名称(可选)
protocol: TCP # 协议类型:TCP 或 UDP
port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
nodePort: 30080 # 映射到物理机的端口号,默认范围 30000 - 32767
status:
loadBalancer:
ingress:
- ip: 192.168.1.100 # 外部负载均衡器分配的 IP
hostname: lb-prod.example.com # 外部负载均衡器的域名
属性名称取值类型是否必选取值说明
spec.ports[].targetPortint需要转发到后端 Pod 的端口号
spec.ports[].nodePortintspec.type=NodePort 时,指定映射到物理机的端口号
statusobjectspec.type=LoadBalance 时,设置外部负载均衡器的地址,用于公有云环境
status.loadBalancerobject外部负载均衡器
status.loadBalancer.ingressobject外部负载均衡器
status.loadBalancer.ingress.ipstring外部负载均衡器的 IP 地址
status.loadBalancer.ingress.hostnamestring外部负载均衡器的主机名

Service 的使用

Service 基础使用

一般来说,对外提供服务的应用程序需要通过一定的机制来实现暴露,而对于容器化应用,最简便的方式就是通过 TCP/IP 协议,并结合监听 IP 和端口号来对外提供服务。比如,创建一个带基本功能的 Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: ReplicationController
metadata:
name: mywebapp # RC 的名称
spec:
replicas: 2 # 副本数量
template: # Pod 的模板
metadata:
name: mywebapp # Pod 的名称
labels:
app: mywebapp # Pod 的标签,用于 Service 或 RC 选择器
spec:
containers:
- name: mywebapp # 容器的名称
image: tomcat # 容器使用的镜像
ports:
- containerPort: 8080 # 容器内应用(比如 Tomcat)监听的端口

可以通过 kubectl get pods -l app=mywebapp -o yaml | grep podIP 命令获取 Pod 的 IP 地址,然后使用 Pod 的 IP 地址和端口号来访问 Tomcat 服务。但是,直接通过 Pod 的 IP 来访问服务是不可靠的,因为当 Pod 所在的 Node(工作节点)发生故障时,Kubernetes 会将该 Pod 重新调度到其他 Node(工作节点),此时 Pod 的 IP 地址会发生变化,导致原有访问地址失效。为了解决这一问题,可以通过 YAML 配置文件再定义一个 Service,并使用以下命令来创建:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: mywebAppService # Service 的名称
spec:
selector: # 选择器,匹配后端 Pod 的标签(labels)
app: mywebapp
ports:
- port: 8081 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
1
2
# 根据指定的 YAML 配置文件创建 Service
kubectl create -f service.yaml
多端口 Service

有时,一个容器应用可能需要对外提供多个端口的服务,这时可以在 Service 的定义中配置多个端口,将每个端口映射到对应的应用服务,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: mywebAppService # Service 的名称
spec:
selector: # 选择器,匹配后端 Pod 的标签(labels)
app: mywebapp
ports:
- name: web # 第一个端口的名称
port: 8080 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8080 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
- name: management # 第二个端口的名称
port: 8005 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8005 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
外部服务 Service

在某些特殊场景下,应用系统可能需要将外部数据库作为后端服务,或者将其他集群或命名空间中的服务作为后端服务。这时候,可以通过创建一个不带 Label 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
# Service 定义
apiVersion: v1
kind: Service
metadata:
name: <service-name> # Service 的名称
spec:
ports:
- protocol: TCP # 协议类型
port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 8080 # Pod 容器或 Endpoints 实际监听的端口

---

# Endpoints 定义
apiVersion: v1
kind: Endpoints
metadata:
name: <service-name> # 对应的 Service 的名称
subsets:
- addresses:
- ip: 10.254.74.3 # 外部服务的 IP
ports:
- port: 8080 # 外部服务实际监听的端口

特别注意

  • 当 Service 有 selector 时,Service 会自动匹配 Pod,并自动生成 Endpoints。
  • 当 Service 没有 selector 时,它会通过 Endpoints 的 metadata.name 与 Service 的 metadata.name 相同(一致)来建立关联。

Ingress

K8s 整体网络架构

  • Kubernetes 网络主要解决四方面的问题:

    • 一个 Pod 中的多个容器之间可以通过本地回路(Loopback)互通。
    • 集群网络在不同 Pod 之间提供通信,Pod 和 Pod 之间互通。
    • Service 资源允许用户对外暴露 Pods 中运行的应用程序,以支持来自于集群外部的访问。Service 和 Pod 之间要互通。
    • 可以使用 Service 来发布仅供集群内部使用的服务。
  • Kubernetes 的整体网络架构

  • Kubernetes 的网络访问流程

Ingress 的基本概念

  • 为什么需要 Ingress?
    • Service 可以使用 NodePort 暴露集群外访问端口,但是性能差不安全。
    • 缺少 Layer7 的统一访问入口,可以负载均衡、限流等。
    • Ingress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由,且流量路由是由 Ingress 资源上定义的规则控制。
    • 使用 Ingress 作为整个集群统一的入口,配置 Ingress 规则将流量转发到对应的 Service(如下图所示)。

  • Service 中 NodePort 的缺点

    • 端口资源有限且容易冲突
      • NodePort 在每个节点(Node)上都会占用相同的端口号。
      • 每个端口只能对应一个 Service,一个节点上的端口号不能重复使用,端口资源有限。
    • 访问方式不够灵活
      • 必须通过节点 IP + 端口号访问,不符合实际生产中通常使用域名访问的方式。
      • 无法直接根据域名自动路由到不同服务,需要额外配置反向代理或者 Ingress 实现域名分流。
    • 对外暴露复杂,安全性较低
      • 在所有节点上都暴露了特定的端口,增加了攻击面和安全风险。
    • 难以与外部负载均衡器集成
      • NodePort 只提供基础的端口转发,不具备智能流量分配、健康检查等高级功能。
  • Ingress 与 Pod 的关系

    • Ingress 并不直接与 Pod 通信,而是通过 Service 与 Pod 进行关联,访问链路是:外部请求 → Ingress → Service → Pod
    • 具体来说,Ingress 作为集群对外的统一访问入口,负责根据访问的域名或路径规则,将外部请求转发到对应的 Service;而 Service 再根据其标签选择器(Label Selector)将请求负载均衡地分发给一组符合条件的 Pod。

Ingress 的两种实现

Ingress 本质上是一个控制器(Controller),需要单独安装,它有两种实现,包括:

  • Nginx Ingress

    • 这是 Nginx 官方开发的,适配 Kubernetes 的,分为开源版和 Nginx Plus 版(收费)。
    • 官方文档:https://docs.nginx.com/nginx-ingress-controller/overview/
    • 官方网站:https://www.nginx.com/products/nginx-ingress-controller
  • Ingress-Nginx

    • 这是 Kubernetes 官方开发的,适配 Nginx 的,开源免费的;它会及时更新一些特性,而且性能很高,被各大互联网公司广泛采用。
    • 官方文档:https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/
    • 官方网站:https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
    • 推荐使用这个镜像来部署 Ingress-Nginx:registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0

Ingress 的安装步骤

特别注意

  • 由于 Ingress 本质上是一个 Kubernetes 控制器(Controller),因此可以通过 YAML 文件进行安装(部署),这里使用的是 Nginx Ingress(由 Nginx 官方开发)。
  • 在下述的 YAML 配置内容中,hostNetwork 参数必须设置为 true,否则在 Kubernetes 集群外部无法直接通过域名访问 Ingress。
  • Ingress 的所有安装(部署)步骤都是在 Kubernetes 集群的 Master 节点上执行。
  • 通过 YAML 文件(比如 nginx-ingress-deploy.yaml) 部署 Nginx Ingress(由 Nginx 官方开发)
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update

---

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
hostNetwork: true
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: lizhenliang/nginx-ingress-controller:0.30.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 101
runAsUser: 101
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
limits:
- min:
memory: 90Mi
cpu: 100m
type: Container
1
2
# 创建或更新 YAML 文件中定义的 K8s 资源对象
kubectl apply -f nginx-ingress-deploy.yaml
  • 查看特定命名空间下所有 Pod 的运行状态
1
kubectl get pods -n ingress-nginx -o wide
1
2
NAME                                       READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
nginx-ingress-controller-5dc64b58f-x7stf 1/1 Running 0 17m 192.168.2.236 k8s-node3 <none> <none>
  • 若需要取消 Ingress 的安装,可以执行以下命令删除 Ingress 相关的所有资源
1
kubectl delete -f nginx-ingress-deploy.yaml

Ingress 的使用案例

提示

本节将使用 Ingress 对外暴露 Pod,让 Kubernetes 集群外部可以直接通过域名访问 Pod。

(1) 创建 Deployment,用于部署 Nginx 的 Pod

  • 通过 YAML 文件(比如 nginx-deploy.yaml)创建 Deployment(用于创建和管理 Pod)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 1
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.15
ports:
- containerPort: 80
1
2
# 创建或更新 YAML 文件中定义的 Deployment 对象
kubectl apply -f nginx-deploy.yaml
  • 查看所有 Pod 的运行状态
1
kubectl get pods -o wide
1
2
NAME                            READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
nginx-deploy-85b7dd6b6d-grk6n 1/1 Running 0 65m 10.244.3.47 k8s-node2 <none> <none>

(2) 创建 Service,用于在集群内部暴露 Nginx 的 Pod

  • 通过 YAML 文件(比如 nginx-service.yaml)创建 Service(用于对外暴露服务)
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: nginx-service # Service 的名称
spec:
type: ClusterIP # Service 类型为 ClusterIP,集群内部可访问
selector:
app: nginx-pod # 选择标签为 app=nginx-pod 的 Pod 作为后端
ports:
- port: 80 # Service 对外暴露的端口,集群内部访问时也可以使用该端口
targetPort: 80 # Pod 内容器实际监听的端口(即将请求转发到容器的 xxxx 端口)
1
2
# 创建或更新 YAML 文件中定义的 Service 对象
kubectl apply -f nginx-service.yaml
  • 查看所有 Service
1
kubectl get services
1
2
3
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 84d
nginx-service ClusterIP 10.0.0.193 <none> 80/TCP 27s

(3) 创建 Ingress 的路由规则,用于将外部请求转发给 Service

  • 通过 YAML 文件(比如 ingress-http.yaml)创建 Ingress 的路由规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: example.ingress.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service # Service 的名称
port:
number: 80 # Service 对外暴露的端口
1
2
# 创建或更新 YAML 文件中定义的 Ingress 对象
kubectl apply -f ingress-http.yaml
  • 查看所有 Ingress 路由规则
1
kubectl get ingress
1
2
NAME              CLASS    HOSTS                 ADDRESS   PORTS   AGE
example-ingress <none> example.ingress.com 80 13s

(4) 在 K8s 集群外部的操作系统中,添加 Hosts 映射记录

  • 在 K8s 集群外部的操作系统中,编辑系统配置文件 /etc/hosts,添加域名映射记录,其中 192.168.2.236 是 Ingress Controller 所在节点的 IP 地址(请自行更改 IP 地址
1
2
# 编辑系统配置文件,添加以下内容
vim /etc/hosts
1
192.168.2.236     example.ingress.com

(5) 在 K8s 集群外部的操作系统中,通过域名访问 Ingress,验证 Pod 是否可以访问

  • 在 K8s 集群外部的操作系统中,通过域名访问 Ingress;如果可以成功访问 Nginx 的首页,则说明 Ingress + Service + Pod 都正常运行
1
curl http://example.ingress.com