k8s service+kube-proxy+endpoint+标签+通信过程规则解释

一、Service资源

service是名称空间级别的资源,并且service创建时会自动创建Endpoint(端点)资源,因为service自己并不会去直接匹配后端pod资源,而是由endpoint匹配,匹配过程是由endpoint控制器完成的。只有pod的就绪探针就绪之后,endpoint才会去匹配

1.1 参数解释

apiVersion: v1
kind: Service
metadata:
  name: …
  namespace: …
  labels:
    key1: value1
    key2: value2
spec:
  type <string>   # Service类型,默认为ClusterIP
  selector <map[string]string>  # 等值类型的标签选择器,内含“与”逻辑
  publishNotReadyAddresses 表示是否将没有就绪的 Pod 关联到SVC上。默认情况是 false,只有就绪状态的 Pod 的地址才会关联到SVC上
  ports:  # Service的端口对象列表
  - name <string>  # 端口名称
    protocol <string>  # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP
    port <integer>  # Service的端口号
    targetPort  <string>  # 后端目标进程的端口号或名称,名称需由Pod规范定义
    nodePort <integer>  # 节点端口号,仅适用于NodePort和LoadBalancer类型
  clusterIP  <string>  # Service的集群IP,建议由系统自动分配,也可自己指定
  externalTrafficPolicy  <string> # 外部流量策略处理方式,Local表示由当前节点处理,Cluster表示向集群范围调度,外部访问时生效
  loadBalancerIP  <string>  # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer
  externalName <string>  # 外部服务名称,该名称将作为Service的DNS CNAME值

1.ClusterIP:建议由K8S动态指定一个; 也支持用户手动明确指定;
2.ServicePort:被映射进Pod上的应用程序监听的端口; 而且如果后端Pod有多个端口,并且每个端口都想通过SErvice暴露的话,每个都要单独定义。
3.最终接收请求的是PodIP和containerPort;

1.2 Service的类型

  1. ClusterIP:通过集群内部IP地址暴露服务,但该地址仅在集群内部可见、可达,它无法被集群外部的客户端访问;默认类型;
  2. NodePort:NodePort是ClusterIP的增强类型,它会于ClusterIP的功能之外,在每个节点上使用一个相同的端口号将外部流量引入到该Service上来。端口范围:30000-32767
  3. LoadBalancer:LB是NodePort的增强类型,要借助于底层IaaS云服务上的LBaaS产品来按需管理LoadBalancer。
  4. ExternalName:借助集群上KubeDNS来实现,服务的名称会被解析为一个CNAME记录,而CNAME名称会被DNS解析为集群外部的服务的IP地址; 这种Service既不会有ClusterIP,也不会有NodePort;

1.3 kube-proxy和service的关系

1.3.1 kube-proxy实际作用

kube-proxy实际作用是Service Controller位于各节点上的agent
主要是注册监听着api-server中的所有的service数据对象,任何的service数据在api-server中添加,每个节点上的kube-proxy都会将service数据象加载到本地节点上

1.3.2 代理模型

  1. User space模型(已淘汰)

Pod访问Service时,iptables会利用自身规则拦截这条请求报文,并将这条请求报文重定向给kube-proxy,然后kube-proxy再去转发到内核中的iptables,然后被调度到后端pod中

  1. iptables模式

此模式会根据service数据对象生成大量的iptables规则,在service很多的情况下建议使用ipvs模式
Pod访问Service时,iptables会拦截这条请求报文,并根据自身规则将请求报文转发出去,其中kube-proxy只负责监听api-server中的service数据对象,并将新生成的service数据转化为iptables拦截规则和调度规则,详情见:本文章的1.3.3 kube-proxy和service的工作过程

  1. ipvs模式

1.3.3 kube-proxy和service的工作过程

  1. service是定义在api-server中的一个数据对象
  2. 每个节点的kube-proxy会注册监听着api-server中的所有service数据对象,任何的service数据在api-server中添加,每个节点上的kube-proxy都会将service数据象加载到本地节点上
  3. kube-proxy将加载到本地节点上的service数据转换成本地节点上的iptables或ipvs规则(拦截和调度规则)
  4. 当nodeX节点中的某一个client pod 想要访问server pod,请求报文就会发往内核空间,进而被iptables或ipvs规则所捕获
  5. 当内核空间中的iptables或ipvs规则捕获到请求报文之后,会根据规则将请求调度到server pod中(至于会调度到那个pod,这个是调度规则来管理的,如轮询等)

