1 Argo Rollouts概览
Argo Rollouts
-
由一个控制器和一组 CRD 组成,rollouts 用来能够实现并取代 K8S 之上的 deployment 编排无状态应用的一个控制器,并为 Kubernetes 提供高级部署功能,包括:
blue-green
(蓝绿部署)canary
(金丝雀部署)canary analysis
(渐近交付)experimentation
(实验性的金丝雀发布)progressive delivery
(渐进式交付过程)
-
支持与 Ingress Controller(Nginx 和 ALB)及 ServiceMesh(Istio、Linkerd 和 SMI)集成,利用它们的流量治理能力实现流量迁移过程
-
能够查询和解释来自多种指标系统(Prometheus、Kubernetes Jobs、Web、Datadog 等)的指标来验证 Blue Green 或 Canary 部署结果,并根据结果自动决定执行升级或回滚
-
Rollout 的几个相关的 CRD
- Rollout、AnalysisTemplate(名称空间级别的模板)、ClusterAnalysisTemplate(集群级别的模板) 和 AnalysisRun
- 其中 AnalysisTemplate、ClusterAnalysisTemplate 和 AnalysisRun 就是为了确保与 rollout 完成结合实现所谓的分析功能
基本工作机制
-
与 Deployment 相似,Argo Rollouts 控制器借助于 ReplicaSet 完成 Pod 的创建、缩放和删除;
-
ReplicaSet 资源由 Rollout 的 spec.template 字段进行定义
1.1 Argo Rollouts 架构
Argo Rollout 主要由 Argo Rollout Controller、Rollout CRD、ReplicaSet、Ingress/Service、AnalysisTemplate/AnalysisRun、Metric providers 和 CLI/GUI 等组件构成
- rollout 与 RS 实现对 Pod 的编排
- rollout 与 Ingress/Service 实现在交付或发布的过程中等比例或者按用户的需求定义精细迁移和分割流量
- AnalysisTemplate 允许用户定义从那种指标系统中来根据指标结果来判定运行是否正常,但是 template 自己是不能直接运行的,所以我们需要将其运行为实例并跑起来,该实例也就是 AnalysisRun
- AnalysisRun :本质就是在被 rollouts 在更具自己的发布过程中临时基于 template 创建的实例,该实例的作用就是结合指标系统获取指标,随后在发布并创建了新 Pod ,这时候新 Pod 就为 canary Pod 老版本就为 Stable Pod
并且 rollout 会像 deployment 一样保存多个 Pod 的版本以便于后续的工作中我们需要按需回滚到指定的版本,另外一旦我们的 Pod 存在新老版本并存的情况下,我们需要将用户的请求流量分割到不同版本的 Pod 上
这个分割可以借助 service 按照 Pod 的数量等比例进行,也可以更具 ingress 的定义从而实现精准定义并导入流量
1.2 Argo Rollouts 架构组件
Rollout Controller
- 负责管理 Rollout CRD 资源对象
Rollout CRD
- 由 Argo Rollout 引入的自定义资源类型,与 Kubernetes Deployment 兼容,但具有控制高级部署方法的阶段、阈值和方法的额外字段
- 并不会对 Kubernetes Deployment 施加任何影响,或要使用 Rollout 的功能,用户需要手动将资源从 Deployment 迁移至 Rollout
Ingress/Service
- Argo Rollouts 使用标准的 Kubernetes Service,但需要一些额外的元数据
- 针对 Canary 部署,Rollouts 支持多种不同的 ServiceMesh 和 Ingress Controller ,实现精细化的流量分割和迁移
AnalysisTemplate 和 AnalysisRun
- Analysis 是将 Rollout 连接至特定的指标采集器中,并为其支持的某些指标定义特定的阈值的能力,于是,这些指标的具体值将决定更新操作是否成功进行;
- 若指标查询结果满足阈值,则继续进行;若不能满足,则执行回滚;若查询结果不确定,则暂停;
- 为了执行 Analysis,Argo Rollouts 提供了 AnalysisTemplate 和 AnalysisRun 两个 CRD
2 部署 Argo Rollouts
argo rollout 官网:https://argoproj.github.io/argo-rollouts/
2.1 部署 Argo Rollouts
1 创建 NS
root@master:~# kubectl create namespace argo-rollouts
2 通关官方 YAML 创建
root@master:~# kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
- 会生成一个
services/ argo-rollouts-metrics
、一个 deployments/argo-rollouts ,以及一组CRD
3 查看当前 argo-rollouts NS
root@master:~# kubectl get pod -n argo-rollouts
NAME READY STATUS RESTARTS AGE
argo-rollouts-99897c965-zvc28 1/1 Running 0 13s
# 查看 svc ,该 SVC 向 Prometheus 提供指标
root@master:~# kubectl get svc -n argo-rollouts
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
argo-rollouts-metrics ClusterIP 10.100.168.25 <none> 8090/TCP 3m9s
2.2 部署 Dashboard
1 部署 yaml
root@master:~# kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/dashboard-install.yaml
- 安装生成的
services/argo-rollouts-dashboard
默认监听于TCP/3100端口
2 查看对应 Pod 和 svc
# 可以看到当前 Pod 依旧运行了 rollouts 和 dashboard 两个
root@master:~# kubectl get pod -n argo-rollouts
NAME READY STATUS RESTARTS AGE
argo-rollouts-99897c965-zvc28 1/1 Running 0 2m24s
argo-rollouts-dashboard-66444c66f9-ndbmv 1/1 Running 0 21s
# 查看 dashboard SVC
root@master:~# kubectl get svc -n argo-rollouts
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
argo-rollouts-dashboard ClusterIP 10.103.63.255 <none> 3100/TCP 66s
2.2.1 浏览器访问
1 提供 external-ip
root@master:~# kubectl edit svc -n argo-rollouts argo-rollouts-dashboard
spec:
# 在 spec 字段中添加下面两个字段,从而实现提供对外暴露
externalIPs:
- 10.0.0.131
2 浏览器访问
2.3 查看对应生成的 CRD
当我们部署完毕之后会默认生成对应的 CRD
root@master:~# kubectl api-resources --api-group=argoproj.io
NAME SHORTNAMES APIVERSION NAMESPACED KIND
analysisruns ar argoproj.io/v1alpha1 true AnalysisRun
analysistemplates at argoproj.io/v1alpha1 true AnalysisTemplate
applications app,apps argoproj.io/v1alpha1 true Application
applicationsets appset,appsets argoproj.io/v1alpha1 true ApplicationSet
appprojects appproj,appprojs argoproj.io/v1alpha1 true AppProject
clusteranalysistemplates cat argoproj.io/v1alpha1 false ClusterAnalysisTemplate
experiments exp argoproj.io/v1alpha1 true Experiment
rollouts ro argoproj.io/v1alpha1 true Rollout
# 可以看到多了 rollouts、analysistemplates、analysisruns、ClusterAnalysisTemplate、Experiment
2.4 部署客户端插件 kubectl argo rollouts
argo rollouts 专用的 kubectl 插件为可选组件,但安装该组件将能够非常便捷地使用 Argo Rollouts
插件下载地址:https://github.com/argoproj/argo-rollouts/releases/
1 下载
root@master:~# wget https://github.com/argoproj/argo-rollouts/releases/download/v1.2.0/kubectl-argo-rollouts-linux-amd64
2 添加执行权限并移动至 usr/local/bin
目录下
# 添加执行权限
root@master:~# chmod +x kubectl-argo-rollouts-linux-amd64
# 移动到 usr/local/bin 目录下,并且改名为 argorollouts
root@master:~# mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts-linux
# 验证 argorollouts 版本
root@master:~# kubectl-argo-rollouts version
kubectl-argo-rollouts: v1.2.0+08cf10e
BuildDate: 2022-03-22T00:25:11Z
GitCommit: 08cf10e554fe99c24c8a37ad07fadd9318e4c8a1
GitTreeState: clean
GoVersion: go1.17.6
Compiler: gc
Platform: linux/amd64
3 Rollout CRD 资源规范介绍
Rollout 的功能在很大程度上与 Deployment 兼容,支持的字段也有不少相同之处
Rollout CRD 的 spec 字段支持使用的字段包括
-
replicas
:运行的Pod实例数量,默认为1; -
selector
:筛选Pod对象的标签选择器; -
template
:ReplicaSet模板对象; -
revisionHistoryLimit
:更新历史中保留的ReplicaSet Revision数量; -
minReadySeconds
:无容器crash的情况下,新建的Pod被视为可用的最短时长,默认为0,即立即转为Ready; -
paused
:是否置为暂停状态; -
progressDeadlineSeconds
:更新过程中,更新步骤的最大等待时长,默认为600秒; -
progressDeadlineAbort
: 未使用analysis或experiment而progressDeadlineSeconds超时的情况下,是否中止更新过程,默认为否; -
restartAt
:重启Pod的时刻,其值为UTC时间戳格式; -
strategy
:更新策略,支持 canary 和 blueGreen 两种
3.1 Rollout 更新策略之 Canary
如果我们需要通过 canary 金丝雀方法发布应用的话,那么就需要通过 spec.strategy.canary
启用 canary 功能
支持内嵌的字段(其中黑体标粗的为重要字段):
-
canaryService
:由控制器用来匹配到 Canary Pods (金丝雀版本的 Pod) 上的 Service,该字段只有在启动了 trafficRouting(流量迁移) 才会使用; -
stableService
:由控制器用来匹配到 Stable Pods (老版本的 Pod) 上的 Service,该字段只有在启动了 trafficRouting(流量迁移) 才会使用; -
canaryMetadata
:需要添加到 Canary 版本的 Pod 上的元数据,仅存于 Canary 更新期间,更新完成后即成为Stable; -
stableMetadata
:需要添加到Stable版本的Pod上的元数据; -
maxSurge
-
maxUnavailable
-
scaleDownDelayRevisionLimit
:在旧 RS 上启动缩容之前,可运行着的旧RS的数量; -
abortScaleDownDelaySeconds
:启用了 trafficRouting 时,因更新中止 而收缩 Canary 版本 Pod 数量之前的延迟时长,默认为30s; -
scaleDownDelaySeconds
: 启用了 trafficRouting 时,缩容前一个 ReplicaSet 规模的延迟时长,默认为 30s; -
analysis
:在滚动更新期间于后台运行的 analysis,可选; -
steps
:Canary 金丝雀发布更新期间要执行的步骤,该步骤至关重要,也就是说当前假如有 100 个 Pod 那么我们需要更新的 Pod 比例是多少,就由该字段控制; -
trafficRouting
:设定 Ingress Controller 或 ServiceMesh 如何动态调整配置以完成精细化地流量分割和流量迁移; -
antiAffinity
:定义 Canary Pod 与旧 ReplicaSet Pod 之间的反亲和关系;
3.1.1 配置 Canary 策略
常用的 Step
字段
-
pause
:暂停 step- 可永久暂停
- 可内嵌
duration
字段指定暂停时长,直到用户手动运行才接触暂停
-
setWeight
:设定新版本 ReplicSet 激活的 Pod 比例,以及调度至新版本的流量比例;- 使用场景,可以第一次将激活更新的 Pod 比例调至 10% 然后 step 暂停,依次类推直到更新完成
-
setCanaryScale
:设定 Canary 扩容期间 Pod 扩增与流量扩增的对应关系,支持如下三种配置之一replicas
:明确设定 Canary RS 的规模为该处指定的 Pod 数量,但不改变先前设定的流量比例;weight
:明确设定 Canary RS 的规模为该处指定的比例,但不改变先前设定的流量比例;matchTrafficWeight
:设定新版的 Pod 规模与调度至这些 Pod 的流量同比例滚动;- 也就是说定义 10% 的话就会有 10% 的流量调度到新版的 Pod 中,其余 90% 的流量就调度给旧版的 Pod
-
analysis
:内联定义或调用的analysis step;args
dryRun
templates
measurementRetention
-
experiment
:内联定义或调用的 experiment step;analyses
duration
templates
4 示例操作说明
4.1 Canary 基础功能示例
案例环境说明:
-
应用:spring-boot-helloworld
- 微服务,默认监听于80/tcp
- 相关的 path:
/
、/version
和/hello
-
使用 Argo Rollouts 提供的 Rollout 资源编排运行该应用
- 使用 Canary 更新策略
- 推出一个 Canary Pod 后即暂停,需要用户手动 Promote
在下面示例中需要使用到的相关命令:
-
更新应用
kubectl argo rollouts set image ROLLOUT_NAME CONTAINTER=NEW_IMAGE
-
继续更新
kubectl-argo-rollouts promote ROLLOUT_NAME [flags]
-
中止更新
kubectl-argo-rollouts abort ROLLOUT_NAME [flags]
-
回滚
kubectl-argo-rollouts undo ROLLOUT_NAME [flags]
示例 yaml 解释:
# 该 yaml 通过 Rollout 实现对 helloworld 这个项目的滚动发布
# 通过这个 yaml 可以发现与我们的 deployment 几乎没有区别
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-spring-boot-helloworld
spec:
# 定义 10 个 Pod 副本集
replicas: 10
# 定义更新策略为 canary 金丝雀
strategy:
canary:
steps:
# 更新激活 canary 版本百分之 10 的 Pod ,并永久暂停,需要用户手动运行 kubectl-argo-rollouts promote ROLLOUT_NAME 即可解除暂停
- setWeight: 10
- pause: {}
# 更新激活 canary 版本百分之 20 的 Pod 并暂停 20 s
- setWeight: 20
- pause: {duration: 20}
# 更新激活 canary 版本百分之 30 的 Pod 并暂停 20 s
- setWeight: 30
- pause: {duration: 20}
# 更新激活 canary 版本百分之 40 的 Pod 并暂停 20 s
- setWeight: 40
- pause: {duration: 20}
# 更新激活 canary 版本百分之 60 的 Pod 并暂停 20 s
- setWeight: 60
- pause: {duration: 20}
# 更新激活 canary 版本百分之 80 的 Pod 并暂停 20 s,然后将剩余的百分之 20 也全部替换
- setWeight: 80
- pause: {duration: 20}
# 在更新历史中保存 5 个版本,以便于回滚
revisionHistoryLimit: 5
# 标签选择器
selector:
matchLabels:
app: spring-boot-helloworld
template:
metadata:
labels:
app: spring-boot-helloworld
......省略......
4.1.1 创建 rollout
1 编写 YAML
# 创建 Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-spring-boot-helloworld
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10
- pause: {}
- setWeight: 20
- pause: {duration: 20}
- setWeight: 30
- pause: {duration: 20}
- setWeight: 40
- pause: {duration: 20}
- setWeight: 60
- pause: {duration: 20}
- setWeight: 80
- pause: {duration: 20}
revisionHistoryLimit: 5
selector:
matchLabels:
app: spring-boot-helloworld
template:
metadata:
labels:
app: spring-boot-helloworld
spec:
containers:
- name: spring-boot-helloworld
image: ikubernetes/spring-boot-helloworld:v0.9.5
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 50m
livenessProbe:
httpGet:
path: '/'
port: 80
scheme: HTTP
initialDelaySeconds: 3
readinessProbe:
httpGet:
path: '/'
port: 80
scheme: HTTP
initialDelaySeconds: 5
# 创建 SVC
---
apiVersion: v1
kind: Service
metadata:
name: spring-boot-helloworld
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: spring-boot-helloworld
2 创建
root@master:~/# kubectl apply -f 01-argo-rollouts-demo.yaml
3 查看
# 10 pod 创建成功
root@master:~/# kubectl get pod
NAME READY STATUS RESTARTS AGE
rollouts-spring-boot-helloworld-96697f77d-2wp7w 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-5qpbs 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-84dmz 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-c9pvq 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-k8m5b 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-lvxnk 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-nrvfh 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-p99rv 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-w6zrv 1/1 Running 0 13s
rollouts-spring-boot-helloworld-96697f77d-wf94w 1/1 Running 0 13s
4 通过客户端命令查看
# 当前 rollouts-spring-boot-helloworld 创建成功
root@master:~# kubectl-argo-rollouts list rollouts
NAME STRATEGY STATUS STEP SET-WEIGHT READY DESIRED UP-TO-DATE AVAILABLE
rollouts-spring-boot-helloworld Canary Healthy 12/12 100 10/10 10 10 10
4.1.2 创建 client Pod 验证
1 创建 Pod
root@master:~# kubectl run client-$RANDOM --image ikubernetes/admin-box:v1.2 --rm -it --restart=Never --command -- /bin/bash
2 对 spring-boot-helloworld SVC 做请求测试,测试成功
root@client-6152 /# while true; do curl spring-boot-helloworld/version ; echo ; sleep 1 ; done
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
4.1.3 通过 web 浏览器验证
1 将 NS 指定为 default NS ,因为刚才将对应 Pod 部署至了该 NS
2 点击该 rollouts
进入到该 rollouts 之后看到更新还需要 12 个步骤,然后里面可以看到对应的
4.1.4 验证修改镜像 tag 实现 canary 更新效果
1 我们先监视查看该 rollouts
# 该命令能够实现查看滚动更新状态
root@master:~# kubectl-argo-rollouts get rollouts rollouts-spring-boot-helloworld --watch
Name: rollouts-spring-boot-helloworld
Namespace: default
Status: ✔ Healthy
Strategy: Canary
Step: 12/12
SetWeight: 100
ActualWeight: 100
Images: ikubernetes/spring-boot-helloworld:v0.9.5 (stable)
Replicas:
Desired: 10
Current: 10
Updated: 10
Ready: 10
Available: 10
NAME KIND STATUS AGE INFO
⟳ rollouts-spring-boot-helloworld Rollout ✔ Healthy 13m
└──# revision:1
└──⧉ rollouts-spring-boot-helloworld-96697f77d ReplicaSet ✔ Healthy 13m stable
├──□ rollouts-spring-boot-helloworld-96697f77d-2wp7w Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-5qpbs Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-84dmz Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-c9pvq Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-k8m5b Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-lvxnk Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-nrvfh Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-p99rv Pod ✔ Running 13m ready:1/1
├──□ rollouts-spring-boot-helloworld-96697f77d-w6zrv Pod ✔ Running 13m ready:1/1
└──□ rollouts-spring-boot-helloworld-96697f77d-wf94w Pod ✔ Running 13m ready:1/1
2 在开启一个新的终端将该 rollouts 的 image 从 0.9.5 改为 0.9.6
root@master:~# kubectl-argo-rollouts set image rollouts-sping-boot-helloworld spring-boot-helloworld=ikubernetes/spring-boot-helloworld:v0.9.6
# set image 修改镜像为 v0.9.6 版本
3 然后再回到监视 rollouts 的终端就会发现有一个 version:2 版本的 canary 发布
4 并且产生 client Pod 也会有差不多百分之 10 的流量调度到了 0.9.6 版本上
root@client-6152 /# while true; do curl spring-boot-helloworld/version ; echo ; sleep 1 ; done
version 0.9.5
version 0.9.5
Spring Boot Helloworld, version 0.9.6
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
version 0.9.5
Spring Boot Helloworld, version 0.9.6
5 web 浏览器可以看到第一个 step 已经实现 canary 版本更新,而且当前有百分之 10 的流量在新版本之上,百分之 90 的流量在老版本之上
4.1.4.1 继续将剩余 step 更新
从上面可以看到他在第一个 step 就已经暂停了,那么如果我们想让这里的步骤继续执行下去就得执行下面操作:
1 执行 promote 参数,然后更上 rollouts 就会执行下面的 step
root@master:~# kubectl-argo-rollouts promote rollouts-spring-boot-helloworld
2 再次通过另一个终端可以看到所有 step 已经更新完成
3 浏览器查看
4 client Pod 访问已经全部将流量切换到了 0.9.6 版本上
root@client-6152 /# while true; do curl spring-boot-helloworld/version ; echo ; sleep 1 ; done
Spring Boot Helloworld, version 0.9.6
Spring Boot Helloworld, version 0.9.6
Spring Boot Helloworld, version 0.9.6
以上就是 canary 的基本操作,这样就能够精细化的实现流量管控,但是此时我们还有一个问题就是无法确定我们的 Pod 是否发布成功能够对外提供访问,所以我们任然需要借助于别的方式来实现,这就是 analysis 的作用