Skip to content

1.Service基本概念

1.1 什么是Service

在Kubernetes中,pod是应用程序的载体,当我们需要访问这个应用时,可以通过Pod的IP进行访问,但是这里有两个问题:

  1. Pod的IP地址不固定-旦Pod异常退出、节点故障,则会造成Pod发生重建,一旦发生重建客户端则会访问失败;
  2. Pod如果扩展多份,会造成客户端无法有效使用新增Pod,如果Pod进行缩容又会造成客户端访问错误;

image-20240516144050288

为了解决这个问题,k8s提供了service资源,Service为动态的一组Pod提供一个固定的访问入口;service资源基于标签选择器把筛选出的一组Pod对象定义成一个逻辑组合,而后Service对外提供自己的IP和端口。

当客户端请求Service的IP和端口时,Service将请求调度给标签所匹配的所有Pod,Service向客户端隐藏了真实处理请求的Pod资源,使得客户端的请求看上去是由Service直接处理并进行响应。

image-20240516145426118

Service对象的IP地址(可称为ClusterIP或ServiceIP)是虚拟IP地址,由Kubernetes系统在Service对象创建时在专有网络(ServiceNetwork)地址中自动分配或由用户手动指定。其次Service是基于端口过滤,并根据事先定义好的规则将请求转发至其后端Pod对应的端口上,因此这种代理机制也称为"端口代理"或"四层代理",工作在TCP/IP协议栈的传输层;

Service的作⽤:

暴露流量:让用户可以通过ServiceIP+ServicePort访问对应后端的Pod应用;

负载均衡:提供基于4层的TCP/IP负载均衡,并不提供HTTP/HTTPS等负载均衡

服务发现:当发现新增Pod则自动加入至Service的后端,如发现Pod异常则自动剔除Service后端;

1.2 Service⼯作逻辑

Service持续监视APIServer,监视Service标签选择器所匹配的后端Pod,并实时跟踪这些Pod对象的变动情况,例如IP地址发生变化、或Pod对象新增与减少。

不过Service并不直接与Pod建立关联关系,它们之间还有一个中间层Endpoints,Endpoints对象是一个由IP地址和端口组成的列表,这些IP地址和端口则来自于Service标签选择器所匹配到的Pod,默认情况下,创建Service资源时,其关联的Endpoints对象会被自动创建。

image-20240516152847044

1.3 Service具体实现

在Kubernetes中,Service只是抽象的一个概念,真正起作用实现负载均衡规则的其实是Kube-Proxy这个进程。它在每个节点上都需要运行一个Kube-Proxy,用来完成负载均衡规则的创建。

1、创建Service资源后,会分配一个随机的ServiceIP,返回给用户,然后写入etcd;

2、ndpoints controller负责生成和维护所有endpoints,它会监听Service和pod的状态,当pod 处于running 且准备就绪时,endpoints controller会将 pod ip 更新到对应Service的 endpoints 对象中,然后写⼊Etcd;

3、be-proxy通过API-Server监听Service、Endpoints的资源变动,一旦Service或Endpoints资源发生变化,Kube-Proxy会将最新的信息转换为对应的IptabLes、IPVS访问规则,而后在本地主机上执行。

4、客户端想要访问Service的时候,其实访问的就是本地节点上的iptabLes、IPVS规则,由它们路由到对应节点;

image-20240516153355138

实现图上的功能,主要需要以下⼏个组件协同⼯作:

1、Service:用户通过kubectl命令向apiServer发送创建Service的请求,APIServer收到后存入Etcd;

2、Endpoints:获取Service所匹配的Pod地址,而后将信息写入与Service同名的endpoints资源中;

3、Kube-Proxy:获取Service和Endpoints资源的变动,而后生成IptabLeS、IPVS规则,在本机执行;

4、ptabLes:当用户请求serviceIP时,使用iptabLes的DNAT技术将ServiceIP的请求调度至endpoint保存ip列表;

2.Kube-Proxy代理模型

2.1 userSpace

userspace模式下,kube-proxy为ServiceIP创建⼀个监听端⼝,当⽤户向ServiceIP发送请求,

1、首先请求会被IptabLes规则拦截,然后重定向到Kube-Proxy对应的端口;

2、然后Kube-Proxy根据调度算法选择挑选一个Pod,将请求调度到该Pod上;

总结:Pod请求ServiceIP时,会被Iptables将请求拦截给⽤户空间的Kube-Proxy,然后再经过内核空间路由到对应的Pod;

image-20240516170001208

问题:该模式流量经过内核空间后,,会送往用户空间Kube-Proxy进程而后又送回内核空间,发往调度分配的目标后端Pod;

2.2 iptables

iptables模式下,kube-proxy为Service后端的所有Pod创建对应的iptabLes规则,当用户向ServiceIP发送请求;

1、首先Iptables会拦截用户请求

2、然后直接将请求调度到后端的Pod;

总结:Pod请求ServiceIP时,IptabLes将请求拦截并且直接完成调度,然后路由到对应的Pod,所以效率比userspace高;

image-20240516170212518

问题:⼀个Service会创建出⼤量的规则,且不⽀持更⾼级的调度算法,当Pod不可⽤也⽆法重试;

2.3 IPVS

ipvs模式和iptables类似,kube-proxy为Service后端所有的Pod创建对应的IPVS规则,一个Service只会生成一条规则,所以规模较大的场景下,应该使用IPVS模式。其次IPVS更多更高级的调度算法。

image-20240516170352058

3.Service资源类型

无论使用那一种代理模型,Service资源都可以其工作逻辑分为ClusterIP,NodePort,LoadBalance、ExternaLName、HostPort这5种类型

3.0 HostPort

在 Kubernetes 中,hostPort 是一种用于将主机上的特定端口映射到运行在 Pod 内部容器的端口的配置选项。通过使用 hostPort,你可以在主机上暴露容器的服务,从而允许外部网络通过主机的 IP 地址和指定的端口访问容器内的应用程序,不推荐使用

img

❌ 注意

hostPort 与 NodePort 的区别是,NodePort 服务默认是把请求转发到随机的一个运行的 Pod 上,而 hostPort 是直接转发到本 Node 上的指定 Pod