下图是演示图

在这里插入图片描述

二、标签

2.1 什么是标签

  1. 标签中的键名称通常由“键前缀”和“键名”组成,其格式形如“KEY_PREFIX/KEY_NAME”,键前缀为可选部分。键名至多能使用63个字符,支持字母、数字、连接号(-)、下划线(_)、点号(.)等字符,且只能以字母或数字开头。而键前缀必须为DNS子域名格式,且不能超过253个字符。省略键前缀时,键将被视为用户的私有数据。那些由Kubernetes系统组件或第三方组件自动为用户资源添加的键必须使用键前缀,kubernetes.io/和k8s.io/前缀预留给了kubernetes的核心组件使用,例如Node对象上常用的kubernetes.io/os、kubernetes.io/arch和kubernetes.io/hostname等。
  2. 标签的键值必须不能多于63个字符,它要么为空,要么是以字母或数字开头及结尾,且中间仅使用了字母、数字、连接号(-)、下划线(_)或点号(.)等字符的数据。

2.2 标签分类

版本标签:"release" : "stable""release" : "canary""release" : "beta"。
环境标签:"environment" : "dev""environment" : "qa""environment" : "prod"。
应用标签:"app" : "ui""app" : "as""app" : "pc""app" : "sc"。
架构层级标签:"tier" : "frontend""tier" : "backend", "tier" : "cache"。
分区标签:"partition" : "customerA""partition" : "customerB"。
品控级别标签:"track" : "daily""track" : "weekly"

2.3 标签选择器

2.3.1 解释及规则

标签选择器用于表达标签的查询条件或选择标准,Kubernetes API目前支持两个选择器:基于等值关系(equality-based)的标签选项器以及基于集合关系(set-based)的标签选择器。同时指定多个选择器时需要以逗号将其分隔,各选择器之间遵循“与”逻辑,即必须要满足所有条件,而且空值的选择器将不选择任何对象。
标签选择器可以基于等值关系的标签选择器的可用操作符有=、==和!=三种,其中前两个意义相同,都表示“等值”关系,最后一个表示“不等”。例如env=dev和env!=prod都是基于等值关系的选择器,而tier in (frontend,backend)则是基于集合关系的选择器。

2.3.1 等值标签选择器用法

1.过滤出键名为app,键值为demoapp的pod
kubectl get pods -l app=demoapp -n dev 
或kubectl get pods -l app==demoapp -n dev   与上一条命令一个意思
或kubectl get pods -l app==demoapp -n dev --show-labels  可显示标签
2.过滤多个标签的pod,这里只有都含有这两个标签的pod才会被过滤出来
kubectl get pods -l 'app=demoapp,env=dev' -n dev

2.3.2 集合标签选择器用法

KEY in (VALUE1,VALUE2,…) :指定的键名的值存在于给定的列表中即满足条件;
KEY notin (VALUE1,VALUE2,…) :指定的键名的值不存在于给定列表中即满足条件;
KEY:所有存在此键名标签的资源;
!KEY:所有不存在此键名标签的资源

1.过滤出键名为app的pod,不需要管键值是什么
kubectl get pods -l app -n dev
2.过滤出键名不为app的pod,没有标签的也会被过滤出来
kubectl get pods -l '!app' -n dev   #因为!在shell命令行中有意义,所以加上了引号
3.过滤出键名为app,键值为emoapp,nginx,mysql,php的pod
kubectl get pods -l 'app in (demoapp,nginx,mysql,php)' -n dev
4.过滤出键名为app,键值不为emoapp,nginx,mysql,php的pod
kubectl get pods -l 'app notin (demoapp,nginx,mysql,php)' -n dev

三、service类型示例

3.1 自定义service-clusterip

可以自己随便定义IP

1. 编写yaml文件
vim services-clusterip-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
   name: demoapp
   labels:
      app: demoapp
      env: dev
   namespace: dev
spec:
   replicas: 3
   selector:
      matchLabels:
         app: demoapp
   template:
      metadata:
         labels:
            app: demoapp
      spec:
         containers:
         - name: demoapp
           image: ikubernetes/demoapp:v1.0
        
---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-svc
  namespace: dev
spec:
  clusterIP: 10.97.72.1
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80

