尼恩的技术社群中(50+),尼恩一直到指导大家面试, 指导大家做简历优化。

生产环境 ,如何进行SpringCloud+Jenkins+ K8s Ingress 灰度发布, 是很多小伙伴都没有实操过的难题。

但是,灰度实操,又是高级开发,和架构师的必须课。 很多小伙伴,由于不懂灰度,不懂云原生,错失了高级开发或者架构师岗位,实在非常可惜。

所以,尼恩基于《K8S学习圣经》, 为大家系统化从原理到实操,介绍一下SpringCloud+Jenkins+ K8s Ingress 自动化灰度发布。作为《K8S学习圣经》的12部分。

《K8S学习圣经》的组成

第一部分:云原生(Cloud Native)的原理与演进第二部分:穿透K8S的8大宏观架构第三部分:最小化K8s环境实操第四部分:Kubernetes 基本概念第五部分:Kubernetes 工作负载第六部分:Kubernetes 的资源控制第七部分: SVC负载均衡底层原理第八部分: Ingress底层原理和实操第九部分: 蓝绿发布、金丝雀发布、滚动发布、A/B测试 实操第十部分: 服务网格Service Mesh 宏观架构模式和实操第十一部分: 使用K8S+Harber 手动部署 Springboot 应用第十二部分: SpringCloud+Jenkins+ K8s Ingress 自动化灰度发布第十三部分: k8s springboot 生产实践(高可用部署、基于qps动态扩缩容、prometheus监控)第十四部分:k8s生产环境容器内部JVM参数配置解析及优化

米饭要一口一口的吃,不能急。

结合《K8S学习圣经》,尼恩从架构师视角出发,左手云原生+右手大数据 +SpringCloud Alibaba 微服务 核心原理做一个宏观的介绍。由于内容确实太多, 所以写多个pdf 电子书:

(1) 《 Docker 学习圣经 》PDF (V1已经完成)

(2) 《 SpringCloud Alibaba 微服务 学习圣经 》PDF (V1已经完成)

(3) 《 K8S 学习圣经 》PDF (coding…)

(4) 《 flink + hbase 学习圣经 》PDF (planning …)

以上学习圣经,并且后续会持续升级,从V1版本一直迭代发布。 就像咱们的《 尼恩 Java 面试宝典 》一样, 已经迭代到V60啦。

40岁老架构师尼恩的掏心窝: 通过一系列的学习圣经,带大家穿透“左手云原生+右手大数据 +SpringCloud Alibaba 微服务“ ,实现技术 自由 ,走向颠覆人生,让大家不迷路。

本PDF 《K8S 学习圣经》完整版PDF的 V1版本,后面会持续迭代和升级。供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

以上学习圣经的 基础知识是 尼恩的 《高并发三部曲》,建议在看 学习圣经之前,一定把尼恩的《 Java高并发三部曲》过一遍,切记,切记。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从这里获取:码云

本部分目录(第12部分)

文章目录

《K8S学习圣经》的组成本部分目录(第12部分)如何进行 SpringCloud+Jenkins+ K8s Ingress 灰度发布?回顾 Nginx-ingress 架构和原理灰度实操之前的准备部署和测试 stable 版本的 deployment 和 svc部署和测试 canary版本的 deployment 和 svc

基于用户的灰度场景接下来,开始基于 用户的灰度实操

基于权重的灰度场景基于权重的 Canary 规则基于权重的发布实操

如何进行自动化灰度?jenkins 安装和 pipeline 流水线的使用下载和启动 jenkins登录 jenkins使用 pipeline 插件安装 Pipeline 插件

pipeline 的 hello worldpipeline 语法介绍Jenkins 插件 SSH Pipeline StepssshCommand 在远程节点上执行给定的命令并响应输出sshGet 从远程主机获取文件或目录sshPut 将文件或目录放入远程主机sshRemove 删除远程主机上的文件或目录sshScript 在远程节点上执行给定的脚本(文件)并响应输出结合 withCredentials 从 Jenkins 凭证存储中读取私钥

pipeline支持的指令

