006-Kubernetes Service

  1. 一、Service 工作原理
    1. 1. Service 简介
    2. 2. 案例一:通过选择器自动将Sevice与Pod绑定
      1. 2.1 部署Deployment
      2. 2.2 部署 Service
    3. 3. 案例二:手动创建 Endpoints, 将Service与Pod关联
      1. 3.1 部署 Deployment
      2. 3.2 部署 Service
      3. 3.3 部署 Endpoints
  2. 二、Service 的负载均衡
    1. 1. userspace 模式(不常用)
    2. 2. iptables 模式(默认方式)
    3. 3. ipvs 模式(适用于大规模集群)
    4. 4. 修改 kube-proxy 模式
  3. 三、Service 的类型
    1. 1. ClusterIp 集群内访问
      1. 1.1 案例实操1:部署 Service
      2. 1.2 案例实操1:部署 Deployment
      3. 1.3 IPVS 持久化连接
        1. 1.3.1 核心作用
        2. 1.3.2 工作原理
      4. 1.4 internalTrafficPolicy
    2. 2. NodePort 集群外访问
      1. 2.1 案例实操:部署Service
      2. 2.2 案例实操:部署Deployment
      3. 2.3 externalTrafficPolicy
    3. 3. LoadBalancer 负载均衡
      1. 3.1 LoadBalancer Service 的工作原理
      2. 3.2 创建 LoadBalancer Service
      3. 3.3 验证 LoadBalancer Service
      4. 3.4 高级配置
        1. 3.4 1 指定外部 IP(可选)
        2. 3.4.2 自定义负载均衡器行为
        3. 3.4.3 限制源 IP(白名单)
        4. 3.4.4 多端口服务
      5. 3.5 使用场景
      6. 3.6 注意事项
      7. 3.7 示例:部署一个简单的应用
    4. 4. ExternalName 指定外部访问域名
      1. 4.1 主要特点
      2. 4.2 使用场景
      3. 4.3 配置示例
      4. 4.4 测试 Pod 访问 External Service
      5. 4.5 工作原理
      6. 4.6 注意事项
      7. 4.7 局限性
  4. 四、Endpoints-Service 的底层模型
    1. 1. 什么是 Endpoint?
    2. 2. Endpoint 的结构
    3. 3. Endpoint 与 Service 的关系
    4. 4. Endpoint 的使用
      1. 4.1 自动创建 Endpoints
      2. 4.2 手动创建 Endpoints
    5. 5. 动态管理Endpoint
    6. 6. Endpoint 增删改查
      1. 6.1 创建 Endpoint
        1. 6.1.1 自动创建 Endpoint
        2. 6.1.2 手动创建 Endpoint
      2. 6.2 Endpoint 更新
        1. 6.2.1 自动更新 Endpoint
        2. 6.2.2 手动更新 Endpoint
      3. 6.3 Endpoint 的删除
    7. 7. Endpoint controlor
    8. 8. Endpoint参数解释
  5. 五、Service 设置 publishNotReadyAddresses
    1. 1. 案例一:未设置 publishNotReadyAddresses
    2. 2. 案例二:设置了 publishNotReadyAddresses

一、Service 工作原理

1. Service 简介

在kubernetes中,pod 是应用程序的载体,我们可以通过 pod 的 ip 来访问应用程序,但是 pod 的 ip 地址不是固定的,这也就意味着不方便直接采用 pod 的 ip 对服务进行访问。

为了解决这个问题,kubernetes 提供了 Service 资源,Service 会对提供同一个服务的多个 pod 进行聚合,并且提供一个统一的入口地址。通过访问 Service 的入口地址就能访问到后面的pod服务。

Service

如下关系图:

Service原理

Service 在很多情况下只是一个概念,真正起作用的其实是 kube-proxy 服务进程,每个Node节点上都运行着一个 kube-proxy 服务进程。当创建 Service 的时候会通过 api-server 向 etcd 写入创建的 service 的信息,而 kube-proxy 会基于监听的机制发现这种 Service 的变动,然后它会将最新的Service信息转换成对应的访问规则

通过 API 创建/修改 Service 对象时,EndpointsController 的 Informer 机制 Listen 到 Service 对象,然后根据 Service 的配置的选择器创建一个 Endpoints 对象,此对象将 Pod 的 IP、容器端口做记录并存储到 etcd,这样 Service 只要看一下自己名下的 Endpoints 就可以知道所对应 Pod 信息了。

当 Pod 发生变更(如新的 Pod 被调度、现有 Pod 的状态变为非 Running 或者 Pod 数量伸缩)时,API Server 会将这些变化以事件的形式通知给 EndpointsController。EndpointsController 随即根据最新的 Pod 状态和 Service 的 Label Selector 重新计算 Endpoints 对象的端点列表,并更新存储在 etcd 中的 Endpoints 资源对象。这样,Service 所关联的 Endpoints 就会动态地反映出当前所有符合 Selector 的 Running 状态 Pod 的 IP 地址和端口信息

针对 EndpointsController:是负责生成和维护所有 Endpoints 对象的控制器,监听 Service 和对应 Pod 的变化,更新对应 Service 的 Endpoints 对象。当用户创建 Service 后 EndpointsController 会监听 Pod 的状态,当 Pod 处于 Running 且准备就绪时,EndpointsController 会将 Pod IP 记录到 Endpoints 对象中,因此,Service 的容器发现是通过 Endpoints 来实现的。而 kube-proxy 会监听 Service 和 Endpoints 的更新,并调用其代理模块在主机上刷新路由转发规则

另外,k8s 中的 Informer 是一个核心组件,专门用于监控 API 资源的变化,并在资源发生变化时通知相关的客户端。

2. 案例一:通过选择器自动将Sevice与Pod绑定

2.1 部署Deployment

001-deployment-demo-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: deployment-demo
spec:
  selector:
    matchLabels: # 选择所有标签为 app: nginx 的 Pod
      app: nginx
  replicas: 3 # 指定需要维持的Pod副本数量为3
  template:
    metadata: # 配置Pod元数据模板,例如 labels
      labels: # # 为 Pod 打上 app: nginx 标签(与 selector.matchLabels 匹配)
        app: nginx
    spec:
      containers:
        - name: nginx
          image: mirrorgooglecontainers/serve_hostname:v1.4
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9376 # 指定容器的内部端口
              protocol: TCP

serve_hostname 是 k8s 官方提供的 debug 镜像,一个返回 hostname 的 web server。

部署Deployment

kubectl apply -f 001-deployment-demo-01.yaml

查看Pod

# 查看Pod列表
$ kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
deployment-demo-5f8599d578-4zb7l   1/1     Running   0          33s   172.16.58.247   k8s-node02   <none>           <none>
deployment-demo-5f8599d578-nbtj9   1/1     Running   0          33s   172.16.85.250   k8s-node01   <none>           <none>
deployment-demo-5f8599d578-zg8tr   1/1     Running   0          33s   172.16.58.218   k8s-node02   <none>           <none>

# 访问Pod
$ curl 172.16.58.247:9376
deployment-demo-5f8599d578-4zb7l

$ curl 172.16.85.250:9376
deployment-demo-5f8599d578-nbtj9

$ curl 172.16.58.218:9376
deployment-demo-5f8599d578-zg8tr

Deployment 创建了 3 个 Pod 副本,通过 curl 访问,打印了对应的host名称。

2.2 部署 Service

001-service-demo-01.yaml

apiVersion: v1
kind: Service
metadata:
  name: service-demo