2. 部署
[root@master01 yaml]# kubectl apply -f services-demoapp.yaml 
deployment.apps/demoapp created
service/demoapp-svc created

3. 查看demoapp-svc的IP及后端pod
[root@master01 yaml]# kubectl describe svc demoapp-svc -n dev
Name:              demoapp-svc
Namespace:         dev
Labels:            <none>
Annotations:       <none>
Selector:          app=demoapp
Type:              ClusterIP
IP:                10.97.72.1
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.5:80,10.244.2.8:80,10.244.2.9:80
Session Affinity:  None
Events:            <none>

4. 访问测试
#发现是轮询访问的后端pod
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-88k2l, ServerIP: 10.244.2.8!
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-jdsrm, ServerIP: 10.244.1.5!
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-85665, ServerIP: 10.244.2.9!

3.2 service-nodeport-demo

集群内部访问过程: Client -> ClusterIP:ServicePort -> PodIP:targetPort
集群外部访问过程: Client -> NodeIP:NodePort -> PodIP:targetPort

1. 编写yaml文件
vim services-nodeport-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
   name: demoapp
   labels:
      app: demoapp
      env: dev
   namespace: dev
spec:
   replicas: 3
   selector:
      matchLabels:
         app: demoapp
   template:
      metadata:
         labels:
            app: demoapp
      spec:
         containers:
         - name: demoapp
           image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-nodeport-svc
  namespace: dev
spec:
  type: NodePort
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 31398   #如果不指定会自动生成


2. 部署
[root@master01 yaml]# kubectl apply -f services-nodeport-demo.yaml 
deployment.apps/demoapp created
service/demoapp-nodeport-svc created

3. 查看 
[root@master01 yaml]# kubectl get svc -n dev
NAME                   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-nodeport-svc   NodePort   10.105.212.237   <none>        80:31398/TCP   8s
[root@master01 yaml]# kubectl describe svc demoapp-nodeport-svc -n dev
Name:                     demoapp-nodeport-svc
Namespace:                dev
Labels:                   <none>
Annotations:              <none>
Selector:                 app=demoapp
Type:                     NodePort
IP:                       10.105.212.237
Port:                     http  80/TCP
TargetPort:               80/TCP
NodePort:                 http  31398/TCP
Endpoints:                10.244.1.10:80,10.244.2.15:80,10.244.2.16:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

4. 使用clusterIP访问测试
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-dqd9f, ServerIP: 10.244.1.10!
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-j6z99, ServerIP: 10.244.2.15!
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-t6lkh, ServerIP: 10.244.2.16!

5. 使用宿主机IP和端口访问,任何一个节点的宿主机IP都可以访问到
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-j6z99, ServerIP: 10.244.2.15!
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-t6lkh, ServerIP: 10.244.2.16!
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-dqd9f, ServerIP: 10.244.1.10!

3.3 services-loadbalancer-demo

如果IaaS云服务上不支持loadbalancer,就算创建了service也会被集群定义为nodeport类型的service

1. 编写yaml文件
vim services-loadbalancer-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
   name: demoapp
   labels:
      app: demoapp
      env: dev
   namespace: dev
spec:
   replicas: 3
   selector:
      matchLabels:
         app: demoapp
   template:
      metadata:
         labels:
            app: demoapp
      spec:
         containers:
         - name: demoapp
           image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-loadbalancer-svc
  namespace: dev
spec:
  type: LoadBalancer
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  #loadBalancerIP: 1.2.3.4  不可以随便定义,取决于底层IaaS云服务上的LBaaS产品支不支持

2. 查看
这里EXTERNAL-IP(外部IP)字段会显示pending,因为我的测试机不支持外部IP,所以还是只能通过nodeport访问
[root@master01 yaml]#  kubectl get svc -n dev -o wide
NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
demoapp-loadbalancer-svc   LoadBalancer   10.97.40.143   <pending>     80:30237/TCP   3s    app=demoapp

3.4 外部IP地址service-externalip-demo

当没有IaaS云服务支持的时候,可以自己定义外部IP,也可以利用VIP,来保证
外部访问过程:域名->externalip->service->pod

1. 编写yaml文件
vim services-externalip-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
   name: demoapp
   labels:
      app: demoapp
      env: dev
   namespace: dev
spec:
   replicas: 3
   selector:
      matchLabels:
         app: demoapp
   template:
      metadata:
         labels:
            app: demoapp
      spec:
         containers:
         - name: demoapp
           image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-externalip-svc
  namespace: dev