ingress 灰度发布流水线设计CICD 流水线预览step1:自动化的制品发布1、克隆 springboot代码项目到本地2、maven构建springboot代码项目3、构建docker镜像4、推送docker镜像到harber上,完成制品发布

step2:生产环境进行 A/B 测试A/B 测试原理:

step3:生产环境进行 A/B 测试step4:生产环境进行版本正式切换

最后总结一下技术自由的实现路径:实现你的 架构自由:实现你的 响应式 自由:实现你的 spring cloud 自由:实现你的 linux 自由:实现你的 网络 自由:实现你的 分布式锁 自由:实现你的 王者组件 自由:实现你的 面试题 自由:

如何进行 SpringCloud+Jenkins+ K8s Ingress 灰度发布?

生产环境 ,如何进行SpringCloud+Jenkins+ K8s Ingress 灰度发布, 是很多小伙伴都没有实操过的难题。

但是,这些又是高级开发,和架构师的必须课。

在 Kubernetes 上的应用实现灰度发布,最简单的方案是引入官方的 Nginx-ingress 来实现。我们通过部署两套 deployment 和 services,分别代表灰度环境和生产环境,通过负载均衡算法,实现对两套环境的按照灰度比例进行分流,进而实现灰度发布。

通常的做法是当项目打包新镜像后,通过修改 yaml 文件的镜像版本,执行 kubectl apply 的方式来更新服务。

如果发布流程还需要进行灰度发布,那么可以通过调整两套服务的配置文件权重来控制灰度发布,这种方式离不开人工执行。

那么,有没有一种方式能够实现逐步灰度呢?比如自动将灰度比例从 10% 权重提高到 100%,且并且出现问题秒级回退.

答案是肯定的,利用 jenkins 就能够满足此类需求。

本文,为大家介绍一下,SpringCloud+Jenkins+ K8s Ingress 自动化灰度发布

回顾 Nginx-ingress 架构和原理

回顾一下 Nginx-ingress 的架构和实现原理:

Nginx-ingress 通过前置的 Loadbalancer 类型的 Service 接收集群流量,将流量转发至 Nginx-ingress Pod 内并对配置的策略进行检查,再转发至目标 Service,最终将流量转发至业务容器。

传统的 Nginx 需要我们配置 conf 文件策略。

但 Nginx-ingress 通过实现 Nginx-ingress-Controller 将原生 conf 配置文件和 yaml 配置文件进行了转化,当我们配置 yaml 文件的策略后,Nginx-ingress-Controller 将对其进行转化,并且动态更新策略,动态 Reload Nginx Pod,实现自动管理。

那么 Nginx-ingress-Controller 如何能够动态感知集群的策略变化呢?

方法有很多种,可以通过 webhook admission 拦截器,也可以通过 ServiceAccount 与 Kubernetes Api 进行交互,动态获取。

Nginx-ingress-Controller 使用后者来实现。所以在部署 Nginx-ingress 我们会发现 Deployment 内指定了 Pod 的 ServiceAccount,以及实现了 RoleBinding ,最终达到 Pod 能够与 Kubernetes Api 交互的目的。

灰度实操之前的准备

部署一套稳定版本的 deployment 和 svc

然后再部署一套灰度版本的 deployment 和 svc

在进行流量的分发测试

部署和测试 stable 版本的 deployment 和 svc

kubectl apply -f app-service.yaml 部署一下

访问一下

curl http://192.168.49.2:30808

部署和测试 canary版本的 deployment 和 svc

canary版本 的 deployment 和 svc

kubectl apply -f app-service.yaml 部署一下

访问一下

curl http://192.168.49.2:30909

以上具体的实操过程, 请参见 尼恩的 《穿透云原生视频》

基于用户的灰度场景

简单的基于用户的发布场景, ingress 需要加上注解:

nginx.ingress.kubernetes.io/canary:"true"

nginxingress.kubernetes.io/canary-by-header:"canary"

用户的Request Header 的值有一个 canary header,当 canary header的值:

当 设置为 always时,请求将会被一直发送到 Canary 版本;

当 设置为 never时,请求不会被发送到 Canary 入口;

对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。

具体如下图:

如果用户要自定义 Request Header 的值,必须与canary-by-header 注解(annotation )一起使用。