spec:
  selector: # 选择具有标签 app: nginx 的 Pod绑定到Service
    app: nginx
  ports:
    - name: nginx80 # 给端口命名,方便后续引用
      protocol: TCP
      port: 80 # Service 对外暴漏的端口
      targetPort: 9376 # 流量转发到后端Pod的端口

部署Service

kubectl apply -f 001-service-demo-01.yaml

查看Service

$ kubectl get svc -o wide -n default
NAME           TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE     SELECTOR
kubernetes     ClusterIP   10.96.0.1     <none>        443/TCP   17m     <none>
service-demo   ClusterIP   10.97.64.44   <none>        80/TCP    7m36s   app=nginx

创建了名为:service-demo 的服务,ip为 10.97.64.44,对外暴露端口:80.
kubernetes 是默认的Service

这样就获得不变的 CLUSTER-IP 10.97.64.44 的 Service

查看Service详情

$ kubectl describe svc service-demo
Name:              service-demo
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.97.64.44
IPs:               10.97.64.44
Port:              nginx80  80/TCP
TargetPort:        9376/TCP
Endpoints:         172.16.58.218:9376,172.16.58.247:9376,172.16.85.250:9376
Session Affinity:  None
Events:            <none>

EndpointsController 自动为Service (service-demo)创建了 Endpoints,内容为:172.16.58.218:9376,172.16.58.247:9376,172.16.85.250:9376, 这正是上面创建Deployment生成的3个 Pod 副本的 ip 端口。

查看Endpoints

$ kubectl get endpoints
NAME           ENDPOINTS                                                  AGE
service-demo   172.16.58.218:9376,172.16.58.247:9376,172.16.85.250:9376   18m

在创建Service的同时,自动创建了与Service同名的Endpoints

访问Service

$ curl 10.97.64.44:80
deployment-demo-5f8599d578-4zb7l

$ curl 10.97.64.44:80
deployment-demo-5f8599d578-zg8tr

$ curl 10.97.64.44:80
deployment-demo-5f8599d578-nbtj9

结论:通过访问Service,自动以负载均衡的方式访问到了Service管理的一组Pod。

3. 案例二:手动创建 Endpoints, 将Service与Pod关联

上面的案例 Service 资源清单中 spec.selector.app=nginx 的设置,自动匹配了满足条件的 Pod,自动创建了同名的 Endpoints,实现负载均衡的网络请求。如果Pod没有自动与Service匹配,应该如何手动创建Endpoints,将Service与Pod关联,实现负载均衡请求?下面的案例将会演示。

清理案例一创建的Service、Deployment

$ kubectl delete -f 001-deployment-demo-01.yaml

$ kubectl delete -f 001-service-demo-01.yaml

3.1 部署 Deployment

001-deployment-demo-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: deployment-demo
spec:
  selector:
    matchLabels: # 选择所有标签为 app: nginx 的 Pod
      app: nginx
  replicas: 3 # 指定需要维持的Pod副本数量为3
  template:
    metadata: # 配置Pod元数据模板,例如 labels
      labels: # # 为 Pod 打上 app: nginx 标签(与 selector.matchLabels 匹配)
        app: nginx
    spec:
      containers:
        - name: nginx
          image: mirrorgooglecontainers/serve_hostname:v1.4
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9376 # 指定容器的内部端口
              protocol: TCP

部署Deployment

kubectl apply -f 001-deployment-demo-01.yaml

3.2 部署 Service

001-service-demo-02.yaml

apiVersion: v1
kind: Service
metadata:
  name: endpoints-demo
spec:
  ports:
    - name: nginx80 # 给端口命名,方便后续引用
      protocol: TCP
      port: 80 # Service 对外暴漏的端口
      targetPort: 9376 # 流量转发到后端Pod的端口

注意:Service 的资源清单中没有定义标签选择器:spec.selector.app: nginx , 也就表明创建的这个 endpoints-demo Service 不会自动关联 Pod。

部署Service

$ kubectl apply -f 001-service-demo-02.yaml

查看Service

$ kubectl get svc -o wide
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
endpoints-demo   ClusterIP   10.98.152.19   <none>        80/TCP    41s   <none>

查看Service详情

$ kubectl describe svc endpoints-demo
Name:              endpoints-demo
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.98.152.19
IPs:               10.98.152.19
Port:              nginx80  80/TCP
TargetPort:        9376/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>

创建了名为:endpoints-demo 的 Service,没有自动创建 Endpoints, 无法通过访问 Service 来负载均衡的访问Pod

访问Service

$ curl 10.98.152.19:80
curl: (7) Failed to connect to 10.98.152.19 port 80: Connection refused

由于 Service 没有关联的 Endpoints,无法通过访问 Service 从而访问Pod

3.3 部署 Endpoints

001-endpoints-demo-02.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: endpoints-demo # endpoints 名称必须与 Service名称完全一致,k8s 才会将 endpoints与service关联起来
subsets: # 定义 Endpoints 的子集,每个子集对应一个后端服务实例
  - addresses: # 列出后端服务实例的 IP 地址列表,通过 kubectl get pods -o wide -l app=nginx 查看
      - ip: 172.16.58.246
      - ip: 172.16.58.216
      - ip: 172.16.85.246
    ports: # 定义后端服务实例监听的端口列表
      - port: 9376
        name: nginx80 # 端口名称,必须要与Service中的port名称一致

部署Endpoints

$ kubectl apply -f 001-endpoints-demo-02.yaml

查看Endpoints

$ kubectl get endpoints
NAME             ENDPOINTS                                                  AGE
endpoints-demo   172.16.58.246:9376,172.16.58.216:9376,172.16.85.246:9376   32s

创建名称为 endpoints-demo 的 endpoints,并管理了 3 个Pod的ip和端口

再次查看Service endpoints-demo 详情

$ kubectl describe svc endpoints-demo
Name:              endpoints-demo
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.98.152.19
IPs:               10.98.152.19
Port:              nginx80  80/TCP
TargetPort:        9376/TCP
Endpoints:         172.16.58.246:9376,172.16.58.216:9376,172.16.85.246:9376
Session Affinity:  None
Events:            <none>

此时Service的 Endpoints 不再为空

访问 Service

$ curl 10.98.152.19:80
deployment-demo-5f8599d578-g6v2g

$ curl 10.98.152.19:80
deployment-demo-5f8599d578-wl4jk

$ curl 10.98.152.19:80
deployment-demo-5f8599d578-zxlfv

结论:通过手动创建Endpoints, 也可以将Service与Pod关联起来,实现了通过Service负载均衡访问Pod。

二、Service 的负载均衡

上面已经提到 Service 实际的路由转发都是由 kube-proxy 组件来实现的,service 仅以一种 VIP(ClusterIP) 的形式存在,kube-proxy 主要实现了集群内部从 Pod 到 Service 和集群外部从 nodePort 到 Service 的访问,kube-proxy 的路由转发规则是通过其后端的代理模块实现的。

kube-proxy 的代理模块目前有四种实现方案,userspace(不常用)、iptables(默认)、ipvs(使用于大规模集群)、kernelspace(适用于 Windows 环境),其发展历程如下所示:

  • kubernetes v1.0:services 仅是一个“4层”代理,代理模块只有 userspace
  • kubernetes v1.1:Ingress API 出现,其代理“7层”服务,并且增加了 iptables 代理模块
  • kubernetes v1.2:iptables 成为默认代理模式
  • kubernetes v1.8:引入 ipvs 代理模块
  • kubernetes v1.9:ipvs 代理模块成为 beta 版本
  • kubernetes v1.11:ipvs 代理模式 GA

在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是 iptables 代理。 在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理。在每种模式下都有自己的负载均衡策略,下文会详解介绍。

1. userspace 模式(不常用)

userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。 该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低。