一个 Node 只能启动一个 hostPort

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-v1-deployment
spec:
  replicas: 2 
  selector:
    matchLabels:
      app: tomcat-v1
  template:
    metadata:
      labels:
        app: tomcat-v1
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080
              hostPort: 9000
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-v1-deployment
spec:
  replicas: 2 
  selector:
    matchLabels:
      app: tomcat-v1
  template:
    metadata:
      labels:
        app: tomcat-v1
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080
              hostPort: 9000

3.1 ClusterIP

ClusterIP:通过集群的内部 IP 暴露服务,选择ServiceIP只能够在集群内部访问。 这也是默认的 ServiceType。

image-20240516174101713

3.2 NodePort

NodePort:NodePort类型是对ClusterIP类型Service资源的扩展。它通过每个节点上的IP和端⼝接⼊集群外部流量,并分发给后端的Pod处理和响应。因此通过<节点IP>:<节点端⼝>,可以从集群外部访问服务。

image-20240516174258879

3.3 LoadBalance

LoadBalancer:这类Service依赖云厂商,需要通过云厂商调用API接口创建软件负载均衡将服务暴露到集群外部。当创建LoadBalance类型的Service对象时,它会在集群上自动创建一个NodePort类型的Service。集群外部的请求流量会先路由至该负载均衡,并由该负载均衡调度至各个节点的NodePort。

image-20240516174454855

3.4 ExternalName

ExternalName:此类型不是用来定义如何访问集群内服务的,而是把集群外部的某些服务以DNS CANME方式映射到集群内,从而让集群内的Pod资源能够访问外部服务的一种实现方式。

image-20240516174823306

4.Service应⽤实践

4.1 Service资源规范

yaml
apiVersion: v1       # API的版本
kind: Service        # 资源类型定义为Service
metadata:           
  name: ...          # Serivce的名称
  namespace: ...     # 默认的default
  labels:
     key1: value1   # 标签 key:value格式;
     key2: value2
spec:
  type <string>      # Service类型,默认为ClusterIP;
  selector <map[string]string> #  标签选择器
  ports:                       #  ClusterIP:ServicePort
  targetPort: <string>  #后端目标进程的端口号或名称。
  nodePort: <integer>  # 节点端口号,仅适用于NodePort和loadbalancer类型。  "建议动态选择30000-32767" 
 clusterIP <string> # Service的集群IP,建议由系统自动分配
 externalTrafficPolicy <string> # 外部流量策略处理方式,local表示由当前节点处理,cluster表示向集群范围调度
 loadBalancerIP <string> # 外部负载均衡器使用的IP地址,仅适用于loadbalancer,前提是你的公有云得支持你自己指定;
 externalName <string> # 外部服务名称,该名称作为Service的DNS CNAME值
apiVersion: v1       # API的版本
kind: Service        # 资源类型定义为Service
metadata:           
  name: ...          # Serivce的名称
  namespace: ...     # 默认的default
  labels:
     key1: value1   # 标签 key:value格式;
     key2: value2
spec:
  type <string>      # Service类型,默认为ClusterIP;
  selector <map[string]string> #  标签选择器
  ports:                       #  ClusterIP:ServicePort
  targetPort: <string>  #后端目标进程的端口号或名称。
  nodePort: <integer>  # 节点端口号,仅适用于NodePort和loadbalancer类型。  "建议动态选择30000-32767" 
 clusterIP <string> # Service的集群IP,建议由系统自动分配
 externalTrafficPolicy <string> # 外部流量策略处理方式,local表示由当前节点处理,cluster表示向集群范围调度
 loadBalancerIP <string> # 外部负载均衡器使用的IP地址,仅适用于loadbalancer,前提是你的公有云得支持你自己指定;
 externalName <string> # 外部服务名称,该名称作为Service的DNS CNAME值

4.2 ClusterIP示例

ClusterIP: 通过集群内部IP暴露服务,选择ServiceIP只能够在集群内部访问,这也是默认的Service类型;该地址仅在集群内部可见、可达。无法被集群外部客户端访问;而且是默认类型,创建的任何Service默认就是ClusterIP类型,而且只能接受集群内部客户端的访问

yaml
cat services-clusterip-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-clusterip
  namespace: default
  labels:
     app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: default
spec:
  clusterIP:
  selector:     # 标签选择器 
    app: nginx   
  ports:
  - name: http  # 端口名称
    protocol: TCP   # 协议类型,目前支持TCP、UDP、SCTP默认为TCP
    port: 80   # Service的端口号
    targetPort: 80  # 后端目标进程的端口号

#
kubectl apply -f services-clusterip-nginx.yaml 
pod/nginx-clusterip created
service/nginx-svc created
cat services-clusterip-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-clusterip
  namespace: default
  labels:
     app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: default
spec:
  clusterIP:
  selector:     # 标签选择器 
    app: nginx   
  ports:
  - name: http  # 端口名称
    protocol: TCP   # 协议类型,目前支持TCP、UDP、SCTP默认为TCP
    port: 80   # Service的端口号
    targetPort: 80  # 后端目标进程的端口号

#
kubectl apply -f services-clusterip-nginx.yaml 
pod/nginx-clusterip created
service/nginx-svc created

4.3 NodePort示例

NodePort即是节点Port,通常在安装集群系统会预留一个端口范围用于NodePort,默认是3000-32767之间,与ClusterIP类型的可省略.spec.type属性所不同的是,定义NodePort类似的Service资源时,需要通过此属性明确指定其类型名称

  • NodePort:在每个节点上启用一个端口来暴露服务,可以在集群
    • 外部访问。也会分配一个稳定内部集群IP地址。
    • 访问地址:<任意NodeIP>:
    • 端口范围:30000-32767
yaml
cat services-nodeport-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport-svc
  namespace: default
spec:
  type: NodePort
  clusterIP:
  selector:
    app: nginx  # 指定关联Pod的标签
  ports:
  - name: http
    protocol: TCP
    port: 80		# Service端口
    targetPort: 80  # 容器端口 # 后端Pod监听什么端口就写什么端口。要不然到达Service的请求转发给Pod,Pod没有那个端口也没用。一定真正转发到后端程序监听的端口。如果没有特殊情况的话,ServicePort和TargetPort保持一致。NodePort可以不用指定。
    nodePort:        # 正常情况下应由系统自己分配,除非事先能够明确知道它不会与某个现存的Service资源产生冲突,没有特别需求,留给系统自动配置总是好的选择。


