• 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 {
             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
    
    作者:Lawrence

    -------------------------------------------

    个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!

    扫描上面二维码关注我
    如果你真心觉得文章写得不错,而且对你有所帮助,那就不妨帮忙“推荐"一下,您的“推荐”和”打赏“将是我最大的写作动力!
    本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.
  • 相关阅读:
    面试准备
    session
    memcached优化方案实例
    MySQL用户管理
    MySQL事务
    Linux防火墙
    Linux权限体系
    Linux查看日志文件
    查看系统状态
    负载均衡(六)分表分库的负载均衡
  • 原文地址:https://www.cnblogs.com/hujinzhong/p/14994030.html
Copyright © 2020-2023  润新知