这里的例子,使用 nginx.ingress.kubernetes.io/canary-by-header,自定义 Request Header 进行流量切分,适用于灰度发布以及 A/B 测试。

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-value: "beijing"

此灰度规则为:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。

如果按照的配置,

当 Request Header的 region 设置为 beijing时,请求将会被一直发送到 Canary 版本;

当 Request Header 没有 region,或者 设置为非 beijing时,请求不会被发送到 Canary 入口;

接下来,开始基于 用户的灰度实操

配置stable版本的ingress

配置canary版本的ingress

当我们访问的时候不带header,则只会访问stable版本应用,如下:

while sleep 1; do curl http://foo.bar.com/ | egrep flag; done

如果我们在访问的时候带上region: sichuan 的header,则只会访问到canary版本应用,如下:

while sleep 1; do curl http://foo.bar.com/ -H "region: beijing" | egrep flag; done

以上具体的实操过程, 请参见 尼恩的 《穿透云原生视频》

基于权重的灰度场景

基于权重的 Canary 规则

使用 nginx.ingress.kubernetes.io/canary-weight 注解:

基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。

权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。

权重为 100 意味着所有请求都将被发送到 Canary 入口。

基于权重的发布实操

(1)配置stable版本的ingress

同基于用户的灰度场景,这里不做赘述

(2)配置canary版本的ingress

具体如下。 源文件可以找尼恩获取。

(3) 启动 基于权重的灰度 ingress

(4)然后我们通过访问测试,效果如下:

while sleep 1; do curl http://foo.bar.com/ | egrep flag; done

以上具体的灰度实操过程, 请参见 尼恩的 《穿透云原生视频》

如何进行自动化灰度?

在生产环境,通过上面的手工命令,一行一行的执行是不现实的

需要借助自动化的流水线

jenkins 安装和 pipeline 流水线的使用

Jenkins pipeline是基于Groovy语言实现的DSL,用于描述流水线如何进行,包括编译、打包、部署、测试等等步骤

此处我们使用离线安装包方式安装/docker-compose 的方式安装

Jenkins 官网:https://www.jenkins.io

官方安装文档指导:https://www.jenkins.io/doc/book/installing/

下载和启动 jenkins

这里简单介绍一下 Jenkins 的本机部署方式.

一般都是下载一个 Jenkins 的 war, 修改配置文件中的 JDK 路径, 然后 service start 就可以正常使用了.

我们在服务器上执行如下命令进行jenkins可运行包下载

mkdir -p /usr/local/jenkins

cd /usr/local/jenkins

下载这个包 https://get.jenkins.io/war-stable/2.375.1/jenkins.war

复制Jenkins war包到 安装目录

cp /vagrant/chapter28/jenkins.2.375.1.war .

找一个 start-jenkins.sh 脚本去启动jenkins。这个shell 和springboot的启动脚本一样,启动命令可以接参数start|restart|stop 分别标识启动,重启,停止服务

复制启动脚本到 安装目录

cp /vagrant/chapter28/deploy_jenkins.sh .

脚本内容如下:

给启动脚本执行权限,并启动jenkins

chmod +x start-jenkins.sh

sh start-jenkins start

查看日志

tail -f nohup.out

这种方式部署简单, 但是容易将我们的服务器环境变得十分复杂.

所以下面介绍本节docker-compose 搭建 Jenkins.

尼恩的环境,首先使用的这种方式。但是发现一个问题,新版本的 jenkins 需要依赖 jdk11, jkd8 不能用了

而尼恩的 公司有要求,一定要用jkd8

所以只能用 docker 安装 jenkins ,实现jdk的隔离。

docker-compose 编排文件 如下:

volumes:

#Jenkins 工作目录,主要存储数据( /user/data/jenkins,安装后初始化密码也在此处 /secrets/initialAdminPassword )

- ./data:/var/jenkins_home'

#将主机的Docker套接字装入容器中,这将允许Jenkins容器使用主机的Docker守护进程来构建映像并运行容器。

- '/var/run/docker.sock:/var/run/docker.sock'

#容器内可共享宿主机的 docker

- '/usr/bin/docker:/usr/bin/docker'