userspace 模式

2. iptables 模式(默认方式)

iptables 模式

iptables 模式是目前默认的代理方式,基于 netfilter 实现。当客户端请求 Service 的 ClusterIP 时,根据 iptables 规则路由到各 Pod 上,iptables 使用 DNAT 来完成转发,其采用了随机数实现负载均衡。

Netfilter 是 Linux 内核中的一个框架,用于在网络层处理数据包。它提供了一种机制,允许用户空间程序通过一系列的钩子(hooks)来监控和修改网络流量。这些钩子可以插入到数据包的生命周期的各个阶段,例如在进入、离开或经过网络堆栈时。

DNAT(Destination Network Address Translation)模块:是一种网络功能,用于将到达集群内部服务的流量重定向到正确的目标IP地址和端口。DNAT通常与负载均衡器一起使用,以便在多个后端服务之间分发流量。

iptables 模式与 userspace 模式最大的区别在于,iptables 模块使用 DNAT 模块实现了 Service 入口地址到 Pod 实际地址的转换,免去了一次内核态到用户态的切换;另一个与 userspace 代理模式不同的是,如果 iptables 代理最初选择的那个 Pod 没有响应,它不会自动重试其他 Pod 。

iptables 模式最主要的问题是在 Service 数量大的时候会产生太多的 iptables 规则,使用非增量式更新会引入一定的时延,大规模情况下有明显的性能问题。

3. ipvs 模式(适用于大规模集群)

ipvs 模式

当集群规模比较大时,iptables 规则刷新会非常慢,难以支持大规模集群,因其底层路由表的实现是链表,对路由规则的增删改查都要涉及遍历一次链表,ipvs 的问世正是解决此问题的。

ipvs 是 LVS 的负载均衡模块,与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数,但是它却使用哈希表作为底层的数据结构并且工作在内核态,也就是说 ipvs 在重定向流量和同步代理规则有着更好的性能,几乎允许无限的规模扩张

ipvs 支持三种负载均衡模式:DR 模式(Direct Routing)、NAT 模式(Network Address Translation)、Tunneling(也称 ipip 模式)。三种模式中只有 NAT 支持端口映射,所以 ipvs 使用 NAT 模式

linux 内核原生的 ipvs 只支持 DNAT,当在数据包过滤,SNAT 和支持 NodePort 类型的服务这几个场景中 ipvs 还是会使用 iptables。

此外,ipvs 也支持更多的负载均衡算法,例如:

  • rr:round-robin/轮询
  • lc:least connection/最少连接
  • dh:destination hashing/目标哈希
  • sh:source hashing/源哈希
  • sed:shortest expected delay/预计延迟时间最短
  • nq:never queue/从不排队

userspace、iptables、ipvs 三种模式中,默认的策略都是 round-robin

在 Service 中可以通过设置 Service .spec.sessionAffinity 的值实现基于客户端 ip 的会话亲和性,其默认值为”None”,可以设置为 “ClientIP”。此外也可以使用 Service .spec.sessionAffinityConfig.clientIP.timeoutSeconds 设置会话保持时间。

另外,kernelspace 模式 主要是在 windows 下使用的,本文略过。

4. 修改 kube-proxy 模式

kube-proxy 默认为 iptables 模式,修改为 ipvs 模式

# 修改集群 configmap
$ kubectl edit configmap kube-proxy -n kube-system

# 将mode修改为 ipvs
mode: "ipvs"

# 保存后重建 kube-proxy
$ kubectl delete pod -n kube-system -l k8s-app=kube-proxy

# 验证 1、安装 ipvsadm 工具
sudo yum install ipvsadm -y

# 验证 2、 查看dns解析插件pod所属的service的ip
ipvsadm -Ln

三、Service 的类型

service 支持的类型也就是 k8s 中服务暴露的方式,默认有四种 ClusterIP、NodePort、LoadBalancer、ExternelName

  • ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
  • NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 : NodePort 来访问该服务
  • LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到: NodePort
  • ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持

1. ClusterIp 集群内访问

ClusterIP 类型的 Service 是 kubernetes 集群默认的服务暴露方式,它只能用于集群内部通信,可以被各 Pod 访问,其访问方式为:

pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort

ClusterIp

1.1 案例实操1:部署 Service

Service资源清单:002-clusterip-service-01.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-clusterip-service
  namespace: default
spec:
  clusterIP: 10.96.120.10 # 手动指定Service的Ip地址,不指定默认自动生成
  type: ClusterIP # Service类型,默认为 ClusterIp,集群内部访问
  selector:
    app: nginx-pod
    release: stable
    svc: clusterip
  ports:
    - name: http
      port: 80 # Service对外暴露的端口
      targetPort: 80 # 转发到Pod的端口

类型为 ClusterIP 的 Service 有一个 Cluster-IP,其实就一个 VIP。具体实现原理依靠 kube-proxy 组件,通过 iptables 或是 ipvs 实现。

在创建 Service 的请求中,你可以通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。 如果在 Service 中将 .spec.clusterIP 设置为 “None”,则 Kubernetes 不会为其分配 IP 地址。

所配置的 IP 地址必须是合法的 IPv4 或者 IPv6 地址,并且这个 IP 地址在 API 服务器上所配置的 service-cluster-ip-range CIDR 范围内。配置了非法 clusterIP 地址的 Service,API 服务器会返回 HTTP 状态码 422,表示值不合法。

# 查看 service-cluster-ip-range CIDR 范围内

# 方式一
ps -ef | grep kube-apiserver | grep service-cluster-ip-range

# 方式二
$ cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep cluster-ip-range
    - --service-cluster-ip-range=10.96.0.0/12
  • 10.96.0.0/12 就是 Service 的 CIDR 范围(涵盖 10.96.0.110.111.255.254)。

部署 Service

# 部署 Service
$ kubectl apply -f 002-clusterip-service-01.yaml

# 查看 Service 列表
$ kubectl get svc -o wide 
NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
kubernetes                ClusterIP   10.96.0.1      <none>        443/TCP   5h36m   <none>
myapp-clusterip-service   ClusterIP   10.96.120.10   <none>        80/TCP    106s    app=nginx-pod,release=stable,svc=clusterip

1.2 案例实操1:部署 Deployment

Deployment 资源清单:002-clusterip-deployment-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment-demo
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels: # 标签选择器,管理具有包含以下标签的Pod
      app: nginx-pod
      release: stable
      svc: clusterip
  template:
    metadata:
      labels: # Pod模板定义标签,需完全包含 spec.selector.matchLabels 定义的标签
        app: nginx-pod
        release: stable
        svc: clusterip
        env: test
    spec:
      containers:
        - name: myapp-container
          image: wangyanglinux/myapp:v1.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
          readinessProbe: # 定义就绪探测
            httpGet:
              port: 80
              path: /index.html
            initialDelaySeconds: 1
            periodSeconds: 3

注意: deployment 资源清单中定义了 readinessProbe 就绪监测,Service 管理 Pod 的前提是 Pod 启动成功,并且已就绪状态,如果就绪监测不通过,就无法通过 Service 访问 Pod.

部署 Deployment

# 部署 Deployment
$ kubectl apply -f 002-clusterip-deployment-01.yaml

查看资源

# 查看 pod
$ kubectl get pods -o wide -l app=nginx-pod
NAME                                    READY   STATUS    RESTARTS   AGE    IP              NODE         NOMINATED NODE   READINESS GATES
myapp-deployment-demo-d8d46cbc4-4qgs8   1/1     Running   0          118s   172.16.58.245   k8s-node02   <none>           <none>
myapp-deployment-demo-d8d46cbc4-5th77   1/1     Running   0          118s   172.16.85.249   k8s-node01   <none>           <none>
myapp-deployment-demo-d8d46cbc4-cp75p   1/1     Running   0          119s   172.16.58.250   k8s-node02   <none>           <none>


