k8s四層負載均衡--Service


k8s四層負載均衡--Service

一、四層負載均衡Service概述

1.1、為什么要有Service

在kubernetes中,Pod是有生命周期的,如果Pod重啟它的IP很有可能會發生變化。如果我們的服務都是將Pod的IP地址寫死,Pod掛掉或者重啟,和剛才重啟的pod相關聯的其他服務將會找不到它所關聯的Pod,為了解決這個問題,在kubernetes中定義了service資源對象,Service 定義了一個服務訪問的入口,客戶端通過這個入口即可訪問服務背后的應用集群實例,service是一組Pod的邏輯集合,這一組Pod能夠被Service訪問到,通常是通過Label Selector實現的。

image-20210710100045005

1)pod ip經常變化,service是pod的代理,我們客戶端訪問,只需要訪問service,就會把請求代理到Pod

2)pod ip在k8s集群之外無法訪問,所以需要創建service,這個service可以在k8s集群外訪問的。

1.2、Service概述

service是一個固定接入層,客戶端可以通過訪問service的ip和端口訪問到service關聯的后端pod,這個service工作依賴於在kubernetes集群之上部署的一個附件,就是kubernetes的dns服務(不同kubernetes版本的dns默認使用的也是不一樣的,1.11之前的版本使用的是kubeDNs,較新的版本使用的是coredns),service的名稱解析是依賴於dns附件的,因此在部署完k8s之后需要再部署dns附件,kubernetes要想給客戶端提供網絡功能,需要依賴第三方的網絡插件(flannel,calico等)。每個K8s節點上都有一個組件叫做kube-proxy,kube-proxy這個組件將始終監視着apiserver中有關service資源的變動信息,需要跟master之上的apiserver交互,隨時連接到apiserver上獲取任何一個與service資源相關的資源變動狀態,這種是通過kubernetes中固有的一種請求方法watch(監視)來實現的,一旦有service資源的內容發生變動(如創建,刪除),kube-proxy都會將它轉化成當前節點之上的能夠實現service資源調度,把我們請求調度到后端特定的pod資源之上的規則,這個規則可能是iptables,也可能是ipvs,取決於service的實現方式。

1.3、Service工作原理

k8s在創建Service時,會根據標簽選擇器selector(lable selector)來查找Pod,據此創建與Service同名的endpoint對象,當Pod 地址發生變化時,endpoint也會隨之發生變化,service接收前端client請求的時候,就會通過endpoint,找到轉發到哪個Pod進行訪問的地址。(至於轉發到哪個節點的Pod,由負載均衡kube-proxy決定)

[root@k8s-master1 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   37h
[root@k8s-master1 ~]# kubectl get endpoints
NAME         ENDPOINTS             AGE
kubernetes   192.168.40.180:6443   37h

1.4、kubernets中三類IP地址

1)Node Network(節點網絡):物理節點或者虛擬節點的網絡,如eth0接口上的網路地址

[root@k8s-master1 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:52:bf:68 brd ff:ff:ff:ff:ff:ff
    inet 192.168.40.180/24 brd 192.168.40.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe52:bf68/64 scope link 
       valid_lft forever preferred_lft forever

2)Pod network(pod 網絡),創建的Pod具有的IP地址

[root@k8s-master1 ~]# kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
myapp-v1-75fb478d6c-89w27   1/1     Running   0          10s   10.244.36.105   k8s-node1   <none>           <none>
myapp-v1-75fb478d6c-dg4bh   1/1     Running   0          10s   10.244.36.104   k8s-node1   <none>           <none>

# Node Network和Pod network這兩種網絡地址是我們實實在在配置的,其中節點網絡地址是配置在節點接口之上,而pod網絡地址是配置在pod資源之上的,因此這些地址都是配置在某些設備之上的,這些設備可能是硬件,也可能是軟件模擬的

3)Cluster Network(集群地址,也稱為service network),這個地址是虛擬的地址(virtual ip),沒有配置在某個接口上,只是出現在service的規則當中