#容器内享有 宿主机的docker-compose 环境

- '/usr/local/bin/docker-compose:/usr/local/bin/docker-compose'

使用尼恩的一键启动shell脚本,一键启动

具体实操,请参见尼恩视频

首次登录需要查看初始化密码:( 挂载数据文件内可以查看 )

cat ./secrets/initialAdminPassword

需要修改插件的镜像源地址为

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

具体的修改方式为,修改

修改后,重新启动,就可以从清华大学的插件镜像站,下载插件了

接下来访问 jenkins

登录 jenkins

查看启动日志,看到初始化管理员密码数据即表示启动成功,

copy对应的密码,通过ip+port方式进行访问,

上面shell 脚本中指定的端口为8980,我们在启动日志中也能够看到启动的端口为8090。

在浏览器中输入:

http://cdh1:8980

终于看到jenkins的登录界面

输入密码后,安装插件 点击选择插件来安装,然后取消掉几个默认安装的插件

插件安装完毕,有几个失败的,无所谓 关键是 pipeline 插件安装好了就行

接下来,创建第一个管理员用户

全部填 admin

进行一下实例配置

保存后完成

使用 pipeline 插件

Jenkins Pipeline是一套插件,支持在Jenkins中实施和集成持续交付管道。pipeline是部署流水线,它支持脚本和声明式语法,能够比较高自由度的构建jenkins任务.个人推荐使用这种方式去构建jenkins。

Pipeline在Jenkins上增加了一套强大的自动化工具,支持从简单的持续集成到全面的CD管道的用例。通过对一系列相关任务建模,用户可以利用Pipeline的更多功能,如:

可维护:管道是在代码中实现的,并且通常会被签入源代码管理,从而使团队能够编辑,审阅和迭代他们的交付管道。可能出现:在继续进行管道运行之前,管道可以选择停止并等待人员输入或批准。复杂场景:管道支持复杂的实际CD需求,包括分叉/连接,循环和并行执行工作的能力。可扩展性:Pipeline插件支持对其DSL的定制扩展 。

一次持续交付(CD)管道是从用户到版本控制软件的自动化表达。对软件的每一次改变(在源代码控制中提交)都会在发布过程中经历一个复杂的过程。

这个过程包括以可靠和可重复的方式构建软件,以及通过测试和部署的多个阶段来推进构建的软件(称为“构建”)。

Pipeline提供了一套可扩展的工具,用于通过管道域特定语言(DSL)语法将“简单到复杂”的交付管道使用“代码”建模 。

安装 Pipeline 插件

如果没有安装 Pipeline 插件,那么还需要单独安装 Jenkins Pipeline 插件

注意,默认 Jenkins 使用 https://updates.jenkins.io/update-center.json 下载并安装扩展,但是速度较慢。

我们可以修改为使用国内镜像站点,比如清华大学镜像站点:

1)Manage Jenkins / Manage Plugins / Advanced

2)Update Site / URL https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

3)Submit

https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

pipeline 的 hello world

pipeline是部署流水线,它支持脚本和声明式语法。

通过界面手动配置来配置CD过程,想要配置一些复杂度高的任务,只能选择自由风格的项目,通过选项等操作进行配置,让jenkins可以下载代码、编译构建、然后部署到远程服务器,这样显然是不方便管理和移植的。

pipeline可以创建一个jenkinsfile来申明一个任务,方便管理和移植。

接下来我们创建一个最简单的pipeline。

登录jenkins,点击创建item:

在流水线中选择hello world 生成代码:

以上便是一个最简单的流水线。

点击build now (立即构建),jenkins任务开始执行,运行完成后点击查看执行记录:

在console output 中可以看到运行记录:

为了提高流水线的复用性以及便于流水线代码的管理,更多的是将pipeline的脚本在远程git仓库,当我们修改了远程仓库的流水线脚本,jenkins就会加载到最新的脚本执行。

在流水线配置中选择pipeline script from SCM:

按照提示配置好脚本git仓库地址,访问仓库的凭证,流水线脚本文件的名称(默认是Jenkinsfile),分支(默认是master)等。