#kubectl apply -f services-nodeport-nginx.yaml 
service/nginx-nodeport-svc created
cat services-nodeport-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport-svc
  namespace: default
spec:
  type: NodePort
  clusterIP:
  selector:
    app: nginx  # 指定关联Pod的标签
  ports:
  - name: http
    protocol: TCP
    port: 80		# Service端口
    targetPort: 80  # 容器端口 # 后端Pod监听什么端口就写什么端口。要不然到达Service的请求转发给Pod,Pod没有那个端口也没用。一定真正转发到后端程序监听的端口。如果没有特殊情况的话,ServicePort和TargetPort保持一致。NodePort可以不用指定。
    nodePort:        # 正常情况下应由系统自己分配,除非事先能够明确知道它不会与某个现存的Service资源产生冲突,没有特别需求,留给系统自动配置总是好的选择。


#kubectl apply -f services-nodeport-nginx.yaml 
service/nginx-nodeport-svc created

示例图片:

image

完整案例

1.创建yaml文件

vim web.yaml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: default
  annotations:       # 记录回滚参数
    kubernetes.io/change-cause: "web.v1-nginx-1.16"   #记录到revision中的内容,记录版本号
spec:
  replicas: 3 # Pod副本预期数量
  revisionHistoryLimit: 10 # RS历史版本保存数量
  selector:
    matchLabels:
      app: web
  strategy:
    rollingUpdate:
      maxSurge: 25%             # 滚动更新过程最大pod副本数
      maxUnavailable: 25%       # 滚动更新过程中最大不可用pod副本数,
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: web # Pod副本的标签
    spec:
      containers:
      - name: web
        image: nginx:1.16
        readinessProbe:          # 存活检查,如果失败,将杀死容器,来重启
          httpGet:
            port: 80
            path: /index.html
          initialDelaySeconds: 10 #启动容器后多少秒健康检查
          periodSeconds: 10 #以后间隔多少秒检查一次

        livenessProbe:   # 就绪检查,失败就会剔除 service 
          httpGet:
            port: 80
            path: /index.html
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: default
  annotations:       # 记录回滚参数
    kubernetes.io/change-cause: "web.v1-nginx-1.16"   #记录到revision中的内容,记录版本号
spec:
  replicas: 3 # Pod副本预期数量
  revisionHistoryLimit: 10 # RS历史版本保存数量
  selector:
    matchLabels:
      app: web
  strategy:
    rollingUpdate:
      maxSurge: 25%             # 滚动更新过程最大pod副本数
      maxUnavailable: 25%       # 滚动更新过程中最大不可用pod副本数,
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: web # Pod副本的标签
    spec:
      containers:
      - name: web
        image: nginx:1.16
        readinessProbe:          # 存活检查,如果失败,将杀死容器,来重启
          httpGet:
            port: 80
            path: /index.html
          initialDelaySeconds: 10 #启动容器后多少秒健康检查
          periodSeconds: 10 #以后间隔多少秒检查一次

        livenessProbe:   # 就绪检查,失败就会剔除 service 
          httpGet:
            port: 80
            path: /index.html

2.创建servcie文件

cat web-NodePort.yaml

yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web
spec:
  type: NodePort # 服务类型
  ports:
  - port: 80 # Service端口
    protocol: TCP # 协议
    targetPort: 80 # 容器端口
    nodePort: 30009  # 对外暴露的端口,可以指定
  selector:
    app: web # 指定关联Pod的标签
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web
spec:
  type: NodePort # 服务类型
  ports:
  - port: 80 # Service端口
    protocol: TCP # 协议
    targetPort: 80 # 容器端口
    nodePort: 30009  # 对外暴露的端口,可以指定
  selector:
    app: web # 指定关联Pod的标签

4.4 LoadBalancer示例

LoadBalancer: 这类Service依赖云厂商,需要通过云厂商调用API接口创建软件负载均衡将服务暴露到集群外部,当创建LoadBalancer类型的Service对象时,它会在集群上自动创建一个NodePort类型的Service,集群外部的请求流量会先路由至该负载均衡,并由该负载均衡调度至各个节点的NodePort;

yaml
cat  services-loadbalancer-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer-svc
  namespace: default
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  loadBalancerIP: 1.2.3.4
cat  services-loadbalancer-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer-svc
  namespace: default
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  loadBalancerIP: 1.2.3.4

5.Service与Endpoint

5.0 什么是Endpoint

  1. 我们创建Service的时候会自动给我们创建一个同名的Endpoint资源,每一个同名的 Servie都有一个Endpoints资源,因为Service自己并不直接匹配后端Pod的标签,而是由Endpoint匹配的。这个匹配过程是由Endpoint控制器来完成的。Endpoint是由Endpoint控制器来控制的;

  2. 事实上我们Service不但能够把标签选择器选中的Pod识别为自己的后端端点。还能够对后端端点做"就绪状态检测"。如果后端的Pod是就绪的,就把它加到后端可用端点列表中来。否则就会移除掉。这个功能其实不是Service来做的,而是Service借助一个中间的组件。这个中间组件也是一个"标准的资源类型"。就叫做"Endpoint";

  3. Service通过Selector和Pod建立关联,K8s会根据Service关联到的PodIP信息组合成一个Endpoint,若Service定义中没有Selector字段,Service被创建时,Endpoint Controller不会自动创建Endpoint;

  4. 我们可以通过配置清单创建Service,而无需使用标签选择器,而后自行创建一个同名的Endpoint对象,指定对应的IP,这种一般用于将外部Mysql\Redis等应用引入kubernetes集群内部,让内部通过Service的方式访问外部资源;

    官方文档: https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/service-resources/endpoints-v1/#Endpoints

5.1 Endpoint与容器探针

Service对象借助Endpoint资源来跟踪其关联的后端端点,Endpoint对象会根据Service标签选择器筛选出的后端端点的IP地址分别保存在subsets.address字段和subsets.notReadyAddress字段中,它通过APIServer持续、动态跟踪每个端点的状态变化,并及时反应到端点IP所属的字段中。

  • subsets.address:保存就绪的容器IP,也就意味着service可以直接将请求调度至该地址段。
  • subsets.notReadyAddress:保存未就绪容器IP,也就意味着Service不会将请求调度至该地址段。

