当前位置: 首页 > ds >正文

K8S Ingress 实现AB测试、蓝绿发布、金丝雀(灰度)发布

假设有如下三个节点的 K8S 集群:

k8s31master 是控制节点

k8s31node1、k8s31node2 是工作节点

容器运行时是 containerd

一、场景分析

阅读本文,默认您已经安装了 Ingress Nginx。

1)A/B 测试

A/B 测试基于用户请求的元信息将流量路由到新版本,这是一种基于请求内容匹配的灰度发布策略。只有匹配特定规则的请求才会被引流到新版本,常见的做法包括基于 HTTP Header 和Cookie。基于 HTTP Header 方式,例如 User-Agent 的值为 Android 的请求(来自安卓系统的请求)可以访问新版本,其他系统仍然访问旧版本。基于 Cookie 方式,Cookie 中通常包含具有业务语义的用户信息,例如普通用户可以访问新版本,VIP 用户仍然访问旧版本。

如下图所示,某服务当前版本为v1,现在新版本v2要上线。希望安卓用户可以尝鲜新功能,其他系统用户保持不变。

通过在监控平台观察旧版本与新版本的成功率、RT对比,当新版本整体服务符合预期后,即可将所有请求切换到新版本v2,最后为了节省资源,可以逐步下线到旧版本v1。

在 K8S 中,可以利用 Ingress Nginx 基于 Header 或 Cookie 进行流量切分的策略来实现 A/B 测试发布。业务使用 Header 或 Cookie 来标识不同类型的用户,我们通过配置 Ingress 来实现让带有指定 Header 或 Cookie 的请求被转发到新版本,其它的仍然转发到旧版本,从而实现将新版本灰度给部分用户。

2)金丝雀发布

金丝雀发布是将少量的请求引流到新版本上,因此部署新版本服务只需极小数的实例。验证新版本符合预期后,逐步调整流量权重比例,使得流量慢慢从老版本迁移至新版本,期间可以根据设置的流量比例,对新版本服务进行扩容,同时对老版本服务进行缩容,使得底层资源得到最大化利用。

如下图所示,某服务当前版本为 v1,现在新版本 v2 要上线。为确保流量在服务升级过程中平稳无损,采用金丝雀发布方案,逐步将流量从老版本迁移至新版本。

 在 K8S 中,可以利用 Ingress Nginx 基于权重进行流量切分的策略来实现金丝雀发布。先切一部分的流量到新版本,然后对新版本进行监控,等观察一段时间稳定后再逐渐加大新版本的流量比例直至完全替换旧版本,最后再平滑下线旧版本,从而实现流量的定向分配。

二、注解介绍

Ingress Nginx 是一个 K8S Ingress 工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试。

  • 前提:
# 注解的键和值只能是字符串。其他类型,如布尔值或数值,必须加引号,例如:"true"、"false"、"100"。
# 开启灰度发布
nginx.ingress.kubernetes.io/canary: "true"
  •  Ingress Nginx Annotations 支持以下几种 Canary 规则:

nginx.ingress.kubernetes.io/canary-by-header:利用请求头,通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当请求头部设置为 always 时,请求将被路由到金丝雀版本。当头部设置为 never 时,请求永远不会被路由到金丝雀版本。对于任何其他值,头部将被忽略,请求将根据优先级与其他金丝雀规则进行比较。

nginx.ingress.kubernetes.io/canary-by-header-value:利用请求头值,通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当请求头设置为该值时,请求将被路由到金丝雀版本。对于任何其他头值,将忽略该头,并按照优先级与其他金丝雀规则进行比较。此注解必须配合使用 nginx.ingress.kubernetes.io/canary-by-header。这个注解是nginx.ingress.kubernetes.io/canary-by-header 的扩展,允许自定义请求头值而不是使用硬编码值。如果未定义 nginx.ingress.kubernetes.io/canary-by-header 注解,则它没有任何效果。

nginx.ingress.kubernetes.io/canary-by-header-pattern: 这个注解的作用与 canary-by-header-value 相同,但它使用的是 PCRE 正则表达式匹配。注意,当设置了 canary-by-header-value 时,这个注解将被忽略。如果给定的正则表达式在请求处理过程中导致错误,该请求将被认为不匹配。

