deployment部署策略

image-20240901113251941

Recreate

​ 重建更新策略,先删除当前副本控制集(RS)管理的所有Pods,而后创建基于新模板的副本控制集(RS),使用新版本的副本控制集(RS)创建新版本的Pods。通常,只应该在应用程序的新旧本不兼容(如依赖的后端数据库的schema不同且无法兼容)时,运行才会使用recreate策略,因为他会导致应用更新期间暂时不可用,好处在于不存在中间状态,用户要么是应用的新版本,要么是旧版本。

RollingUpdate

​ 滚动更新(RollingUpdate)一次仅更新一批Pod,当更新的Pod就绪(可用)后,再更新另一批,直到全部更新完成为止,该策略实现了不间断服务的目标,在更新过程中可能会出现不同的应用版本且并存,同时提供服务的情况,Rolling Update升级策略分为三个步骤:

  • 创建新的RS,然后根据新的镜像运行新的Pod。
  • 删除旧的Pod,启动新的Pod,当新Pod就绪后,继续删除旧Pod,启动新Pod。
  • 持续第二步过程,直到所有Pod都被更新成功。

注意 1:注意滚动升级步骤第2步,存在先增新后减旧、先减旧后增新、同时增减(少减多增)多种情况,具体与滚动升级策略属性spec.strategy.rollingUpdate.maxSurge和spec.strategy.rollingUpdate.maxUnavailable配置相关,此部分会在下文进行详细解释。

注意 2:在Deployment的滚动升级期间,可以通过以下方式判断Pod是否处于可用状态:Pod配置就绪探针(Readiness Probe)的情况下:如果就绪探针的探测结果为成功,则表示容器已经准备好接收流量,该Pod被认为是就绪状态。Pod没有就绪探针的情况下:判断Pod是否就绪的依据主要是基于Pod的运行状态,一般Pod的状态为Running则认为Pod是就绪状态。

注意 3:修改 Deployment 控制器的 minReadySeconds、replicas 和 strategy 等字段的值并不会触发 Pod 资源的更新操作,因为它们不属于 template 的内嵌字段,对现存的 Pod 对象不产生任何影响。

相关字段

image-20240901113848032

实战

echo "apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-demo
  namespace: default
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%        # 向上取整
      maxUnavailable: 25%  # 向下取整
  replicas: 10
  selector:
    matchLabels:
      app: nginx-deployment
  template:
    metadata:
      labels:
        app: nginx-deployment
    spec:
      containers:
      - name: nginx
        image: nginx:1.23
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80" | kubectl apply -f -
kubectl annotate deployments.apps deployment-demo kubernetes.io/change-cause="set image nginx:1.23"

触发更新

kubectl set  image  deploy deployment-demo  nginx=nginx:1.24
kubectl annotate deployments.apps deployment-demo kubernetes.io/change-cause="set image nginx:1.24"

image-20240901140132856

版本回退

image-20240901140219312

image-20240901140451236

金丝雀(灰度发布)

​ 金丝雀发布是一种软件发布策略,其中应用的新版本被发布到一小部分用户客户端进行测试,这些用户就像 “金丝雀” 一样,可以提供对新版本功能和性能的真实反馈。 新版本如果没有问题,可以逐步扩大到更多的用户客户端,如果出现问题时,可以迅速回滚或修复。金丝雀发布核心目的在于减少对已有系统的潜在影响,提高发布过程的可控性

可以利用 Kubernetes 中 Deployment + Ingress 两者的结合,简单平滑地将金丝雀发布流程集成到 Kubernetes 架构体系中。

具体的流程如下:

  1. 创建应用的金丝雀版本 (新版本) 镜像和 Deployment (资源请求和限制,副本数量,HPA 等)
  2. 配置服务发现,为金丝雀版本应用 Pod 配置专属的 Service
  3. 流量切分,将生产环境的一小部分流量切分到金丝雀版本中
  4. 持续监控 (业务稳定三板斧: 日志, 监控指标, 链路追踪)
  5. 渐进式更新 (逐步加大金丝雀版本的流量比例) 或及时回滚 (只需要将金丝雀版本流量比例重置为 0 即可)
  6. 删除旧的生产 Service 和 Deployment

image-20240901193318371

业务流程

上面谈到了 Kubernetes 中金丝雀发布的技术方案,下面简单说一下业务方面需要哪些配合工作。

  1. 后台发放金丝雀版本的客户端 (用户) 名额,可以根据用户画像发放,也可以随机发放
  2. 客户端收到金丝雀版本更新后,提示当前用户是否更新到金丝雀版本
  3. 用户下载更新金丝雀版本应用 (例如 Android 绕过应用商城直接下载安装,IOS 通过官方 TestFlight 方案)
  4. 金丝雀版本应用访问时,流量被切分到金丝雀版本的 Pod, 产生对应的日志, 监控指标, 链路追踪数据