spec:
  type: ClusterIP   #或NodePort
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  externalIPs:
  - 172.29.9.26   #此IP可以是节点上VIP,外部可用过这个IP的service访问到后端pod

四、endpoint资源

4.1 endpoint解释

service是名称空间级别的资源,并且service创建时会自动创建Endpoint(端点)资源,因为service自己并不会去直接匹配后端pod资源,而是由endpoint匹配,匹配过程是由endpoint控制器完成的。只有pod的就绪探针就绪之后,endpoint才会去匹配

4.2 参数解释

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>  # 应用层协议;

4.3 示例

4.3.1 利用readiness测试endpoint

此次目的:1.测试是否创建service就会自动创建endpoint资源
2.利用readiness(就绪探针)模拟后端pod宕机,查看endpoint资源是否会将宕机的pod下线

  1. 编写services-readiness.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp-with-readiness
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: demoapp-with-readiness
    spec:
      containers:
      - image: ikubernetes/demoapp:v1.0
        name: demoapp
        imagePullPolicy: IfNotPresent
        readinessProbe:
          httpGet:
            path: '/readyz'
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 10

---
apiVersion: v1
kind: Service
metadata:
  name: services-readiness-demo
  namespace: dev
spec:
  selector:
    app: demoapp-with-readiness
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  1. 查看部署状态
[root@master01 yaml]# kubectl get pod -n dev
NAME                       READY   STATUS    RESTARTS   AGE
demoapp-677db795b4-9zf5t   1/1     Running   0          63s
demoapp-677db795b4-wdf7g   1/1     Running   0          63s
demoapp-677db795b4-wn76j   1/1     Running   0          63s
[root@master01 yaml]# kubectl get svc -n dev
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
services-readiness-demo   ClusterIP   10.109.208.211   <none>        80/TCP    66s
#这里看到确实有一个endpoint的资源而且和svc重名,因为endpoint资源和svc资源就是以名字来进行关联的
[root@master01 yaml]# kubectl get endpoints -n dev
NAME                      ENDPOINTS                                      AGE
services-readiness-demo   10.244.1.15:80,10.244.2.23:80,10.244.2.24:80   68s
#可以看到以下A
[root@master01 yaml]# kubectl describe endpoints services-readiness-demo -n dev
Name:         services-readiness-demo
Namespace:    dev
Labels:       <none>
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2022-06-17T07:02:49Z
Subsets:
  Addresses:          10.244.1.15,10.244.2.23,10.244.2.24
  NotReadyAddresses:  <none>
  Ports:
    Name  Port  Protocol
    ----  ----  --------
    http  80    TCP

Events:  <none>
  1. 访问测试
正常是可以访问200的,并且现在endpoint资源里后端pod还是三个
[root@master01 yaml]# curl 10.109.208.211:80/readyz
OK 
[root@master01 yaml]# curl -I 10.109.208.211:80/readyz
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 2
Server: Werkzeug/1.0.0 Python/3.8.2
Date: Fri, 17 Jun 2022 07:25:57 GMT

修改某一个pod的readyz参数,就会导致访问出现5xx,之后readiness探针就会检测到pod宕机,endpoint就会下线宕机的pod
curl -X POST -d "readyz=fail" 10.244.2.23/readyz
[root@master01 yaml]# curl 10.244.2.23/readyz
fail
[root@master01 yaml]# curl -I 10.244.2.23/readyz
HTTP/1.0 507 INSUFFICIENT STORAGE
Content-Type: text/html; charset=utf-8
Content-Length: 4
Server: Werkzeug/1.0.0 Python/3.8.2
Date: Fri, 17 Jun 2022 07:32:23 GMT

查看endpoint详细信息,会看到NotReadAddresses字段中已经有刚刚宕机pod了
[root@master01 yaml]# kubectl describe endpoints services-readiness-demo -n dev
Name:         services-readiness-demo
Namespace:    dev
Labels:       <none>
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2022-06-17T07:32:28Z
Subsets:
  Addresses:          10.244.1.15,10.244.2.24
  NotReadyAddresses:  10.244.2.23
  Ports:
    Name  Port  Protocol
    ----  ----  --------
    http  80    TCP

Events:  <none>

