Istio 结合 Flagger 进行灰度发布

云计算 云原生
Flagger 是一个渐进式交付的 Kubernetes Operator,它可以自动执行 Kubernetes 上运行的应用程序的发布过程。它通过在测量指标和运行一致性测试的同时逐渐将流量转移到新版本,降低了在生产中引入新软件版本的风险。

灰度发布也叫金丝雀部署 ,是指通过控制流量的比例,实现新老版本的逐步替换。比如对于服务 A 有两个版本(蓝和绿两个版本),当前两个版本同时部署,但是 version1 比例 90% ,version2 比例 10% ,然后我们可以观察 version2 的实际运行效果,如果符合预期,则可以逐步调整流量占比,比如调整为 80:20 -> 70:30 -> 10:90 -> 0:100 ,最终 version1 版本下线,全部替换成 version2 版本。如果验证失败,切换 100%流量回 v1 版本(回滚)。灰度发布的特点是:

flagger

在 Istio 中要实现灰度发布有多种方案,比如 Flagger、Argo Rollouts 等。

Flagger

Flagger 是一个渐进式交付的 Kubernetes Operator,它可以自动执行 Kubernetes 上运行的应用程序的发布过程。它通过在测量指标和运行一致性测试的同时逐渐将流量转移到新版本,降低了在生产中引入新软件版本的风险。

Flagger 通过使用服务网格(App Mesh、Istio、Linkerd、Kuma、Open Service Mesh)或 Ingress 控制器(Contour、Gloo、NGINX、Skipper、 Traefik、APISIX)用于流量路由。对于发布分析,Flagger 可以查询 Prometheus、InfluxDB、Datadog、New Relic、CloudWatch、Stackdriver 或 Graphite,并使用 Slack、MS Teams、Discord 和 Rocket 来发出警报。

Flagger

Flagger 可以使用 Kubernetes CRD 进行配置,并且与任何为 Kubernetes 制作的 CI/CD 解决方案兼容。由于 Flagger 是声明性的对 Kubernetes 事件做出反应,因此它可以与诸如此类的工具一起在 GitOps 管道中使用。

安装 Flagger

要使用 Flagger,需要先选择一个受支持的路由提供商(比如我们这里使用 Istio),然后使用 Helm 或 Kustomize 安装 Flagger。

Flagger 需要 Kubernetes 集群 v1.16 或更高版本以及 Istio v1.5 或更高版本。

首先当然需要安装 Istio,并开启 Prometheus 插件:

# demo 或者 default 都可以
istioctl manifest install --set profile=demo -y

# istio 根目录
kubectl apply -f samples/addons/prometheus.yaml

然后在 istio-system 命名空间安装 Flagger:

$ git clone https://github.com/fluxcd/flagger && cd flagger
$ kubectl apply -k kustomize/istio
$ kubectl get pods -n istio-system -l app=flagger
NAME                      READY   STATUS    RESTARTS   AGE
flagger-ff76bfdff-kkcmz   1/1     Running   0          17m

测试应用

下面我们创建一个名为 test 的命名空间,并为其启用 Istio sidecar 自动注入:

kubectl create ns test
kubectl label namespace test istio-injectinotallow=enabled

接下来我们使用 flagger 官方提供的 podinfo 应用来进行测试:

kubectl apply -k kustomize/podinfo

该命令会为 podinfo 应用创建对应的 Deployment 和一个 HPA 对象。

$ kubectl get deployment -n test
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
podinfo   2/2     2            0           96s
$ kubectl get hpa -n test
NAME      REFERENCE            TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
podinfo   Deployment/podinfo   <unknown>/99%   2         4         2          60s

部署后,我们可以看到 podinfo 应用的容器数量已经变成了 2 个(自动注入了 istio sidecar),而且 HPA 也已经生效。