配置完成后在仓库中添加文件Jenkinsfile把脚本提交并push, 最后执行任务,发现执行成功。

通过这个特性,我们可以把我们的流水线脚本和项目代码本身放到一个仓库中管理,达到多版本控制并和代码版本统一的效果。

如果我们编写jenkinsfile需要语法提示相关的编辑器,可以使用jenkins官方提供的vscode插件Jenkins Pipeline Linter Connector 。

使用idea Groovy 也能提示部分语法。

idea 设置jenkinsfile 语法提示方法 settings > editor > File Types > Groovy 新增一列Jenkinsfile.

pipeline 语法介绍

jenkins pipeline有2种语法:脚本式(Scripted)语法和声明式(Declar-ative)语法。

pipeline插件从2.5版本开始同时支持两种语法,官方推荐的是使用申明式语法,

在这里也只对申明式语法进行介绍。

申明式语法demo:

pipeline {

agent any

stages {

stage('pull') {

steps {

echo '拉取代码'

}

}

stage('build') {

steps {

echo '构建代码'

}

}

}

}

声明式语法中,以下结构是必须的,缺少就会报错:

pipeline:固定语法,代表整条流水线agent 执行代理人:指定流水线在哪执行,

​ 默认any即可,也可以指定在docker、虚拟机等等里执行

environment 环境变量,类似全局变量

environment {

//构建执行者

BUILD_USER = ""

}

triggers 构建触发器,Jenkins自动构建条件

triggers{

//每3分钟判断一次代码是否有变化

pollSCM('H/3 * * * *')

}

stages:流水线阶段集合节点 该节点中至少有一个stagestage:流水线的阶段节点 每个阶段中至少包含一个stepssteps:执行步骤集合 每个集合至少包含一个step。step: 执行步骤。

下面是一个比较大的例子

pipeline {

//代理,通常是一个机器或容器

agent any

//环境变量,类似全局变量

environment {

//构建执行者

BUILD_USER = ""

}

//构建触发器,Jenkins自动构建条件

triggers{

//每3分钟判断一次代码是否有变化

pollSCM('H/3 * * * *')

}

stages {

//构建阶段

stage('Build') {

//使用build user vars插件,获取构建执行者

steps {

wrap([$class: 'BuildUser']) {

script {

//将构建执行者注入到环境变量中,方便最后通知使用

BUILD_USER = "${env.BUILD_USER}"

}

}

/* 从Bitbucket上拉取分支

* @url git地址

* @branch 分支名称

* @credentialsId Jenkins凭证Id,用于远程访问

*/

git(url: 'https://demo@bitbucket.org/demo/demo.git', branch: 'master', credentialsId: 'Bitbucket')

//执行maven打包

//-B --batch-mode 在非交互(批处理)模式下运行(该模式下,当Mven需要输入时,它不会停下来接受用户的输入,而是使用合理的默认值);

//打包时跳过JUnit测试用例,

//-DskipTests 不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下

//-Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类

sh 'npm install && npm run build'

}

}

//部署阶段

stage('Deliver') {

steps {

//执行脚本

sh '''

cd /var/lib/jenkins/workspace/demo/

tar -cvf dist.tar.gz ./dist

'''

}

}

stage('SSH') {

steps {

script {

def remote = [:]

remote.name = 'test'

remote.host = '192.168.12.12'

remote.allowAnyHosts = true

withCredentials([usernamePassword(credentialsId: '192.168.12.12', passwordVariable: 'password', usernameVariable: 'username')]) {

remote.user = "${username}"

remote.password = "${password}"

}

sshCommand remote: remote, command: "pwd"

sshRemove remote: remote, path: '/usr/share/nginx/html/demo/dist'

sshPut remote: remote, from: '/var/lib/jenkins/workspace/demo/dist.tar.gz', into: '/usr/share/nginx/html/test-monitor/'

sshCommand remote: remote, command: "cd /usr/share/nginx/html/demo/ && tar -xvf ./dist.tar.gz"

}

}

}

}

}

Jenkins 插件 SSH Pipeline Steps

安装插件 SSH Pipeline Steps, 以支持 跨机器执行脚本。

此插件可通过SSH在远程服务器执行命令和传输文件。