案例

1.创建⼀个资源清单,会⾃动创建出同名的Endpoints对象

vi demoapp-readiness.yaml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-readiness
  template:
    metadata:
      labels:
        app: web-readiness
    spec:
      containers:
      - name: demoapp2
        image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
        ports:
        - containerPort: 80
        readinessProbe:			# 就绪探针
          httpGet:
            path: '/readyz'
            port: 80
          initialDelaySeconds: 15	# 初次检测延时时⻓
          periodSeconds: 10			# 检测周期

---
apiVersion: v1
kind: Service
metadata:
  name: demoapp-readiness-service
spec:
  selector:
    app: web-readiness
  ports:
    - protocol: TCP
      port: 8888
      targetPort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-readiness
  template:
    metadata:
      labels:
        app: web-readiness
    spec:
      containers:
      - name: demoapp2
        image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
        ports:
        - containerPort: 80
        readinessProbe:			# 就绪探针
          httpGet:
            path: '/readyz'
            port: 80
          initialDelaySeconds: 15	# 初次检测延时时⻓
          periodSeconds: 10			# 检测周期

---
apiVersion: v1
kind: Service
metadata:
  name: demoapp-readiness-service
spec:
  selector:
    app: web-readiness
  ports:
    - protocol: TCP
      port: 8888
      targetPort: 80

2.容器初次启动延迟15s,也就意味着⾄少15s以后才能转为就绪状态,对外提供服务

shell
[root@kube-master endpoint]# kubectl get ep demoapp-readiness-service -w
NAME                        ENDPOINTS                           AGE
demoapp-readiness-service   172.23.127.120:80,172.30.0.179:80   2m55s
[root@kube-master endpoint]# kubectl get ep demoapp-readiness-service -w
NAME                        ENDPOINTS                           AGE
demoapp-readiness-service   172.23.127.120:80,172.30.0.179:80   2m55s

3.因任何原因导致后端的端点就绪状态监测失败,都会触发Endpoint对象将该端点的IP地址从subset.address字段移至subsets.notReadyAddress字段.

  • 模拟⼀个Pod故障
shell
[root@kube-master endpoint]# curl -s -X POST -d 'readyz=Err'  172.23.127.120/readyz


# ⼤约等待30s之后在检查endpoints资源
[root@kube-master endpoint]# kubectl get ep demoapp-readiness-service -w
NAME                        ENDPOINTS                           AGE
demoapp-readiness-service   172.23.127.120:80,172.30.0.179:80   4m19s



demoapp-readiness-service   172.30.0.179:80                     6m20s

[root@kube-master endpoint]# kubectl describe endpoints demoapp-readiness-service
Name:         demoapp-readiness-service
Namespace:    default
Labels:       <none>
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2024-05-16T10:12:08Z
Subsets:
  Addresses:          172.30.0.179
  NotReadyAddresses:  172.23.127.120   # 故障Pod的IP会转⼊NotReadyAddress
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  80    TCP

Events:  <none>
[root@kube-master endpoint]# curl -s -X POST -d 'readyz=Err'  172.23.127.120/readyz


# ⼤约等待30s之后在检查endpoints资源
[root@kube-master endpoint]# kubectl get ep demoapp-readiness-service -w
NAME                        ENDPOINTS                           AGE
demoapp-readiness-service   172.23.127.120:80,172.30.0.179:80   4m19s



demoapp-readiness-service   172.30.0.179:80                     6m20s

[root@kube-master endpoint]# kubectl describe endpoints demoapp-readiness-service
Name:         demoapp-readiness-service
Namespace:    default
Labels:       <none>
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2024-05-16T10:12:08Z
Subsets:
  Addresses:          172.30.0.179
  NotReadyAddresses:  172.23.127.120   # 故障Pod的IP会转⼊NotReadyAddress
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  80    TCP

Events:  <none>

4.将故障端点重新转为就绪状态后,Endpoints对象会将其移回subsets.address字段,这种处理机制确保了Service对象不会将客户端请求流量调度给那些处于运行状态但服务未就绪的端点。

shell
# 恢复故障
 curl -s -X POST -d 'readyz=OK'  172.23.127.120/readyz
# 恢复故障
 curl -s -X POST -d 'readyz=OK'  172.23.127.120/readyz

5.2 ⾃定义endpoint实践

service通过selector和pod建立关联,k8s会根据service关联到的podIP信息组合成一个endpoint。若service定义中没有seLector字段,service被创建时,endpoint controller不会自动创建endpoint.

我们可以通过配置清单创建Service,而无需使用标签选择器,而后自行创建一个同名的endpoint对象,指定对应的IP。这种一般用于将外部MySQL\Redis等应用引l入Kubernetes集群内部,让内部通过Service的方式访问外部资源。

image-20240517103219305

案例

1.准备外部MySQL服务

shell
#安装
yum install mariadb mariadb-server -y

#关闭firewalld
systemctl stop firewalld.service
systemctl disable firewalld.service

#创建远程用户
MariaDB [(none)]> grant all privileges on *.* to 'han' identified by 'han123456' ;
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> exit
#安装
yum install mariadb mariadb-server -y

#关闭firewalld
systemctl stop firewalld.service
systemctl disable firewalld.service

#创建远程用户
MariaDB [(none)]> grant all privileges on *.* to 'han' identified by 'han123456' ;
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.000 sec)

MariaDB [(none)]> exit

2.创建Endpoints资源清单

yaml
[root@kube-master endpoint]# cat mysql-external-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: mysql-external
subsets:
  - addresses:
      - ip: 10.103.236.199  # 外部宿主机ip,如果有多个ip,继续写 - ip
    ports:
      - protocol: TCP
        port: 3306  # 外部MySQL运⾏的端⼝
[root@kube-master endpoint]# cat mysql-external-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: mysql-external
subsets:
  - addresses:
      - ip: 10.103.236.199  # 外部宿主机ip,如果有多个ip,继续写 - ip
    ports:
      - protocol: TCP
        port: 3306  # 外部MySQL运⾏的端⼝
  • 检查endpoints
shell
[root@kube-master endpoint]# kubectl get endpoints mysql-external
NAME             ENDPOINTS             AGE
mysql-external   10.103.236.199:3306   63m
[root@kube-master endpoint]# kubectl get endpoints mysql-external
NAME             ENDPOINTS             AGE
mysql-external   10.103.236.199:3306   63m