[root@k8s-master1 ~]# kubectl get svc
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        
kubernetes         ClusterIP   10.96.0.1           <none>        443/TCP

1.5、Service的四種類型

1.5.1、ExternalName

適用於k8s集群內部容器訪問外部資源,它沒有selector,也沒有定義任何的端口和Endpoint。以下Service 定義的是將prod名稱空間中的my-service服務映射到my.database.example.com

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com
  
# 當查詢主機 my-service.prod.svc.cluster.local 時,DNS將返回值為my.database.example.com的CNAME記錄
# service的FQDN是: <service_name>.<namespace>.svc.cluster.local

1.5.2、ClusterIP

# 通過k8s集群內部IP暴露服務,選擇該值,服務只能夠在集群內部訪問,這也是默認的ServiceType。
[root@k8s-master1 ~]# kubectl get svc -n kube-system 
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                  AGE
kube-dns         ClusterIP   10.96.0.10    <none>        53/UDP,53/TCP,9153/TCP   37h
metrics-server   ClusterIP   10.100.13.0   <none>        443/TCP                  35h

1.5.3、NodePort

通過每個Node節點上的IP和靜態端口暴露k8s集群內部的服務。通過請求<NodeIP>:<NodePort>可以把請求代理到內部的pod。

訪問流量:Client ==> NodeIP:NodePort ==> Service Ip:ServicePort ==> PodIP:ContainerPort

1.5.4、LoadBalancer

使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到NodePort服務和ClusterIP 服務

二、Service創建

2.1、創建類型ClusterIP的Service

1)創建deployment

[root@k8s-master1 ~]# cat pod_test.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80  #pod中的容器需要暴露的端口

# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f pod_test.yaml

# 查看剛才創建的Pod ip地址
[root@k8s-master1 ~]# kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
my-nginx-69f769d56f-gr5jg   1/1     Running   0          6s    10.244.169.149   k8s-node2   <none>           <none>
my-nginx-69f769d56f-h52dw   1/1     Running   0          6s    10.244.36.108    k8s-node1   <none>           <none>
[root@k8s-master1 ~]# kubectl get pods --show-labels
NAME                        READY   STATUS    RESTARTS   AGE   LABELS
my-nginx-69f769d56f-gr5jg   1/1     Running   0          52s   pod-template-hash=69f769d56f,run=my-nginx
my-nginx-69f769d56f-h52dw   1/1     Running   0          52s   pod-template-hash=69f769d56f,run=my-nginx

2)創建service

[root@k8s-master1 ~]# cat service_test.yaml 
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: ClusterIP
  ports:
  - port: 80   #service的端口,暴露給k8s集群內部服務訪問
    protocol: TCP
    targetPort: 80    #pod容器中定義的端口
  selector:
    run: my-nginx  #選擇擁有run=my-nginx標簽的pod
    
[root@k8s-master1 ~]# kubectl apply -f service_test.yaml
service/my-nginx created
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
my-nginx   ClusterIP   10.107.97.200   <none>        80/TCP    14s
[root@k8s-master1 ~]# curl 10.107.97.200
<!DOCTYPE html>
<html>
<title>Welcome to nginx!</title>
</html>
.....

# 查看service詳細信息
[root@k8s-master1 ~]# kubectl describe svc my-nginx
Name:              my-nginx
Namespace:         default
Labels:            run=my-nginx
Annotations:       <none>
Selector:          run=my-nginx
Type:              ClusterIP
IP Families:       <none>
IP:                10.107.97.200
IPs:               10.107.97.200
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.169.149:80,10.244.36.108:80
Session Affinity:  None
Events:            <none>

# 查看endpoints詳細信息
[root@k8s-master1 ~]# kubectl get ep my-nginx
NAME       ENDPOINTS                            AGE
my-nginx   10.244.169.149:80,10.244.36.108:80   2m21s

