通过Ingress-nginx实现灰度发布---灰度发布(22)
1.通过Ingress-nginx实现灰度发布
场景一: 将新版本灰度给部分用户
假设线上运行了一套对外提供 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 是一个 K8S ingress 工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试。 Nginx Annotations 支持以下几种 Canary 规则:
假设我们现在部署了两个版本的服务,老版本和 canary 版本
nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,适用于 灰度发布以及 A/B 测试。当 Request Header 设置为 always 时,请求将会被一直发送到 Canary 版 本;当 Request Header 设置为 never 时,请求不会被发送到 Canary 入口。
nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值, 用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值 时,它将被路由到 Canary 入口。
nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为 60 意味着 60%流量转到 canary。权重为 100 意 味着所有请求都将被发送到 Canary 入口。
nginx.ingress.kubernetes.io/canary-by-cookie:基于Cookie 的流量切分,适用于灰度发布 与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的 cookie。当cookie 值设置为 always 时,它将被路由到 Canary 入口;当 cookie 值设置为 never 时,请求不会 被发送到 Canary 入口。
2.部署实例
2.1 部署2个版本的服务
# 1.node1和node3节点导入镜像
docker load -i openresty.tar.gz
Loaded image: openresty/openresty:centos # 2.部署v1版本
[root@master header-ingress]# cat v1.yaml
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"
imagePullPolicy: IfNotPresent
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 [root@master header-ingress]# kubectl apply -f v1.yaml
deployment.apps/nginx-v1 created
configmap/nginx-v1 created
service/nginx-v1 created [root@master header-ingress]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-v1 ClusterIP 10.108.228.242 <none> 80/TCP 31s
tomcat ClusterIP 10.109.238.78 <none> 8080/TCP,8009/TCP 3h20m
[root@master header-ingress]# kubectl get configmap
NAME DATA AGE
nginx-v1 1 51s
[root@master header-ingress]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-v1 0/1 0 0 78s
[root@master header-ingress]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-v1-79bc94ff97-7bfg4 1/1 Running 0 55s # 3.部署v2版本
[root@master header-ingress]# cat v2.yaml
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"
imagePullPolicy: IfNotPresent
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 [root@master header-ingress]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-v1-79bc94ff97-7bfg4 1/1 Running 0 3m21s
nginx-v2-5f885975d5-4pnkq 1/1 Running 0 4s
[root@master header-ingress]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-v1 ClusterIP 10.108.228.242 <none> 80/TCP 7m13s
nginx-v2 ClusterIP 10.104.97.248 <none> 80/TCP 10s # 4.再创建一个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: / [root@master header-ingress]# kubectl apply -f v1-ingress.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/nginx created [root@master header-ingress]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx <none> canary.example.com 192.168.10.11,192.168.10.13 80 30s #访问验证结果
curl -H "Host: canary.example.com" http://EXTERNAL-IP
[root@master header-ingress]# curl -H "Host: canary.example.com" http://192.168.10.11
nginx-v1
# EXTERNAL-IP替换为nginx Ingress自身对外暴露的IP
2.2 Ingress-controller基于请求头和地域流量代理
2.2.1 基于 Header 的流量切分:
创建 Canary Ingress,指定 v2 版本的后端服务,且加上一些 annotation,实现仅将带有名为 Region 且值为 cd 或 sz 的请求头的请求转发给当前 Canary Ingress,模拟灰度新版本给成都和深 圳地域的用户
[root@master header-ingress]# cat v2-ingress.yaml
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: / [root@master header-ingress]# kubectl apply -f v2-ingress.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/nginx-canary created [root@master header-ingress]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx <none> canary.example.com 192.168.10.11,192.168.10.13 80 11m
nginx-canary <none> canary.example.com 192.168.10.11,192.168.10.13 80 47s 测试访问:
curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP [root@master header-ingress]# curl -H "Host:canary.example.com" -H "Region:cd" http://192.168.10.11
nginx-v2
[root@master header-ingress]# curl -H "Host:canary.example.com" -H "Region:sh" http://192.168.10.11
nginx-v1
[root@master header-ingress]# curl -H "Host:canary.example.com" -H "Region:cd" http://192.168.10.199
nginx-v2
[root@master header-ingress]# curl -H "Host:canary.example.com" -H "Region:sz" http://192.168.10.199
nginx-v2
[root@master header-ingress]# curl -H "host:canary.example.com" http://192.168.10.11
nginx-v1
可以看到,只有 header Region 为 cd 或 sz 的请求才由 v2 版本服务响应。
2.2.2 基于Cookie的流量切分
与前面 Header 类似,不过使用 Cookie 就无法自定义 value 了,这里以模拟灰度成都地域用户 为例,仅将带有名为 user_from_cd 的 cookie 的请求转发给当前 Canary Ingress 。先删除前面基 于 Header 的流量切分的 Canary Ingress,然后创建下面新的 Canary Ingress:
[root@master header-ingress]# kubectl delete -f v2-ingress.yaml [root@master header-ingress]# cat v1-cookie.yaml
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: / [root@master header-ingress]# kubectl apply -f v1-cookie.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/nginx-canary created [root@master header-ingress]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-canary <none> canary.example.com 192.168.10.11,192.168.10.13 80 30s 测试访问:
[root@master header-ingress]# curl -s -H "Host:canary.example.com" --cookie "user_from_cd=always" http://192.168.10.199
nginx-v2
[root@master header-ingress]# curl -s -H "Host:canary.example.com" --cookie "user_from_sh=always" http://192.168.10.199
nginx-v1
[root@master header-ingress]# curl -s -H "Host:canary.example.com" http://192.168.10.199
nginx-v1
可以看到,只有 cookie user_from_cd 为 always 的请求才由 v2 版本的服务响应。
2.2.3 基于服务权重的流量切分
基于服务权重的 Canary Ingress 就简单了,直接定义需要导入的流量比例,这里以导入 10% 流 量到 v2 版本为例 (如果有,先删除之前的 Canary Ingress):
[root@master header-ingress]# kubectl delete -f v1-cookie.yaml [root@master header-ingress]# cat v1-weight.yaml
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: / [root@master header-ingress]# kubectl apply -f v1-weight.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/nginx-canary created
[root@master header-ingress]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx <none> canary.example.com 192.168.10.11,192.168.10.13 80 28m
nginx-canary <none> canary.example.com 80 13s [root@master header-ingress]# for i in {1..10}; do curl -H "Host: canary.example.com" http://192.168.10.199;done;
nginx-v1
nginx-v2
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v2
nginx-v1
可以看到,大概只有十分之一的几率由 v2 版本的服务响应,符合 10% 服务权重的设置,目前只有10个所以看到有2个v2
通过Ingress-nginx实现灰度发布---灰度发布(22)的更多相关文章
- nginx+lua+redis实现灰度发布_test
nginx+lua+redis实现灰度发布: 灰度发布是指在黑白之间能够平滑过渡的一种方式 AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见, ...
- 蓝绿部署、金丝雀发布(灰度发布)、A/B测试
本文转载自蓝绿部署.金丝雀发布(灰度发布).A/B测试的准确定义 概述 蓝绿部署.A/B测试.金丝雀发布,以及灰度发布.流量切分等,经常被混为一谈,影响沟通效率. 根本原因是这些名词经常出现,人们耳熟 ...
- nginx+uWSGI+django+virtualenv+supervisor发布web服务器
nginx+uWSGI+django+virtualenv+supervisor发布web服务器 导论 WSGI是Web服务器网关接口.它是一个规范,描述了Web服务器如何与Web应用程序通信,以 ...
- Linux - nginx+uWSGI+django+virtualenv+supervisor发布web服务器
目录 Linux - nginx+uWSGI+django+virtualenv+supervisor发布web服务器 crm django项目部署流程 使用supervisro启动uwsgi,退出虚 ...
- OpenCV2+入门系列(四):计算图像的直方图,平均灰度,灰度方差
本篇懒得排版,直接在网页html编辑器编辑 在图像处理时,我们常常需要求出图像的直方图.灰度平均值.灰度的方差,这里给出一个opencv2+自带程序,实现这些功能. 直方图 对于直方图,使用cv::c ...
- Atitit 图像处理 灰度图片 灰度化的原理与实现
Atitit 图像处理 灰度图片 灰度化的原理与实现 24位彩色图与8位灰度图 首先要先介绍一下24位彩色图像,在一个24位彩色图像中,每个像素由三个字节表示,通常表示为RGB.通常,许多24位彩色图 ...
- IIS上发布WCF发布服务,访问不到
1 环境是IIS7,发布WCF发布服务,访问不到. 一种原因站点自动生成“程序应用池”和站点的Framwork版本不一致. 解决的办法:新建一个“程序应用池”,然后站点指向这个新建的“程序应用池”
- SQL2005 到 SQL2008R2 发布订阅----发布'xxxxx'的初始快照尚不可用。
步骤略! SQL2005 到 SQL2008R2 发布订阅----发布'xxxxx'的初始快照尚不可用. 发布库快照已经创建完成为什么到订阅就快照不可用呢! 订阅通过日志读取代理解析! 查了下代理安全 ...
- 织梦DedeCMS信息发布员发布文章阅读权限不用审核自动开放亲测试通过!
文章发布员在织梦dedecms后台添加文章时却要超级管理员审核,这无疑是增加了没必要的工作. 登录该账号发布文章你会发现该文章显示的是待审核稿件,且并没有生成静态文件,在前台是看不到这篇文章的,而多数 ...
- 织梦DedeCMS信息发布员发布文章默认自动审核更新并生成HTML页面
织梦DedeCMS信息发布员发布文章默认自动审核更新并生成HTML页面 一直以为DEDECMS的信息发布员在后台发布文章后,非要管理员审核才能显示,今天一哥们问我这个问题.问:“能不能直接发布,并自动 ...
随机推荐
- AntvG6-graph图谱工具
1 快速上手 1.1 在项目中使用 npm 包引入 Step 1: 使用命令行在项目目录下执行以下命令 npm install --save @antv/g6 Step 2: 在需要用的 G6 的 J ...
- 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
private static void stringSubLen(String msg) { int max = 0; int left = 0; Map<Character,Integer&g ...
- 在 kubernetes 环境下如何优雅扩缩容 Pulsar
背景 在整个大环境的降本增效的熏陶下,我们也不得不做好应对方案. 根据对线上流量.存储以及系统资源的占用,发现我们的 Pulsar 集群有许多的冗余,所以考虑进行缩容从而减少资源浪费,最终也能省一些费 ...
- python [pymysql] 操作MySQL数据库
python [pymysql] 操作MySQL数据库 连接.关闭数据库 def get_conn_(): """ :return: 连接,游标 "" ...
- windows系统命令行cmd查看显卡驱动版本号CUDA
Win+R 输入cmd 进入命令行 输入 nvidia-smi
- rsync 运维利器,同步工具
NAME rsync - faster, flexible replacement for rcp SYNOPSIS rsync [OPTION]... SRC [SRC]... DEST rsync ...
- #提交答案题#LOJ 6467 'Zip' Quine
题目 一种比较巧妙的方式 print 1 print 1 print 1 print 1 print 1 print 1 repeat 3 2 print 2 repeat 3 2 print 2 r ...
- docker运行javaWeb服务,操作文件异常
一.问题由来 部署一个测试服务在自己的服务器上面,然后运行其中的一个功能.然后报错,报错信息如下 二.问题分析 自己一开始也很疑惑,怎么会出现这个问题呢,自己明明把对应的文件放在对应的目录下面,并且已 ...
- 诚邀报名丨首期OpenHarmony开发者成长计划分享日
OpenAtom OpenHarmony(以下简称"OpenHarmony")开源开发者成长计划,是一项为了鼓励开发者积极参与开源软件的开发维护.帮助开发者在开源项目中成长的社会 ...
- loguru 简单使用
使用Python自带的 logging 来记录日志会比较麻烦,查了下 大家都在用 loguru,看了下文档,发现是挺好用的,记录下笔记 安装 pip install loguru 简单使用 f ...