nginx.ingress.kubernetes.io/canary-by-cookie:利用 cookie,通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 cookie 值设置为 always 时,请求将始终路由到金丝雀版本。当 cookie 设置为 never 时,请求永远不会路由到金丝雀版本。对于任何其他值,将忽略 cookie,并根据优先级将请求与其他金丝雀规则进行比较。

nginx.ingress.kubernetes.io/canary-weight:整数(0-)百分比的随机请求将会被路由到金丝雀 Ingress 中指定的服务。权重为 0 表示该金丝雀规则不会将任何请求发送到金丝雀 Ingress 中的服务。权重为 <weight-total> 表示所有请求都将发送到 Ingress 中指定的备用服务。 <weight-total> 默认为 100,可以通过 nginx.ingress.kubernetes.io/canary-weight-total 进行增加。

nginx.ingress.kubernetes.io/canary-weight-total:流量的总权重。如果未指定,默认为 100。

金丝雀规则的评估顺序遵循优先级

优先级顺序如下:按头部信息金丝雀 -> 按Cookie金丝雀 -> 权重金丝雀

三、实验准备

  • 镜像下载

[root@k8s31node1 ~]# ctr -n=k8s.io images pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/openresty/openresty:latest
[root@k8s31node1 ~]# ctr -n=k8s.io images tag  swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/openresty/openresty:latest  docker.io/openresty/openresty:latest[root@k8s31node2 ~]# ctr -n=k8s.io images pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/openresty/openresty:latest
[root@k8s31node2 ~]# ctr -n=k8s.io images tag  swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/openresty/openresty:latest  docker.io/openresty/openresty:latest
  •  部署 v1

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-v1
spec:replicas: 1selector:matchLabels:app: nginxversion: v1template:metadata:labels:app: nginxversion: v1spec:containers:- name: nginximage: "openresty/openresty:latest"imagePullPolicy: IfNotPresentports:- name: httpprotocol: TCPcontainerPort: 80volumeMounts:- mountPath: /usr/local/openresty/nginx/conf/nginx.confname: configsubPath: nginx.confvolumes:- name: configconfigMap:name: nginx-v1
---
apiVersion: v1
kind: ConfigMap
metadata:labels:app: nginxversion: v1name: 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: ClusterIPports:- port: 80protocol: TCPname: httpselector:app: nginxversion: v1

该 yml 定义了三个资源 ConfigMap、Deployment、Service。

  • ConfigMap 定义了一个 nginx.conf 配置文件,使用 lua 脚本输出 nginx-v1。
  • Deployment 定义了一个 Pod,里面运行 openresty 它是一个封装了 nginx+lua 的 web 服务器。Pod 有两个标签 app: nginx、version: v1。
  • Service 代理了 Deployment 运行的 Pod。

部署 v2

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-v2
spec:replicas: 1selector:matchLabels:app: nginxversion: v2template:metadata:labels:app: nginxversion: v2spec:containers:- name: nginximage: "openresty/openresty:latest"imagePullPolicy: IfNotPresentports:- name: httpprotocol: TCPcontainerPort: 80volumeMounts:- mountPath: /usr/local/openresty/nginx/conf/nginx.confname: configsubPath: nginx.confvolumes:- name: configconfigMap:name: nginx-v2
---
apiVersion: v1
kind: ConfigMap
metadata:labels:app: nginxversion: v2name: 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: ClusterIPports:- port: 80protocol: TCPname: httpselector:app: nginxversion: v2

 创建 v1 ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: nginx
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /          pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v1port:number: 80

 对外暴露域名 canary.example.com 访问。

修改本机 hosts

192.168.40.20 canary.example.com 

 浏览器访问

四、实战

1)nginx.ingress.kubernetes.io/canary-by-header

  • 创建 v2 ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true" # 开启金丝雀nginx.ingress.kubernetes.io/canary-by-header: "Canary"name: nginx-canary
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v2port:number: 80

现在系统里面有两个 ingress,一个 v1 版本,一个 v2 金丝雀版本。 

注意:

ingress 要 ADDRESS 那一栏出来才能访问。

curl -H "Host: canary.example.com" -H "Canary: always" 192.168.40.20

请求头参数 Canary 匹配 always,走金丝雀版本服务。

请求头参数 Canary 不匹配 always,走 v1 服务。

2) nginx.ingress.kubernetes.io/canary-by-header-value

删掉上一个 ingress,以免干扰下面实验。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-header: "Canary"nginx.ingress.kubernetes.io/canary-by-header-value: "v2"name: nginx-canary
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v2port:number: 80

 请求头参数 Canary 匹配 v2,走金丝雀版本服务。