# service可以對外提供統一固定的ip地址,並將請求重定向至集群中的pod。其中“將請求重定向至集群中的pod”就是通過endpoint與selector協同工作實現。selector是用於選擇pod,由selector選擇出來的pod的ip地址和端口號,將會被記錄在endpoint中。endpoint便記錄了所有pod的ip地址和端口號。當一個請求訪問到service的ip地址時,就會從endpoint中選擇出一個ip地址和端口號,然后將請求重定向至pod中。具體把請求代理到哪個pod,需要的就是kube-proxy的輪詢實現的。service不會直接到pod,service是直接到endpoint資源,就是地址加端口,再由endpoint再關聯到pod。

3)service解析FQDN

# service解析
service只要創建完成,我們就可以直接解析它的服務名,每一個服務創建完成后都會在集群dns中動態添加一個資源記錄,添加完成后我們就可以解析了
資源記錄格式是:
    SVC_NAME.NS_NAME.DOMAIN.LTD.
    服務名.命名空間.域名后綴
集群默認的域名后綴是svc.cluster.local.
就像我們上面創建的my-nginx這個服務,它的完整名稱解析就是:my-nginx.default.svc.cluster.local
[root@k8s-master1 ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-69f769d56f-gr5jg   1/1     Running   0          9m55s
my-nginx-69f769d56f-h52dw   1/1     Running   0          9m55s
[root@k8s-master1 ~]# kubectl exec -it my-nginx-69f769d56f-gr5jg -- /bin/bash
root@my-nginx-69f769d56f-gr5jg:/# cat /etc/resolv.conf                   
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
root@my-nginx-69f769d56f-gr5jg:/# curl my-nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@my-nginx-69f769d56f-gr5jg:/#

2.2、創建類型NodePort的Service

1)創建deployment

# 創建一個pod資源
[root@k8s-master1 ~]# cat pod_nodeport.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx-nodeport
spec:
  selector:
    matchLabels:
      run: my-nginx-nodeport
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx-nodeport
    spec:
      containers:
      - name: my-nginx-nodeport-container
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f pod_nodeport.yaml 
deployment.apps/my-nginx-nodeport created
# 查看pod是否創建成功
[root@k8s-master1 ~]# kubectl get pods -l run=my-nginx-nodeport
NAME                                 READY   STATUS    RESTARTS   AGE
my-nginx-nodeport-649c945f85-7nngb   1/1     Running   0          9s
my-nginx-nodeport-649c945f85-cfgjk   1/1     Running   0          10s

2)創建service

[root@k8s-master1 ~]# cat service_nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-nodeport
  labels:
    run: my-nginx-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30380
  selector:
    run: my-nginx-nodeport

# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f service_nodeport.yaml 
service/my-nginx-nodeport created

# 查看剛才創建的service
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx-nodeport
NAME                TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
my-nginx-nodeport   NodePort   10.109.246.208   <none>        80:30380/TCP   11s

# 查看詳細信息
[root@k8s-master1 ~]# kubectl describe svc my-nginx-nodeport 
Name:                     my-nginx-nodeport
Namespace:                default
Labels:                   run=my-nginx-nodeport
Annotations:              <none>
Selector:                 run=my-nginx-nodeport
Type:                     NodePort
IP Families:              <none>
IP:                       10.109.246.208	# 只能在集群內訪問
IPs:                      10.109.246.208
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30380/TCP
Endpoints:                10.244.36.109:80,10.244.36.110:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

# 在集群外訪問:http://nodeIP:30380
# 服務請求走向:Client-node ip:30380 -> service ip:80 -> pod ip:container port

image-20210710112812135

2.3、創建類型ExternalName的Service

應用場景:跨名稱空間訪問
需求:default名稱空間下的client 服務想要訪問nginx-ns名稱空間下的nginx-svc服務

1)創建client的deployment及service

[root@k8s-master1 ~]# cat client.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
spec: 
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
   metadata:
    labels:
      app: busybox
   spec:
     containers:
     - name: busybox
       image: busybox
       command: ["/bin/sh","-c","sleep 36000"]

[root@k8s-master1 ~]# kubectl apply -f client.yaml

[root@k8s-master1 ~]# cat client_svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: client-svc
spec:
  type: ExternalName
  externalName: nginx-svc.nginx-ns.svc.cluster.local
  ports:
  - name: http
    port: 80
    targetPort: 80