# 查看 service详情(此时Service关联了Endpoints)
$ kubectl describe svc myapp-clusterip-service
Name:              myapp-clusterip-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx-pod,release=stable,svc=clusterip
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.120.10
IPs:               10.96.120.10
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         172.16.58.245:80,172.16.58.250:80,172.16.85.249:80
Session Affinity:  None
Events:            <none>

# 查看ipvs(查看dns解析插件pod所属的service的ip)
$ ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn      
TCP  10.96.120.10:80 rr
  -> 172.16.58.245:80             Masq    1      0          0         
  -> 172.16.58.250:80             Masq    1      0          0         
  -> 172.16.85.249:80             Masq    1      0          0

ipvs自动创建了路由规则,通过 Service 的 IP 和端口(10.96.120.10:80)使用 rr 策略(round-robin/轮询) 访问3个 Pod

测试Service

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-cp75p

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-4qgs8

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77

通过Service实现了轮询访问Pod

1.3 IPVS 持久化连接

ClusterIP 默认情况下会基于 RR 策略轮询访问 Pod,如果需要同一客户端的请求始终转发到同一个 Pod,则需要给Service开启持久化链接。

1.3.1 核心作用

  • 会话保持:将同一客户端的连续请求固定转发到同一个 Pod。
  • 适用场景
    • 需要维护会话状态的应用(如用户登录、购物车)。
    • 依赖本地缓存或内存数据的服务(如 Redis、数据库连接池)。

1.3.2 工作原理

机制 说明
哈希算法 对客户端 源 IP + 源端口 计算哈希值,映射到后端 Pod。
超时控制 默认会话保持 3 小时(可通过 sessionAffinityConfig 调整)。
负载均衡 仅在首次请求时计算哈希,后续请求直接复用结果。

配置示例

spec.sessionAffinity: ClientIP

# 完整资源清单配置 002-clusterip-service-02.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 9376
  sessionAffinity: ClientIP  # 启用会话保持
  # 可选:调整会话超时时间(默认10800秒=3小时)
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600  # 改为1小时

部署 Service

$ kubectl apply -f 002-clusterip-service-02.yaml

测试持久化链接配置

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77

$ curl http://10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77

客户端在指定时间段内每次请求,都会落到同一个Pod上

1.4 internalTrafficPolicy

internalTrafficPolicy 是 Kubernetes Service 的一个关键配置项,用于控制集群内部流量(Pod-to-Service 通信)的路由策略。它主要解决跨节点访问导致的额外网络跳转和延迟问题,尤其在需要优化网络性能或保证本地性时非常有用。

可选值及行为

作用 适用场景
Cluster (默认) 流量随机转发到所有匹配的 Pod(可能跨节点) 通用场景,无需特殊优化
Local 流量仅转发到当前节点上运行的 Pod 需要低延迟、避免跨节点流量

核心功能

  • Local 模式的优势

    • 减少网络跳转:避免流量绕行到其他节点,降低延迟。

    • 保留客户端 IP:真实客户端 IP 可直接传递给 Pod(无需 SNAT)。

    • 适配本地缓存:适合需要利用 Pod 本地缓存的应用(如内存缓存)。

  • Cluster 模式的特点

    • 默认行为:流量均匀分布到所有 Pod(无论是否在相同节点)。
    • 可能增加延迟:如果 Pod 分布在其他节点,需额外网络跳转。

工作原理

  • Local 模式下的流量路由

    • 请求到达 Service:客户端 Pod 访问 Service 的 ClusterIP。

    • kube-proxy 过滤:仅选择当前节点上运行的 Pod 作为后端。

    • 直接转发:流量不离开节点,直接发给本地 Pod。

  • Cluster 模式下的流量路由

    • 请求到达 Service:客户端 Pod 访问 Service 的 ClusterIP。
    • kube-proxy 轮询:选择所有匹配的 Pod(可能跨节点)。
    • 可能跨节点转发:流量可能被发送到其他节点上的 Pod。

配置示例

spec.internalTrafficPolicy: Local

# 完整资源清单配置 002-clusterip-service-03.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-clusterip-service
  namespace: default
spec:
  clusterIP: 10.96.120.10 # 手动指定Service的Ip地址,不指定默认自动生成
  type: ClusterIP # Service类型,默认为 ClusterIp,集群内部访问
  selector:
    app: nginx-pod
    release: stable
    svc: clusterip
  ports:
    - name: http
      port: 80 # Service对外暴露的端口
      targetPort: 80 # 转发到Pod的端口
  internalTrafficPolicy: Local # 流量仅转发到**当前节点**上运行的 Pod, 默认为:Cluster

**验证 internalTrafficPolicy **

# 部署 Service
$ kubectl apply -f 002-clusterip-service-03.yaml

# 查看 Pods(可以看到Node1节点只有一个Pod,Node2节点有两个)
$ kubectl get pods -o wide
NAME                                    READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
myapp-deployment-demo-d8d46cbc4-4qgs8   1/1     Running   0          44m   172.16.58.245   k8s-node02   <none>           <none>
myapp-deployment-demo-d8d46cbc4-5th77   1/1     Running   0          44m   172.16.85.249   k8s-node01   <none>           <none>
myapp-deployment-demo-d8d46cbc4-cp75p   1/1     Running   0          44m   172.16.58.250   k8s-node02   <none>           <none>

# 在Master节点访问Service(连接失败,因为设置了本地访问策略,而master节点没有Pod)
$ curl 10.96.120.10:80/hostname.html
curl: (7) Failed to connect to 10.96.120.10 port 80: Connection refused

# 在node1节点访问Service(每次都只访问到Node1节点的Pod)
$ curl 10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77
$ curl 10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77
$ curl 10.96.120.10:80/hostname.html
myapp-deployment-demo-d8d46cbc4-5th77

2. NodePort 集群外访问

若要在集群外访问集群内部的服务,可以使用这种类型的 Service 。

NodePort结构

NodePort 类型的 Service 会在集群内部署了 kube-proxy 的物理节点,打开一个指定的物理端口,之后所有的流量直接发送到这个端口,然后会被转发到 Service 后端真实的服务进行访问。

Nodeport 构建在 ClusterIP 上,其访问链路如下所示:

client ---> NodeIP(物理节点IP):NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort

2.1 案例实操:部署Service

资源清单:003-nodeport-service-01.yaml

apiVersion: v1
kind: Service
metadata:
  name: nodeport-service-demo
  namespace: default
  labels:
    type: nodeport
spec:
  type: NodePort
  clusterIP: 10.100.1.1 # 手动指定Service的Ip地址,不指定默认自动生成
  selector: # 标签选择器,只管理包含下面标签的Pod
    app: myapp
    release: stabel
    svc: nodeport
  ports:
    - name: http
      port: 80 # service对外暴露的端口
      targetPort: 80 # 转发到容器内部的端口
      nodePort: 30008 # 手动指定NodePort端口,默认范围:30000-32767 ,不指定则默认分配一个端口

部署Service

# 部署Service
$ kubectl apply -f 003-nodeport-service-01.yaml

# 查看 Service 列表
$ kubectl get svc -o wide --show-labels -l type=nodeport
NAME                    TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE    SELECTOR                                LABELS
nodeport-service-demo   NodePort   10.100.1.1   <none>        80:30008/TCP   4m8s   app=myapp,release=stabel,svc=nodeport   type=nodeport

2.2 案例实操:部署Deployment