流量切分

​ 上文中谈到的将生产环境的一小部分流量切分到金丝雀版本中,下面来介绍两种常见的业务金丝雀部署场景。 限于篇幅,本文仅讨论使用 Kubernetes 原生功能作为流量切分方案,不考虑 Istio, Linkerd 等 Service Mesh 方案。

REST接口不同

这种实现方案是最简单的,我们可以直接使用 Ingress + Service 来实现,通过将不同版本的接口路由到不同的服务即可,例如:

  • 当前运行版本的路由 /api/v1/users/:id/profile 流量切分到生产应用版本的 Service
  • 金丝雀版本的路由 /api/v2/users/:id/profile 流量切分到金丝雀版本的 Service

其中,Deployment 对应的 yaml 声明式代码大致如下:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-app
  labels:
    version: prod # 标签 = prod
spec:
  # 定义生产版本的 Deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-app
  labels:
    version: canary # 标签 = canary
spec:
  # 定义金丝雀版本的 Deployment

Service 对应的 yaml 声明式代码大致如下:

---
apiVersion: v1
kind: Service
metadata:
  name: app-prod-service
spec:
  selector:
    version: prod  # 将流量路由到标签为 prod 的 Pod
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: app-canary-service
spec:
  selector:
    version: canary  # 将一部分流量路由到标签为 canary 的 Pod
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Ingress 对应的 yaml 声明式代码大致如下 (这里以 Nginx Ingress Controller 为例):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/service-upstream: "true"
spec:
  ingressClassName: nginx
  rules:
    - host: www.lab.example.com
      http:
        paths:
          - path: /api/v1/users
            pathType: Prefix
            backend:
              service:
                name: app-prod-service
                port:
                  number: 80
            weight: 10  # 权重 1:10
          - path: /api/v2/users
            pathType: Prefix
            backend:
              service:
                name: app-canary-service
                port:
                  number: 80
            weight: 1   # 权重 1:10

REST 接口版本相同

现实中更常见的场景是: 应用的生产版本和金丝雀版本使用相同的 REST 接口,但是需要将流量切分到不同版本中,同样可以使用 Ingress 来实现。

Nginx Ingress Controller 下列 canary-* Annotation 来支持金丝雀发布机制,下面是几个常用字段的描述。

字段说明
nginx.ingress.kubernetes.io/canarytrue:启用 canary false:不启用 canary
nginx.ingress.kubernetes.io/canary-by-header基于请求头的名称进行金丝雀发布
nginx.ingress.kubernetes.io/canary-by-header-value基于请求头的值进行金丝雀发布
nginx.ingress.kubernetes.io/canary-by-header-value基于请求头的值进行金丝雀发布
nginx.ingress.kubernetes.io/canary-weight基于权重进行金丝雀发布 (0 - 100)

Service 对应的 yaml 声明式代码直接复用上文中的即可,这里不再赘述,Ingress 对应的 yaml 声明式代码大致如下:

---
# 生产版本 Ingress 配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: prod
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: www.lab.example.com
      http:
        paths:
          - path: /api/v1/users
            pathType: Prefix
            backend:
              service:
                name: app-prod-service
                port:
                  number: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    # 启用金丝雀发布
    # nginx.ingress.kubernetes.io/canary: "true"
    # 这里以 Request.Header 中的 app_release_version 作为金丝雀流量标识名称
    #nginx.ingress.kubernetes.io/canary-by-header: "app_release_version"
    #  这里以 v1.2.3 版本号作为金丝雀流量标识值
    # nginx.ingress.kubernetes.io/canary-by-header-value: "v1.2.3"
    # 给金丝雀版本切分 10% 的流量
    # nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
    - host: www.lab.example.com
      http:
        paths:
          - path: /api/v2/users
            pathType: Prefix
            backend:
              service:
                name: app-canary-service
                port:
                  number: 80

触发更新的方式

kubectl apply -f | kubectl replace -f | kubectl patch
kubectl edit
kubectl set | kubectl scale | kubectl autoscale

kubectl set env子命令说明

image-20240901131635505

kubectl set image子命令说明

image-20240901131705812

kubectl set resource子命令说明

image-20240901131819123

kubectl set serviceaccount子命令说明

image-20240901132012577

回滚

image-20240901131348597