# 該文件中指定了到 nginx-svc 的軟鏈,讓使用者感覺就好像調用自己命名空間的服務一樣。
# 查看pod是否正常運行
[root@k8s-master1 ~]# kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
client-76b6556d97-zx77m              1/1     Running   0          90s      
[root@k8s-master1 ~]# kubectl apply -f client_svc.yaml
[root@k8s-master1 ~]# kubectl get svc
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP                            PORT(S)        AGE
client-svc          ExternalName   <none>           nginx-svc.nginx-ns.svc.cluster.local   80/TCP         4s
[root@k8s-master1 ~]# kubectl describe svc client-svc
Name:              client-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ExternalName
IP Families:       <none>
IP:                
IPs:               <none>
External Name:     nginx-svc.nginx-ns.svc.cluster.local
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>

2)創建server的deployment及service

[root@k8s-master1 ~]# kubectl create ns nginx-ns
[root@k8s-master1 ~]# cat server_nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: nginx-ns
spec: 
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
   metadata:
    labels:
      app: nginx
   spec:
     containers:
     - name: nginx
       image: nginx
       imagePullPolicy: IfNotPresent
       
[root@k8s-master1 ~]# kubectl apply -f server_nginx.yaml
#查看pod是否創建成功
[root@k8s-master1 ~]# kubectl get pods -n nginx-ns
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7cf7d6dbc8-slmv4   1/1     Running   0          8s

[root@k8s-master1 ~]# cat nginx_svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: nginx-ns
spec:
  selector:
    app: nginx
  ports:
   - name: http
     protocol: TCP
     port: 80
     targetPort: 80
[root@k8s-master1 exter]# kubectl apply -f nginx_svc.yaml
[root@k8s-master1 ~]# kubectl get svc -n nginx-ns 
NAME        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
nginx-svc   ClusterIP   10.103.192.106   <none>        80/TCP    46s

3)訪問測試

# 登錄到client pod,訪問的結果一樣
[root@k8s-master1 ~]# kubectl exec -it  client-76b6556d97-zx77m -- /bin/sh
/ # wget -q -O - client-svc.default.svc.cluster.local
/ # wget -q -O - nginx-svc.nginx-ns.svc.cluster.local

2.4、自定義endpoint實現映射外部服務

需求:k8s集群引用外部的mysql數據庫

# 1.在k8s-node2上安裝mysql數據庫:
[root@k8s-node2 ~]# yum install mariadb-server.x86_64 -y
[root@k8s-node2 ~]# systemctl start mariadb
[root@k8s-node2 ~]# netstat -lntp|grep 3306
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN      36719/mysqld 

# 2.創建mysql_service
[root@k8s-master1 mysql]# cat mysql_service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: ClusterIP
  ports:
  - port: 3306
[root@k8s-master1 mysql]# kubectl apply -f mysql_service.yaml 
service/mysql created
[root@k8s-master1 mysql]# kubectl get svc | grep mysql
mysql               ClusterIP      10.103.151.40    <none>                                 3306/TCP       7s 

root@k8s-master1 mysql]# kubectl describe svc mysql
[root@k8s-master1 mysql]# kubectl describe svc mysql
Name:              mysql
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Families:       <none>
IP:                10.103.151.40
IPs:               10.103.151.40
Port:              <unset>  3306/TCP
TargetPort:        3306/TCP
Endpoints:         <none>	# 還沒有endpoint
Session Affinity:  None
Events:            <none>

# 3.創建自定義endpoint
[root@k8s-master1 mysql]# cat mysql_endpoint.yaml 
apiVersion: v1
kind: Endpoints
metadata:
  name: mysql
subsets:
- addresses:
  - ip: 192.168.40.182
  ports:
  - port: 3306

[root@k8s-master1 mysql]# kubectl apply -f mysql_endpoint.yaml 
endpoints/mysql created

