zoukankan      html  css  js  c++  java
  • 【解构云原生】初识Kubernetes Service

    编者按:云原生是网易杭州研究院(网易杭研)奉行的核心技术方向之一,开源容器平台Kubernetes作为云原生产业技术标准、云原生生态基石,在设计上不可避免有其复杂性,Kubernetes系列文章基于网易杭研资深工程师总结,多角度多层次介绍Kubernetes的原理及运用,如何解决生产中的实际需求及规避风险,希望与读者深入交流共同进步。

    本文由作者授权发布,未经许可,请勿转载。

    作者:李岚清,网易杭州研究院云计算技术中心资深工程师

    为什么引入service

    众所周知,pod的生命周期是不稳定的,可能会朝生夕死,这也就意味着pod的ip是不固定的。

    比如我们使用三副本的deployment部署了nginx服务,每个pod都会被分配一个ip,由于pod的生命周期不稳定,pod可能会被删除重建,而重建的话pod的ip地址就会改变。也有一种场景,我们可能会对nginx deployment进行扩缩容,从3副本扩容为5副本或者缩容为2副本。当我们需要访问上述的nginx服务时,客户端对于nginx服务的ip地址就很难配置和管理。

    因此,kubernetes社区就抽象出了service这个资源对象或者说逻辑概念。

    什么是service

    service是kubernetes中最核心的资源对象之一,kubernetes中的每个service其实就是我们经常提到的“微服务”。

    service定义了一个服务的入口地址,它通过label selector 关联后端的pod。service会被自动分配一个ClusterIP,service的生命周期是稳定的,它的ClusterIP也不会发生改变,用户通过访问service的ClusterIP来访问后端的pod。所以,不管后端pod如何扩缩容、如何删除重建,客户端都不需要关心。

    (1)创建一个三副本的nginx deployment:
    nginx.yaml

    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - image: nginx
            imagePullPolicy: Always
            name: nginx
    # kubectl create -f nginx.yaml
    deployment.extensions/nginx created
    
    # kubectl get pods -o wide
    nginx-5c7588df-5dmmp                                            1/1     Running       0          57s     10.120.49.230   pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>
    nginx-5c7588df-gb2d8                                            1/1     Running       0          57s     10.120.49.152   pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>
    nginx-5c7588df-gdngk                                            1/1     Running       0          57s     10.120.49.23    pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>

    (2)创建service,通过label selector关联nginx pod:

    svc.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
    spec:
      type: ClusterIP
      selector:
        app: nginx
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
    # kubectl create -f svc.yaml
    service/nginx created
    
    # kubectl get svc nginx -o wide
    NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
    nginx   ClusterIP   10.178.4.2   <none>        80/TCP    23s   app=nginx

    (3)在k8s节点上访问service地址

    # curl 10.178.4.2:80
    <!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>

    实现原理

    service中有几个关键字段:

    • spec.selector: 通过该字段关联属于该service的pod
    • spec.clusterIP: k8s自动分配的虚拟ip地址
    • spec.ports: 定义了监听端口和目的端口。用户可以通过访问clusterip:监听端口来访问后端的pod

    当用户创建一个service时,kube-controller-manager会自动创建一个跟service同名的endpoints资源:

    # kubectl get endpoints nginx
    NAME    ENDPOINTS                                           AGE
    nginx   10.120.49.152:80,10.120.49.23:80,10.120.49.230:80   12m

    endpoints资源中,保存了该service关联的pod列表,这个列表是kube-controller-manager自动维护的,当发生pod的增删时,这个列表会被自动刷新。

    比如,我们删除了其中的一个pod:

    # kubectl delete pods nginx-5c7588df-5dmmp
    pod "nginx-5c7588df-5dmmp" deleted
    
    # kubectl get pods
    nginx-5c7588df-ctcml                                            1/1     Running     0          6s
    nginx-5c7588df-gb2d8                                            1/1     Running     0          18m
    nginx-5c7588df-gdngk                                            1/1     Running     0          18m

    可以看到kube-controller-manager立马补充了一个新的pod。然后我们再看一下endpoints资源,后端pod列表也被自动更新了:

    # kubectl get endpoints nginx
    NAME    ENDPOINTS                                          AGE
    nginx   10.120.49.152:80,10.120.49.23:80,10.120.49.73:80   16m

    那么,当用户去访问clusterip:port时,流量是如何负载均衡到后端pod的呢?

    k8s在每个node上运行了一个kube-proxy组件,kube-proxy会watch service和endpoints资源,通过配置iptables规则(现在也支持ipvs,不过不在本文章讨论范围之内)来实现service的负载均衡。

    可以在任一个k8s node上看一下上述nginx service的iptables规则:

    # iptables -t nat -L PREROUTING
    Chain PREROUTING (policy ACCEPT)
    target     prot opt source               destination
    KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
    
    # iptables -t nat -L KUBE-SERVICES
    Chain KUBE-SERVICES (2 references)
    target     prot opt source               destination
    KUBE-SVC-4N57TFCL4MD7ZTDA  tcp  --  anywhere             10.178.4.2           /* default/nginx: cluster IP */ tcp dpt:http
    
    # iptables -t nat -L KUBE-SVC-4N57TFCL4MD7ZTDA
    Chain KUBE-SVC-4N57TFCL4MD7ZTDA (1 references)
    target     prot opt source               destination
    KUBE-SEP-AHN4ALGUQHWJZNII  all  --  anywhere             anywhere             statistic mode random probability 0.33332999982
    KUBE-SEP-BDD6UBFFJ4G2PJDO  all  --  anywhere             anywhere             statistic mode random probability 0.50000000000
    KUBE-SEP-UR2OSKI3P5GEGC2Q  all  --  anywhere             anywhere
    
    # iptables -t nat -L KUBE-SEP-AHN4ALGUQHWJZNII
    Chain KUBE-SEP-AHN4ALGUQHWJZNII (1 references)
    target     prot opt source               destination
    KUBE-MARK-MASQ  all  --  10.120.49.152        anywhere
    DNAT       tcp  --  anywhere             anywhere             tcp to:10.120.49.152:80

    当用户访问clusterip:port时,iptables会通过iptables DNAT 均衡的负载均衡到后端pod。

    service ClusterIP

    service的ClusterIP是一个虚拟ip,它没有附着在任何的网络设备上,仅仅存在于iptables规则中,通过dnat实现访问clusterIP时的负载均衡。

    当用户创建service时,k8s会自动从service网段中分配一个空闲ip设置到.spec.clusterIP字段。当然,k8s也支持用户在创建svc时自己指定clusterIP。

    service的网段是通过 kube-apiserver的命令行参数--service-cluster-ip-range配置的,不允许变更。

    service网段不能跟机房网络、docker网段、容器网段冲突,否则可能会导致网络不通。

    service的clusterIP是k8s集群内的虚拟ip,不同的k8s集群可以使用相同的service网段,在k8s集群外是访问不通service的clusterIP的。

    service域名

    kubernetes是有自己的域名解析服务的。比如我们可以通过访问域名nginx.default.svc.cluster.local来访问上述的nginx服务:

    $ curl 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>

    域名格式为: ${ServiceName}.${Namespace}.svc.${ClusterDomain}. 其中${ClusterDomain}的默认值是cluster.local,可以通过kubelet的命令行参数----cluster-domain进行配置。

    headless service

    当不需要service ip的时候,可以在创建service的时候指定spec.clusterIP: None,这种service即是headless service。由于没有分配service ip,kube-proxy也不会处理这种service。

    DNS对这种service的解析:

    • 当service里定义selector的时候:Endpoints controller会创建相应的endpoints。DNS里的A记录会将svc地址解析为这些pods的地址
    • 当service里没有定义selector:Endpoints controller不会创建endpoints。DNS会这样处理:
      • 首先CNAME到service里定义的ExternalName
      • 没有定义ExternalName的话,会搜寻所有的和这个service共享name的Endpoints,然后将A记录解析到这些Endpoints的地址

     

    service的不同类型

    service支持多种不同的类型,包括ClusterIPNodePortLoadBalancer,通过字段spec.type进行配置。

    ClusterIP service

    默认类型。对于ClusterIP service, k8s会自动分配一个只在集群内可达的虚拟的ClusterIP,在k8s集群外无法访问。

    NodePort service

    k8s除了会给NodePort service自动分配一个ClusterIP,还会自动分配一个nodeport端口。集群外的客户端可以访问任一node的ip加nodeport,即可负载均衡到后端pod。

    nodeport的端口范围可以通过kube-apiserver的命令行参数--service-node-port-range配置,默认值是30000-32767,当前我们的配置是30000-34999

    但是客户端访问哪个node ip也是需要考虑的一个问题,需要考虑高可用。而且NodePort会导致访问后端服务时多了一跳,并且可能会做snat看不到源ip。

    另外需要注意的是,service-node-port-range 不能够跟几个端口范围冲突:

    • Linux的net.ipv4.ip_local_port_range,可以配置为 35000-60999
    • ingress nginx中的四层负载均衡,端口必须小于30000
    • 其他普通业务的端口也需要小于30000

    LoadBalancer service

    LoadBalancer service需要对接云服务提供商的NLB服务。当用户创建一个LoadBalancer类型的sevice时,cloud-controller-manager会调用NLB的API自动创建LB实例,并且将service后端的pod挂到LB实例后端。

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
    spec:
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
      type: LoadBalancer
    $ kubectl get svc nginx
    NAME      TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    nginx     LoadBalancer   10.178.8.216   10.194.73.147   80:32514/TCP   3s

    service中会话保持

    用户可以通过配置spec.serviceAffinity=ClientIP来实现基于客户端ip的会话保持功能。 该字段默认为None。

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

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
    spec:
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
      type: ClusterIP
      sessionAffinity: ClientIP

    kubernetes service

    当我们部署好一个k8s集群之后,发现系统自动帮忙在default namespace下创建了一个name为kubernetes的service:

    # kubectl get svc kubernetes -o yaml
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        component: apiserver
        provider: kubernetes
      name: kubernetes
      namespace: default
    spec:
      clusterIP: 10.178.4.1
      ports:
      - name: https
        port: 443
        protocol: TCP
        targetPort: 6443
      sessionAffinity: None
      type: ClusterIP
    status:
      loadBalancer: {}

    可以看到kubernetes svc的ip是--service-cluster-ip-range的第一个ip,并且该service没有设置spec.selector。理论上来说,对于没有设置selector的svc,kube-controller-manager不会自动创建同名的endpoints资源出来。

    但是我们看到是有同名的endpoints存在的,并且多个apiserver的地址也被保存在endpoints资源中:

    # kubectl get ep kubernetes
    NAME         ENDPOINTS                         AGE
    kubernetes   10.120.0.2:6443,10.120.0.3:6443   137d

    具体是如何实现的,感兴趣的可以看下源码k8s.io/kubernetes/pkg/master/reconcilers

    Frequently Asked Questions

    问题一 为什么service clusterip无法ping通

    因为service clusterip是一个k8s集群内部的虚拟ip,没有附着在任何网络设备上,仅仅存在于iptables nat规则中,用来实现负载均衡。

    问题二 为什么service的网段不能跟docker网段、容器网段、机房网段冲突

    假如service网段跟上述网段冲突,很容易导致容器或者在k8s node上访问上述网段时发生网络不通的情况。

    问题三 为什么在k8s集群外无法访问service clusterip

    service clusterip是k8s集群内可达的虚拟ip,集群外不可达。不同的k8s集群可以使用相同的service网段。

    或者说,集群外的机器上没有本k8s集群的kube-proxy组件,没有创建对应的iptables规则,因此集群外访问不通service clusterip。

    问题四 能否扩容service网段

    原则上这个网段是不允许更改的,但是假如因为前期规划的问题分配的网段过小,实际可以通过比较hack的运维手段扩容service网段。

    问题五 service是否支持七层的负载均衡

    service仅支持四层的负载均衡,七层的负载均衡需要使用ingress

    参考文档

     

    作者简介

    李岚清,网易杭州研究院云计算技术中心容器编排团队资深系统开发工程师,具有多年Kubernetes开发、运维经验,主导实现了容器网络管理、容器混部等生产级核心系统研发,推动网易集团内部电商、音乐、传媒、教育等多个业务的容器化。

  • 相关阅读:
    (二)springmvc+mybatis+dubbo+zookeeper分布式架构 整合
    (一)springmvc+mybatis+dubbo+zookeeper分布式架构 整合
    微服务架构 SpringCloud(四)Ribbon
    微服务架构 SpringCloud(三)注册中心集群篇
    微服务架构 SpringCloud(二)Eureka(服务注册和服务发现基础篇)
    微服务架构 SpringCloud(一)组件和概念介绍
    企业分布式微服务云SpringCloud SpringBoot mybatis (十四)服务注册(consul)
    20 TextView显示阴影
    19 shape
    18 手动发广播扫描sd卡
  • 原文地址:https://www.cnblogs.com/163yun/p/12965323.html
Copyright © 2011-2022 走看看