概述

本文将介绍如何使用 Nginx Ingress 实现金丝雀发布,从使用场景分析,到用法详解,再到上手实践。

前提条件

集群中需要部署 Nginx Ingress 作为 Ingress Controller,并且对外暴露了统一的流量入口,参考 在 TKE 上部署 Nginx Ingress

Nginx Ingress 可以用在哪些发布场景 ?

使用 Nginx Ingress 来实现金丝雀发布,可以用在哪些场景呢?这个主要看使用什么策略进行流量切分,目前 Nginx Ingress 支持基于 Header、Cookie 和服务权重这 3 种流量切分的策略,基于它们可以实现以下两种发布场景。

场景一: 将新版本灰度给部分用户

假设线上运行了一套对外提供 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 详细介绍下:

  • nginx.ingress.kubernetes.io/canary-by-header: 表示如果请求头中包含这里指定的 header 名称,并且值为 always 的话,就将该请求转发给该 Ingress 定义的对应后端服务;如果值为 never 就不转发,可以用于回滚到旧版;如果是其它值则忽略该 annotation。
  • nginx.ingress.kubernetes.io/canary-by-header-value: 这个可以作为 canary-by-header的补充,允许指定请求头的值可以自定义成其它值,不再只能是 alwaysnever;当请求头的值命中这里的自定义值时,请求将会转发给该 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,同样也是只支持 alwaysnever 的值。
  • nginx.ingress.kubernetes.io/canary-weight: 表示 Canary Ingress 所分配流量的比例的百分比,取值范围 [0-100],比如设置为 10,意思是分配 10% 的流量给 Canary Ingress 对应的后端服务。

上面的规则会按优先顺序进行评估,优先顺序如下: canary-by-header -> canary-by-cookie -> canary-weight

注意: 当 Ingress 被标记为 Canary Ingress 时,除了nginx.ingress.kubernetes.io/load-balancenginx.ingress.kubernetes.io/upstream-hash-by 之外,所有其他非 Canary 注释都将被忽略。

上手实践

下面我们给出一些例子,让你快速上手 Nginx Ingress 的金丝雀发布,环境为 TKE 集群。

使用 YAML 创建资源

本文的示例将使用 yaml 的方式部署工作负载和创建 Service,有两种操作方式。

方式一:在 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

基于 Header 的流量切分

创建 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 版本服务响应。

基于 Cookie 的流量切分

与前面 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_cdalways 的请求才由 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 实现了几种不同姿势的金丝雀发布,但还存在一些缺陷:

  1. 相同服务的 Canary Ingress 只能定义一个,所以后端服务最多支持两个版本。
  2. Ingress 里必须配置域名,否则不会有效果。
  3. 即便流量完全切到了 Canary Ingress 上,旧版服务也还是必须存在,不然会报错。

总结

本文全方位总结了 Nginx Ingress 的金丝雀发布用法,虽然 Nginx Ingress 在金丝雀发布这方面的能力有限,并且还存在一些缺陷,但基本也能覆盖一些常见的场景,如果集群中使用了 Nginx Ingress,并且发布的需求也不复杂,可以考虑使用这种方案。

参考资料

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