3.创建与endpoint同名的Service资源清单

yaml
[root@kube-master endpoint]# cat mysql-external-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-external
spec:
  type: ClusterIP
  ports:
  - port: 13306			# 访问Service的端⼝
    targetPort: 3306	# 后端应⽤的端⼝
[root@kube-master endpoint]# cat mysql-external-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-external
spec:
  type: ClusterIP
  ports:
  - port: 13306			# 访问Service的端⼝
    targetPort: 3306	# 后端应⽤的端⼝
  • 检查Service
shell
[root@kube-master endpoint]# kubectl describe endpoints mysql-external
Name:         mysql-external
Namespace:    default
Labels:       <none>
Annotations:  <none>
Subsets:
  Addresses:          10.103.236.199
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  3306  TCP

Events:  <none>
[root@kube-master endpoint]# kubectl describe endpoints mysql-external
Name:         mysql-external
Namespace:    default
Labels:       <none>
Annotations:  <none>
Subsets:
  Addresses:          10.103.236.199
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  3306  TCP

Events:  <none>

4.使⽤Pod访问Service,验证能否正常访问MySQL服务

shell
#通过ServiceIP,或ServiceName(mysql-external)都可以访问到外部数据库

[root@tools /]# mysql -uhan -h 192.168.20.102 -P13306 -phan123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 15
Server version: 10.3.39-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create database hello_service ;
Query OK, 1 row affected (0.01 sec)

MariaDB [(none)]> exit
Bye
[root@tools /]# exit
exit
#通过ServiceIP,或ServiceName(mysql-external)都可以访问到外部数据库

[root@tools /]# mysql -uhan -h 192.168.20.102 -P13306 -phan123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 15
Server version: 10.3.39-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create database hello_service ;
Query OK, 1 row affected (0.01 sec)

MariaDB [(none)]> exit
Bye
[root@tools /]# exit
exit

6.Service相关字段

6.1 sessionAffinity

如果要将来自于特定客户端的连接调度至同一Pod,可以使用sessionAffinity基于客户端的IP 地址进行会话保持.

还可以通过sessionAffinityConfig.clientIP.timeoutSeconds来设置最大会话停留时间。(默认10800秒,即3小时)

案例

6.2 externalTrafficPolicy

外部流量策略:当外部用户通过NodePort请求Service,是将外部流量路由到本地节点上的Pod,还是路由到集群范围的Pod:

  • Cluster(默认):将用户请求路由到集群范围的所有Pod节点,具有良好的整体负载均衡。
  • Local:仅会将流量调度至请求的目标节点本地运行的Pod对象之上,以减少网络跳跃,降低网络延迟,但当请求指向的节点本地不存在目标Service相关的Pod对象时直接丢弃该报文。

案例

6.3 internalTrafficPolicy

本地流量策略:当本地Pod对Service发起访问时,是将流量路由到本地节点上的Pod,还是路由到集群范围的Pod:

  • Cluster(默认):将Pod的请求路由到集群范围的所有Pod节点,具有良好的整体负载均衡
  • ocal:将请求路由到与发起方处于相同节点的端点,这种机制有助于节省开销,提升效率。但当请求指向的节点本地不存在目标Service相关的Pod对象时直接丢弃该报文

image-20240517142258872

❌ 注意

注意:在一个Service上,当externalTrafficPolicy已设置为Loca时,internaTrafficPoicy则无法使用。

换句话说,在一个集群的不同Service上可以同时使用这两个特性,但在一个Service 上不行

案例

6.4 publishNotReadyAddresses

publishNotReadyAddresses:表示Pod就绪探针探测失败,也不会将失败的PodIP加入notReadyAddress列表中

案例

7.Service深⼊理解

image-20240517143234811

7.1 Iptables模型分析

ClusterIP 模式分析

NodePort 分析

7.2 IPVS模型分析

7.3 Iptables和IPVS对比

  • Iptables:
    • 灵活,功能强大
    • 规则遍历匹配和更新,呈线性时延
  • IPVS:
    • 工作在内核态,有更好的性能
    • 调度算法丰富:rr,wrr,lc,wlc,ip hash...
  • 生产环境推荐使用IPVS

8.服务发现

当Pod需要访问Service时,通过Service提供的clusterIP就可以实现了,但是有几个问题;

1、Service的IP不稳定,删除重建会发生变化;

2、ServiceIP难以记忆,如果能通过一个固定的名称访问就好了;

为了解决这样的问题,Kubernetes引l入了环境变量和DNS两种方案来解决这样的问题;

1、环境变量方式:通过特定的名称将环境变量注入到Pod内部;

2、DNS方式:通过APIServer来监视Service变动,而后动态创建对应Service名称与ServiceIP的域名解析记录;

8.1 环境变量

每个Pod启动的时候,会通过环境变量的方式将Service的IP以及Port信息注入进去,这样Pod 中的应用可以通过读取环境变量来获取对应Service服务的地址信息,这种方法使用起来相对简单,但是也存在一定的问题。就是Pod所依赖的Service必须优Pod启动,否则无法注入到环境变量中。

1、创建Service资源

yaml
[root@kube-master endpoint]# cat env-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-env
spec:
  ports:
  - port: 80
    targetPort: 80
[root@kube-master endpoint]# cat env-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-env
spec:
  ports:
  - port: 80
    targetPort: 80

2、创建容器,然后验证对应的环境变量

shell
[root@kube-master endpoint]# kubectl exec -it pod-env -- /bin/bash

