原生Ingress灰度发布能力不够?我们是这么干的

灰度发布是一种常见的服务滚动升级或 A/B 测试策略。在新版本服务正式发布前,可以部署少量的新版本服务和上个版本共存,用部分生产流量测试新版本的功能和特性。如果新版本反馈良好,则可以渐进地提高新版本的比例或者全部替换成新版本,如果有问题也能够及时撤回,不至于造成太大范围的影响。

目前,原生容器发布基本都是使用 deployment,通过给 deployment 和 service 灵活配置 labels ,可以实现一种基于服务版本的灰度发布。

由于原生 Ingress 对象描述能力的限制,一些常见 Ingress controller 的灰度发布功能也大打折扣,很难满足用户灰度发布的实际需求。

博云基于原生Ingress,做了大量增强,基于请求特征的灰度发布是其中一个重要特性。

使用 deployment 实施灰度发布

通过配置 pod labels 和 service label selector,Kubernetes 原生支持灰度发布。假设我们部署了 echo-demo 服务的两个版本的 deployment:

> echo-demo-v1.yaml

     name: echo-demo-v1

     replicas: 3

     ...

     labels:

        app: echo-demo

        track: stable

     ...

     image: deploy.bocloud/ingress/echo-demo:1.0.0

> echo-demo-v2.yaml

     name: echo-demo-v2

     replicas: 2

     ...

     labels:

        app: echo-demo

        track: canary

     ...

     image: deploy.bocloud/ingress/echo-demo:2.0.0

以及一个 echo-demo service:

 apiVersion: v1

kind: Service

metadata:

  name: echo-demo

spec:

  ports:

    - port: 80

      protocol: TCP

      targetPort: 8080

  selector:

    app: echo-demo

上述配置中,echo-demo service 聚合了 echo-demo-v1 和 echo-demo-v2 两个版本的服务,两个版本分别有 3 个和 2 个实例。此时我们访问 echo-demo service,请求将根据实例数量的占比按 3:2 的比例分布到 v1 和 v2 两个服务中。这样就实现了基于权重的灰度发布。

然而这种灰度发布却自有其限制和问题。首先,如果我们给服务加上自动水平伸缩(HPA),那么两个版本的服务将完全根据各自的负载情况独立调整 pod 实例数量,这样一来两个版本的实例数量和比例就会发生变化,打破我们一开始设置的灰度比例。其次,如果我们只想配置很小比例的请求到灰度版本,比如 100:1,那么在最少提供一个灰度版本 pod 的前提下,需要配置最少 100 个旧版本实例,很不灵活。第三,除了按比例的灰度策略,有时可能还需要根据请求特征来辨别用户并分配灰度服务到其中的一小部分。由于deployment有着上述的缺陷,导致其很少被当做灰度使用的原因。所以在实际应用当中,灰度发布基本上 由ingress来做。而这几个问题,都可以通过使用 Ingress 灰度发布方案来解决。

Ingress 能描述灰度发布吗?

Ingress 是 Kubernetes 平台的一个原生 API,定义了 HTTP 路由到 Kubernetes service 的映射。Ingress Controller 依据 Ingress 对象的声明,动态生成负载均衡器的配置文件,由负载均衡器将 k8s 内部服务暴露出去,Nginx Ingress Controller 是使用最广泛的一个 Ingress 控制器。一个典型的 Ingress 描述文件如下:

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: echo-demo

  labels:

    app: echo-demo

  annotations:

    kubernetes.io/ingress.class: nginx

  namespace: default

spec:

  rules:

    - host: test.domain.com

      http:

        paths:

          - backend:

              serviceName: echo-demo-v1

              servicePort: 80

            path: /echo

  - backend:

       serviceName: hello-world

       servicePort: 8080

     path: /hello

由 Ingress API 可以看到,一个域名下可以定义多个访问路径,然而一个路径却只能定义一个 service。于是,在使用 Ingress 对象来描述灰度发布的情形下,要实现一个访问端点与多个服务版本的映射,仍然只能采用上述一个 kubernetes service 聚合多个版本的 deployment 的方式,这种方案的种种问题上文已经分析过了。社区版 Nginx Ingress Controller 受限于 Ingress API 的描述能力,对灰度发布的支持完全是不可用的状态。有没有一种办法,既兼容 Ingress API,又能做到一个访问端点映射到多个 service 呢?