手把手教你使用 Nginx Ingress 实现金丝雀发布的更多相关文章

  1. 手把手教你开发Nginx模块

    前面的哪些话 关于Nginx模块开发的博客资料,网上很多,很多.但是,每篇博客都只提要点,无法"step by step"照着做,对于初次接触Nginx开发的同学,只能像只盲目的蚂 ...

  2. 手把手教你认识并搭建Nginx

    手把手教你认识并搭建Nginx Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器. Nginx 是由 Igor ...

  3. 手把手教你在Ubuntu上分别安装Nginx、PHP和Mysql

    手把手教你在Ubuntu上分别安装Nginx.PHP和Mysql

  4. iOS回顾笔记(05) -- 手把手教你封装一个广告轮播图框架

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  5. 手把手教你搭建FastDFS集群(下)

    手把手教你搭建FastDFS集群(下) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...

  6. 手把手教你搭建FastDFS集群(中)

    手把手教你搭建FastDFS集群(中) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u0 ...

  7. 手把手教你搭建FastDFS集群(上)

    手把手教你搭建FastDFS集群(上) 本文链接:https://blog.csdn.net/u012453843/article/details/68957209        FastDFS是一个 ...

  8. 手把手教你用 FastDFS 构建分布式文件管理系统

    说起分布式文件管理系统,大家可能很容易想到 HDFS.GFS 等系统,前者是 Hadoop 的一部分,后者则是 Google 提供的分布式文件管理系统.除了这些之外,国内淘宝和腾讯也有自己的分布式文件 ...

  9. 庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境

    庐山真面目之十一微服务架构手把手教你搭建基于Jenkins的企业级CI/CD环境 一.介绍 说起微服务架构来,有一个环节是少不了的,那就是CI/CD持续集成的环境.当然,搭建CI/CD环境的工具很多, ...

随机推荐

  1. Redis主从复制、多实例、高可用

    Redis主从复制 在开始实现redis的高可用之前,首先来学习一下如何实现redis的主从复制,毕竟高可用也会依赖主从复制的技术. Redis的主从复制,可以实现一个主节点master可以有多个从节 ...

  2. Docker镜像构建的两种方式(六)

    镜像构建介绍 在什么情况下我们需要自己构建镜像那? (1)当我们找不到现有的镜像,比如自己开发的应用程序 (2)需要在镜像中加入特定的功能 docker构建镜像有两种方式:docker commit命 ...

  3. (专题一)03 matlab变量及其操作

    给内存单元取名字就可以访问内存单元 变量的命名:变量名区分大小写 标准函数名以及命名方式必须用小写字母 matlab赋值语句有两种表达式 变量的管理       1.预定义变量  ans 是默认赋值变 ...

  4. Mybatis快速逆向生成代码

    先下载生成器的文件, 并在eclipse或者IDEA里面打开这个工程 热乎乎的链接 然后配置一下 选择你需要生成的数据的ip和端口 点击运行入口函数 运行成功 接着在浏览器输入localhost: 这 ...

  5. 容器云平台No.9~kubernetes日志收集系统EFK

    EFK介绍 EFK,全称Elasticsearch Fluentd Kibana ,是kubernetes中比较常用的日志收集方案,也是官方比较推荐的方案. 通过EFK,可以把集群的所有日志收集到El ...

  6. python与Oracle

    1.python 3.6.6 2.使用cx_Oracle      -----------安装方法:pip install cx_Oracle 3.游标 cursor -----游标是系统为用户开创的 ...

  7. Spring系列之事务的控制 注解实现+xml实现+事务的隔离等级

    Spring系列之事务的控制 注解实现+xml实现 在前面我写过一篇关于事务的文章,大家可以先去看看那一篇再看这一篇,学习起来会更加得心应手 链接:https://blog.csdn.net/pjh8 ...

  8. makefile从入门到入门

    makefile文件是用来帮助编译和管理C++项目代码的,需要配合make命令使用.makefile里也可以执行shell操作,具备一部分.sh脚本的功能. makefile格式 makefile内容 ...

  9. 我把这个贼好用的Excel导出工具开源了!!

    写在前面 不管是传统软件企业还是互联网企业,不管是管理软件还是面向C端的互联网应用.都不可避免的会涉及到报表操作,而对于报表业务来说,一个很重要的功能就是将数据导出到Excel.如果我们在业务代码中, ...

  10. shiro入门学习--使用MD5和salt进行加密|练气后期

    写在前面 在上一篇文章<Shiro入门学习---使用自定义Realm完成认证|练气中期>当中,我们学会了使用自定义Realm实现shiro数据源的切换,我们可以切换成从关系数据库如MySQ ...