资源清单:003-nodeport-deployment-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment-demo
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels: # Deployment控制器匹配管理具有下面标签的Pod
      app: myapp
      release: stabel
      svc: nodeport
  template:
    metadata: # 定义Pod模板元数据,Pod具有如下标签
      labels:
        app: myapp
        release: stabel
        svc: nodeport
        env: test
    spec:
      containers:
        - name: myapp-container
          image: wangyanglinux/myapp:v1.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80

部署Deployment

# 部署Deployment
$ kubectl apply -f 003-nodeport-deployment-01.yaml

# 查看Pod
$ kubectl get pods -o wide -l app=myapp -n default
NAME                                     READY   STATUS    RESTARTS   AGE     IP              NODE         NOMINATED NODE   READINESS GATES
myapp-deployment-demo-685dcc6ddf-j2vp5   1/1     Running   0          4m10s   172.16.85.255   k8s-node01   <none>           <none>
myapp-deployment-demo-685dcc6ddf-p4xrf   1/1     Running   0          4m10s   172.16.58.208   k8s-node02   <none>           <none>
myapp-deployment-demo-685dcc6ddf-tmkr9   1/1     Running   0          4m10s   172.16.58.243   k8s-node02   <none>           <none>

测试NodePort

此时 Service 设置的Ip是:10.100.1.1,对外暴露端口:80

# 1.测试通过Service IP端口负载均衡的访问Pod
$ curl 10.100.1.1:80/hostname.html
myapp-deployment-demo-685dcc6ddf-p4xrf

$ curl 10.100.1.1:80/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5

$ curl 10.100.1.1:80/hostname.html
myapp-deployment-demo-685dcc6ddf-tmkr9

# 2.测试通过物理节点IP和设置的NodePort端口,负载均衡的访问Pod
### 2.1 在Master节点测试
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-p4xrf
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-tmkr9

### 2.2 在node节点测试
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-tmkr9
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-p4xrf
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5

此时就可以通过服务器物理节点和物理端口访问Pod了, 注意:手动指定NodePort要避免端口冲突

该端口有一定的范围,比如默认 k8s 控制平面将在 --service-node-port-range 标志指定的范围内分配端口(默认值:30000-32767)。

为 NodePort 服务分配端口的策略既适用于自动分配的情况,也适用于手动分配的场景。当某个用于希望创建一个使用特定端口的 NodePort 服务时,该目标端口可能与另一个已经被分配的端口冲突。为了避免这个问题,用于 NodePort 服务的端口范围被分为两段。动态端口分配默认使用较高的端口段,并且在较高的端口段耗尽时也可以使用较低的端口段。用户可以从较低端口段中分配端口,降低端口冲突的风险。

2.3 externalTrafficPolicy

externalTrafficPolicy 是 Kubernetes Service 的一个关键配置项,用于控制外部流量如何路由到 Pod,特别是对于 LoadBalancerNodePort 类型的服务。它的与前面提到的 internalTrafficPolicy 很相似,只是 internalTrafficPolicy 控制的是内部的流量转发策略。

两种模式:

  • Cluster (默认值)

    externalTrafficPolicy: Cluster

    特点

    • 流量可以路由到集群中任何节点上的 Pod(即使 Pod 不在该节点上)
    • 会做 SNAT(源网络地址转换),客户端 IP 会被隐藏
    • 提供更好的负载均衡,因为流量可以分布到所有节点
    • 可能导致额外的网络跳转(跨节点流量)

    适用场景

    • 需要均匀分布流量时
    • 不关心保留客户端源 IP 时
    • 集群内节点间网络性能良好时
  • Local

    externalTrafficPolicy: Local

    特点

    • 流量只会路由到接收流量的节点上运行的 Pod
    • 保留原始客户端 IP 地址(不做 SNAT)
    • 如果节点上没有 Pod,连接会挂起(不会转发到其他节点)
    • 需要配合 Pod 反亲和性确保 Pod 分布均匀

    适用场景

    • 需要保留客户端源 IP 时
    • 希望避免跨节点流量时
    • 配合节点本地缓存等场景

测试 externalTrafficPolicy

资源清单 003-nodeport-service-02.yaml

apiVersion: v1
kind: Service
metadata:
  name: nodeport-service-demo
  namespace: default
  labels:
    type: nodeport
spec:
  type: NodePort
  clusterIP: 10.100.1.1 # 手动指定Service的Ip地址,不指定默认自动生成
  externalTrafficPolicy: Local # 流量只会路由到接收流量的节点上运行的 Pod
  selector: # 标签选择器,只管理包含下面标签的Pod
    app: myapp
    release: stabel
    svc: nodeport
  ports:
    - name: http
      port: 80 # service对外暴露的端口
      targetPort: 80 # 转发到容器内部的端口
      nodePort: 30008 # 手动指定NodePort端口,默认范围:30000-32767 ,不指定则默认分配一个端口

部署 Service

$ kubectl apply -f 003-nodeport-service-02.yaml

# 查看Pod部署节点
$ kubectl get pods -o wide -l app=myapp
NAME                                     READY   STATUS    RESTARTS   AGE    IP              NODE         NOMINATED NODE   READINESS GATES
myapp-deployment-demo-685dcc6ddf-j2vp5   1/1     Running   0          173m   172.16.85.255   k8s-node01   <none>           <none>
myapp-deployment-demo-685dcc6ddf-p4xrf   1/1     Running   0          173m   172.16.58.208   k8s-node02   <none>           <none>
myapp-deployment-demo-685dcc6ddf-tmkr9   1/1     Running   0          173m   172.16.58.243   k8s-node02   <none>           <none>

# 在node1节点访问Service
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5
$ curl 192.168.6.140:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5

# 在master节点访问Service
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-j2vp5
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-tmkr9
$ curl 192.168.6.139:30008/hostname.html
myapp-deployment-demo-685dcc6ddf-p4xrf

设置 externalTrafficPolicy: Local ,当通过master节点访问service时,依然是负载均衡访问Pod,当通过Pod所在节点访问Service时,请求只会落到请求节点运行的Pod上。

3. LoadBalancer 负载均衡

参考:https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/use-an-existing-slb-instance-to-expose-an-application

LoadBalancer 和 NodePort 很相似,目的都是向外部暴露一个端口,区别在于 LoadBalancer 会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

LoadBalancerLoadBalancer

3.1 LoadBalancer Service 的工作原理

  • LoadBalancer Service 会创建一个外部负载均衡器(由云提供商管理),并为服务分配一个外部 IP 地址。
  • 外部客户端通过该 IP 访问服务,负载均衡器将流量分发到后端的 Pod。
  • 它基于 ClusterIPNodePort,但额外提供外部访问能力。
  • 适用于需要在公网或云环境中暴露服务的场景。

3.2 创建 LoadBalancer Service

资源清单:004-loadBalancer-service-01.yaml

apiVersion: v1
kind: Service
metadata:
  name: loadbalancer-service
  namespace: default
  labels:
    app: my-service
spec:
  type: LoadBalancer # 指定Service类型为 LoadBalancer
  clusterIP: 10.100.1.1 # 手动指定Service的Ip地址,不指定默认自动生成
  selector: # 标签选择器,只管理包含下面标签的Pod
    app: myapp
  ports:
    - name: http
      port: 80 # service对外暴露的端口
      targetPort: 80 # 转发到容器内部的端口
      nodePort: 30010 # 手动指定NodePort端口,默认范围:30000-32767 ,不指定则默认分配一个端口