curl -H "Host: canary.example.com" -H "Canary: v1" 192.168.40.20

 3) nginx.ingress.kubernetes.io/canary-by-header-pattern

删掉上一个 ingress,以免干扰下面实验。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-header: "Canary"nginx.ingress.kubernetes.io/canary-by-header-pattern: "v2|v3" # 匹配v2或v3name: nginx-canary
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v2port:number: 80

 请求头参数 Canary 匹配 v2 或 v3,走金丝雀版本服务。

curl -H "Host: canary.example.com" -H "Canary: v1" 192.168.40.20

 4)nginx.ingress.kubernetes.io/canary-by-cookie

删掉上一个 ingress,以免干扰下面实验。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-cookie: "Canary"name: nginx-canary
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v2port:number: 80

cookie 参数 Canary 匹配 always,走金丝雀版本服务。

cookie 参数 Canary 不匹配 always,走 v1 服务。

curl -H "Host: canary.example.com" -H "Cookie: Canary=always" 192.168.40.20

5)nginx.ingress.kubernetes.io/canary-weight

 删掉上一个 ingress,以免干扰下面实验。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-weight: "10"name: nginx-canary
spec:ingressClassName: nginxrules:- host: canary.example.comhttp:paths:- path: /pathType:  Prefixbackend:  #配置后端服务service:name: nginx-v2port:number: 80

10%的流量打到金丝雀服务。

for i in {1..10}; do curl -H "Host: canary.example.com" 192.168.40.20; done;

五、金丝雀比较

实现金丝雀发布的方式有很多,从Java程序员的角度来看,就有:

基于 Spring Cloud Gateway 路由断言工厂、基于 Nginx、基于 K8S Deployment 伪金丝雀、基于 Ingress Nginx 注解、基于 Istio 流量切分。

基于 Spring Cloud Gateway 路由断言工厂:路由规则变化很难做到实时响应,要实现实时响应代码实现复杂。

基于 Nginx:要有很多的配置。

基于 K8S Deployment 伪金丝雀:没有实现流量的切分。

基于 Istio 流量切分:技术栈门槛高。需要对于服务网格的一整套有所了解。

综上来看,基于 Ingress Nginx 注解 是配置最简单的方式了。

http://www.xdnf.cn/news/6393.html

相关文章:

  • 基于大模型预测的全面惊厥性癫痫持续状态技术方案大纲
  • 力扣-98.验证二叉搜索树
  • C# winform 日志 NLog
  • 【vue】脚手架
  • 瀑布模型VS敏捷模型VS喷泉模型
  • 【Linux】多路转接epoll、Linux高并发I/O多路复用
  • SpringAI
  • 印度尼西亚数据源对接技术指南
  • YOLOv11融合[CVPR2025]OverLock中的模块
  • 合并有重叠的时间区间的极简方法
  • 【证书与信任机制​】​​SSL证书类型全解析:DV、OV、EV的区别与应用场景
  • 【C#基础】集合.Any() 与 判断集合的长度有啥区别?
  • atoi函数,sprintf函数,memcmp函数,strchar函数的具体原型,功能,返回值;以及使用方法
  • 现代计算机图形学Games101入门笔记(六)
  • 19、云端工业物联网生态组件 - 工厂能效与预测维护 - /数据与物联网组件/cloud-iiot-factory-analysis
  • 紫外波段太阳光模拟器介绍
  • Python Matplotlib 库【绘图基础库】全面解析
  • 在UI 原型设计中,交互规则有哪些核心要素?
  • 数据统计分析及可视化
  • 开源 Web Shell 工具
  • 万文c++继承
  • 前端表格滑动滚动条太费事,做个浮动滑动插件
  • Java基于SpringBoot的外卖系统小程序【附源码、文档说明】
  • 功能连接计算的科学选择:静息态fMRI中20种指标的全面评估
  • 卓力达红外热成像靶标:革新军事训练与航空检测的关键技术
  • FastAPI系列16:从API文档到TypeScript 前端客户端(SDKs)
  • 3天重庆和成都旅游规划
  • 【PmHub后端篇】PmHub集成 Sentinel+OpenFeign实现网关流量控制与服务降级
  • acwing 4275. Dijkstra序列
  • 二叉树复习(C语言版)