sshCommand 在远程节点上执行给定的命令并响应输出

node {

def remote = [:]

remote.name = 'root'

remote.host = 'cdh1'

remote.user = 'root'

remote.password = 'vagrant'

remote.allowAnyHosts = true

stage('Remote SSH') {

sshCommand remote: remote, command: "ls -lrt"

sshCommand remote: remote, command: "for i in {1..5}; do echo -n \"Loop \$i \"; date ; sleep 1; done"

}

}

sshGet 从远程主机获取文件或目录

node {

def remote = [:]

remote.name = 'test'

remote.host = 'test.domain.com'

remote.user = 'root'

remote.password = 'password'

remote.allowAnyHosts = true

stage('Remote SSH') {

sshGet remote: remote, from: 'abc.sh', into: 'abc_get.sh', override: true

}

}

sshPut 将文件或目录放入远程主机

node {

def remote = [:]

remote.name = 'test'

remote.host = 'test.domain.com'

remote.user = 'root'

remote.password = 'password'

remote.allowAnyHosts = true

stage('Remote SSH') {

writeFile file: 'abc.sh', text: 'ls -lrt'

sshPut remote: remote, from: 'abc.sh', into: '.'

}

}

sshRemove 删除远程主机上的文件或目录

node {

def remote = [:]

remote.name = 'test'

remote.host = 'test.domain.com'

remote.user = 'root'

remote.password = 'password'

remote.allowAnyHosts = true

stage('Remote SSH') {

sshRemove remote: remote, path: "abc.sh"

}

}

sshScript 在远程节点上执行给定的脚本(文件)并响应输出

node {

def remote = [:]

remote.name = 'test'

remote.host = 'test.domain.com'

remote.user = 'root'

remote.password = 'password'

remote.allowAnyHosts = true

stage('Remote SSH') {

writeFile file: 'abc.sh', text: 'ls -lrt'

sshScript remote: remote, script: "abc.sh"

}

}

结合 withCredentials 从 Jenkins 凭证存储中读取私钥

为了提高安全性,可以用Credentials Plugin屏蔽用户名密码。

def remote = [:]

remote.name = "node-1"

remote.host = "10.000.000.153"

remote.allowAnyHosts = true

node {

withCredentials([sshUserPrivateKey(credentialsId: 'sshUser', keyFileVariable: 'identity', passphraseVariable: '', usernameVariable: 'userName')]) {

remote.user = userName

remote.identityFile = identity

stage("SSH Steps Rocks!") {

writeFile file: 'abc.sh', text: 'ls'

sshCommand remote: remote, command: 'for i in {1..5}; do echo -n \"Loop \$i \"; date ; sleep 1; done'

sshPut remote: remote, from: 'abc.sh', into: '.'

sshGet remote: remote, from: 'abc.sh', into: 'bac.sh', override: true

sshScript remote: remote, script: 'abc.sh'

sshRemove remote: remote, path: 'abc.sh'

}

}

}

pipeline支持的指令

pipeline的基本结构满足不了现实多变的需求。所以,jenkins pipeline通过各种指令(directive)来丰富自己。

Jenkins pipeline支持的指令有:

environment:用于设置环境变量,可定义在stage或pipeline部分tools:可定义在pipeline或stage部分。它会自动下载并安装我们指定的工具,并将其加入PATH变量中input:定义在stage部分,会暂停pipeline,提示你输入内容options:用于配置jenkins pipeline本身的选项,比如options { retry(3) }表示,当pipeline失败时再重试2次。options指令可定义在stage或pipeline部分parallel:并行执行多个step。parameters:与input不同,parameters是执行pipeline前传入的一些参数triggers:用于定义执行pipeline的触发器when:当满足when定义的条件时,阶段才执行。

ingress 灰度发布流水线设计

在使用ingress 进行灰度之前,直接使用service+deployment的方式进行灰度。这种场景下,可以 通过不同的镜像(含deployment)来进行灰度大概的流程如下:

发布canary版本应用进行测试测试完成将canary版本替换成stable版本删除canary版本的ingress配置删除老的stable版本

整个过程中,对于已经部署好的deployment是不能直接修改labels标签的。