#进入容器执行env
[root@pod-env /]# env
DEMO_SERVICE_PORT_8080_TCP=tcp://192.168.15.149:8080
NGINX_PORT_8080_TCP_PORT=8080
HOSTNAME=pod-env
DEMO_SERVICE_SERVICE_HOST=192.168.15.149
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT=tcp://192.168.0.1:443
TERM=xterm
NGINX_PORT_8080_TCP_ADDR=192.168.253.94
DEMO_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_PORT_8080_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=192.168.0.1
MY_ENV_SERVICE_HOST=192.168.8.93
DEMO_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_HOST=192.168.253.94
MY_ENV_PORT_80_TCP_ADDR=192.168.8.93
NGINX_PORT_8080_TCP=tcp://192.168.253.94:8080
DEMO_SERVICE_PORT=tcp://192.168.15.149:8080
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
NGINX_SERVICE_PORT=8080
MY_ENV_PORT_80_TCP_PROTO=tcp
DEMO_SERVICE_SERVICE_PORT_HTTP=8080
SHLVL=1
HOME=/root
KUBERNETES_PORT_443_TCP_PROTO=tcp
MY_ENV_PORT=tcp://192.168.8.93:80
KUBERNETES_SERVICE_PORT_HTTPS=443
MY_ENV_SERVICE_PORT=80
DEMO_SERVICE_SERVICE_PORT=8080
KUBERNETES_PORT_443_TCP_ADDR=192.168.0.1
MY_ENV_PORT_80_TCP_PORT=80
KUBERNETES_PORT_443_TCP=tcp://192.168.0.1:443
NGINX_PORT=tcp://192.168.253.94:8080
DEMO_SERVICE_PORT_8080_TCP_ADDR=192.168.15.149
MY_ENV_PORT_80_TCP=tcp://192.168.8.93:80
_=/usr/bin/env
[root@kube-master endpoint]# kubectl exec -it pod-env -- /bin/bash

#进入容器执行env
[root@pod-env /]# env
DEMO_SERVICE_PORT_8080_TCP=tcp://192.168.15.149:8080
NGINX_PORT_8080_TCP_PORT=8080
HOSTNAME=pod-env
DEMO_SERVICE_SERVICE_HOST=192.168.15.149
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT=tcp://192.168.0.1:443
TERM=xterm
NGINX_PORT_8080_TCP_ADDR=192.168.253.94
DEMO_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_PORT_8080_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_HOST=192.168.0.1
MY_ENV_SERVICE_HOST=192.168.8.93
DEMO_SERVICE_PORT_8080_TCP_PROTO=tcp
NGINX_SERVICE_HOST=192.168.253.94
MY_ENV_PORT_80_TCP_ADDR=192.168.8.93
NGINX_PORT_8080_TCP=tcp://192.168.253.94:8080
DEMO_SERVICE_PORT=tcp://192.168.15.149:8080
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
NGINX_SERVICE_PORT=8080
MY_ENV_PORT_80_TCP_PROTO=tcp
DEMO_SERVICE_SERVICE_PORT_HTTP=8080
SHLVL=1
HOME=/root
KUBERNETES_PORT_443_TCP_PROTO=tcp
MY_ENV_PORT=tcp://192.168.8.93:80
KUBERNETES_SERVICE_PORT_HTTPS=443
MY_ENV_SERVICE_PORT=80
DEMO_SERVICE_SERVICE_PORT=8080
KUBERNETES_PORT_443_TCP_ADDR=192.168.0.1
MY_ENV_PORT_80_TCP_PORT=80
KUBERNETES_PORT_443_TCP=tcp://192.168.0.1:443
NGINX_PORT=tcp://192.168.253.94:8080
DEMO_SERVICE_PORT_8080_TCP_ADDR=192.168.15.149
MY_ENV_PORT_80_TCP=tcp://192.168.8.93:80
_=/usr/bin/env

❌ 注意

在使用k8s 配置文件传入变量的时候,需要注意在变量上,整数或者字符串需要使用 单引号或双引号,否则或报错

8.2 CoreDNS

在安装Kubernetes集群时,CoreDNS作为附加组件,用来为Pod提供DNS域名解析。CoreDNS监视 Kubernetes API 中的新Service,并为每个Service名称创建一组DNS记录。这样我们就可以通过固定的Service名称来转换出不固定的ServiceIP

1、了解CoreDNS的配置

