Istio 太复杂?KubeSphere基于Ingress-Nginx实现灰度发布
在 bookinfo 微服务的灰度发布示例 中,kubesphere 基于 istio 对 bookinfo 微服务示例应用实现了灰度发布。有用户表示自己的项目还没有上 istio,要如何实现灰度发布?
在 ingress-nginx (0.21.0 版本) 中,引入了一个新的 canary 功能,可用于为网关入口配置多个后端服务,还可以使用指定的 annotation 来控制多个后端服务之间的流量分配。 kubesphere 在 2.0.2 的版本 中,升级了项目网关 (ingress controller) 版本至 0.24.1,支持基于 ingress-nginx 的灰度发布。
上一篇文章已经对灰度发布的几个应用场景进行了详细介绍,本文将直接介绍和演示基于 kubesphere 使用应用路由 (ingress) 和项目网关 (ingress controller) 实现灰度发布。
说明: 本文用到的示例 yaml 源文件及代码已上传至 github,可 clone 至本地方便参考。
ingress-nginx annotation 简介
kubesphere 基于 nginx ingress controller 实现了项目的网关,作为项目对外的流量入口和项目中各个服务的反向代理。而 ingress-nginx 支持配置 ingress annotations 来实现不同场景下的灰度发布和测试,可以满足金丝雀发布、蓝绿部署与 a/b 测试等业务场景。
nginx annotations 支持以下 4 种 canary 规则:
nginx.ingress.kubernetes.io/canary-by-header
:基于 request header 的流量切分,适用于灰度发布以及 a/b 测试。当 request header 设置为always
时,请求将会被一直发送到 canary 版本;当 request header 设置为never
时,请求不会被发送到 canary 入口;对于任何其他 header 值,将忽略 header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。nginx.ingress.kubernetes.io/canary-by-header-value
:要匹配的 request header 的值,用于通知 ingress 将请求路由到 canary ingress 中指定的服务。当 request header 设置为此值时,它将被路由到 canary 入口。该规则允许用户自定义 request header 的值,必须与上一个 annotation (即:canary-by-header)一起使用。nginx.ingress.kubernetes.io/canary-weight
:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 canary ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 canary 入口的服务发送任何请求。权重为 100 意味着所有请求都将被发送到 canary 入口。nginx.ingress.kubernetes.io/canary-by-cookie
:基于 cookie 的流量切分,适用于灰度发布与 a/b 测试。用于通知 ingress 将请求路由到 canary ingress 中指定的服务的cookie。当 cookie 值设置为always
时,它将被路由到 canary 入口;当 cookie 值设置为never
时,请求不会被发送到 canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。注意:金丝雀规则按优先顺序进行如下排序:
canary-by-header - > canary-by-cookie - > canary-weight
把以上的四个 annotation 规则可以总体划分为以下两类:
- 基于权重的 canary 规则
- 基于用户请求的 canary 规则
第一步:创建项目和 production 版本的应用
1.1. 在 kubesphere 中创建一个企业空间 (workspace) 和项目 (namespace) ,可参考 多租户管理快速入门。如下已创建了一个示例项目。
1.2. 为了快速创建应用,在项目中创建工作负载和服务时可通过 编辑 yaml
的方式,或使用 kubesphere 右下角的工具箱打开 web kubectl
并使用以下命令和 yaml 文件创建一个 production 版本的应用并暴露给集群外访问。如下创建 production 版本的 deployment
和 service
。
$ kubectl appy -f production.yaml -n ingress-demo deployment.extensions/production created service/production created
其中用到的 yaml 文件如下:
production.yaml
apiversion: extensions/v1beta1 kind: deployment metadata: name: production spec: replicas: 1 selector: matchlabels: app: production template: metadata: labels: app: production spec: containers: - name: production image: mirrorgooglecontainers/echoserver:1.10 ports: - containerport: 8080 env: - name: node_name valuefrom: fieldref: fieldpath: spec.nodename - name: pod_name valuefrom: fieldref: fieldpath: metadata.name - name: pod_namespace valuefrom: fieldref: fieldpath: metadata.namespace - name: pod_ip valuefrom: fieldref: fieldpath: status.podip --- apiversion: v1 kind: service metadata: name: production labels: app: production spec: ports: - port: 80 targetport: 8080 protocol: tcp name: http selector: app: production
1.3. 创建 production 版本的应用路由 (ingress)。
$ kubectl appy -f production.ingress -n ingress-demo ingress.extensions/production created
其中用到的 yaml 文件如下:
production.ingress
apiversion: extensions/v1beta1 kind: ingress metadata: name: production annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: kubesphere.io http: paths: - backend: servicename: production serviceport: 80
第二步:访问 production 版本的应用
2.1. 此时,在 kubesphere ui 的企业空间 demo-workspace 下,可以看到 ingress-demo 项目下的所有资源。
deployment
service
ingress
2.2. 访问 production 版本的应用需确保当前项目已开启了网关,在外网访问下打开网关,类型为 nodeport
。
2.3. 如下访问 production 版本的应用。
$ curl kubesphere.io:30205 hostname: production-6b4bb8d58d-7r889 pod information: node name: ks-allinone pod name: production-6b4bb8d58d-7r889 pod namespace: ingress-demo pod ip: 10.233.87.165 server values: server_version=nginx: 1.12.2 - lua: 10010 request information: client_address=10.233.87.225 method=get real path=/ query= request_version=1.1 request_scheme=http request_uri=http://kubesphere.io:8080/ request headers: accept=*/* host=kubesphere.io:30205 user-agent=curl/7.29.0 apiversion: extensions/v1beta1 x-forwarded-for=192.168.0.88 x-forwarded-host=kubesphere.io:30205 x-forwarded-port=80 x-forwarded-proto=http x-original-uri=/ x-real-ip=192.168.0.88 x-request-id=9596df96e994ea05bece2ebbe689a2cc x-scheme=http request body: -no body in request-
第三步:创建 canary 版本
参考将上述 production 版本的 production.yaml
文件,再创建一个 canary 版本的应用,包括一个 canary 版本的 deployment
和 service
(为方便快速演示,仅需将 production.yaml
的 deployment 和 service 中的关键字 production
直接替换为 canary
,实际场景中可能涉及业务代码变更)。
第四步:ingress-nginx annotation 规则
基于权重 (weight)
基于权重的流量切分的典型应用场景就是蓝绿部署
,可通过将权重设置为 0 或 100 来实现。例如,可将 green 版本设置为主要部分,并将 blue 版本的入口配置为 canary。最初,将权重设置为 0,因此不会将流量代理到 blue 版本。一旦新版本测试和验证都成功后,即可将 blue 版本的权重设置为 100,即所有流量从 green 版本转向 blue。
4.1. 使用以下 canary.ingress
的 yaml 文件再创建一个基于权重的 canary 版本的应用路由 (ingress)。
注意:要开启灰度发布机制,首先需设置
nginx.ingress.kubernetes.io/canary: "true"
启用 canary,以下 ingress 示例的 canary 版本使用了基于权重进行流量切分的 annotation 规则,将分配 30% 的流量请求发送至 canary 版本。
apiversion: extensions/v1beta1 kind: ingress metadata: name: canary annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "30" spec: rules: - host: kubesphere.io http: paths: - backend: servicename: canary serviceport: 80
4.2. 访问应用的域名。
说明:应用的 canary 版本基于权重 (30%) 进行流量切分后,访问到 canary 版本的概率接近 30%,流量比例可能会有小范围的浮动。
基于 request header
4.3. 基于 request header 进行流量切分的典型应用场景即灰度发布或 a/b 测试场景
。参考以下截图,在 kubesphere 给 canary 版本的 ingress 新增一条 annotation nginx.ingress.kubernetes.io/canary-by-header: canary
(这里的 annotation 的 value 可以是任意值),使当前的 ingress 实现基于 request header 进行流量切分。
说明:金丝雀规则按优先顺序
canary-by-header - > canary-by-cookie - > canary-weight
进行如下排序,因此以下情况将忽略原有 canary-weight 的规则。
4.4. 在请求中加入不同的 header 值,再次访问应用的域名。
说明:
举两个例子,如开篇提到的当 request header 设置为
never
或always
时,请求将不会
或一直
被发送到 canary 版本;对于任何其他 header 值,将忽略 header,并通过优先级将请求与其他 canary 规则进行优先级的比较(如下第二次请求已将
基于 30% 权重
作为第一优先级)。
4.5. 此时可以在上一个 annotation (即 canary-by-header)的基础上添加一条 nginx.ingress.kubernetes.io/canary-by-header-value: user-value
。用于通知 ingress 将请求路由到 canary ingress 中指定的服务。
4.6. 如下访问应用的域名,当 request header 满足此值时,所有请求被路由到 canary 版本(该规则允许用户自定义 request header 的值)。
基于 cookie
4.7. 与基于 request header 的 annotation 用法规则类似。例如在 a/b 测试场景
下,需要让地域为北京的用户访问 canary 版本。那么当 cookie 的 annotation 设置为 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_beijing"
,此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置 cookie users_from_beijing
的值为 always
,这样就可以确保北京的用户仅访问 canary 版本。
总结
灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以对新版本进行测试、发现和调整问题,以保证其影响度。本文通过多个示例演示和说明了基于 kubesphere 使用应用路由 (ingress) 和项目网关 (ingress controller) 实现灰度发布,并详细介绍了 ingress-nginx 的四种 annotation,还未使用 istio 的用户也能借助 ingress-nginx 轻松实现灰度发布与金丝雀发布。
参考
- nginx ingress controller - annotations
-
canary deployments on kubernetes without service mesh
kubesphere (https://github.com/kubesphere/kubesphere) 是一个开源的以应用为中心的容器管理平台,支持部署在任何基础设施之上,并提供简单易用的 ui,极大减轻日常开发、测试、运维的复杂度,旨在解决 kubernetes 本身存在的存储、网络、安全和易用性等痛点,帮助企业轻松应对敏捷开发与自动化监控运维、端到端应用交付、微服务治理、多租户管理、多集群管理、服务与网络管理、镜像仓库、ai 平台、边缘计算等业务场景。