所以,在canary版本测试完成后,还要更新stable版本的镜像。当然,可以使用滚动更新的方式去完成。那我们流水线可以这样设计,如下:

在使用ingress 进行灰度之后,灰度测试通过之后,就使用灰度镜像作为new stable 镜像使用,不需要重新的打包镜像, 但是在发布期间,线上同时会存在旧的 old stable 和 canary 两套镜像, 虽然浪费一些资源,但是好处随时可以回退。

其实尼恩告大家,版本的发布是惊心动魄, 回退的概率非常大。 同时存在两套版本,实现s级回退, 也是一件非常爽的事情。

在使用ingress 进行灰度之后,灰度的流程,可以变得更加灵活,更加丰富,更加稳健。

在使用ingress 进行灰度 ,总体流程为3部分,大致如下:

CICD 流水线预览

为了实现以上目标,我们设计了以下持续部署流水线。

AB测试和灰度,部署流水线主要实现了以下几个步骤:

1、自动化的制品发布,通过gitlab进行自动化打包之后,通过钩子方法触发流水线,打包镜像到harber仓库 2、生产环境进行 A/B 测试 3、生产环境自动灰度发布(自动进行4次逐渐提升灰度比例) 4、生产环境进行版本正式切换

step1:自动化的制品发布

自动化的制品发布流程图:

从 Gitlab 提交代码到自动触发持续集成的步骤:

1、提交代码后触发持续集成,自动构建镜像 2、镜像构建完成后,自动推送镜像到制品库 3、触发持续部署

在gitlab上, 有回调jenkins的hook 链接设置,设置之后,可以触发 jenkins 的 自动化制品构建流水线。

这里的制品就是docker 镜像。 制品的 仓库就是harber。

模拟的 制品构建流水线如下

具体的步骤,大概如下:

1、克隆 springboot代码项目到本地

stage('Clone') {

echo "1.Clone Stage"

git url: "https://gitee.com/xujk-27400861/springboot-dubbo.git"

script {

build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()

}

echo "${build_tag}"

}

2、maven构建springboot代码项目

编写脚本,因为我们要编译构建项目的子项目,所以需要跳转到子目录去构建

stage('执行构建') {

container('maven')

{

sh "mvn --version"

sh 'pwd'

sh '''cd provider/

mvn clean package -DskipTests'''

//sh 'mvn package'

echo '构建完成'

}

}

provider/为项目子目录,我们要编译的项目;-DskipTests参数,表示跳过代码测试

3、构建docker镜像

编写脚本,构建docker镜像;

stage('Build') {

echo "3.Build Stage"

container('docker') {

sh "docker -v"

sh 'pwd'

sh """cd /home/jenkins/agent/workspace/linetest/provider/

ls

docker build -t xjk27400861/springbootapp:${build_tag} ."""

}

}

4、推送docker镜像到harber上,完成制品发布

编写脚本,推送docker镜像;

stage('Push') {

echo "4.Push Docker Image Stage"

container('docker') {

sh "docker login --username=xjk27400861 -p xujingkun@123"

sh "docker push xjk27400861/springbootapp:${build_tag}"

}

}

具体的实操,请参见 尼恩的云原生视频。

step2:生产环境进行 A/B 测试

进行 A/B 测试流程如下:

进行 A/B 测试时,只有 Header 包含 location=beijing 可以访问新版本,其他用户访问生产环境仍然为旧版本。 或者header可以设置为其他的参数。

这里要注意,可以把参与 A/B 测试 的用户,在数据库里边进行配置, 前端能带有AB测试的标志。

AB测试手动触发,流水线比较简单。

发布AB测试的ingress之后, 流量分发的架构图如下:

A/B 测试原理:

部署了两套应用,一套旧的稳定的stable应用,一套新版canary 灰度应用。

新旧的应用都配置了ingress相同的host。

而新的ingress还添加了注解canary:true 代表开启使用灰度发布

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-value: "beijing"

只有 Header 包含 location=beijing 的流量是会从new-ingress这个ingress进来访问新版本应用。

其他流量从old-ingress的ingress去访问老版本的应用。

具体的实操,请参见 尼恩的云原生视频。