```bash
$ kubectl get pods -n test
NAME                                  READY   STATUS            RESTARTS   AGE
podinfo-584c4546df-fzw4d              0/2     PodInitializing   0          5m26s
podinfo-584c4546df-n28cf              0/2     PodInitializing   0          5m11s

接着我们再部署一个负载测试服务用于在金丝雀分析期间生成流量:

kubectl apply -k kustomize/tester

创建金丝雀

接下来我们就可以创建一个 Canary 自定义资源来实现我们的金丝雀发布了。Canary 对象是 Flagger 的核心,它描述了金丝雀发布的目标。如下所示,我们为 podinfo 应用创建一个 Canary 对象:

# podinfo-canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  targetRef: # deployment 引用
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  progressDeadlineSeconds: 60 # 金丝雀部署升级最大处理时间(以秒为单位)(默认600秒)
  autoscalerRef: # HPA 引用(可选)
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: podinfo
  service:
    port: 9898
    targetPort: 9898
    gateways: # Istio 网关(可选)
      - istio-system/public-gateway
    hosts: # VirtualService 主机名 (optional)
      - podinfo.k8s.local
    trafficPolicy: # Istio 流量策略(可选)
      tls:
        # use ISTIO_MUTUAL when mTLS is enabled
        mode: DISABLE
    retries: # Istio 重试策略(可选)
      attempts: 3
      perTryTimeout: 1s
      retryOn: "gateway-error,connect-failure,refused-stream"
  analysis: # 金丝雀分析
    interval: 1m # 金丝雀分析间隔时间(默认 60s)
    threshold: 5 # 金丝雀分析失败阈值(默认 5)
    maxWeight: 50 # 金丝雀最大流量权重(默认 50)
    stepWeight: 10 # 金丝雀流量权重步长(默认 10)
    metrics:
      - name: request-success-rate
        # minimum req success rate (non 5xx responses)
        # percentage (0-100)
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        # maximum req duration P99
        # milliseconds
        thresholdRange:
          max: 500
        interval: 30s
    # testing (optional)
    webhooks:
      - name: acceptance-test
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 30s
        metadata:
          type: bash
          cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
      - name: load-test
        url: http://flagger-loadtester.test/
        timeout: 5s
        metadata:
          # 使用 hey 工具对 podinfo-canary 进行为期1分钟的负载测试,每秒发送10个请求,且测试过程中会维持2个并发连接。
          cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"

上面的配置文件中,我们定义了 podinfo 应用的金丝雀发布策略,其中 targetRef 指定了要进行金丝雀发布的 Deployment 对象,service 指定了金丝雀发布的服务,analysis 指定了金丝雀分析策略,这里我们指定了两个内置的指标检查:request-success-rate 和 request-duration,其中 request-success-rate 指定了 HTTP 请求成功率,request-duration 指定了请求持续时间。对于每个指标,你可以使用 thresholdRange 和窗口大小或时间序列指定可接受的值范围和时间间隔。内置检查适用于每个服务网格/Ingress 控制器,并通过 Prometheus 查询实现。

在 service 中我们指定了 Istio 的 Gateway(istio-system/public-gateway)以及 VirtualService 要使用的主机名,首先我们可以为该应用创建一个 Gateway 对象:

# public-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"

另外我们在上面的对象中通过 webhooks 字段指定了金丝雀分析期间要执行的测试,其中 acceptance-test 用于在金丝雀分析开始之前执行,load-test 用于在金丝雀分析期间执行。

金丝雀部署

接下来我们可以直接创建 Canary 对象了:

kubectl apply -f podinfo-canary.yaml

当创建了 Canary 对象后,Flagger 会自动创建一个名为 pod-info-primary 的 Deployment 以及两个版本的 Service 对象:

$ kubectl get deploy -n test
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
flagger-loadtester   1/1     1            1           42m
podinfo              0/0     0            0           46m
podinfo-primary      2/2     2            2           7m3s
$ kubectl get svc -ntest
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
flagger-loadtester   ClusterIP   10.106.172.190   <none>        80/TCP     35m
podinfo-canary       ClusterIP   10.101.184.213   <none>        9898/TCP   39s
podinfo-primary      ClusterIP   10.110.105.36    <none>        9898/TCP   39s

可以看到我们原本的 podinfo 应用已经从 podinfo 这个 Deployment 迁移到了 podinfo-primary 这个 Deployment 之上。

此外还有 Istio 相关的对象:

$ kubectl get vs -ntest
NAME      GATEWAYS                          HOSTS                             AGE
podinfo   ["istio-system/public-gateway"]   ["podinfo.k8s.local","podinfo"]   91s
$ kubectl get dr -ntest
NAME              HOST              AGE
podinfo-canary    podinfo-canary    95s
podinfo-primary   podinfo-primary   95s

我们可以查看下自动生成的 VirtualService 对象:

$ kubectl get vs -ntest podinfo -oyaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: podinfo
  namespace: test
spec:
  gateways:
  - istio-system/public-gateway
  hosts:
  - podinfo.k8s.local
  - podinfo
  http:
  - retries:
      attempts: 3
      perTryTimeout: 1s
      retryOn: gateway-error,connect-failure,refused-stream
    route:
    - destination:
        host: podinfo-primary
      weight: 100
    - destination:
        host: podinfo-canary
      weight: 0

从上面的配置中我们可以看到当前 podinfo 应用的流量全部被路由到了 podinfo-primary 对象上,而 podinfo-canary 对象的流量权重为 0,当然同样可以查看 DestinationRule 对象:

$ kubectl get dr podinfo-primary -ntest -oyaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: podinfo-primary
  namespace: test
spec:
  host: podinfo-primary
  trafficPolicy:
    tls:
      mode: DISABLE
$ kubectl get dr podinfo-canary -ntest -oyaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: podinfo-canary
  namespace: test
spec:
  host: podinfo-canary
  trafficPolicy:
    tls:
      mode: DISABLE

所以默认情况下现在我们访问到的就是 podinfo-primary 这个 Deployment 对象,也就是目前的默认版本。我们可以在浏览器中访问 podinfo 来查看当前的版本:

podinfo

自动金丝雀发布

我们可以看到现在的版本是 podinfo v6.0.0,接下来我们来升级应用触发金丝雀发布。要触发金丝雀发布,可以由以下任何对象的更改来触发:

  • Deployment PodSpec(容器镜像、命令、端口、环境变量、资源等)
  • 作为卷挂载或映射到环境变量的 ConfigMaps
  • 作为卷挂载或映射到环境变量的 Secrets

比如我们可以直接修改 Deployment 对象的镜像版本来触发自动化的金丝雀发布:

kubectl -n test set image deployment/podinfo podinfod=ghcr.io/stefanprodan/podinfo:6.0.1

Flagger 检测到 Deployment 更改后就会开始新的部署:

$ kubectl describe canaries podinfo -ntest
# ......
Events:
  Type     Reason  Age                From     Message
  ----     ------  ----               ----     -------
  Warning  Synced  15m                flagger  podinfo-primary.test not ready: waiting for rollout to finish: observed deployment generation less than desired generation
  Normal   Synced  14m (x2 over 15m)  flagger  all the metrics providers are available!
  Normal   Synced  14m                flagger  Initialization done! podinfo.test
  Normal   Synced  56s                flagger  New revision detected! Scaling up podinfo.test

需要注意在金丝雀分析期间对 Deployment 应用新的更改,Flagger 将重新启动分析。第一步是先会去扩容 podinfo 应用:

$ kubectl get pods -ntest
NAME                                  READY   STATUS    RESTARTS   AGE
flagger-loadtester-78dd9787d4-dq5fc   2/2     Running   0          67m
podinfo-5d5dbc4d84-f2mp6              2/2     Running   0          31s
podinfo-5d5dbc4d84-gd8ln              2/2     Running   0          31s
podinfo-primary-64f865cf4-bhr79       2/2     Running   0          3m31s
podinfo-primary-64f865cf4-tgsdj       2/2     Running   0          3m31s

然后就会根据我们在 Canary 对象中定义的金丝雀分析策略来进行分析,并一步步将金丝雀版本的权重提高。

Warning Synced 4m4s flagger podinfo-primary.test not ready: waiting for rollout to finish: observed deployment generation less than desired generation
Normal Synced 3m4s (x2 over 4m4s) flagger all the metrics providers are available!
Normal Synced 3m4s flagger Initialization done! podinfo.test
Normal Synced 64s flagger New revision detected! Scaling up podinfo.test
Normal Synced 4s flagger Starting canary analysis for podinfo.test
Normal Synced 4s flagger Pre-rollout check acceptance-test passed
Normal Synced 4s flagger Advance podinfo.test canary weight 10

最后会自动将流量全部切换到金丝雀版本上。

金丝雀版本

整个过程就是通过控制 VirtualService 的权重来实现的金丝雀发布。

自动回滚

在金丝雀分析期间,我们可以生成 HTTP 500 错误和高延迟来测试 Flagger 是否暂停发布。

比如我们触发另一个金丝雀发布:

kubectl -n test set image deployment/podinfo podinfod=ghcr.io/stefanprodan/podinfo:6.0.2

然后进入 loadtester 容器:

kubectl -n test exec -it flagger-loadtester-xx-xx sh

使用下面的命令来生成 HTTP 500 错误:

watch curl http://podinfo-canary:9898/status/500

也可以添加延迟:

watch curl http://podinfo-canary:9898/delay/1

当失败检查的次数达到金丝雀分析配置的阈值时,流量将路由回主节点,金丝雀将缩放为零,并将部署标记为失败。

Normal   Synced  8m10s (x3 over 45m)  flagger  New revision detected! Scaling up podinfo.test
Normal   Synced  7m10s (x2 over 44m)  flagger  Pre-rollout check acceptance-test passed
Normal   Synced  7m10s (x2 over 44m)  flagger  Advance podinfo.test canary weight 10
Normal   Synced  7m10s (x2 over 44m)  flagger  Starting canary analysis for podinfo.test
Warning  Synced  6m10s                flagger  Halt podinfo.test advancement success rate 55.86% < 99%
Warning  Synced  5m10s                flagger  Halt podinfo.test advancement success rate 97.61% < 99%
Warning  Synced  4m10s                flagger  Halt podinfo.test advancement success rate 8.00% < 99%
Warning  Synced  3m10s                flagger  Halt podinfo.test advancement success rate 98.13% < 99%
Warning  Synced  2m10s                flagger  Halt podinfo.test advancement success rate 7.69% < 99%
Warning  Synced  70s (x2 over 14m)    flagger  Canary failed! Scaling down podinfo.test
Warning  Synced  70s                  flagger  Rolling back podinfo.test failed checks threshold reached 5

关于 Flagger 的更多使用请关注后续文章。

责任编辑:姜华 来源: k8s技术圈
相关推荐

2019-05-23 10:55:22

Istio灰度发布ServiceMesh

2022-12-05 10:47:08

RocketMQ灰度消息

2021-06-03 05:48:58

GitOps 云原生Kubernetes

2023-12-12 07:30:54

IstioWasm前端

2021-11-18 10:01:00

Istio 全链路灰度微服务框架

2024-01-05 00:29:36

全链路灰度发布云原生

2021-12-27 15:01:21

KubernetesLinux命令

2022-01-19 18:31:54

前端灰度代码

2019-06-09 09:13:14

Istio负载均衡架构

2018-04-10 14:17:09

蓝绿发布滚动发布灰度发布

2012-07-20 14:48:54

Nginx测试

2021-02-20 08:06:37

CTO灰度系统

2023-11-02 08:46:19

微服务开发Istio

2023-02-20 10:13:00

灰度发布实现

2022-04-28 09:22:46

Vue灰度发布代码

2022-02-15 14:22:46

灰度发布互联网业务

2021-08-27 07:47:07

Nacos灰度源码

2022-12-05 09:08:12

微服务灰度发布

2024-01-09 08:20:23

OpenCV二值化灰度化

2010-12-14 11:30:11

点赞
收藏

51CTO技术栈公众号