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

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

目前,原生容器发布基本都是使用 deployment,通过给 deployment 和 ser& 1 ] A V 4 X %vicP n 7e 灵活配置 labels ,可以实现一种基于服务版本的灰度发布

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

博云基于原生Ingress,做d M * # T了大Q f ] d K o量增强,基于请求9 c * Z ) U特征的u x 3灰度发布是其中一个4 5 h 5 = d v重要特性。

使用 deployment 实施灰度V n 5 B y 2 Q发布

通过配置 pod labels 和 service label seleco ( D o P d ~tor,Kubernet2 3 l Res 原生支持灰度发布。假设我们部署了 echo-demo 服务的两个版本的 deployment:

echo-demo-v1.yaml

 name: echo-demo-v1
replicas: 3
...
labels:
app: echo-demoZ o c
track: stable
...
image: deploy.bocloud/ingress/echo-demo:1.0.0

echo-demo-v2.yaml

 name: echo-demo-v2
replicas: 2
...
lP 5 E - L S )abels:
app: echo-demo
track: canaryh t w _ { p s y
...
image: dk p ~ ~ c ;eploy.bocloud/ingress/echo-demo:2.0.0

以及一个 echo-demo service:

api a h 6 AVersion: v1

kind: Service

metadata:

name: echo-demo

spec:

ports:

- port: 80
protocol: TCP
targetPo# h # ; | N ` I [rt: 8080

selector:

ap5 - T W e _p: echo-demo

上述配置中,echo-demo service 聚合了 echo-demo-v1 和 echo-demo-v2 两个版[ l t B本的服务,两个版本分别有 3 个和 2 个实例。此时我们访问 echo-deV P % 2 W 9 Q Rmo servicp = : f r Ee,请求将根据实例数量的占比按 3:2 的比例分布到 v1 和 v2 两个服务中。这样就实现了基于权重的灰度发布。

然而这种灰度发布却自有其限制和问题。首先,如果我们给服务$ 8 ) P S ) ) &加上自动水平伸缩(HPA),那么两个版本的服务将完全根据各自的负载情况独立调整 pm = f W ~ r ^ Y Lod 实例数量M _ X b,这样一来两个版本的实例数量和比例就会发生变化,打破我们一开始设置的灰度比例。其次,} x u / c m ;如果我们只想( e ] Z配置很小比例I ? 0 i $ -的请求到灰度版本,比如 100:1,那么在最少提供一个灰度版本 pod 的i J _ &前提下,需要配置最少 100 个旧版本实例,很不灵活。第三,除了按比例的灰度策略,有时可能还需! B 6 ? x Q 1 (要根据请求特征来辨( _ X Z D 1 E ) u别用户并分配灰度服务到其中的一小. N e [ T Z ` w部分。由于deployment有着上述的缺陷,导致其很少被当做灰度使用的原因。所以d : ; t r在实际应用当中,灰度发布基本上o } ( k W ! W 1 由ingress来做。而这几个问| $ a r 0题,都可以通过使用 IngresD Y :s 灰度发布方案来解决。

Ingress 能描述灰度发布吗?

Ingress 是 Kubernetes 平台的一个原生 API,定义了 HTTP 路由e 8 z 0 D - 1到 Kubernetes service 的映射。Ingr( # b n mesv . { E F X , q Gs Controller 依据 Ingress 对象的声明,动态生成负载F F G B s R均衡器的配置文件,由负载均衡j k R器将 k8s 内部服务暴露出去,Nginx Ingress Controller 是使用最广泛的一个 Ingress 控制器。一个典型的 Ingress 描述文件如下:

apiVersion: extensions/v1beta1

kind: Inf a vgress

metadata:$ K N G O 8 ~ r S

name: echo-demo

labels:

app: es | 8 T ?cho-demo

annotations:

kubernetes.io/inA ~ c l c gress.| U * O Ucb 5 D m ( Klass: nginx

namespace: default

spec:

r{ ] 0 9 l L K 3 0ules:

- host: test.domain. . v G ] &com
http:
paths:
- backend:
serviceName: echo-demd G o r 9 p : & +o-v1
servicePort| i D =: 80
path: /echo
- backend:
serviceName:# = ( ` x [ hello-world
serviJ  i 6 W e |cePort: 8080
path: /hello

由 Ingress API 可以h i i & = r看到,一个域名下可以定义多个) [ T Y 4 2 6 j访问路径,然而一个路径却只能c G t 3 5 s & i L定义一个 sI h Hervice。于是,在使用 Ingress 对象来描S T 5 4述灰度发布的情形下,要实现一个访问端点与多个服务版本的映射,仍然只能采用上述一个 kubernetes seU 7 U ^ ;rvi$ i -ce 聚合多个版本的 deployment 的方式,这种方案的种种问题上文已经M & ( 1 % V y E分析过了。社区版 Nginx Ingress Controller 受限于 Ingres l A s M m $ss API 的描述能力,对灰度发布的支持完全是不_ E p I t P可用的状态。有没有一种办法,既兼容 Ingress API,又能做到一个访问端点映射到多个 service 呢?

博云基于 Nginx Ingress Controller 开发的 Ingress 控制器 BeyondELB,设计了一种 Ingress 组合模型,在兼容 InD f 2 5 : 1 . *gress APV % B H s y cI 的基础上,用 labels 给 Ingress 对象分类,并将多个不同类别的 Ingress 对象组合成一个逻% O - ( j ( | !{ i P 1 g Ingress,9 8 L h 1从而实现了一个访问端点到多个 service 的映射。这种组合模型为实现另一种灰度发布j M o & Q x o l方案提供了可能。

使用 BeyondELB

实施基于权重的灰度发布

上面可以看到,使用 deployment labels 实现的基于权重的灰度发布,其灰度比例完全依赖于服务的实例数量J 4 ( C R & v,而引入 HPA 之后服务实例数量可能会发生倾斜从而打破灰度比例。而由 Ingress 组合模型实现的r . | } 0 j m W灰度方案中,一个访问端点能够配置多个 service,每个p G 0 $ : ser) % K % i :vice 有自己的灰度权重和同一版本的服务实例,灰度权重不再和服务实例数量相绑定。通过将灰度权重和服务实例数量解耦,权重可以随时按需调整,服务的实例数量则可以根据负载情况自行伸缩,不会影响到设定好的灰V P y r [ M r度比例。
原生Ingress灰度发布能力不够?我们是这么干的

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

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

$ for i in seq 1 100; do curl -s http://test.dM ) Y g 3 $ Iomain.com/web X u @ gight; done | jq .version | sort | uniq -c

78 "1.0.0"
22 "2.0.0"

使用 BeyondELB

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

P = g h m D q某些场景下,可能会需要对灰度选择有更好 a n @ r H的控制,比如对于 HTTP 服务来说,可以通过请求特征甄别用户,允许将具有特定请求 Header 或 Cookie 的用L + r Y g m E _ 3户发送到灰度版本。

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

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

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

请求携带名为 canary 且其值为 "t2 Z Y ` q $ 9rue" 的 HK G = k & C @ ?eader:

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

100 "1.# ` J M ~ ) B0.0"

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

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

100 "2t g 3.0.0"

除了基于请求 He; A {ader 的灰度匹配策) J R {略,博云的 Ingress Controller 还支持基于请求 Cookie 的匹配策略,以及多个 Header 或 Cookie 灰度条件组合的匹配策略。我们还可以用正则表达式匹配 operator 来实现更具体化的灰度方案,比如如下匹配L k ? K p . @ { ?表达式可以将 User ID 以 39 P 6 结尾的用户发送到! L $ ( , K灰度服务版本。

(header("x-userid" rf w s n 0 = m z zegex9 L ` j 7 i n M r "^[0-9]+3$")

总结

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