step3:生产环境进行 A/B 测试

在AB测试通过之后,可以结束ab测试的ingress,通过 流水线结束。

接下来,开始灰度,一共四轮灰度

第一次灰度:新版本 30% 的灰度比例,此时访问生产环境大约有 30% 的流量进入新版本灰度环境:

30s 后自动进行第二轮灰度:新版本 60% 的灰度比例:

60s 后自动进行第三轮灰度:新版本 90% 的灰度比例:

60s 后自动进行第四轮灰度:新版本 100% 的灰度比例:

每一轮灰度,可以两种方式步进:

(1) 人工确认:是否自动灰度发布, 流水线上设置确认按钮, 单击确认进入下一步

(2) 定时步进:流水线上设置时间,比如每轮间隔 30s

本案例中,配置了定时步进方式,渐进式进行,每次持续 30s 后自动进入下一个灰度阶段。

灰度过程中的流量架构图,大致如下:

在不同的灰度阶段,会发现请求新版本出现的概率越来越高。

灰度流水线的示意图如下;

如果过程当中需要回退,可以结束流水线,然后执行 灰度撤销流水线。 灰度撤销流水线的示意图,大致如下 jenkins 流水线上,更多的是shell脚本的编写

由此可见,shell脚本的编写能力,是多么重要

具体的实操,请参见 尼恩的云原生视频。

step4:生产环境进行版本正式切换

灰度版本稳定一段时间之后, 进行善后的工作

把老的稳定版本去掉, 完成生产环境进行版本正式切换

流程图如下:

步骤:

1、把stable-service指向访问cannary-deloyment

应为canary 已经足够稳定了

2、删除canary-ingress

不需要 灰度的ingress 了

3、删除canary-service

不需要灰度的svc 了

4、删除stable-deployment

不需要 老的stable-deployment 了

canary deployment 已经足够稳定了,已经编程新的stable deployment

具体的实操,请参见 尼恩的云原生视频。

最后总结一下

上面的灰度流程, 很多环节也是手动的,需要人工参与的。

如果不需要手动参与,可以使用argo-rollouts结合argocd进行灰度发布,argo-rollouts自定义了一套CRD用于控制发布流程,可以省去很多手动操作过程,argocd是基于gitops实现的一套软件,便于我们进行CD控制,也提供了UI面板进行操作。

但是线上发布, 是非常重要,非常谨慎的一个操作。

而且很容易出问题,及时有预发布环境, 也容易出问题, 常常出现紧急回退的场景。

在尼恩亲手尽力的几百次发布过程中, 回退是大概率事件。

常常进行版本回退。

所以,不建议使用全自动的发布流程,全自动无值守,往往意味着高风险。

一句话:背锅的滋味,不好受呀。

技术自由的实现路径:

实现你的 架构自由:

《吃透8图1模板,人人可以做架构》

《10Wqps评论中台,如何架构?B站是这么做的!!!》

《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》

《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》

《100亿级订单怎么调度,来一个大厂的极品方案》

《2个大厂 100亿级 超大流量 红包 架构方案》

… 更多架构文章,正在添加中

实现你的 响应式 自由:

《响应式圣经:10W字,实现Spring响应式编程自由》

这是老版本 《Flux、Mono、Reactor 实战(史上最全)》

实现你的 spring cloud 自由:

《Spring cloud Alibaba 学习圣经》 PDF

《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》

《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》

实现你的 linux 自由:

《Linux命令大全:2W多字,一次实现Linux自由》

实现你的 网络 自由:

《TCP协议详解 (史上最全)》

《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》

实现你的 分布式锁 自由:

《Redis分布式锁(图解 - 秒懂 - 史上最全)》

《Zookeeper 分布式锁 - 图解 - 秒懂》

实现你的 王者组件 自由:

《队列之王: Disruptor 原理、架构、源码 一文穿透》

《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》

《缓存之王:Caffeine 的使用(史上最全)》

《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》

实现你的 面试题 自由:

4000页《尼恩Java面试宝典 》 40个专题

以上尼恩 架构笔记、面试题 的PDF文件,请到《技术自由圈》公众号领取 ↓ ↓ ↓

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: