• istio流量管理


    1 准备工作

    1.1 在k8s部署istio

    Istio在k8s集群内的部署很简单,非生产要求的部署,可以直接在https://github.com/istio/istio/releases 下载最新的发布包,压缩包里有供简单部署的yaml文件:

    $ for i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl apply -f $i; done
    $ kubectl apply -f install/kubernetes/istio-demo.yaml
    

    注意,上面两条指令会把主要的组件都给装上,镜像下载比较费力,内存消耗也比较大。没个十几G内存的同学请慎重。

    部署完成后会新增一个命名空间istio-system:

    $ kubectl get pods -n istio-system
    NAME                                                           READY   STATUS      RESTARTS   AGE
    grafana-f8467cc6-rbjlg                                         1/1     Running     0          1m
    istio-citadel-78df5b548f-g5cpw                                 1/1     Running     0          1m
    istio-cleanup-secrets-release-1.1-20190308-09-16-8s2mp         0/1     Completed   0          2m
    istio-egressgateway-78569df5c4-zwtb5                           1/1     Running     0          1m
    istio-galley-74d5f764fc-q7nrk                                  1/1     Running     0          1m
    istio-grafana-post-install-release-1.1-20190308-09-16-2p7m5    0/1     Completed   0          2m
    istio-ingressgateway-7ddcfd665c-dmtqz                          1/1     Running     0          1m
    istio-pilot-f479bbf5c-qwr28                                    2/2     Running     0          1m
    istio-policy-6fccc5c868-xhblv                                  2/2     Running     2          1m
    istio-security-post-install-release-1.1-20190308-09-16-bmfs4   0/1     Completed   0          2m
    istio-sidecar-injector-78499d85b8-x44m6                        1/1     Running     0          1m
    istio-telemetry-78b96c6cb6-ldm9q                               2/2     Running     2          1m
    istio-tracing-69b5f778b7-s2zvw                                 1/1     Running     0          1m
    kiali-99f7467dc-6rvwp                                          1/1     Running     0          1m
    prometheus-67cdb66cbb-9w2hm                                    1/1     Running     0          1m
    

    1.2 istio自动注入

    在介绍istio原理时有提到,istio会在每一个被管理的pod里注入一个sidecar容器envoy。那么istio是如何完成注入的呢,主要有2种方式:

    1. 手动注入:如下命令,在kubectl前先执行istioctl手动注入
    $ istioctl kube-inject -f <your-app-spec>.yaml | kubectl apply -f -
    
    1. 自动注入:运用k8s的admission-control完成自动注入
      • kube-apiserver配置文件的admission-control参数,增加MutatingAdmissionWebhook 以及 ValidatingAdmissionWebhook 两项
      • 给命名空间打上自动注入的标签(下方我们允许istio在default空间自动注入,同时禁用了istio-system空间的自动注入):
    $ kubectl label namespace default istio-injection=enabled
    $ kubectl get namespace -L istio-injection
    NAME              STATUS   AGE     ISTIO-INJECTION
    default           Active   4d21h   enabled
    ingress-nginx     Active   4d17h   
    istio-system      Active   4d17h   disabled
    kube-node-lease   Active   4d21h   
    kube-public       Active   4d21h   
    kube-system       Active   4d21h
    

    后面的测试,我们以第2种方式自动注入envoy。
    注:通过在deployment文件的annotations参数里加上sidecar.istio.io/inject: "false",可以覆盖命名空间的标签,禁止istio对本pod的自动注入

    1.3 应用部署要求

    Istio原生与k8s能够无缝对接, istio的存在与否对应用程序本身来说,也是透明的。所以应用的部署,主要涉及的是yaml文件的修改完善:

    1. Deployment文件的要求:
      • 应带有app和version标签,这个主要是用来识别不同版本的pod。
      • Deployment应明确列出端口列表,istio会忽略未列出的端口。
      • 下方为data-product的示例,注释部分为需要注意的地方:
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      # 建议pod名称 = 应用名 + 版本号
      name: data-product-v4.0
      namespace: default
    spec:
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "true"
          namespace: default
          labels:
            # 标签1:应用名称
            app: data-product
            # 标签2:版本号
            version: v4.0
        spec:
          containers:
          - name: data-product
            ports:
            # 需要Istio管理的端口号列表
            - containerPort: 50051
    ……
    
    1. Service文件的要求:
      • 需要指定端口的协议类型,否则默认按TCP协议处理:
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: data-product
      namespace: default
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/path:   /metrics
        prometheus.io/port:   '8081'
      labels:
        kubernetes.io/cluster-service: "true"
        kubernetes.io/name: "data-product"
    spec:
      ports:
        - name: data-product
          port: 50051
          # 指定端口的协议类型为grpc
          name: grpc
          targetPort: 50051
      selector:
        app: data-product
    

    2 负载均衡

    $ kubectl get po -o wide
    NAME                                     READY   STATUS  
    business-product-6b954db744-cgn8k        2/2     Running 
    business-product-6b954db744-dgm4x        2/2     Running 
    business-product-6b954db744-n5c8p        2/2     Running 
    business-product-v4.0-747ccffbb4-2p2z8   2/2     Running 
    business-product-v4.0-747ccffbb4-4bh6z   2/2     Running 
    business-product-v4.0-747ccffbb4-8kgqs   2/2     Running 
    business-product-v4.0-747ccffbb4-hz6lb   2/2     Running 
    business-product-v4.0-747ccffbb4-kgs6p   2/2     Running 
    business-product-v4.0-747ccffbb4-rmmwg   2/2     Running 
    business-product-v4.0-747ccffbb4-sspgr   2/2     Running 
    business-product-v4.0-747ccffbb4-sz7ll   2/2     Running 
    business-product-v4.0-747ccffbb4-vnd58   2/2     Running 
    data-product-5bc9f7bb9c-dfchd            2/2     Running 
    data-product-5bc9f7bb9c-tv8cj            2/2     Running 
    data-product-5bc9f7bb9c-xzz7s            2/2     Running 
    

    这里我们启用了9个business-product的pod,和3个data-product的pod。上表有一个细节,每个pod里有2个容器,除了我们的应用程序容器外,还有一个就是enovy。

    当pod被注入了enovy,并且是按1.3节的要求将port暴露给了istio,则Istio会自动接管pod的流量,实现负载均衡。
    Business-product作为client与data-product server之间通过grpc通讯,这里我简单修改了下business-product和data-product的逻辑,当收到请求时data-product会将自身和client的ip作为响应,而business-product则会在这个响应的基础上再加上自己的ip。所以我们对business-product发起连续的请求,就能得到下方的一组ip对(其中每一行的第1个ip是client的ip,第2个是server检测到的client ip,第3个是server的ip):

    "ip": "172.30.27.8/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.95.17/24 - 127.0.0.1:43120 - 172.30.86.3/24",
    "ip": "172.30.27.9/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.86.9/24 - 127.0.0.1:41662 - 172.30.27.3/24",
    "ip": "172.30.95.15/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.86.7/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    "ip": "172.30.27.11/24 - 127.0.0.1:43872 - 172.30.95.7/24",
    "ip": "172.30.86.8/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    "ip": "172.30.95.14/24 - 127.0.0.1:43944 - 172.30.95.7/24",
    
    "ip": "172.30.27.8/24 - 127.0.0.1:43944 - 172.30.95.7/24",
    "ip": "172.30.95.17/24 - 127.0.0.1:43872 - 172.30.95.7/24",
    "ip": "172.30.27.9/24 - 127.0.0.1:43944 - 172.30.95.7/24",
    "ip": "172.30.86.9/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.95.15/24 - 127.0.0.1:43872 - 172.30.95.7/24",
    "ip": "172.30.86.7/24 - 127.0.0.1:43120 - 172.30.86.3/24",
    "ip": "172.30.27.11/24 - 127.0.0.1:41662 - 172.30.27.3/24",
    "ip": "172.30.86.8/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.95.14/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    
    "ip": "172.30.27.8/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    "ip": "172.30.95.17/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    "ip": "172.30.27.9/24 - 127.0.0.1:41712 - 172.30.27.3/24",
    "ip": "172.30.86.9/24 - 127.0.0.1:43872 - 172.30.95.7/24",
    "ip": "172.30.95.15/24 - 127.0.0.1:41662 - 172.30.27.3/24",
    "ip": "172.30.86.7/24 - 127.0.0.1:43944 - 172.30.95.7/24",
    "ip": "172.30.27.11/24 - 127.0.0.1:43094 - 172.30.86.3/24",
    "ip": "172.30.86.8/24 - 127.0.0.1:43872 - 172.30.95.7/24",
    "ip": "172.30.95.14/24 - 127.0.0.1:43120 - 172.30.86.3/24",
    

    我们连续发起了27次请求,不出所料,client的9个Pod分别被分配到了3次请求,接下来我们看看server是否负载上了:
    • 27次请求中,3个server的pod均被分配了9次,负载是均衡的。
    • 进一步分析,同一个client,每次被分配到的server是不同的,这是因为envoy代理在中间拦了一手,这正是负载的实现原理。
    • 再进一步分析,server检测到的客户端ip,是127.0.0.1的本地ip,这个连接其实是面向本pod的envoy的。

    所以我们可以认为envoy为我们在网格内建立起了一个服务之间的连接池,从而实现了负载均衡。

    最后,我们再回过头来对比针对grpc长连接的情况,k8s的负载和istio的负载的差异:
    • K8s的负载是在连接发起时实现的,比如我们的例子里有9个客户端,则调用了9次负载算法,次数太少,负载未必是均衡的。
    • 基于连接的负载,一旦pod异常退出,连接断开,重新建立起的连接有更大的概率被集中到个别没断开的pod上,这会加剧负载的不均衡性。
    • Istio维护的负载是在每次通讯的时候完成的动态调度,不受连接数的限制,在大量通讯的情况下,istio能够保证负载的均衡。

    3 流量迁移:金丝雀发布

    上一章envoy已经接管了我们的流量,接下来就可以让envoy为我们做更细致的流量管理的动作了。本章我们会通过简单的配置来实现金丝雀发布。

    假设我们已经发布了data-product的v3.0版本,现在我们有一个v4.0的版本需要更新上去。版本发布过程中我们期望先将10%的流量导入到v4.0版本,剩余90%的流量还是由v3.0版本来处理。待v4.0版本运行稳定无异常后,我们再将100%的流量迁移到v4.0版本。

    3.1 发布应用

    回顾1.3章节,我们在发布data-product时,需要deployment的名称区分版本(想想为啥?)。为实现两个版本的平滑过渡、金丝雀发布,我们需要同时发布两个版本的deployment:

    $ kubectl get deployment 
    NAME                READY   UP-TO-DATE   AVAILABLE   AGE
    business-product    3/3     3            3           5h13m
    data-product        3/3     3            3           4h30m
    data-product-v4.0   3/3     3            3           4h16m
    

    上表data-product是v3.0版本的(这里我命名不规范),data-product-v4.0是v4.0版本的,各有3个pod在运行。

    3.2 创建目标规则:DestinationRule

    目标规则(DestinationRule)是用来定义流量应该由哪些pod来接收的。下方的yaml文件我们创建了一个DestinationRule,标识发往data-product的流量,由data-product的v3.0和v4.0两个版本来接收。这个yaml文件的关键是subsets字段的两个labels,这对应着1.3和3.1节的应用版本。

    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: data-product
    spec:
      host: data-product
      subsets:
      - name: v3
        labels:
          version: v3.0
      - name: v4
        labels:
          version: v4.0
    

    3.3 创建虚拟服务:VirtualService

    Service和virtualservice的目标是一致的,都是将流量导向后端的pod。区别是由istio引入的virtualservice可以完成更细致的流量划分。
    下方的yaml文件,我们将10%的流量导向了v4版本,90%的流量导向v3版本。这个文件的关键是subset字段,这对应于DestinationRule的subsets;以及weight权重,按百分比划分。

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: data-product
    spec:
      hosts:
      - data-product
      http:
      - route:
        - destination:
            host: data-product
            subset: v3
          weight: 90
        - destination:
            host: data-product
            subset: v4
          weight: 10
    

    3.4 验证和结语

    限于篇幅,验证数据就不贴了。平均每10次请求,会有1次导向v4,另外9次还在v3。
    不难理解,运行一段时间觉得v4版本ok了,我们可以将权重配比调整到50%,75%,直到100%的流量都导向v4。之后v3版本的deployment就可以删了。

  • 相关阅读:
    Golang Gin 框架 Route备注
    golang的时区和神奇的time.Parse
    Linux下查看内存使用情况方法总结
    Golang 图片上绘制文字
    在linux中安装字体
    一键解决 go get golang.org/x 包失败
    go如何进行交叉编译
    Ubuntu中apt与apt-get命令的区别
    linux dns 工具包 -- bind-utils
    nohup 和 &的含义
  • 原文地址:https://www.cnblogs.com/JoZSM/p/11784002.html
Copyright © 2020-2023  润新知