shell
[root@kube-master endpoint]# kubectl get configmap coredns -n kube-system -oyaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors			# 错误记录
        health {		# 健康检查
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {	# ⽤于解析Kubernetes集群内域名
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153				# 监控的端⼝
        forward . /etc/resolv.conf {	# 如果请求⾮Kubernetes域名,则由节点的resolv.conf中dns解析
           max_concurrent 1000
        }
        cache 30		# 缓存所有内容
        loop
        reload			# ⽀持热更新
        loadbalance		# 负载均衡,默认轮询
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2024-04-10T08:32:04Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "239"
  uid: 95de04df-4c46-436d-84e1-956c792c0ca9
[root@kube-master endpoint]# kubectl get configmap coredns -n kube-system -oyaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors			# 错误记录
        health {		# 健康检查
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {	# ⽤于解析Kubernetes集群内域名
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153				# 监控的端⼝
        forward . /etc/resolv.conf {	# 如果请求⾮Kubernetes域名,则由节点的resolv.conf中dns解析
           max_concurrent 1000
        }
        cache 30		# 缓存所有内容
        loop
        reload			# ⽀持热更新
        loadbalance		# 负载均衡,默认轮询
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2024-04-10T08:32:04Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "239"
  uid: 95de04df-4c46-436d-84e1-956c792c0ca9

2、CoreDNS只所以是固定的IP以及固定的搜索域。是因为kubeLet将--cluster-dns=<dns-service-ip> 、 --cluster-domain=<default-local-domain>对应的配置传递给了每个容器。

[root@kube-master kubelet]# cat /var/lib/kubelet/config.yaml
....
clusterDNS:
- 192.168.0.10 # DNS的固定ServiceIP
clusterDomain: cluster.local # 域名
[root@kube-master kubelet]# cat /var/lib/kubelet/config.yaml
....
clusterDNS:
- 192.168.0.10 # DNS的固定ServiceIP
clusterDomain: cluster.local # 域名

3、进⼊任意Pod中,验证/etc/resolv.conf以及域名解析

shell
[root@kube-master kubelet]# kubectl exec -it pod-env -- /bin/bash
[root@pod-env /]#
[root@pod-env /]#
[root@pod-env /]# cat /etc/resolv.conf
nameserver 192.168.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

# 通过域名解析对应的ServiceIP
[root@pod-env /]# dig @192.168.0.10 demo-service.default.svc.cluster.local +short
192.168.15.149
[root@kube-master kubelet]# kubectl exec -it pod-env -- /bin/bash
[root@pod-env /]#
[root@pod-env /]#
[root@pod-env /]# cat /etc/resolv.conf
nameserver 192.168.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

# 通过域名解析对应的ServiceIP
[root@pod-env /]# dig @192.168.0.10 demo-service.default.svc.cluster.local +short
192.168.15.149

8.3 CoreDNS策略

DNS策略可以单独对Pod进行设定,在创建Pod时可以为其指定DNS的策略,最终配置会落在Pod的/etc/resolv.conf文件中,可以通过pod.spec.dnsPolicy字段设置DNS的策略。

1、ClusterFirst(默认DNS策略)

表示Pod内的DNS使用集群中配置的DNS服务,简单来说就是使用Kubernetes中的coredns服务进行域名解析。如果解析不成功,会使用当前Pod所在的宿主机DNS进行解析。

yaml
apiVersion: V1
kind: Pod
metadata:
  name: dns-test
spec:
  dnsPolicy: ClusterFirst
  containers:
  - name: tools
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 80
apiVersion: V1
kind: Pod
metadata:
  name: dns-test
spec:
  dnsPolicy: ClusterFirst
  containers:
  - name: tools
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 80

2、ClusterFirstWithHostNet

在某些场景下,我们的 Pod 是角HostNetwork 模式启动的,一旦使用HostNetwork模式,那该Pod则会使用当前宿主机的/etc/resoLv.conf来进行 DNS 查询,但如果任然想继续使用Kubernetes 的DNS服务,那就将dnsPolicy设置为ClusterFirstWithHostNet.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  hostNetwork: true                 # 开启host网络模式
  dnsPolicy: ClusterFirstWithHostNet  # 如果没配置使⽤当前Pod所在宿主机的DNS
  containers:
  - name:  tools
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 8080
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  hostNetwork: true                 # 开启host网络模式
  dnsPolicy: ClusterFirstWithHostNet  # 如果没配置使⽤当前Pod所在宿主机的DNS
  containers:
  - name:  tools
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 8080

3、Default

默认使用宿主机的/etc/resolv.conf但可以使用kubelet 的--resolv-conf=/etc/resolv.conf 来指定DNS解析文件地址。

4、None

空的DNS设置,这种方式一般用于自定义DNS配置的场景,往往需要和dnsConfig一起使用才可以达到自定义DNS的目的。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-dns
spec:
  containers:
  - name: myapp-dns
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 8080
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
    - 192.168.0.10
    - 114.114.114.114
    searches:
    - cluster.local
    - svc.cluster.local
    - default.svc.cluster.local
    - freehan.ink
    options:
    - name: ndots
      value: "5"
apiVersion: v1
kind: Pod
metadata:
  name: myapp-dns
spec:
  containers:
  - name: myapp-dns
    image: registry.cn-zhangjiakou.aliyuncs.com/hsuing/demoapp:v1
    ports:
    - containerPort: 8080
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
    - 192.168.0.10
    - 114.114.114.114
    searches:
    - cluster.local
    - svc.cluster.local
    - default.svc.cluster.local
    - freehan.ink
    options:
    - name: ndots
      value: "5"
shell
# 检查/etc/resolv.conf配置
kubectl exec -it myapp-dns -- cat /etc/resolv.conf
# 检查/etc/resolv.conf配置
kubectl exec -it myapp-dns -- cat /etc/resolv.conf

8.4 服务发现原理

在Kubernetes中,服务发现主要通过以下几个组件和机制实现:

  1. Service:Service是Kubernetes中用于定义服务的抽象层,它可以将一组Pod封装成一个逻辑服务,并通过一个稳定的网络地址(通常是Cluster IP)和端口号对外暴露。Service通过标签选择器(Label Selector)将流量路由到匹配的Pod。当Pod的IP地址发生变化时,Service会自动更新其Endpoint,确保流量能够正确路由到新的Pod。
  2. Endpoint:Endpoint是Kubernetes中的一个资源对象,用于存储Service对应的Pod的网络地址和端口号。当Service被创建时,Kubernetes会自动为其创建一个Endpoint对象,并根据Service的标签选择器将匹配的Pod的IP地址和端口号添加到Endpoint中。其他Pod或Service可以通过查询Endpoint来获取要访问的Pod的网络地址和端口号。
  3. DNS:在Kubernetes集群中,每个Service都会被分配一个DNS名称,格式为<service-name>.<namespace-name>.svc.cluster.local。Pod可以通过这个DNS名称来访问Service,而无需知道其具体的IP地址和端口号。Kubernetes集群中的DNS服务器(如CoreDNS)会负责解析这些DNS名称,将请求路由到正确的Service。
  4. kube-proxy:kube-proxy是Kubernetes集群中的网络代理程序,它运行在每个工作节点上。kube-proxy负责监听Service和Endpoint的变化,并根据这些变化更新节点的网络规则,以确保流量能够正确路由到目标Pod。kube-proxy可以使用不同的代理模式(如iptables、ipvs等)来实现流量转发和负载均衡。

9.HeadLess Service

9.1 什么是HeadLess

HeadlessService也叫无头服务,就是创建的Service没有CLusterIP,而是为Service所匹配的每个Pod都创建一条DNS的解析记录,这样每个Pod都有一个唯一的DNS名称标识身份,访问的格式如下

$(service_name).$(namespace).svc.cluster.local
$(service_name).$(namespace).svc.cluster.local

image-20240517175116805

9.2 HeadLess的作⽤

像 elasticsearch,mongodb,kafka 等分布式服务,在做集群初始化时,配置文件中要写上集群中所有节点的IP(或是域名)但Pod是没有固定IP的,所以配置文件里写DNS名称是最合适的。

那为什么不用Service,因为Service 作为 Pod 前置的负载均衡,一般是为一组相同的后端 Pod 提供访问入口,而且Service的selector也没有办法区分同一组Pod的不同身份。

但是我们可以使用Statefulset控制器,它在创建每个Pod的时候,能为每个Pod 做一个编号,就是为了能区分这一组Pod的不同角色,各个节点的角色不会变得混乱,然后再创建 headless service 资源,集群内的节点通过Pod名称+序号.Service名称,来进行彼此间通信的,只要序号不变,访问就不会出错。

案例

9.3 Service和Headless Service的区别

  • 普通的 Service,只能通过解析 service 的 DNS 返回 service 的 Cluster IP。
  • headless service作为service的一种类型,顾名思义无头服务,无头 Service 不会获得集群 IP,kube-proxy 不会处理这类 Service, 而且平台也不会为它们提供负载均衡或路由支持。
  • 无头 Service 允许客户端直接连接到它所偏好的任一 Pod。 无头 Service 不使用虚拟 IP 地址和代理配置路由和数据包转发;相反,无头 Service 通过内部 DNS 记录报告各个 Pod 的端点 IP 地址,这些 DNS 记录是由集群的 DNS 服务所提供的。
  • Cluster IP 类型的 Service 用于有状态的服务,而 Headless Service 则用于有状态的服务,比如有时候 client 想自己决定使用哪个 Real Server,可以通过查询 DNS 来获取 Real Server 的信息。又或者 headless service关联的每个 endpoint(也就是 Pod),都会有对应的 DNS 域名;这样 Pod 之间就可以互相访问。
  • 无状态的服务,其中一个代表就是 Deployment,而有状态的服务,代表则是 StatefulSet。 其中,Headless Service 主要是为了 StatefulSet 的网络拓扑状态而服务的。

普通service解析DNS

Service的ClusterIP工作原理:一个 Service 可能对应一组 endpoints(所有 pod 的地址+端口),client 访问 ClusterIP,通过 iptables 或者 ipvs 转发到 Real Server(Pod)

bash
# kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
busybox                  1/1     Running   0          18s   10.104.157.68   k8s    <none>           <none>
nginx-6b85df66b4-gnbwg   1/1     Running   0          18s   10.104.157.70   k8s    <none>           <none>
nginx-6b85df66b4-rjf6g   1/1     Running   0          18s   10.104.157.71   k8s    <none>           <none>
#
# kubectl get service -l service=nginx
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx-service    ClusterIP   172.16.100.1   <none>        80/TCP    21s
#
# kubectl get endpoints nginx-service
NAME            ENDPOINTS                           AGE
nginx-service   10.104.157.70:80,10.104.157.71:80   45s
#
# kubectl exec -t busybox -- nslookup nginx-service.default.svc.cluster.local
Server:    172.16.0.10
Address 1: 172.16.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service.default.svc.cluster.local
Address 1: 172.16.100.1 nginx-service.default.svc.cluster.local
# kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
busybox                  1/1     Running   0          18s   10.104.157.68   k8s    <none>           <none>
nginx-6b85df66b4-gnbwg   1/1     Running   0          18s   10.104.157.70   k8s    <none>           <none>
nginx-6b85df66b4-rjf6g   1/1     Running   0          18s   10.104.157.71   k8s    <none>           <none>
#
# kubectl get service -l service=nginx
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx-service    ClusterIP   172.16.100.1   <none>        80/TCP    21s
#
# kubectl get endpoints nginx-service
NAME            ENDPOINTS                           AGE
nginx-service   10.104.157.70:80,10.104.157.71:80   45s
#
# kubectl exec -t busybox -- nslookup nginx-service.default.svc.cluster.local
Server:    172.16.0.10
Address 1: 172.16.0.10 kube-dns.kube-system.svc.cluster.local

Name:      nginx-service.default.svc.cluster.local
Address 1: 172.16.100.1 nginx-service.default.svc.cluster.local

从上面看到:

虽然 Service 有 2 个 endpoint(10.104.157.70:80,10.104.157.71:80),但是 DNS 查询时只会返回 Service 的 ClusterIP 地址(172.16.100.1),具体 Client 访问的是哪个 real server,由 iptables 或者 ipvs 决定

headless Service的解析service的DNS结果

bash
# kubectl get statefulsets
NAME    READY   AGE
redis   2/2     38s
#
# kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          59s   10.104.157.77   k8s    <none>           <none>
redis-0   1/1     Running   0          49s   10.104.157.80   k8s    <none>           <none>
redis-1   1/1     Running   0          48s   10.104.157.81   k8s    <none>           <none>
#
# kubectl get service -l service=redis
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
redis-headless   ClusterIP   None           <none>        6379/TCP   17s
redis-service    ClusterIP   172.16.200.1   <none>        6379/TCP   23s
#
# kubectl get endpoints  redis-service 
NAME            ENDPOINTS                               AGE
redis-service   10.104.157.80:6379,10.104.157.81:6379   25s
#
# kubectl exec -t busybox -- nslookup redis-headless.default.svc.cluster.local
Server:    172.16.0.10
Address 1: 172.16.0.10 kube-dns.kube-system.svc.cluster.local

Name:      redis-headless.default.svc.cluster.local
Address 1: 10.104.157.80 redis-0.redis-headless.default.svc.cluster.local
Address 2: 10.104.157.81 redis-0.redis-headless.default.svc.cluster.local
# kubectl get statefulsets
NAME    READY   AGE
redis   2/2     38s
#
# kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          59s   10.104.157.77   k8s    <none>           <none>
redis-0   1/1     Running   0          49s   10.104.157.80   k8s    <none>           <none>
redis-1   1/1     Running   0          48s   10.104.157.81   k8s    <none>           <none>
#
# kubectl get service -l service=redis
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
redis-headless   ClusterIP   None           <none>        6379/TCP   17s
redis-service    ClusterIP   172.16.200.1   <none>        6379/TCP   23s
#
# kubectl get endpoints  redis-service 
NAME            ENDPOINTS                               AGE
redis-service   10.104.157.80:6379,10.104.157.81:6379   25s
#
# kubectl exec -t busybox -- nslookup redis-headless.default.svc.cluster.local
Server:    172.16.0.10
Address 1: 172.16.0.10 kube-dns.kube-system.svc.cluster.local

Name:      redis-headless.default.svc.cluster.local
Address 1: 10.104.157.80 redis-0.redis-headless.default.svc.cluster.local
Address 2: 10.104.157.81 redis-0.redis-headless.default.svc.cluster.local

Headless Service 的作用在于是让 <pod name> 加入了域名中,这样才能完成网络拓扑的关系确定

10. Service和pod关系

  • service通过标签关联一组Pod
  • Service使用iptables或者ipvs为一组Pod提供负载均衡能力
  • 示例图: image

注意这里的后缀:"default.svc.cluster.local"

"default" 是我们正在操作的 Namespace。

"svc" 表示这是一个 Service。

"cluster.local" 是您的集群域,在您自己的集群中可能会有所不同。