可以看到READY字段中有一个pod的状态已经是0了
[root@master01 yaml]# kubectl get pod -n dev -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
demoapp-677db795b4-9zf5t   1/1     Running   0          32m   10.244.2.24   node02   <none>           <none>
demoapp-677db795b4-wdf7g   1/1     Running   0          32m   10.244.1.15   node01   <none>           <none>
demoapp-677db795b4-wn76j   0/1     Running   0          32m   10.244.2.23   node02   <none>           <none>

4.3.2 利用endpoint代理外部mysql(externalName的示例)

  1. 编写mysql-endpoints-demo.yaml

这里Endpoints和Service不需要通过labels来进行关联,只需要通过metadata.name字段关联即可(保持一致)

vim mysql-endpoints-demo.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: mysql-external
  namespace: default
subsets:
- addresses:
  - ip: 172.29.9.51    #这里的地址可以是外部mysql的读写分离器的IP这样就能保证数据一致性了
  - ip: 172.29.9.52
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-external
  namespace: default
spec:
  type: ClusterIP
  ports:
  - name: mysql
    port: 3306
    targetPort: 3306
    protocol: TCP
  1. 部署之后

部署之后,任何pod都可以通过service来访问数据库了

五、iptables模式下的service

详情见本文章的 1.3

5.1 k8s在iptables中的规则解释

  1. KUBE-SERVICES:包含所有ClusterIP类型的Service的流量匹配规则,由PREROUTING和OUTPUT两个内置链直接调用;每个Service对象包含两条规则定义,对于所有发往该Service(目标IP为Service_IP且目标端口为Service_Port)的请求报文,前一条用于为那些非源自Pod网络(! -s 10.244.0.0/16)中请求报文借助于KUBE-MARQ-MASK自定义链中的规则打上特有的防火墙标记,后一条负责将所有报文转至专用的以KUBE-SVC为名称前缀的自定义链,后缀是Service信息hash值。
  2. KUBE-MARK-MASQ:专用目的自定义链,所有转至该自定义链的报文都将被置入特有的防火墙标记(0x4000)以便于将特定的类型的报文定义为单独的分类,目的在将该类报文转发到目标端点之前由POSTROUTING规则链进行源地址转换。
  3. KUBE-SVC-<HASH>:定义一个服务的流量调度规则,它通过随机调度算法(RANDOM)将请求分发给该Service的所有后端端点,每个后端端点定义在以KUBE-SEP为前缀名称的自定链上,后缀是端点信息的hash值。
  4. KUBE-SEP-<HASH>:定义一个端点相关的流量处理规则,它通常包含两条规则,前一条用于为那些源自该端点自身(-s ep_ip)的请求流量调用自定义链KUBE-MARQ-MASK打上特有的防火墙标记,后一条负责将发往该端点的所有流量进行目标IP地址和端口转换,新目标为该端点的IP和端口(-j DNAT --to-destination ep_ip:ep_port)。
  5. KUBE-POSTROUTING:专用的自定义链,由内置链POSTROUTING无条件调用,负责将拥有特有防火墙标记0x4000的请求报文进行源地址转换(Target为实现地址伪装的MASQUERADE),新的源地址为报文离开协议栈时流经接口的主IP(primary ip)地址。

5.2 Pod-Pod通信过程和非Pod网络-Pod的通信过程

iptables代理模式下的ClusterIP,每个Service在每个节点上(由kube-proxy负责生成)都会生成相应的iptables规则

下图是流量从pod或者k8s集群上的独立的docker容器或者节点上的应用进程中流出并流向目的地的过程
iptables规则中出站流量必须经过OUTPUT->POSTROUTING,这里表示的是k8s集群中出站流量的过程
以下的序号都表示请求报文的流经路径
(1)表示从NodeX节点上的一个独立的容器中流出的请求报文(这里独立的容器表示的是docker自己的网络172.17.0.0/16)
(2)表示从NodeX节点上的某一个Pod中流出请求报文
(3)表示从NodeX节点上的应用进程中流出的请求报文
这里规则间的序号都是规则的调用,以下说的将报文发给某一条规则,只是为了更好的捋清楚关系而已。

  1. 请求由k8s集群中的Pod或者k8s集群节点上的应用进程出站的过程