[root@k8s-master1 mysql]# kubectl describe svc mysql
Name:              mysql
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Families:       <none>
IP:                10.103.151.40
IPs:               10.103.151.40
Port:              <unset>  3306/TCP
TargetPort:        3306/TCP
Endpoints:         192.168.40.182:3306	# 自定義的endpoint
Session Affinity:  None
Events:            <none>
# 上面配置就是將外部IP地址和服務引入到k8s集群內部,由service作為一個代理來達到能夠訪問外部服務的目的。

三、Service代理:kube-proxy組件

3.1、kube-proxy組件介紹

Kubernetes service只是把應用對外提供服務的方式做了抽象,真正的應用跑在Pod中的container里,我們的請求轉到kubernetes nodes對應的nodePort上,那么nodePort上的請求是如何進一步轉到提供后台服務的Pod的呢? 就是通過kube-proxy實現的:

kube-proxy部署在k8s的每一個Node節點上,是Kubernetes的核心組件,我們創建一個 service 的時候,kube-proxy 會在iptables中追加一些規則,為我們實現路由與負載均衡的功能。在k8s1.8之前,kube-proxy默認使用的是iptables模式,通過各個node節點上的iptables規則來實現service的負載均衡,但是隨着service數量的增大,iptables模式由於線性查找匹配、全量更新等特點,其性能會顯著下降。從k8s的1.8版本開始,kube-proxy引入了IPVS模式,IPVS模式與iptables同樣基於Netfilter,但是采用的hash表,因此當service數量達到一定規模時,hash查表的速度優勢就會顯現出來,從而提高service的服務性能。

service是一組pod的服務抽象,相當於一組pod的LB,負責將請求分發給對應的pod。service會為這個LB提供一個IP,一般稱為cluster IP。kube-proxy的作用主要是負責service的實現,具體來說,就是實現了內部從pod到service和外部的從node port向service的訪問。

1、kube-proxy其實就是管理service的訪問入口,包括集群內Pod到Service的訪問和集群外訪問service。

2、kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也可以稱為是Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。

3.2、kube-proxy三種工作模式

3.2.1、Userspace方式

image-20210710152503743

Client Pod要訪問Server Pod時,它先將請求發給內核空間中的service iptables規則,由它再將請求轉給監聽在指定套接字上的kube-proxy的端口,kube-proxy處理完請求,並分發請求到指定Server Pod后,再將請求轉發給內核空間中的service ip,由service iptables將請求轉給各個節點中的Server Pod。

這個模式有很大的問題,客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成后再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。由於其需要來回在用戶空間和內核空間交互通信,因此效率很差。在Kubernetes 1.1版本之前,userspace是默認的代理模型。

3.2.2、iptables方式

image-20210710152650631

客戶端IP請求時,直接請求本地內核service ip,根據iptables的規則直接將請求轉發到到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折

iptables代理模式由Kubernetes 1.1版本引入,自1.2版本開始成為默認類型

3.2.3、ipvs方式

image-20210710152808276

Kubernetes自1.9-alpha版本引入了ipvs代理模式,自1.11版本開始成為默認設置。客戶端請求時到達內核空間時,根據ipvs的規則直接分發到各pod上。kube-proxy會監視Kubernetes Service對象和Endpoints,調用netlink接口以相應地創建ipvs規則並定期與Kubernetes Service對象和Endpoints對象同步ipvs規則,以確保ipvs狀態與期望一致。訪問服務時,流量將被重定向到其中一個后端Pod。與iptables類似,ipvs基於netfilter 的 hook 功能,但使用哈希表作為底層數據結構並在內核空間中工作。這意味着ipvs可以更快地重定向流量,並且在同步代理規則時具有更好的性能。此外,ipvs為負載均衡算法提供了更多選項,例如:

rr:輪詢調度
lc:最小連接數
dh:目標哈希
sh:源哈希
sed:最短期望延遲
nq:不排隊調度

3.3、kube-proxy的watch機制

如果某個服務后端pod發生變化,標簽選擇器適應的pod又多一個,適應的信息會立即反映到apiserver上,而kube-proxy一定可以watch到etcd中的信息變化,而將它立即轉為ipvs或者iptables中的規則,這一切都是動態和實時的,刪除一個pod也是同樣的原理