关键字段说明

  • metadata.name: 服务的名称。
  • spec.selector: 选择后端 Pod 的标签(如 app: my-app)。
  • spec.ports: 定义服务监听的端口。
    • port: 服务暴露的端口(外部访问的端口)。
    • targetPort: 后端 Pod 的容器端口。
  • spec.type: 设置为 LoadBalancer。

部署Service

$ kubectl apply -f 004-loadBalancer-service-01.yaml

3.3 验证 LoadBalancer Service

$ kubectl get svc -o wide -w -l app=my-service
NAME                   TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE   SELECTOR
loadbalancer-service   LoadBalancer   10.100.1.1   <pending>     80:30010/TCP   28s   app=myapp

EXTERNAL-IP 是负载均衡器的外部 IP(可能需要几分钟分配)。显示 ,说明负载均衡器仍在创建。

访问服务

查看负载均衡器详情

在云提供商的控制台(如 AWS、GCP、Azure)中,检查负载均衡器的状态和配置。

3.4 高级配置

3.4 1 指定外部 IP(可选)

某些云提供商允许指定静态 IP:

spec:
  loadBalancerIP: 203.0.113.100

3.4.2 自定义负载均衡器行为

使用 Annotations 配置特定云提供商的功能,例如:

  • AWS ELB 的健康检查:

    metadata:
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /health
  • GCP 启用 HTTPS:

    metadata:
      annotations:
        cloud.google.com/neg: "true"

    查看云提供商的文档以获取支持的 Annotations。

3.4.3 限制源 IP(白名单)

限制访问的源 IP 范围:

spec:
  loadBalancerSourceRanges:
    - "203.0.113.0/24"

3.4.4 多端口服务

支持多个端口:

spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: https
      protocol: TCP
      port: 443
      targetPort: 8443

3.5 使用场景

  • Web 应用:暴露 HTTP/HTTPS 服务。
  • API 网关:提供统一的外部访问入口。
  • 微服务架构:将特定服务暴露给外部客户端。

3.6 注意事项

  1. 云提供商依赖:
    • LoadBalancer Service 依赖云提供商的负载均衡器支持。如果在本地集群(如 Minikube)运行,可能需要使用 MetalLB 等工具模拟。
  2. 成本:
    • 云提供商的负载均衡器通常会产生费用,注意监控使用情况。
  3. 延迟:
    • 负载均衡器创建可能需要几分钟,EXTERNAL-IP 可能暂时显示为
  4. 安全性:
    • 默认情况下,LoadBalancer 暴露到公网,建议配置 loadBalancerSourceRanges 或使用防火墙规则限制访问。
  5. Ingress 替代:
    • 如果需要更复杂的路由规则(基于域名或路径),考虑使用 Ingress 资源结合 Ingress Controller(如 Nginx、Traefik)。

3.7 示例:部署一个简单的应用

资源清单:004-loadBalancer-deployment-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: loadbalancer-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80

验证

  • 部署完成后,获取 EXTERNAL-IP 并访问:

    curl http://<EXTERNAL-IP>

    应该返回 Nginx 的欢迎页面。

4. ExternalName 指定外部访问域名

ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。用于将外部服务(即集群外部的资源)映射到 Kubernetes 集群内部的 DNS 名称,而无需在集群内创建实际的代理或负载均衡器。它主要通过 DNS 解析将请求直接重定向到外部端点。

ExternalName

4.1 主要特点

  1. 无代理转发:与 ClusterIP、NodePort 或 LoadBalancer 类型的服务不同,ExternalName 不创建集群内的代理,而是通过 DNS CNAME 记录直接指向外部域名。
  2. 简化外部服务访问:集群内的 Pod 可以通过 Kubernetes DNS 名称访问外部服务,而无需直接使用外部服务的完整域名。
  3. 无 IP 分配:ExternalName 服务不会分配 ClusterIP 或其他内部 IP,仅依赖 DNS 解析。

4.2 使用场景

  • 访问外部 API 或服务:例如,集群内的应用需要调用外部的数据库、云服务(如 AWS S3)或第三方 API。
  • 服务迁移:在迁移服务时,可以通过 ExternalName 指向外部服务,逐步过渡而无需修改应用代码。
  • 多集群或混合云环境:在跨集群或混合云环境中,通过 ExternalName 统一访问外部资源。

4.3 配置示例

资源清单:005-externalName-Service-01.yaml

kind: Service
apiVersion: v1
metadata:
  name: externalname-service
spec:
  type: ExternalName # 指定Service类型
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  externalName: www.baidu.com
  • metadata.name:集群内用于访问服务的名称,例如 externalname-service.default.svc.cluster.local 。
  • spec.type:指定服务类型为 ExternalName。
  • spec.externalName:外部服务的实际域名,例如 www.baidu.com。

4.4 测试 Pod 访问 External Service

在 Pod 中测试 DNS 解析: 创建一个测试 Pod 并检查 DNS 解析:

Pod资源清单:005-externalName-Depolyment-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels: # Deployment控制器匹配管理具有下面标签的Pod
      app: myapp
  template:
    metadata: # 定义Pod模板元数据,Pod具有如下标签
      labels:
        app: myapp
    spec:
      containers:
        - name: my-busybox
          image: busybox:latest
          imagePullPolicy: IfNotPresent
          # 启动Pod后探测域名:externalname-service.default.svc.cluster.local,探测到域名后停止运行Pod
          args: ['sh', '-c', 'until nslookup externalname-service.default.svc.cluster.local; do echo waiting for externalname-service; sleep 2; done;']

部署Pod

# 部署Pod
$ kubectl apply -f 005-externalName-Depolyment-01.yaml

# 查看Pod
$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
myapp-deployment-demo-78f57f7d7b-c6rkn   1/1     Running   0          10s

# 查看Pod日志
$ kubectl logs myapp-deployment-demo-78f57f7d7b-c6rkn -f
Server:		10.96.0.10
Address:	10.96.0.10:53

externalname-service.default.svc.cluster.local	canonical name = www.baidu.com
www.baidu.com	canonical name = www.a.shifen.com
Name:	www.a.shifen.com
Address: 183.2.172.177
Name:	www.a.shifen.com
Address: 183.2.172.17

externalname-service.default.svc.cluster.local	canonical name = www.baidu.com
www.baidu.com	canonical name = www.a.shifen.com
Name:	www.a.shifen.com
Address: 240e:ff:e020:99b:0:ff:b099:cff1
Name:	www.a.shifen.com
Address: 240e:ff:e020:98c:0:ff:b061:c306

Pod通过 External Service 的域名 externalname-service.default.svc.cluster.local 成功解析到 www.baidu.com 这个外部域名

4.5 工作原理

  1. 当 Pod 访问 externalname-service.default.svc.cluster.local 时,Kubernetes 的 DNS 解析器返回一个 CNAME 记录,指向 www.baidu.com。
  2. 请求直接发送到外部服务,Kubernetes 不进行任何代理或流量转发。

4.6 注意事项

  • 无端口映射:ExternalName 服务不定义端口,因为它仅处理 DNS 级别重定向。如果需要特定端口,需在外部服务端配置。
  • 依赖外部服务可用性:如果外部域名不可用,服务访问会失败,Kubernetes 不会提供额外的容错机制。
  • 不支持复杂路由:ExternalName 不适合需要负载均衡或高级路由的场景,建议使用其他服务类型或 Ingress。

4.7 局限性

  • 不支持在 ExternalName 服务上定义 selector 或 ports,因为它不关联任何 Pod 或代理。
  • 如果需要更复杂的外部服务集成(例如负载均衡或健康检查),可以考虑使用 Endpoints 或 Ingress 资源。

四、Endpoints-Service 的底层模型