博云基于 Nginx Ingress Controller 开发的 Ingress 控制器 BeyondELB,设计了一种 Ingress 组合模型,在兼容 Ingress API 的基础上,用 labels 给 Ingress 对象分类,并将多个不同类别的 Ingress 对象组合成一个逻辑 Ingress,从而实现了一个访问端点到多个 service 的映射。这种组合模型为实现另一种灰度发布方案提供了可能。

使用 BeyondELB

实施基于权重的灰度发布

上面可以看到,使用 deployment labels 实现的基于权重的灰度发布,其灰度比例完全依赖于服务的实例数量,而引入 HPA 之后服务实例数量可能会发生倾斜从而打破灰度比例。而由 Ingress 组合模型实现的灰度方案中,一个访问端点能够配置多个 service,每个 service 有自己的灰度权重和同一版本的服务实例,灰度权重不再和服务实例数量相绑定。通过将灰度权重和服务实例数量解耦,权重可以随时按需调整,服务的实例数量则可以根据负载情况自行伸缩,不会影响到设定好的灰度比例。

原生Ingress灰度发布能力不够?我们是这么干的_第1张图片

采用了 Ingress 组合模型的 BeyondELB 支持上述权重灰度发布策略。可以为某个 Ingress 访问路径定义一个或多个服务版本,然后为不同服务版本设定灰度权重,请求将按照灰度权重分配到不同版本的服务中。

上图中,选择开启基于权重的灰度发布,定义了一个灰度服务并设定 20 的权重,如果主版本服务的权重设定为 80(图中未给出主服务版本定义),则请求将按照 4:1 的比例分配到主版本服务和灰度版本服务。下面对配置了 80/20 权重的服务连续请求 100 次,可见流量按设定的比例分配到 v1 和 v2 服务中。

$ for i in `seq 1 100`; do curl -s http://test.domain.com/weight; done | jq .version | sort | uniq -c

    78 "1.0.0"

    22 "2.0.0"

使用 BeyondELB

实施基于请求特征的版本级灰度发布

在某些场景下,可能会需要对灰度选择有更好的控制,比如对于 HTTP 服务来说,可以通过请求特征甄别用户,允许将具有特定请求 Header 或 Cookie 的用户发送到灰度版本。

原生Ingress灰度发布能力不够?我们是这么干的_第2张图片

上图中,我们添加了一个灰度服务版本,并且设定灰度策略为“基于请求特征”。当请求附有名为 "canary" 值为 "true" 的请求 Header 时,将由该灰度服务版本响应;而其它未匹配该灰度条件的请求则由主服务版本响应(图中未给出主服务版本定义)。

我们通过以下两个脚本测试基于 Header 的灰度效果。

请求携带名为 canary 且其值为 "true" 的 Header:

$ for i in `seq 1 100`; do curl -s -H "canary: true" http://test.domain.com/header; done | jq .version | sort | uniq -c

100 "1.0.0"

请求不携带名为 "canary" 的 Header:

        $ for i in `seq 1 100`; do curl -s http://test.domain.com/header; done | jq .version | sort | uniq -c

100 "2.0.0"

除了基于请求 Header 的灰度匹配策略,博云的 Ingress Controller 还支持基于请求 Cookie 的匹配策略,以及多个 Header 或 Cookie 灰度条件组合的匹配策略。我们还可以用正则表达式匹配 operator 来实现更具体化的灰度方案,比如如下匹配表达式可以将 User ID 以 3 结尾的用户发送到灰度服务版本。

(header("x-userid" regex "^[0-9]+3$")

总结

通过使用 Kubernetes pod labels 和 service,可以初步实现基于权重的灰度发布。但这种灰度发布依赖于服务版本的实例数量,不仅不灵活而且在引入 HPA 时会造成服务比例倾斜。博云自研的 Ingress Controller 将灰度权重和服务实例数量解耦,服务可依据负载和 HPA 规则自行伸缩实例,不影响灰度比例。而基于 Header 或 Cookie 的灰度匹配策略,为实现更可控的灰度方案提供了支持。除了增强的灰度发布能力,博云商用版本的 Ingress Controller 还支持租户级负载、热更新等特性,后续将会逐步介绍。

你可能感兴趣的:(灰度发布,容器)