image-20210710153119123

以上不論哪種,kube-proxy都通過watch的方式監控着apiserver寫入etcd中關於Pod的最新狀態信息,它一旦檢查到一個Pod資源被刪除了或新建了,它將立即將這些變化,反應再iptables 或 ipvs規則中,以便iptables和ipvs在調度Clinet Pod請求到Server Pod時,不會出現Server Pod不存在的情況。

自k8s1.11以后,service默認使用ipvs規則,若ipvs沒有被激活,則降級使用iptables規則

3.4、kube-proxy生成的iptables規則分析

3.4.1、ClusterIp類型Service的iptables規則分析

在k8s創建的service,雖然有ip地址,但是service的ip是虛擬的,不存在物理機上的,是在iptables或者ipvs規則里的

# 1.創建pod及clusterIp類型的service
[root@k8s-master1 ~]# cat pod_test.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80  #pod中的容器需要暴露的端口
[root@k8s-master1 ~]# cat service_test.yaml 
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: ClusterIP
  ports:
  - port: 80   #service的端口,暴露給k8s集群內部服務訪問
    protocol: TCP
    targetPort: 80    #pod容器中定義的端口
  selector:
    run: my-nginx  #選擇擁有run=my-nginx標簽的pod
    
[root@k8s-master1 ~]# kubectl apply -f pod_test.yaml 
deployment.apps/my-nginx created
[root@k8s-master1 ~]# kubectl apply -f service_test.yaml 
service/my-nginx created

# 2.查看svc和pod的ip
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
my-nginx   ClusterIP   10.103.178.2   <none>        80/TCP    107s
[root@k8s-master1 ~]# kubectl get pods -l run=my-nginx -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP              NODE        NOMINATED NODE   READINESS GATES
my-nginx-69f769d56f-drwh7   1/1     Running   0          2m10s   10.244.36.112   k8s-node1   <none>           <none>
my-nginx-69f769d56f-phqq8   1/1     Running   0          2m10s   10.244.36.113   k8s-node1   <none>           <none>

# 3.查看svc ip的iptables規則
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.103.178.2
KUBE-MARK-MASQ  tcp  -- !10.244.0.0/16        10.103.178.2         /* default/my-nginx cluster IP */ tcp dpt:http
KUBE-SVC-L65ENXXZWWSAPRCR  tcp  --  anywhere             10.103.178.2         /* default/my-nginx cluster IP */ tcp dpt:http
[root@k8s-master1 ~]# iptables -t nat -L | grep KUBE-SVC-L65ENXXZWWSAPRCR
KUBE-SVC-L65ENXXZWWSAPRCR  tcp  --  anywhere             10.103.178.2         /* default/my-nginx cluster IP */ tcp dpt:http
Chain KUBE-SVC-L65ENXXZWWSAPRCR (1 references)

# 4、查看pod ip的iptables規則
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.244.36.112
KUBE-MARK-MASQ  all  --  10.244.36.112        anywhere             /* default/my-nginx */
DNAT       tcp  --  anywhere             anywhere             /* default/my-nginx */ tcp to:10.244.36.112:80
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.244.36.113
KUBE-MARK-MASQ  all  --  10.244.36.113        anywhere             /* default/my-nginx */
DNAT       tcp  --  anywhere             anywhere             /* default/my-nginx */ tcp to:10.244.36.113:80

總結:通過上面可以看到之前創建的service,會通過kube-proxy在iptables中生成一個規則,來實現流量路由,有一系列目標為 KUBE-SVC-xxx 鏈的規則,每條規則都會匹配某個目標 ip 與端口。也就是說訪問某個 ip:port 的請求會由 KUBE-SVC-xxx 鏈來處理。這個目標 IP 其實就是service ip。

3.4.2、nodePort類型Service的iptables規則分析