在 Kubernetes 中,Endpoint是一个关键的核心对象,它承担着连接Service和后端Pod的重要角色。Endpoint提供了对服务后端的抽象,允许用户在集群中动态地管理服务的网络终端。

1. 什么是 Endpoint?

Endpoint代表了Service后端的一组IP地址和端口号,用于将流量从Service引导到实际运行应用程序的Pod。每个Service都关联着一个对应的Endpoint,这个Endpoint动态地维护了所有Service所选择的Pod的网络终端信息。

简而言之,Endpoint是Service的一种实现,是Service背后真实运行的Pod的地址和端口的集合。通过Endpoint,K8s可以实现服务的动态发现和负载均衡

2. Endpoint 的结构

Endpoint主要由以下几个部分组成:

  • IP地址: 指定Pod的IP地址,用于标识网络上的唯一位置。
  • 端口号: 指定Pod中运行应用程序的端口,用于标识应用程序的通信端口。

一个Endpoint可以包含多个IP地址和端口号的组合,这取决于与Service相关联的Pod的数量。Endpoint的结构使得它能够适应不同Service的需求,实现对多个Pod的动态管理。

3. Endpoint 与 Service 的关系

在K8s中,每个 Service 都有一个相应的 Endpoint 。当 Service 被创建时,K8s会自动创建对应的 Endpoint,并将 Service 选择的 Pod 的 IP 地址和端口号添加到Endpoint 中。这种关系保证了 Service 与 Pod 之间的正确通信。

在 Service 和 Endpoint 之间的关系中,S ervice 充当了一种抽象,为应用程序提供了一个稳定的入口点,而 Endpoint 则提供了 Service 后端的真实网络终端。这种分离使得用户能够更加灵活地管理和维护后端 Pod 的变化,而不需要改变 Service 的定义。

Endpoint 与 Service 和 Pod 间的关联

4. Endpoint 的使用

4.1 自动创建 Endpoints

见本文第一章节:Service 工作原理 –> 案例一:通过选择器自动将 Service与 Pod 绑定

4.2 手动创建 Endpoints

见本文第一章节:Service 工作原理 –> 案例二:手动创建 Endpoints, 将 Service 与 Pod 关联

5. 动态管理Endpoint

在K8s中,Endpoint的管理是动态的。当Service的相关Pod发生变化时,Endpoint会相应地更新。例如,当我们扩展了前端Pod的数量时,Endpoint会自动添加新的IP地址和端口号。

资源清单:006-endpoints-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: default
  name: deployment-demo
spec:
  selector:
    matchLabels: # 选择所有标签为 app: nginx 的 Pod
      app: nginx
  replicas: 3 # 指定需要维持的Pod副本数量为3
  template:
    metadata: # 配置Pod元数据模板,例如 labels
      labels: # # 为 Pod 打上 app: nginx 标签(与 selector.matchLabels 匹配)
        app: nginx
    spec:
      containers:
        - name: nginx
          image: mirrorgooglecontainers/serve_hostname:v1.4
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9376 # 指定容器的内部端口
              protocol: TCP

---

apiVersion: v1
kind: Service
metadata:
  name: service-demo
spec:
  selector: # 选择具有标签 app: nginx 的 Pod绑定到Service
    app: nginx
  ports:
    - name: nginx80 # 给端口命名,方便后续引用
      protocol: TCP
      port: 80 # Service 对外暴漏的端口
      targetPort: 9376 # 流量转发到后端Pod的端口

部署资源清单

$ kubectl apply -f 006-endpoints-01.yaml

查看 Service、Pod、Endpoints

# 查看Service
$ kubectl get svc
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service-demo   ClusterIP   10.108.117.152   <none>        80/TCP    7s

# 查看Pod
$ kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
deployment-demo-5f8599d578-2llvx   1/1     Running   0          92s   172.16.58.222   k8s-node02   <none>           <none>
deployment-demo-5f8599d578-4wvhn   1/1     Running   0          92s   172.16.85.252   k8s-node01   <none>           <none>
deployment-demo-5f8599d578-b72j2   1/1     Running   0          93s   172.16.58.196   k8s-node02   <none>           <none>

# 查看Endpoints
$ kubectl get endpoints
NAME           ENDPOINTS                                                  AGE
service-demo   172.16.58.196:9376,172.16.58.222:9376,172.16.85.252:9376   107s

扩展Pod数量实例

# Pod数量扩展到5个
$ kubectl scale deployment deployment-demo --replicas=5

# 查看Endpoints(管理的Pod也扩展到5个)
$ kubectl describe endpoints service-demo
Name:         service-demo
Namespace:    default
Labels:       <none>
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2025-04-29T11:59:57Z
Subsets:
  Addresses:          172.16.58.196,172.16.58.222,172.16.58.233,172.16.85.251,172.16.85.252
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    nginx80  9376  TCP

Events:  <none>

扩展了 Pod 的数量,Endpoint 相应地添加了新的IP地址和端口号,确保了与 Service 相关联的所有 Pod 都能够被正确地服务。

6. Endpoint 增删改查

6.1 创建 Endpoint

6.1.1 自动创建 Endpoint

在 Kubernetes 中,Endpoint 的定义会随着 Service 的创建自动生成与 Service 同名的 Endpoints

apiVersion: v1
kind: Service
metadata:
  name: service-demo
spec:
  selector: # 选择具有标签 app: nginx 的 Pod绑定到Service
    app: nginx
  ports:
    - name: nginx80 # 给端口命名,方便后续引用
      protocol: TCP
      port: 80 # Service 对外暴漏的端口
      targetPort: 9376 # 流量转发到后端Pod的端口

6.1.2 手动创建 Endpoint

apiVersion: v1
kind: Endpoints
metadata:
  name: endpoints-demo # endpoints 名称必须与 Service名称完全一致,k8s 才会将 endpoints与service关联起来
subsets: # 定义 Endpoints 的子集,每个子集对应一个后端服务实例
  - addresses: # 列出后端服务实例的 IP 地址列表,通过 kubectl get pods -o wide -l app=nginx 查看
      - ip: 172.16.58.246
      - ip: 172.16.58.216
      - ip: 172.16.85.246
    ports: # 定义后端服务实例监听的端口列表
      - port: 9376
        name: nginx80 # 端口名称,必须要与Service中的port名称一致

6.2 Endpoint 更新

在 Kubernetes 中,当 Endpoint 中的某些 Pod 不可用时,Kubernetes 会自动地从 Endpoint 中删除这些 Pod,并将 Endpoint 更新为不可用状态。新的 Endpoint 会在缺失 Pod 的同一端口上生成一个新的地址,并将服务转发到可用的 Pod。

为了避免 Endpoint 的数据丢失,Kubernetes 具有自动和手动两种更新 Endpoint 的方式。

6.2.1 自动更新 Endpoint

当 Pod 恢复正常时,Kubernetes 会自动更新 Endpoint 并将该 Pod 重新添加到 Endpoint 中,从而使该 Service 再次可用。这种自动更新是 Kubernetes Endpoint 更改监控机制的一部分。

6.2.2 手动更新 Endpoint

在某些情况下,特别是在进行操作系统多次启动后,Kubernetes 可能无法监视 Endpoint 中的所有更改。此时,可以手动更新 Endpoint。

我们可以使用以下命令手动更新 Endpoint:

$ kubectl get endpoints -n <名称空间> <endpoint名称> -o yaml | kubectl apply -f -
# 例如
$ kubectl get endpoints -n default service-demo -o yaml | kubectl apply -f -

执行上面的命令,Kubernetes 将自动更新 Endpoint 并重新加载流量。

6.3 Endpoint 的删除

Endpoint 可以通过以下命令进行删除:

$ kubectl delete endpoints <endpoint名称> -n <名称空间>

执行上述命令后,Endpoint 会被删除,但 Service 仍然存在。如果需要同时删除 Service,请执行以下命令:

$ kubectl delete service <Service名称> -n <名称空间>

7. Endpoint controlor

endpoint controlor 是 k8s 集群的其中一个组件,主要是维护 endpoint,保证 endpoint 内 ip:port 能够提供正常服务,其功能如下:

  • endpoint controller 建立 service 与 pod 的 list-watch
  • 轮询 service 队列,查询属于对应 service 的 pods
  • 判断 pods 状态以及 port 情况,生成 endpoint 对象
    • 过滤掉状态为 not ready 的 pods
    • 过滤掉不满足 service.spec.targetPort 的 pods
    • 获取 pods 的 ip 以及 service.spec.ports ,生成 endpoint 对象
  • 比较该 endpoint 对象与当前 endpoint ,若当前 endpoint 不存在,则发送给 apiserver 创建请求,若与当前 endpoint 不一致,则发送给 apiserver 更新请求

8. Endpoint参数解释

apiVersion: v1
kind: Endpoint
metadata:  # 对象元数据
  name:
  namespace:
subsets:      # 端点对象的列表
- addresses:  # 处于“就绪”状态的端点地址对象列表
  - hostname  <string>  # 端点主机名
    ip <string>          # 端点的IP地址,必选字段
    nodeName <string>   # 节点主机名
    targetRef:              # 提供了该端点的对象引用
      apiVersion <string>  # 被引用对象所属的API群组及版本
      kind <string>  # 被引用对象的资源类型,多为Pod
      name <string>  # 对象名称
      namespace <string>  # 对象所属的名称空间
      fieldPath <string>  # 被引用的对象的字段,在未引用整个对象时使用,常用于仅引用
# 指定Pod对象中的单容器,例如spec.containers[1]
      uid <string>     # 对象的标识符;
  notReadyAddresses:  # 处于“未就绪”状态的端点地址对象列表,格式与address相同
  ports:                # 端口对象列表
  - name <string>  # 端口名称;
    port <integer>  # 端口号,必选字段;
    protocol <string>     # 协议类型,仅支持UDP、TCP和SCTP,默认为TCP;
    appProtocol <string>  # 应用层协议;
  • endpoint.metadata.name : 代表着 service name
  • endpoint.subsets[].addresses[].ip 代表 状态为 ready pod 的ip
  • endpoint.subsets[].targetRef: 代表来自该 ip 的 pod 详情
  • endpoint. subsets[].ports[]: 数据来源于 service.spec.ports ,当 pods 内的 port 并不满足 targetPort ,则会在 endpoint.subsets[].addresses[] 中剔除该pod ip
  • endpoint. subsets[].notReadyAddresses :未就绪 pod 列表

五、Service 设置 publishNotReadyAddresses

在 Kubernetes 中,默认情况下 Service 只将通过了就绪探针的 Pod 的 IP 和端口添加到其 Endpoints 对象中。这确保客户端只访问到健康且可用的 Pod。如果需要将未通过就绪探测的 Pod 也添加到 Endpoints 对象中,可通过 Service 访问。那么就需要设置 Service 的 publishNotReadyAddresses 属性。

例如如下情况,Pod未能通过就绪探测,无法通过 Service 访问 Pod

1. 案例一:未设置 publishNotReadyAddresses

资源清单: 007-publishNotReadyAddresses-01.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels: # 标签选择器,管理具有包含以下标签的Pod
      app: nginx-pod
      release: stable
      svc: clusterip
  template:
    metadata:
      labels: # Pod模板定义标签,需完全包含 spec.selector.matchLabels 定义的标签
        app: nginx-pod
        release: stable
        svc: clusterip
        env: test
    spec:
      containers:
        - name: myapp-container
          image: wangyanglinux/myapp:v1.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
          readinessProbe: # 定义就绪探测
            httpGet:
              port: 80
              path: /index1.html
            initialDelaySeconds: 1
            periodSeconds: 3
            
---

apiVersion: v1
kind: Service
metadata:
  name: myapp-clusterip-service
  namespace: default
spec:
  clusterIP: 10.96.120.10 # 手动指定Service的Ip地址,不指定默认自动生成
  type: ClusterIP # Service类型,默认为 ClusterIp,集群内部访问
  selector:
    app: nginx-pod
    release: stable
    svc: clusterip
  ports:
    - name: http
      port: 80 # Service对外暴露的端口
      targetPort: 80 # 转发到Pod的端口

部署后查看Pod、Service、Endpoints

# 部署资源
$ kubectl apply -f 007-publishNotReadyAddresses-01.yaml

# 查看 Pod
$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
myapp-deployment-demo-54c7c7df5f-z7j57   0/1     Running   0          9s

# 查看 Service
$ kubectl get svc myapp-clusterip-service
NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
myapp-clusterip-service   ClusterIP   10.96.120.10   <none>        80/TCP    2m27s

# 查看 Endpoints
$ kubectl get Endpoints
NAME                      ENDPOINTS            AGE
kubernetes                192.168.6.139:6443   30h
myapp-clusterip-service                        2m49s

# 访问 Service
$ curl 10.96.120.10
curl: (7) Failed to connect to 10.96.120.10 port 80: Connection refused

由于部署的Deployment 没有 /index1.html 这个资源,导致 Pod 无法通过就绪探测。因此 Pod 不能添加到 Endpoints 中,Service无法访问 Pod.

2. 案例二:设置了 publishNotReadyAddresses

为了让未通过就绪探测的 Pod 也能添加 Endpoints中,暴露给 Service 访问,可以通过设置 publishNotReadyAddresses 实现。

方式一:通过命令行动态设置

$ kubectl patch service myapp-clusterip-service -p '{"spec":{"publishNotReadyAddresses": true}}'

方式二:配置资源清单文件

007-publishNotReadyAddresses-02.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment-demo
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels: # 标签选择器,管理具有包含以下标签的Pod
      app: nginx-pod
      release: stable
      svc: clusterip
  template:
    metadata:
      labels: # Pod模板定义标签,需完全包含 spec.selector.matchLabels 定义的标签
        app: nginx-pod
        release: stable
        svc: clusterip
        env: test
    spec:
      containers:
        - name: myapp-container
          image: wangyanglinux/myapp:v1.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
          readinessProbe: # 定义就绪探测
            httpGet:
              port: 80
              path: /index1.html
            initialDelaySeconds: 1
            periodSeconds: 3

---

apiVersion: v1
kind: Service
metadata:
  name: myapp-clusterip-service
  namespace: default
spec:
  clusterIP: 10.96.120.10 # 手动指定Service的Ip地址,不指定默认自动生成
  type: ClusterIP # Service类型,默认为 ClusterIp,集群内部访问
  selector:
    app: nginx-pod
    release: stable
    svc: clusterip
  ports:
    - name: http
      port: 80 # Service对外暴露的端口
      targetPort: 80 # 转发到Pod的端口
  publishNotReadyAddresses: true # 允许未就绪的 Pod 添加到 Endpoints 中

添加了 publishNotReadyAddresses: true , 允许未就绪的 Pod 添加到 Endpoints 中

重新部署资源清单

$ kubectl apply -f 007-publishNotReadyAddresses-02.yaml

# 查看 Endpoints 
$ kubectl get endpoints 
NAME                      ENDPOINTS            AGE
myapp-clusterip-service   172.16.58.211:80     18s

# 测试 Service 访问
$ curl 172.16.58.211:80
www.xinxianghf.com | hello MyAPP | version v1.0

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com