(集群内部通信,Pod-Pod间通信过程,因为Pod-Pod间通信需要经过Service,所以需要经过这些规则)

  1. (2)号请求从Pod中流出进入内核空间,并被iptables出站规则捕获,并转发给KUBE-SERVICES
  2. KUBE-SERVICES中的第一条规则会判断是否是从Pod网络中出站的报文,发现(2)请求是,然后匹配第二条规则,发现请求(2)的目标地址和端口是另一个Service的,之后将这条请求转发给KUBE-SVC-<HASH>
  3. 请求(2)到达KUBE-SVC-<HASH>之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-SERVICES | grep svc_name 得到的),将请求调度给后端的Pod,(2)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>
  4. 请求(2)到达KUBE-SEP-<HASH>之后,先匹配第一条规则,如果发现源地址是自己发出请求时的Pod_IP,就需要经由序号(9)发送给KUBE-MARQ-MASK打标签(进行源地址转换)转换之后就会返回给KUBE-SEP-<HASH>,并进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING;如果发现源地址不是自己发出请求时的Pod_IP,就会进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING
  5. 请求到达POSTROUTING之后,又会转发给KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,然后发送给Pod。
  1. 请求由k8s集群中非Pod网络出站的过程(这里举例是docker网络总运行的容器)
  1. (1)号请求从一个独立的docker网络中的容器内部发出,进入内核空间,并被iptables出站规则捕获,并转发给KUBE-SERVICES
  2. KUBE-SERVICES的第一条规则会判断是否是从Pod网络中出站的报文,发现(1)请求不是,就会将报文经由(6)发给KUBE-MARQ-MASK打标签,并将打好标签的报文返回给KUBE-SERVICES,并进行第二条规则的匹配,发现(1)报文目的地址是k8s集群中service的地址和端口,就将报文发给KUBE-SVC-<HASH>规则。
  3. 报文到达KUBE-SVC-<HASH>规则之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-SERVICES | grep svc_name 得到的),将请求调度给后端的Pod,(1)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>规则
  4. 请求(1)到达KUBE-SEP-<HASH>之后,先匹配第一条规则,如果发现源地址并不是集群中的Pod网络,就需要经由序号(9)发送给KUBE-MARQ-MASK打标签之后,就会返回给KUBE-SEP-<HASH>,并进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING
  5. 请求到达POSTROUTING之后,又会转发给KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文标记做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,发给目标Pod。

在这里插入图片描述

5.3 外部-Pod通信

iptables代理模式下的NodePort,外部流量从NodePort流入有两种类型,一个是从NodeX的NodePort流入,而访问的Pod_IP在另一台节点上;一个是一个是从NodeX的NodePort流入,而访问的Pod_IP在本节点。

  1. 下图是第一种类型从NodeX的NodePort流入,流入另一台节点上的Pod_IP
  1. 外部请求从(1)进入PREROUTING(通过iptables -t nat -S PREROUTING命令查看下一步规则)
  2. KUBE-SERVICES规则判断目标地址是否是本地节点IP地址(因为请求是从nodeX流入的,所以目标地址应该是nodeX的IP),之后调用KUBE-NODEPORTS
  3. KUBE-NODEPORTS第一条规则判断请求的端口是否是NodePort,然后调用KUBE-MARK-MASQ规则打标签
  4. KUBE-MARK-MASQ规则打好标签之后,开始KUBE-SERVICES第二条规则,匹配第二条规则,判断目标端口,调用KUBE-SVC-<HASH>
  5. 请求(1)到达KUBE-SVC-<HASH>之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-NODEPORTS | grep svc_name 得到的),将请求调度给后端的Pod,(1)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>
  6. KUBE-SEP-<HASH>第一条规则匹配源地址是否和上一步所获取到的目标Pod_IP一样,如果一样就会调用KUBE-MARK-MASQ规则,再次打标签,然后进行第二条规则匹配,第二条规则对报文做目标地址转换(目标地址转换成目标Pod_IP),然后调用POSTROUTING
  7. POSTROUTING调用KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文标记做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,发给另一个节点的目标Pod。
  8. 报文从目标Pod返回时,会再次经过NodeX并做源地址转换(源地址转换成NodeX的节点IP),然后返回给用户。

在这里插入图片描述

  1. 下图是第二种类型从NodeX的NodePort流入,流入本节点上的Pod_IP
    在这里插入图片描述

5.5 externalIP-Pod通信

在这里插入图片描述

六、ipvs模式下的service

模式类型见本文章的 1.3

6.1 特点作用

