首页 > 系统相关 >基于 Nginx Ingress + 云效 AppStack 实现灰度发布

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

时间:2024-06-13 15:10:49浏览次数:22  
标签:Ingress name AppStack 环境 grey 灰度 go

作者:子丑

场景简介

灰度发布是降低生产部署风险,提升线上服务稳定性的重要手段,这在当前快速迭代的软件研发中尤为重要。相对于 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/

标签:Ingress,name,AppStack,环境,grey,灰度,go
From: https://www.cnblogs.com/alisystemsoftware/p/18245910

相关文章

  • k8s学习--ingress详细解释与应用(nginx ingress controller))
    文章目录lngress简介什么是IngressIngress的用途Ingress的工作原理Ingress的工作流程Ingress的应用场景应用实验环境部署nginxingresscontroller1.安装metalLB2.nginxingresscontroller部署3.ingress对象应用案例(基于名称的负载均衡)(1)创建deployment控制......
  • k8s中ingress访问不到服务
    情况:通过k8s部署nacos,部署之后,测试通过service进行访问,但是通过ingress域名访问失败。原因:1、我的ingress-controller部署时使用的是deployment,该方式,只会在一个节点上部署pod,而不是全部的pod上进行部署。2、采用的网络使用的hostNetwork:true,这种方式使用的是本机的网络,除非......
  • k8s 对外服务-Ingress
    一、Ingress简介:service的作用体现在两个方面,对集群内部,它不断跟踪pod的变化,更新endpoint中对应pod的对象,提供了ip不断变化的pod的服务发现机制;对集群外部,他类似负载均衡器,可以在集群内外部对pod进行访问。在Kubernetes中,Pod的IP地址和service的ClusterIP仅可以在集群网络内......
  • 灰度思维
     灰度思维是一种平衡的思维;灰度思维让我们不要极端的思考和处理事情,而是更加柔性的、变通的去妥善的处理; 真实的世界不是棱角分明的,不是非黑即白的,而是圆润的,混沌的,无常的,它黑中有白,白中有黑,黑随时可以变成白,白随时可以变成黑,这就是灰度。灰度思维,才是......
  • 一个python文件做图像 灰度化 反转 通道分离 噪声、滤波 高斯双边滤波 均值偏移滤波
    代码测试可以正常运行。灰度化反转通道分离噪声、滤波高斯双边滤波均值偏移滤波图像二值化Canny边缘检测直线检测圆检测轮廓发现人脸检测测试可以运行代码如下:'''file:ImageMiniLab.pydate:2019/04/1920:00author:itisyan......
  • ingress 日志切割
    背景ingress本质上也是个nginx,多个项目日志全部往access.log中输入就显得杂乱无章可读性不高,不利于日志阅读关键信息获取,排错就异常痛苦。so闲话少叙开干!1、部署ingress我这里ingress是用helm部署的[root@master~]#helmlistNAMENAMESPACEREVISION......
  • Istio-Ingress 与 nginx-ingress的差别
    istio-Ingress和nginx-ingress的差别istio-Ingress和nginx-ingress都是Kubernetes环境中的Ingress解决方案,但它们有以下几点主要差异:架构差异:Istio-Ingress:是IstioServiceMesh的一部分,由Envoy代理实现,与Istio的其他组件如Pilot等协作。nginx-in......
  • 利用Python+OpenCV实现截图匹配图像,支持自适应缩放、灰度匹配、区域匹配、匹配多个结
    一、依赖安装pipinstallopencv-pythonpipinstallpyautogui二、获取系统缩放比例注意:必须先通过ctypes获取wid之后才能导入pyautogui,如果需要在其它代码中引用该模块,最好把获取分辨率这部分代码放到程序入口处,然后传递给识图函数,避免提前导入pyautogui导致获取分辨率失......
  • python将矩阵转化为灰度图
    1.python将矩阵转化为灰度图的方法要将一个矩阵(通常是一个二维数组,其中每个元素代表一个像素的灰度值)转换为灰度图,我们可以使用Python的matplotlib库。下面是一个详细的步骤和完整的代码示例:(1)准备数据:首先,我们需要一个二维数组(矩阵),其中每个元素代表一个灰度值。灰度值通常在0(黑......
  • python将矩阵转化为灰度图
    1.python将矩阵转化为灰度图的方法要将一个矩阵(通常是一个二维数组,其中每个元素代表一个像素的灰度值)转换为灰度图,我们可以使用Python的matplotlib库。下面是一个详细的步骤和完整的代码示例:(1)准备数据:首先,我们需要一个二维数组(矩阵),其中每个元素代表一个灰度值。灰度值通常在0......