本文将介绍如何使用 Nginx Ingress 实现金丝雀发布,从使用场景分析,到用法详解,再到上手实践。nginx
集群中须要部署 Nginx Ingress 做为 Ingress Controller,而且对外暴露了统一的流量入口,参考 在 TKE 上部署 Nginx Ingress。git
使用 Nginx Ingress 来实现金丝雀发布,能够用在哪些场景呢?这个主要看使用什么策略进行流量切分,目前 Nginx Ingress 支持基于 Header、Cookie 和服务权重这 3 种流量切分的策略,基于它们能够实现如下两种发布场景。github
假设线上运行了一套对外提供 7 层服务的 Service A 服务,后来开发了个新版本 Service A' 想要上线,但又不想直接替换掉原来的 Service A,但愿先灰度一小部分用户,等运行一段时间足够稳定了再逐渐全量上线新版本,最后平滑下线旧版本。这个时候就能够利用 Nginx Ingress 基于 Header 或 Cookie 进行流量切分的策略来发布,业务使用 Header 或 Cookie 来标识不一样类型的用户,咱们经过配置 Ingress 来实现让带有指定 Header 或 Cookie 的请求被转发到新版本,其它的仍然转发到旧版本,从而实现将新版本灰度给部分用户:正则表达式
假设线上运行了一套对外提供 7 层服务的 Service B 服务,后来修复了一些问题,须要灰度上线一个新版本 Service B',但又不想直接替换掉原来的 Service B,而是让先切 10% 的流量到新版本,等观察一段时间稳定后再逐渐加大新版本的流量比例直至彻底替换旧版本,最后再滑下线旧版本,从而实现切必定比例的流量给新版本:后端
咱们经过给 Ingress 资源指定 Nginx Ingress 所支持的一些 annotation 能够实现金丝雀发布,须要给服务建立两个 Ingress,一个正常的 Ingress,另外一个是带 nginx.ingress.kubernetes.io/canary: "true"
这个固定的 annotation 的 Ingress,咱们姑且称它为 Canary Ingress,通常表明新版本的服务,结合另外针对流量切分策略的 annotation 一块儿配置便可实现多种场景的金丝雀发布,如下对这些 annotation 详细介绍下:centos
nginx.ingress.kubernetes.io/canary-by-header
: 表示若是请求头中包含这里指定的 header 名称,而且值为 always
的话,就将该请求转发给该 Ingress 定义的对应后端服务;若是值为 never
就不转发,能够用于回滚到旧版;若是是其它值则忽略该 annotation。nginx.ingress.kubernetes.io/canary-by-header-value
: 这个能够做为 canary-by-header
的补充,容许指定请求头的值能够自定义成其它值,再也不只能是 always
或 never
;当请求头的值命中这里的自定义值时,请求将会转发给该 Ingress 定义的对应后端服务,若是是其它值则将会忽略该 annotation。nginx.ingress.kubernetes.io/canary-by-header-pattern
: 这个与上面的 canary-by-header-value
相似,惟一的区别是它是用正则表达式对来匹配请求头的值,而不是只固定某一个值;须要注意的是,若是它与 canary-by-header-value
同时存在,这个 annotation 将会被忽略。nginx.ingress.kubernetes.io/canary-by-cookie
: 这个与 canary-by-header
相似,只是这个用于 cookie,一样也是只支持 always
和 never
的值。nginx.ingress.kubernetes.io/canary-weight
: 表示 Canary Ingress 所分配流量的比例的百分比,取值范围 [0-100],好比设置为 10,意思是分配 10% 的流量给 Canary Ingress 对应的后端服务。上面的规则会按优先顺序进行评估,优先顺序以下: canary-by-header -> canary-by-cookie -> canary-weight
api
注意: 当 Ingress 被标记为 Canary Ingress 时,除了nginx.ingress.kubernetes.io/load-balance
和 nginx.ingress.kubernetes.io/upstream-hash-by
以外,全部其余非 Canary 注释都将被忽略。cookie
下面咱们给出一些例子,让你快速上手 Nginx Ingress 的金丝雀发布,环境为 TKE 集群。app
本文的示例将使用 yaml 的方式部署工做负载和建立 Service,有两种操做方式。curl
方式一:在 TKE 或 EKS 控制台右上角点击 YAML 建立资源
,而后将本文示例的 yaml 粘贴进去:
方式二:将示例的 yaml 保存成文件,而后使用 kubectl 指定 yaml 文件来建立,如: kubectl apply -f xx.yaml
。
这里以简单的 nginx 为例,先部署一个 v1 版本:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v1 spec: replicas: 1 selector: matchLabels: app: nginx version: v1 template: metadata: labels: app: nginx version: v1 spec: containers: - name: nginx image: "openresty/openresty:centos" ports: - name: http protocol: TCP containerPort: 80 volumeMounts: - mountPath: /usr/local/openresty/nginx/conf/nginx.conf name: config subPath: nginx.conf volumes: - name: config configMap: name: nginx-v1 --- apiVersion: v1 kind: ConfigMap metadata: labels: app: nginx version: v1 name: nginx-v1 data: nginx.conf: |- worker_processes 1; events { accept_mutex on; multi_accept on; use epoll; worker_connections 1024; } http { ignore_invalid_headers off; server { listen 80; location / { access_by_lua ' local header_str = ngx.say("nginx-v1") '; } } } --- apiVersion: v1 kind: Service metadata: name: nginx-v1 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: nginx version: v1
再部署一个 v2 版本:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v2 spec: replicas: 1 selector: matchLabels: app: nginx version: v2 template: metadata: labels: app: nginx version: v2 spec: containers: - name: nginx image: "openresty/openresty:centos" ports: - name: http protocol: TCP containerPort: 80 volumeMounts: - mountPath: /usr/local/openresty/nginx/conf/nginx.conf name: config subPath: nginx.conf volumes: - name: config configMap: name: nginx-v2 --- apiVersion: v1 kind: ConfigMap metadata: labels: app: nginx version: v2 name: nginx-v2 data: nginx.conf: |- worker_processes 1; events { accept_mutex on; multi_accept on; use epoll; worker_connections 1024; } http { ignore_invalid_headers off; server { listen 80; location / { access_by_lua ' local header_str = ngx.say("nginx-v2") '; } } } --- apiVersion: v1 kind: Service metadata: name: nginx-v2 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: nginx version: v2
能够在控制台看到部署的状况:
再建立一个 Ingress,对外暴露服务,指向 v1 版本的服务:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v1 servicePort: 80 path: /
访问验证一下:
$ curl -H "Host: canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP nginx-v1
建立 Canary Ingress,指定 v2 版本的后端服务,且加上一些 annotation,实现仅将带有名为 Region 且值为 cd 或 sz 的请求头的请求转发给当前 Canary Ingress,模拟灰度新版本给成都和深圳地域的用户:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "Region" nginx.ingress.kubernetes.io/canary-by-header-pattern: "cd|sz" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
测试访问:
$ curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP nginx-v2 $ curl -H "Host: canary.example.com" -H "Region: bj" http://EXTERNAL-IP nginx-v1 $ curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP nginx-v2 $ curl -H "Host: canary.example.com" http://EXTERNAL-IP nginx-v1
能够看到,只有 header Region
为 cd 或 sz 的请求才由 v2 版本服务响应。
与前面 Header 相似,不过使用 Cookie 就没法自定义 value 了,这里以模拟灰度成都地域用户为例,仅将带有名为 user_from_cd
的 cookie 的请求转发给当前 Canary Ingress 。先删除前面基于 Header 的流量切分的 Canary Ingress,而后建立下面新的 Canary Ingress:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_cd" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
测试访问:
$ curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP nginx-v2 $ curl -s -H "Host: canary.example.com" --cookie "user_from_bj=always" http://EXTERNAL-IP nginx-v1 $ curl -s -H "Host: canary.example.com" http://EXTERNAL-IP nginx-v1
能够看到,只有 cookie user_from_cd
为 always
的请求才由 v2 版本的服务响应。
基于服务权重的 Canary Ingress 就简单了,直接定义须要导入的流量比例,这里以导入 10% 流量到 v2 版本为例 (若是有,先删除以前的 Canary Ingress):
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10" name: nginx-canary spec: rules: - host: canary.example.com http: paths: - backend: serviceName: nginx-v2 servicePort: 80 path: /
测试访问:
$ for i in {1..10}; do curl -H "Host: canary.example.com" http://EXTERNAL-IP; done; nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v1 nginx-v2 nginx-v1 nginx-v1 nginx-v1
能够看到,大概只有十分之一的概率由 v2 版本的服务响应,符合 10% 服务权重的设置。
虽然咱们使用 Nginx Ingress 实现了几种不一样姿式的金丝雀发布,但还存在一些缺陷:
本文全方位总结了 Nginx Ingress 的金丝雀发布用法,虽然 Nginx Ingress 在金丝雀发布这方面的能力有限,而且还存在一些缺陷,但基本也能覆盖一些常见的场景,若是集群中使用了 Nginx Ingress,而且发布的需求也不复杂,能够考虑使用这种方案。
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!