ipvs会在每个节点上创建一个名为kube-ipvs0的虚拟接口,并将集群所有Service对象的ClusterIP和ExternalIP都配置在该接口; kube-proxy为每个service生成一个虚拟服务器(Virtual Server)的定义。

ipvs类型:默认是nat; 仅需要借助于极少量的iptables规则完成源地址转换等功能。
ipvs支持更多的调度算法wrr等

6.2 修改为ipvs模式

因为ipvs规则是由kube-proxy生成的,所以需要修改kube-proxy的配置

  1. 查看kube-proxy配置文件
1. 修改
kubectl edit cm kube-proxy -n kube-system

解释:
   ipvs:
      excludeCIDRs: null
      minSyncPeriod: 0s
      scheduler: ""			#调度算法,默认是轮询(rr)
      strictARP: false
      syncPeriod: 0s
      tcpFinTimeout: 0s
      tcpTimeout: 0s
      udpTimeout: 0s

修改data字段中的mode字段,默认是空(默认就是iptables模式),改为ipvs
mode: "ipvs"
修改完之后保存退出即可,之后会自动应用的

2. 手动删除kube-proxy
如果不自动应用,可以手动删除以下kube-proxy,因为删除之后,会重新创建,但是需要注意一个一个删除,等第一个起来在删除第二个,否则集群就不能访问了。
[root@master01 ~]# kubectl get pod -n kube-system -o wide
NAME                               READY   STATUS    RESTARTS   AGE    IP             NODE       NOMINATED NODE   READINESS GATES
coredns-6c76c8bb89-pskcq           1/1     Running   71         103d   10.244.0.39    master01   <none>           <none>
coredns-6c76c8bb89-tk2m6           1/1     Running   71         103d   10.244.0.38    master01   <none>           <none>
etcd-master01                      1/1     Running   18         103d   192.168.8.10   master01   <none>           <none>
kube-apiserver-master01            1/1     Running   22         103d   192.168.8.10   master01   <none>           <none>
kube-controller-manager-master01   1/1     Running   32         103d   192.168.8.10   master01   <none>           <none>
kube-flannel-ds-f8g89              1/1     Running   19         103d   192.168.8.30   node02     <none>           <none>
kube-flannel-ds-fv62j              1/1     Running   16         103d   192.168.8.20   node01     <none>           <none>
kube-flannel-ds-kjqnb              1/1     Running   24         103d   192.168.8.10   master01   <none>           <none>
kube-proxy-cb4t9                   1/1     Running   15         103d   192.168.8.20   node01     <none>           <none>
kube-proxy-kv8mj                   1/1     Running   18         103d   192.168.8.30   node02     <none>           <none>
kube-proxy-n622g                   1/1     Running   18         103d   192.168.8.10   master01   <none>           <none>
kube-scheduler-master01            1/1     Running   31         103d   192.168.8.10   master01   <none>           <none>

3. 删除操作
kubectl delete pods kube-proxy-cb4t9 -n kube-system

4. 查看网卡是否出现kube-ipvs0,就会发现每个service的IP都会在这个网卡上配置
ip a


5. 最好重启一下kubelet,不重启应该也没关系,如果生产环境在运行这,最好不要重启

6.3 使用ipvsadm查看生成的规则

yum -y install ipvsadm

会看到svc的ip和后端pod_ip
[root@master01 kubernetes]# 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.0.1:443 rr
  -> 192.168.8.10:6443            Masq    1      0          0         
TCP  10.96.0.10:53 rr
  -> 10.244.0.38:53               Masq    1      0          0         
  -> 10.244.0.39:53               Masq    1      0          0         
TCP  10.96.0.10:9153 rr
  -> 10.244.0.38:9153             Masq    1      0          0         
  -> 10.244.0.39:9153             Masq    1      0          0         
TCP  10.109.208.211:80 rr
  -> 10.244.1.17:80               Masq    1      0          0         
  -> 10.244.2.25:80               Masq    1      0          0         
  -> 10.244.2.26:80               Masq    1      0          0         
UDP  10.96.0.10:53 rr
  -> 10.244.0.38:53               Masq    1      0          0         
  -> 10.244.0.39:53               Masq    1      0          0 

七、特殊类型的service

7.1 Headless Service