# 1.創建pod及nodePort類型的service
[root@k8s-master1 ~]# cat pod_nodeport.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx-nodeport
spec:
  selector:
    matchLabels:
      run: my-nginx-nodeport
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx-nodeport
    spec:
      containers:
      - name: my-nginx-nodeport-container
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
[root@k8s-master1 ~]# cat service_nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-nodeport
  labels:
    run: my-nginx-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30380
  selector:
    run: my-nginx-nodeport
    
[root@k8s-master1 ~]# kubectl apply -f pod_nodeport.yaml 
deployment.apps/my-nginx-nodeport created
[root@k8s-master1 ~]# kubectl apply -f service_nodeport.yaml
service/my-nginx-nodeport created

# 2.查看svc和pod的ip
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx-nodeport
NAME                TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-nginx-nodeport   NodePort   10.109.183.22   <none>        80:30380/TCP   41s
[root@k8s-master1 ~]# kubectl get pods -l  run=my-nginx-nodeport -o wide
NAME                                 READY   STATUS    RESTARTS   AGE     IP              NODE        NOMINATED NODE   READINESS GATES
my-nginx-nodeport-649c945f85-4zk46   1/1     Running   0          9m27s   10.244.36.115   k8s-node1   <none>           <none>
my-nginx-nodeport-649c945f85-xnwks   1/1     Running   0          9m27s   10.244.36.114   k8s-node1   <none>           <none>

# 3.查看關於nodeport=30380的iptables規則
[root@k8s-master1 ~]# iptables -t nat -S | grep 30380
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q

# 4.查看KUBE-SVC-J5QV2XWG4FEBPH3Q鏈
[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SVC-J5QV2XWG4FEBPH3Q
-N KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SERVICES -d 10.109.183.22/32 -p tcp -m comment --comment "default/my-nginx-nodeport cluster IP" -m tcp --dport 80 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EBCJC5WP2H42KJXA
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-BGGUKAMS6QUZEUGX

# 5、查看KUBE-SEP-EBCJC5WP2H42KJXA鏈及KUBE-SEP-BGGUKAMS6QUZEUGX
[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SEP-EBCJC5WP2H42KJXA
-N KUBE-SEP-EBCJC5WP2H42KJXA
-A KUBE-SEP-EBCJC5WP2H42KJXA -s 10.244.36.114/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-EBCJC5WP2H42KJXA -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.36.114:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EBCJC5WP2H42KJXA

[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SEP-BGGUKAMS6QUZEUGX
-N KUBE-SEP-BGGUKAMS6QUZEUGX
-A KUBE-SEP-BGGUKAMS6QUZEUGX -s 10.244.36.115/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-BGGUKAMS6QUZEUGX -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.36.115:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-BGGUKAMS6QUZEUGX

四、Service服務發現:coredns組件

CoreDNS 其實就是一個 DNS 服務,而 DNS 作為一種常見的服務發現手段,所以很多開源項目以及工程師都會使用 CoreDNS 為集群提供服務發現的功能,Kubernetes 就在集群中使用 CoreDNS 解決服務發現的問題。 作為一個加入 CNCF(Cloud Native Computing Foundation)的服務, CoreDNS 的實現非常簡單。

[root@k8s-master1 ~]# cat dig.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dig
  namespace: default
spec:
  containers:
  - name: dig
    image:  xianchao/dig:latest
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f dig.yaml 
pod/dig configured

# 查看默認名稱空間的kubernetes服務
[root@k8s-master1 ~]# kubectl get svc | grep kubernetes
kubernetes          ClusterIP   10.96.0.1       <none>        443/TCP        43h
[root@k8s-master1 ~]# kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP              NODE        NOMINATED NODE   READINESS GATES
dig                                  1/1     Running   0          2m5s   10.244.36.116   k8s-node1   <none>           <none>

# 解析dns,如有以下返回說明dns安裝成功
[root@k8s-master1 ~]# kubectl exec -it dig -- nslookup kubernetes
Server:		10.96.0.10
Address:	10.96.0.10#53
Name:	kubernetes.default.svc.cluster.local
Address: 10.96.0.1

[root@k8s-master1 ~]# kubectl exec -it dig -- cat /etc/resolv.conf 
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM