基于 Nginx Ingress + 云效 AppStack 实现灰度发布

news2025/2/24 9:27:20

作者:子丑

场景简介

灰度发布是降低生产部署风险,提升线上服务稳定性的重要手段,这在当前快速迭代的软件研发中尤为重要。相对于 K8s 默认的滚动部署或者简单的 Pod 分批,基于流量特征的灰度发布验证更精准,风险更低。

在云原生场景下,基于 Nginx Ingress 的灰度发布是被广泛使用的方案之一。该方案通过在流量入口侧进行灰度和正常流量的路由调配,将灰度流量导入到灰度的服务版本,从而可以在全量部署到生产环境前,对新版本进行验证,当验证不通过时,可以及时回退,将风险控制在较低的范围内。而整个发布和验证,能做到对终端用户服务的不间断和体验一致。

基本原理

对于常见的 web 服务,Ingress 灰度发布的基本逻辑是:将指向同一入口(HOST)的访问请求,根据特征或流量比例,将一部分流量路由到灰度服务中,通过对灰度流量的监控和验证,判断灰度服务是否符合上线标准。

按照流量的路径,我们可以画出 Ingress 流量灰度在 K8s 上的简单架构(如下图)。

图片

流量从入口的 HOST 进来后,会根据 Nginx-Ingress 的流量标识(定义在 Ingress 的 annotation 中),将符合灰度标识的请求路由到灰度环境的 service 中,进而进入背后的工作负载 Pod。对于每个环境内部的 service 和 deployment,它们并不主动感知灰度标识,也就是说,通常情况下,内部的 rpc 流量并不携带灰度标识。因此,这种架构主要解决的是外部流量灰度的问题,适合服务数量不太多,服务间调用比较简单的场景。

具体实践

接下来,我们结合云效 AppStack,来看下如何在阿里云 ACK 集群上进行应用的 Ingress 灰度发布。

我们首先导入 ACK 集群并创建应用。

图片

3.1 定义和管理灰度环境

3.1.1 定义环境

在应用中,我们分别定义灰度环境与生产环境,两个环境共享同一个 K8s 集群,我们将灰度环境命名为 grey,生产环境命名为 ack-prod。

图片

3.1.2 定义部署编排

我们打开应用设置,编排配置来定义部署编排。灰度发布生效的关键是 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 }}

3.2 准备示例代码并关联到应用中

可从 atomgit 上下载实例代码,路径为:https://atomgit.com/feiyuw/demo-go-echo.git

将代码导入到云效代码管理 Codeup,然后关联到应用中。

图片

3.3 定义灰度发布的流程

接下来,我们通过云效 AppStack 的研发流程,定义灰度发布的整个过程,见下图:

图片

在该流程中,每次执行,我们都会从 master 分支拉取代码进行镜像构建,构建完成后会转交运维进行审批,审批通过后即开始部署过程。

部署过程包含灰度部署、灰度验证、生产部署和灰度清理四个步骤。

  • 灰度部署步骤会将前面构建出来的镜像更新到 ACK 灰度环境,此时通过在请求中携带 _env:grey 的 header 就可进行灰度验证。
  • 灰度验证是一个人工卡点,用于对灰度环境的观测和验证,如果验证通过即自动进行生产环境的部署,如果验证失败,则跳过生产部署,执行灰度清理
  • 生产部署步骤会将镜像更新到 ACK 生产环境,此时新的服务版本将对普通用户可见。注意,这里为了降低风险,生产部署的策略被设置为了分批,且首批暂停的模式,保证线上仍然是逐步放量的,且有机会快速回退。
  • 灰度清理步骤主要是清理灰度环境的资源,一方面节约资源,另一方面避免灰度验证不通过时对线上的影响。

在云效 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"

3.4 验证灰度发布流程

我们假设研发流程仅包含生产阶段,先运行一次生产阶段,将应用部署到灰度和生产环境中,注意:环境第一次部署可能需要手动创建部署单。

修改代码,将 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    # 请求灰度环境

常见问题

4.1 有了灰度环境,生产环境部署的时候还需要分批吗?

建议在生产环境部署的时候仍然开启分批,因为灰度环境虽然验证通过了,但受到数据量等影响,生产仍然不建议全量一起上,通过分批,可以把风险控制在小范围内,避免大的故障的发生。

4.2 如何在研发流程上整合配置变更和数据变更?

可以将配置变更和数据变更作为研发阶段的步骤,编排到研发流程的流水线 yaml 中,通常建议在应用部署前执行数据变更,部署后执行配置变更。同时,对应于 K8s 上的灰度环境,如果采用了类似 Nacos 这样的配置中心,也应当由对应的灰度 namespace,从而避免直接修改生产配置。

4.3 灰度环境包含多个应用,如何保证其内部服务间调用也是走的灰度环境?

完整的方案建议参考 MSE 等产品。

如果应用数量比较少,链路比较简单,且接受基于 K8s 的简单方案,可以为每个应用都定义一个灰度环境,共享同一个集群和 namespace,应用间通过 service 进行调用。此时,由于 namespace 的隔离,灰度环境内的应用互相调用只会调用同 namespace 下的其它应用的灰度版本。

这种方案需要保证各应用灰度环境的长期可用,因此研发流程最后的清理环境步骤需要被移除。

4.4 如何在流程中关联其它类型的发布如函数计算?

可以在函数计算的相关步骤编排到研发流程的流水线 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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1821008.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安鸾学院靶场——安全基础

文章目录 1、Burp抓包2、指纹识别3、压缩包解密4、Nginx整数溢出漏洞5、PHP代码基础6、linux基础命令7、Mysql数据库基础8、目录扫描9、端口扫描10、docker容器基础11、文件类型 1、Burp抓包 抓取http://47.100.220.113:8007/的返回包&#xff0c;可以拿到包含flag的txt文件。…

buuctf----warmup_csaw_2016

进来医院先来一套常规检查 啥保护都没,看大佬说基本栈溢出 CT一看 OK cat flag 更喜欢了 40060D 找到地址 get也来了,稳啦! 0x80-0x40 8 根据上道题的exp from pwn import * ghust remote("node5.buuoj.cn",27229) addr 0x40060D payload bA * 0x40 bB*8…

SpringMVC-基础架构

一、什么是MVC 二、什么是SpringMVC 三、SpringMVC的特点 四、配置SpringMVC 简单流程&#xff1a; 总体框架 1.创建pom.xml依赖 <!--打包方式--><packaging>war</packaging><!--依赖--><dependencies><dependency><groupId>org.s…

路由器怎么设置局域网?

局域网&#xff08;Local Area Network&#xff0c;LAN&#xff09;是指在一个相对较小的地理范围内&#xff0c;如家庭、办公室或学校等&#xff0c;通过路由器等设备连接起来的计算机网络。设置局域网可以方便地实现内部资源共享和信息交流。本文将介绍如何设置局域网以及一个…

C# 设置PDF表单不可编辑、或提取PDF表单数据

PDF表单是PDF中的可编辑区域&#xff0c;允许用户填写指定信息。当表单填写完成后&#xff0c;有时候我们可能需要将其设置为不可编辑&#xff0c;以保护表单内容的完整性和可靠性。或者需要从PDF表单中提取数据以便后续处理或分析。 之前文章详细介绍过如何使用免费Spire.PDF…

晶振的匹配电容的计算

晶振 等效电路 C0是晶振的静态电容 L1是晶振的等效电感 C1是晶振的等效电容 R1是晶振的等效串联电阻 芯片内部已有反相器和负载电阻 计算公式 参考1 参考2

maven 显式依赖包包含隐式依赖包,引起依赖包冲突

问题&#xff1a;FlinkCDC 3.0.1 代码 maven依赖包冲突 什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包&#xff0c;有多个不同的版本&#xff0c;因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的。每个显式声明的类包都会依赖于一些其它…

Mongodb在UPDATE操作中使用$push向数组中插入数据

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第69篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

基数排序O(n)时间复杂度的实现

基数排序O(n)时间复杂度的实现 前言 之前写过一篇文章六种常见排序算法分析与实现&#xff0c;讲了六种常见的排序算法&#xff0c;但是没有了解到桶排序&#xff0c;基数排序这两种排序算法&#xff0c;今天刷LeetCode发现了这两种算法&#xff0c;本文先来聊聊基数排序的思…

前端 CSS 经典:在 Vue3 中使用渐进式图片

1. 什么是渐进式图片 当我们网站会加载很多图片的时候&#xff0c;有些图片尺寸很大&#xff0c;加载就会很慢&#xff0c;会导致页面长时间陷入白屏状态&#xff0c;用户体验很不好。所以可以使用渐进式图片&#xff0c;先给用户展示模糊图&#xff0c;这些图尺寸小&#xff…

数据分析-Excel基础函数的使用

Excel基础函数&#xff1a; sum:求和 sumif:单条件求和 sumifs:多条件求和 subtotal:根据筛选求和 if:逻辑判断 vlookup:连接匹配数据 match:查找数值在区域中的位置 index:根据区域的位置返回数值 match、index:一起使用&#xff1a;自动根据列名查找数据 sumifs、match、ind…

k8s+RabbitMQ单机部署

1 k8s 配置文件yaml: apiVersion: apps/v1 kind: Deployment metadata:name: rabbitmq-deploynamespace: rz-dt spec:replicas: 1selector:matchLabels:app: rabbitmqtemplate:metadata:labels:app: rabbitmqspec:containers:- name: rabbitmqimage: "rz-dt-image-server…

ubtun虚拟机安装

选择镜像后启动 选择第一个回车 加载完成后 &#xff0c;进入Ubuntu安装界面&#xff0c;安装语言选择English&#xff0c;完成后按一下回车&#xff1a; 此时弹出安装器可更新提示&#xff0c;下方选项选择第二个Continue without updating&#xff08;不更新&#xff0c;继续…

计算机网络 —— 应用层(应用层概述及服务方式)

计算机网络 —— 应用层&#xff08;应用层概述及服务方式&#xff09; 应用层服务方式C/S&#xff08;客户端-服务器&#xff08;C/S&#xff09;模型&#xff09;基本概念特点B/S&#xff08;Browser/Server&#xff09;基本概念特点应用场景 p2p &#xff08;对等网络&#…

精彩回顾!安全智能体的前沿技术研究与实践

&#xff08;关注“安全极客”&#xff0c;回复“智能体”下载第一期系列专题PPT&#xff01;&#xff09; 近日&#xff0c;安全极客和Wisemodel社区联合发起并主办了“AISecurity”系列第1期&#xff1a;大模型与网络空间安全前沿探索线下活动。在这次活动中&#xff0c;云起…

Unity与Js通信交互

目录 1.Js给Unity传递消息 2.Unity给Js传递消息 简介: Unity 与 JavaScript 通信交互是指在 Unity 项目中实现与 JavaScript 代码进行数据交换和功能调用的过程。 在 Unity 中&#xff0c;可以通过特定的接口和技术来与外部的 JavaScript 环境进行连接。这使得 Unity 能够利…

机器学习:回顾总结

学了什么 进阶内容 接下来如何学习 找个项目自己练习多读前沿paper 学员分布

Adobe illustrator教程——超实用的三个进阶小技巧!

AI2024(64bit) Adobe illustrator 软件安装包下载地址&#xff1a; 百度网盘下载https://pan.baidu.com/s/1C10-2JVN1rxFF5VFRuV2Yw?pwdSIMS 01 进阶技巧1——曲率工具 基于之前的入门教程&#xff0c;大家肯定会快速想到“画笔工具”&#xff0c;但是画出来的曲线往往不够平…

k8s+pv+pvc+nas 数据持久化volumes使用

1 k8s pod申请持久化卷配置 apiVersion: v1 kind: Service metadata:name: $IMG_NAMEnamespace: rz-dtlabels:app: $IMG_NAME spec:type: NodePortports:- port: 8091nodePort: 31082 #service对外开放端口selector:app: $IMG_NAME --- apiVersion: apps/v1 kind: Deployment …

【Css】纯css展开、收起超出的文本

效果 展开 收起 未超出 码 -webkit-line-clamp: 3; 设置限制行数 <div class"wrap"> <inputtype"checkbox"id"exp-txt"><div class"text"><labelfor"exp-txt"class"btn"></label&g…