Headless Service
因为k8s中有DNS可以将service_name的名称解析为cluster_ip,这种类型的service就是为了没有cluster_ip的service_name所存在的,并且这种的service可以被解析为后端的pod_ip不需要cluster_ip,这service都称为无头服务,一般都是有状态服务
stateful(有状态):每个个体都具有一定程度的独特性,由其存储的状态决定;
stateless(无状态)
无头服务的DNS解析记录:
(正向解析记录):<a>-<b>-<c>-<d>.<service>.<ns>.svc.<zone> A PodIP
(反向解析记录):<d>.<c>.<b>.<a>.in-addr.arpa IN PTR <hostname>.<service>.<ns>.svc.<zone>

例:

  1. 编写无头服务svc
vim demoapp-headless-svc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
   name: demoapp01
   labels:
      app: demoapp01
      env: dev
   namespace: dev
spec:
   replicas: 3
   selector:
      matchLabels:
         app: demoapp01
   template:
      metadata:
         labels:
            app: demoapp01
      spec:
         containers:
         - name: demoapp
           image: ikubernetes/demoapp:v1.0

---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-headless-svc
  namespace: dev
spec:
  clusterIP: None          #这里需要把集群ip置为空
  selector:
    app: demoapp01
  ports:
  - port: 80
    targetPort: 80
    name: http
  1. 查看svc和后端pod
可以看到并没有集群ip
[root@master01 yaml]# kubectl get svc -n dev
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
demoapp-headless-svc      ClusterIP   None             <none>        80/TCP    4s

查看ep后端pod_ip,可以看到已经代理到后端pod了
[root@master01 yaml]# kubectl describe ep demoapp-headless-svc -n dev
Name:         demoapp-headless-svc
Namespace:    dev
Labels:       service.kubernetes.io/headless=
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2022-06-18T12:23:37Z
Subsets:
  Addresses:          10.244.1.19,10.244.1.20,10.244.2.29
  NotReadyAddresses:  <none>
  Ports:
    Name  Port  Protocol
    ----  ----  --------
    http  80    TCP

Events:  <none>
  1. 进入某个pod内的容器内测试解析结果
kubectl exec demoapp01-5bd6cd658d-8kzll -it /bin/sh  -n dev
正向解析,可以看到解析刚刚创建的无头svc就会得到后端pod的IP
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=A demoapp-headless-svc
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.2.29
Name:	demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.1.19
Name:	demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.1.20

反向解析,找刚刚部署的pod_ip进行反向解析会看到以下结果
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=PTR 10.244.1.19
Server:		10.96.0.10
Address:	10.96.0.10#53

19.1.244.10.in-addr.arpa	name = 10-244-1-19.demoapp-headless-svc.dev.svc.cluster.local.

7.2 ExternalName(外部服务名称)

svc创建的时候svc_name需要对应一个域名,svc_name -> CNAME, 对应是外部服务的名称,该服务要能在外部DNS服务中被解析;

例:

  1. yaml文件
kind: Service
apiVersion: v1
metadata:
  name: externalname-redis-svc
  namespace: dev
spec:
  type: ExternalName
  externalName: redis.ik8s.io     #这个是外部dns可以解析的地址
  ports:
  - protocol: TCP
    port: 6379
    targetPort: 6379
    nodePort: 0
  selector: {}      #定义为空,不需要标签选择器,可以去掉
  1. 查看
这种没有后端pod的svc是不会创建endpoint资源的
[root@master01 yaml]# kubectl get svc -n dev
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)    AGE
externalname-redis-svc    ExternalName   <none>           redis.ik8s.io   6379/TCP   10s

[root@master01 yaml]# kubectl describe svc externalname-redis-svc -n dev
Name:              externalname-redis-svc
Namespace:         dev
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ExternalName
IP:                
External Name:     redis.ik8s.io
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>
  1. 解析测试
进入任何一个pod容器内
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=CNAME externalname-redis-svc
Server:		10.96.0.10
Address:	10.96.0.10#53

externalname-redis-svc.dev.svc.cluster.local	canonical name = redis.ik8s.io.

#这里的正向解析是k8s中coreDNS将externalname-redis-svc解析为redis.ik8s.io.
#之后当k8s在试图解析redis.ik8s.io.这个域名时,发现coreDNS中没有这条记录,所以就会使用节点上的DNS,所以就解析出1.2.3.4
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup  externalname-redis-svc
Server:		10.96.0.10
Address:	10.96.0.10#53

externalname-redis-svc.dev.svc.cluster.local	canonical name = redis.ik8s.io.
Name:	redis.ik8s.io
Address: 1.2.3.4

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值