赞
踩
作者:子丑
灰度发布是降低生产部署风险,提升线上服务稳定性的重要手段,这在当前快速迭代的软件研发中尤为重要。相对于 K8s 默认的滚动部署或者简单的 Pod 分批,基于流量特征的灰度发布验证更精准,风险更低。
在云原生场景下,基于 Nginx Ingress 的灰度发布是被广泛使用的方案之一。该方案通过在流量入口侧进行灰度和正常流量的路由调配,将灰度流量导入到灰度的服务版本,从而可以在全量部署到生产环境前,对新版本进行验证,当验证不通过时,可以及时回退,将风险控制在较低的范围内。而整个发布和验证,能做到对终端用户服务的不间断和体验一致。
对于常见的 web 服务,Ingress 灰度发布的基本逻辑是:将指向同一入口(HOST)的访问请求,根据特征或流量比例,将一部分流量路由到灰度服务中,通过对灰度流量的监控和验证,判断灰度服务是否符合上线标准。
按照流量的路径,我们可以画出 Ingress 流量灰度在 K8s 上的简单架构(如下图)。
流量从入口的 HOST 进来后,会根据 Nginx-Ingress 的流量标识(定义在 Ingress 的 annotation 中),将符合灰度标识的请求路由到灰度环境的 service 中,进而进入背后的工作负载 Pod。对于每个环境内部的 service 和 deployment,它们并不主动感知灰度标识,也就是说,通常情况下,内部的 rpc 流量并不携带灰度标识。因此,这种架构主要解决的是外部流量灰度的问题,适合服务数量不太多,服务间调用比较简单的场景。
接下来,我们结合云效 AppStack,来看下如何在阿里云 ACK 集群上进行应用的 Ingress 灰度发布。
我们首先导入 ACK 集群并创建应用。
在应用中,我们分别定义灰度环境与生产环境,两个环境共享同一个 K8s 集群,我们将灰度环境命名为 grey,生产环境命名为 ack-prod。
我们打开应用设置,编排配置来定义部署编排。灰度发布生效的关键是 Ingress 中的 canary 注解,需要查看你的 nginx-ingress-controller 版本,以确定可以支持的注解。我测试的集群上 nginx-ingress-controller 的版本是 0.30,采用 header 来标识灰度流量。
为了做到只在灰度环境中开启灰度的路由配置,我们使用了编排模板的条件语句,见下面的编排示例:
--- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .AppStack.appName }}-{{ .AppStack.envName }} namespace: {{ .Values.namespace }} {{ if eq .AppStack.envName "grey" }} annotations: # 开启Canary。 nginx.ingress.kubernetes.io/canary: "true" # 请求头为_env。 nginx.ingress.kubernetes.io/canary-by-header: "_env" # 请求头_env的值为grey时,请求才会被路由到新版本服务中。 nginx.ingress.kubernetes.io/canary-by-header-value: "grey" {{ end }} spec: rules: - host: {{ .Values.host }} http: paths: - path: / pathType: Prefix backend: service: name: {{ .AppStack.appName }}-{{ .AppStack.envName }} port: number: 80
可以看到在 Ingress 的编排中,仅名称为 “grey” 的环境,会开启 canary 的 annotaion,并且将灰度标识设置为通过 _env: grey 的 header 标签。
同时在 Ingress 的 rules 配置中,通过变量 host 来指定路由关联的 HOST,这里 ACK 灰度环境和 ACK 生产环境分别关联灰度环境变量组和生产环境变量组,这两个变量组的 host 变量采用相同的值。
示例:
#
namespace = demo-pre
#
host = go.demo.prod
#
namespace = demo-prod
#
host = go.demo.prod
除了 Ingress 外,还需要配置对应的 service 和 deployment。
--- apiVersion: v1 kind: Service metadata: name: {{ .AppStack.appName }}-{{ .AppStack.envName }} namespace: {{ .Values.namespace }} spec: selector: run: {{ .AppStack.appName }}-{{ .AppStack.envName }} ports: - protocol: TCP port: 80 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: {{ .AppStack.appName }}-{{ .AppStack.envName }} labels: run: {{ .AppStack.appName }}-{{ .AppStack.envName }} namespace: {{ .Values.namespace }} spec: replicas: {{ .Values.replicas }} selector: matchLabels: run: {{ .AppStack.appName }}-{{ .AppStack.envName }} template: metadata: labels: run: {{ .AppStack.appName }}-{{ .AppStack.envName }} spec: containers: - name: main image: {{ .AppStack.image.backend }} ports: - containerPort: 8080 resources: limits: cpu: {{ .Values.cpuLimit }} memory: {{ .Values.memoryLimit }} requests: cpu: {{ .Values.cpuRequest }} memory: {{ .Values.memoryRequest }}
可从 atomgit 上下载实例代码,路径为:https://atomgit.com/feiyuw/demo-go-echo.git
将代码导入到云效代码管理 Codeup,然后关联到应用中。
接下来,我们通过云效 AppStack 的研发流程,定义灰度发布的整个过程,见下图:
在该流程中,每次执行,我们都会从 master 分支拉取代码进行镜像构建,构建完成后会转交运维进行审批,审批通过后即开始部署过程。
部署过程包含灰度部署、灰度验证、生产部署和灰度清理四个步骤。
在云效 AppStack 上相关应用的设置中,进行研发流程设置(如果仅是体验灰度发布,可仅配置生产阶段)。
上述流程可以参考下面的流水线 yaml 来定义,请注意将其中的 acr_docker_build_step 步骤的镜像地址和服务连接修改为正确的值,并把 grey_validate 和 ops_validate 包含的 userId 替换为自己的阿里云的账号 ID。
--- stages: build: name: "构建" jobs: go_build: name: "Go 镜像构建" steps: golang_build_step: name: "Golang 构建" step: "GolangBuild" with: goVersion: "1.20.x" run: | export GOPROXY=https://goproxy.cn make build upload_step: step: "ArtifactUpload" name: "构建物上传" with: serviceConnection: "wtdbdh89rrfdsod6" repo: "flow_generic_repo" artifact: "demo-go-echo" version: "prod-${CI_COMMIT_ID}.${DATETIME}" filePath: - "demo-go-echo" - "deploy.sh" acr_docker_build_step: name: "镜像构建并推送至阿里云镜像仓库个人版" step: "ACRDockerBuild" with: artifact: "image" dockerfilePath: "Dockerfile" dockerRegistry: "registry.cn-zhangjiakou.aliyuncs.com/docker007/demo-go-echo" dockerTag: "prod-${CI_COMMIT_ID}.${DATETIME}" region: "cn-zhangjiakou" serviceConnection: "<connectionId>" approve: name: "部署审核" jobs: ops_validate: name: "运维审批" component: "ManualValidate" with: validatorType: "users" validators: - <userId> grey: name: "灰度验证" jobs: grey_deploy_job: name: "ACK灰度部署" component: "AppStackFlowDeploy" with: application: "demo-go-echo" environment: "grey" artifacts: - label: "backend" value: "$[stages.build.go_build.acr_docker_build_step.artifacts.image.dockerUrl]" autoDeploy: true grey_validate: name: "灰度验证" component: "ManualValidate" needs: - "grey_deploy_job" with: validatorType: "users" validators: - <userId> deploy: name: "部署" jobs: ack_deploy_job: name: "ACK生产部署" component: "AppStackFlowDeploy" condition: | succeed('grey.grey_validate') with: application: "demo-go-echo" environment: "ack-prod" artifacts: - label: "backend" value: "$[stages.build.go_build.acr_docker_build_step.artifacts.image.dockerUrl]" autoDeploy: true cleanup: name: "清理环境" jobs: cleanup_grey_env_job: name: "清理灰度环境" component: "AppStackCleanEnv" needs: - "grey.grey_validate" - "deploy.ack_deploy_job" condition: | failed('grey.grey_validate') || succeed('deploy.ack_deploy_job') with: application: "demo-go-echo" environment: "grey" deleteEnv: "cleanEnv"
我们假设研发流程仅包含生产阶段,先运行一次生产阶段,将应用部署到灰度和生产环境中,注意:环境第一次部署可能需要手动创建部署单。
修改代码,将 routes.go 里面的版本号修改为新的值,再次执行生产阶段,直到灰度验证步骤,此时生产环境与灰度环境运行不同的版本。
通过 kubectl get ing -A 获取 ingress 的出口 IP,在本地 /etc/hosts 中将其绑定到 go.demo.prod 中,如:
127.0.0.1 go.demo.prod # 将127.0.0.1修改为正确的出口IP
打开终端,通过 httpie 或者 curl 请求/version 接口,以 httpie 为例,请求命令为:
http -v http://go.demo.prod/version # 请求正式环境
http -v http://go.demo.prod/version _env:grey # 请求灰度环境
建议在生产环境部署的时候仍然开启分批,因为灰度环境虽然验证通过了,但受到数据量等影响,生产仍然不建议全量一起上,通过分批,可以把风险控制在小范围内,避免大的故障的发生。
可以将配置变更和数据变更作为研发阶段的步骤,编排到研发流程的流水线 yaml 中,通常建议在应用部署前执行数据变更,部署后执行配置变更。同时,对应于 K8s 上的灰度环境,如果采用了类似 Nacos 这样的配置中心,也应当由对应的灰度 namespace,从而避免直接修改生产配置。
完整的方案建议参考 MSE 等产品。
如果应用数量比较少,链路比较简单,且接受基于 K8s 的简单方案,可以为每个应用都定义一个灰度环境,共享同一个集群和 namespace,应用间通过 service 进行调用。此时,由于 namespace 的隔离,灰度环境内的应用互相调用只会调用同 namespace 下的其它应用的灰度版本。
这种方案需要保证各应用灰度环境的长期可用,因此研发流程最后的清理环境步骤需要被移除。
可以在函数计算的相关步骤编排到研发流程的流水线 yaml 中,从而实现双方的联动。
点击此处,手把手带你体验云效应用交付 AppStack,还可抽取漫步者蓝牙耳机、护眼台灯等丰厚礼品。
参考文档:
[1] 如何通过 Nginx Ingress Controller 实现应用服务的灰度发布_容器服务 Kubernetes 版 ACK(ACK)
https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/implement-gray-scale-and-blue-green-publishing-through-nginx-ingress#38348104fea19
[2] Canary Deployments - Ingress-Nginx Controller
https://kubernetes.github.io/ingress-nginx/examples/canary/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。