Kubernetes的部署策略,你常用哪种?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes的部署策略,你常用哪种?相关的知识,希望对你有一定的参考价值。

参考技术A

确定哪种Kubernetes部署方法最适合不断的推出,而不会影响用户的更新。当今开发云原生应用的最大挑战之一是加快部署的速度。通过微服务方法,开发人员已经开始使用并设计完全模块化的应用,允许多个团队同时编写和部署更改到应用去。

更短和更频繁的部署提供了以下优势:

但随着更频繁的发布,对应用可靠性或客户体验产生负面影响的可能性也会增加。这就是为什么DevOps团队必须开发流程和管理部署策略,以最大限度地降低产品和客户风险的原因。

以下,一起讨论Kubernetes的部署策略,包括滚动部署和更高级的方法,如金丝雀及其变体。

根据你的目标,可以利用几种不同类型的部署策略。例如,可能需要将更改推广到特定环境以进行更多测试,或者是用户/客户的子集,或者你可能需要在创建“一般可用”功能之前进行一些用户测试。

滚动部署是Kubernetes的标准默认部署。它逐个缓慢地工作,使用新版本的pod替换以前版本的应用的pod,而不会导致任何集群停机。

在开始缩小旧容器之前,滚动更新会通过准备探针等待新的pod就绪。如果出现问题,可以中止滚动更新或部署,而无需关闭整个集群。在此类部署的YAML定义文件中,新镜像将替换旧镜像。

通过调整清单文件中的参数,可以进一步细化滚动更新:

在这种非常简单的部署中,所有旧的pod都被一次性杀死,并且用新的pod一次全部替换。

这个清单看起来像这样:

在蓝/绿部署策略(有时称为红/黑)中,旧版本的应用(绿色)和新版本(蓝色)同时部署。当部署这两个用户时,用户只能访问绿色,而蓝色可供QA团队在单独的服务上进行测试自动化或通过直接端口转发。

在测试新版本并签署发布后,该服务将切换为蓝色版本,旧版绿色版本将按比例缩小:

金丝雀部署有点像蓝/绿部署,但更受控制并使用更“ 渐进式交付 ”的分阶段方法。有许多策略属于金丝雀的保护范围,包括灰度发布或A/B测试。

当你想要测试一些新功能时,通常在应用程序的后端使用金丝雀。传统上,你可能拥有两个几乎完全相同的服务器:一个用于所有用户,另一个用新功能推送到一部分用户然后进行比较。如果未报告任何错误,则新版本可逐渐推广到基础架构的其余部分。

虽然可以通过替换旧的和新的pod来使用Kubernetes资源来完成此策略,但使用像Istio这样的服务网格实现此策略会更方便,更容易。

例如,你可以在Git中检查两个不同的清单:GA标记为0.1.0,金丝雀标记为0.2.0。通过更改Istio虚拟网关清单中的权重,可以管理这两个部署的流量百分比。

管理金丝雀部署的一种简单有效的方法是使用Weaveworks Flagger。

使用Flagger,金丝雀部署的推广是自动化的。它使用Istio或App Mesh来路由和转移流量,使用Prometheus指标进行金丝雀分析。金丝雀分析还可以通过webhook进行扩展,以运行验收测试,负载测试或任何其他类型的自定义验证。

Flagger采用Kubernetes部署和可选的水平pod自动缩放器(HPA)来创建一系列对象(Kubernetes部署,ClusterIP服务和Istio或App Mesh虚拟服务),以推动金丝雀分析和推广。

通过实施控制回路,Flagger逐渐将流量转移到金丝雀,同时测量关键性能指标,如HTTP请求成功率,请求平均持续时间和pod 健康 状况。根据对KPI的分析,金丝雀被提升或中止,分析结果发布给Slack。

灰度部署是金丝雀的另一种变体(顺便提一下,也可以由Flagger处理)。灰度部署和金丝雀之间的区别在于,灰度部署处理前端而不是后端的功能。

灰度部署的另一个名称是A/B测试。你可以将其发布给少数用户,而不是为所有用户启动新功能。用户通常不知道他们被用作新功能的测试人员,因此称为“灰度”部署。

通过使用功能切换和其他工具,你可以监控用户与新功能的交互方式,以及是否正在转换用户,或者他们是否发现新的UI令人困惑以及其他类型的指标。

除了加权路由,Flagger还可以根据HTTP匹配条件将流量路由到金丝雀。在A/B测试场景中,你将使用HTTP标头或Cookie来定位用户的特定部分。这对需要会话关联的前端应用特别有用。

Kubernetes 部署策略

文章目录

Kubernetes 部署策略

在Kubernetes中有几种不同的方式发布应用,所以为了让应用在升级期间依然平稳提供服务,选择一个正确的发布策略就非常重要了。

选择正确的部署策略是要依赖于我们的业务需求的,下面我们列出了一些可能会使用到的策略:

  • 重建(recreate):停止旧版本部署新版本
  • 滚动更新(rolling-update):一个接一个地以滚动更新方式发布新版本
  • 蓝绿(blue/green):新版本与旧版本一起存在,然后切换流量
  • 金丝雀(canary):将新版本面向一部分用户发布,然后继续全量发布
  • A/B测(a/b testing):以精确的方式(HTTP 头、cookie、权重等)向部分用户发布新版本。A/B测实际上是一种基于数据统计做出业务决策的技术。在 Kubernetes 中并不原生支持,需要额外的一些高级组件来完成改设置(比如Istio、Linkerd、Traefik、或者自定义 Nginx/Haproxy 等)。

重建(Recreate) - 最好在开发环境

策略定义为Recreate的Deployment,会终止所有正在运行的实例,然后用较新的版本来重新创建它们。

spec:
  replicas: 3
  strategy:
    type: Recreate

重新创建策略是一个虚拟部署,包括关闭版本A,然后在关闭版本A后部署版本B. 此技术意味着服务的停机时间取决于应用程序的关闭和启动持续时间。

我们这里创建两个相关的资源清单文件,app-v1.yaml:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

app-v2.yaml 文件内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

上面两个资源清单文件中的 Deployment 定义几乎是一致的,唯一不同的是定义的环境变量VERSION值不同,接下来按照下面的步骤来验证Recreate策略:

版本1提供服务
删除版本1
部署版本2
等待所有副本准备就绪

首先部署第一个应用:

$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created

测试版本1是否部署成功:

$ kubectl get pods -l app=my-app
NAME                      READY     STATUS    RESTARTS   AGE
my-app-7b4874cd75-m5kct   1/1       Running   0          19m
my-app-7b4874cd75-pc444   1/1       Running   0          19m
my-app-7b4874cd75-tlctl   1/1       Running   0          19m
$ kubectl get svc my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.108.238.76   <none>        80:32532/TCP   5m
$ curl http://127.0.0.1:32532
Host: my-app-7b4874cd75-pc444, Version: v1.0.0

可以看到版本1的应用正常运行了。为了查看部署的运行情况,打开一个新终端并运行以下命令:

$ watch kubectl get po -l app=my-app

然后部署版本2的应用:

$ kubectl apply -f app-v2.yaml

这个时候可以观察上面新开的终端中的 Pod 列表的变化,可以看到之前的3个 Pod 都会先处于Terminating状态,并且3个 Pod 都被删除后才开始创建新的 Pod。

然后测试第二个版本应用的部署进度:

$ while sleep 0.1; do curl http://127.0.0.1:32532; done
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
curl: (7) Failed connect to 127.0.0.1:32532; Connection refused
......
Host: my-app-f885c8d45-sp44p, Version: v2.0.0
Host: my-app-f885c8d45-t8g7g, Version: v2.0.0
Host: my-app-f885c8d45-sp44p, Version: v2.0.0
......

可以看到最开始的阶段服务都是处于不可访问的状态,然后到第二个版本的应用部署成功后才正常访问,可以看到现在访问的数据是版本2了。

最后,可以执行下面的命令来清空上面的资源对象:

$ kubectl delete all -l app=my-app

结论:

应用状态全部更新
停机时间取决于应用程序的关闭和启动消耗的时间

滚动更新(rolling-update)

滚动更新通过逐个替换实例来逐步部署新版本的应用,直到所有实例都被替换完成为止。它通常遵循以下过程:在负载均衡器后面使用版本 A 的实例池,然后部署版本 B 的一个实例,当服务准备好接收流量时(Readiness Probe 正常),将该实例添加到实例池中,然后从实例池中删除一个版本 A 的实例并关闭,如下图所示:

下图是滚动更新过程应用接收流量的示意图:

下面是 Kubernetes 中通过 Deployment 来进行滚动更新的关键参数:

spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2        # 一次可以添加多少个Pod
      maxUnavailable: 1  # 滚动更新期间最大多少个Pod不可用

现在仍然使用上面的 app-v1.yaml 这个资源清单文件,新建一个定义滚动更新的资源清单文件 app-v2-rolling-update.yaml,文件内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 10
  # maxUnavailable设置为0可以完全确保在滚动更新期间服务不受影响,还可以使用百分比的值来进行设置。
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          # 初始延迟设置高点可以更好地观察滚动更新过程
          initialDelaySeconds: 15
          periodSeconds: 5

上面的资源清单中我们在环境变量中定义了版本2,然后通过设置strategy.type=RollingUpdate来定义该 Deployment 使用滚动更新的策略来更新应用,接下来我们按下面的步骤来验证滚动更新策略:

版本1提供服务
部署版本2
等待直到所有副本都被版本2替换完成

同样,首先部署版本1应用:

$ kubectl apply -f app-v1.yaml
service "my-app" created
deployment.apps "my-app" created

测试版本1是否部署成功:

$ kubectl get pods -l app=my-app
NAME                      READY     STATUS    RESTARTS   AGE
my-app-7b4874cd75-h8c4d   1/1       Running   0          47s
my-app-7b4874cd75-p4l8f   1/1       Running   0          47s
my-app-7b4874cd75-qnt7p   1/1       Running   0          47s
$ kubectl get svc my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.109.99.184   <none>        80:30486/TCP   1m
$ curl http://127.0.0.1:30486
Host: my-app-7b4874cd75-qnt7p, Version: v1.0.0

同样,在一个新终端中执行下面命令观察 Pod 变化:

$ watch kubectl get pod -l app=my-app

然后部署滚动更新版本2应用:

$ kubectl apply -f app-v2-rolling-update.yaml
deployment.apps "my-app" configured

这个时候在上面的 watch 终端中可以看到多了很多 Pod,还在创建当中,并没有一开始就删除之前的 Pod,同样,这个时候执行下面命令,测试应用状态:

$ while sleep 0.1; do curl http://127.0.0.1:30486; done
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
......
Host: my-app-7b4874cd75-vrlj7, Version: v1.0.0
Host: my-app-6b5479d97f-2fk24, Version: v2.0.0
Host: my-app-7b4874cd75-p4l8f, Version: v1.0.0
......
Host: my-app-6b5479d97f-s5ctz, Version: v2.0.0
Host: my-app-7b4874cd75-5ldqx, Version: v1.0.0
......
Host: my-app-6b5479d97f-5z6ww, Version: v2.0.0

们可以看到上面的应用并没有出现不可用的情况,最开始访问到的都是版本1的应用,然后偶尔会出现版本2的应用,直到最后全都变成了版本2的应用,而这个时候看上面 watch 终端中 Pod 已经全部变成10个版本2的应用了,我们可以看到这就是一个逐步替换的过程。

如果在滚动更新过程中发现新版本应用有问题,我们可以通过下面的命令来进行一键回滚:

$ kubectl rollout undo deploy my-app
deployment.apps "my-app"

如果你想保持两个版本的应用都存在,那么我们也可以执行 pause 命令来暂停更新:

$ kubectl rollout pause deploy my-app
deployment.apps "my-app" paused

这个时候我们再去循环访问我们的应用就可以看到偶尔会出现版本1的应用信息了。

如果新版本应用程序没问题了,也可以继续恢复更新:

$ kubectl rollout resume deploy my-app
deployment.apps "my-app" resumed

最后,可以执行下面的命令来清空上面的资源对象:

$ kubectl delete all -l app=my-app

结论:

版本在实例之间缓慢替换
rollout/rollback 可能需要一定时间
无法控制流量

蓝/绿(blue/green) - 最好用来验证 API 版本问题

蓝/绿发布是版本2 与版本1 一起发布,然后流量切换到版本2,也称为红/黑部署。蓝/绿发布与滚动更新不同,版本2(绿) 与版本1(蓝)一起部署,在测试新版本满足要求后,然后更新更新 Kubernetes 中扮演负载均衡器角色的 Service 对象,通过替换 label selector 中的版本标签来将流量发送到新版本,如下图所示:

下面是蓝绿发布策略下应用方法的示例图:

在 Kubernetes 中,我们可以用两种方法来实现蓝绿发布,通过单个 Service 对象或者 Ingress 控制器来实现蓝绿发布,实际操作都是类似的,都是通过 label 标签去控制。
实现蓝绿发布的关键点就在于 Service 对象中 label selector 标签的匹配方法,比如我们重新定义版本1 的资源清单文件 app-v1-single-svc.yaml,文件内容如下:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  # 注意这里我们匹配 app 和 version 标签,当要切换流量的时候,我们更新 version 标签的值,比如:v2.0.0
  selector:
    app: my-app
    version: v1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

上面定义的资源对象中,最重要的就是 Service 中 label selector 的定义:

selector:
  app: my-app
  version: v1.0.0

版本2 的应用定义和以前一样,新建文件 app-v2-single-svc.yaml,文件内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v2.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

然后按照下面的步骤来验证使用单个 Service 对象实现蓝/绿部署的策略:

版本1 应用提供服务
部署版本2 应用
等到版本2 应用全部部署完成
切换入口流量从版本1 到版本2
关闭版本1 应用

首先,部署版本1 应用:

$ kubectl apply -f app-v1-single-svc.yaml
service "my-app" created
deployment.apps "my-app-v1" created

测试版本1 应用是否部署成功:

$ kubectl get pods -l app=my-app
NAME                         READY     STATUS    RESTARTS   AGE
my-app-v1-7b4874cd75-7xh6s   1/1       Running   0          41s
my-app-v1-7b4874cd75-dmq8f   1/1       Running   0          41s
my-app-v1-7b4874cd75-t64z7   1/1       Running   0          41s
$ kubectl get svc -l app=my-app
NAME      TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.106.184.144   <none>        80:31539/TCP   50s
$ curl http://127.0.0.1:31539
Host: my-app-v1-7b4874cd75-7xh6s, Version: v1.0.0

同样,新开一个终端,执行如下命令观察 Pod 变化:

$ watch kubectl get pod -l app=my-app

然后部署版本2 应用:

$ kubectl apply -f app-v2-single-svc.yaml
deployment.apps "my-app-v2" created

然后在上面 watch 终端中可以看到会多3个my-app-v2开头的 Pod,待这些 Pod 部署成功后,我们再去访问当前的应用:

$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
......

我们会发现访问到的都是版本1 的应用,和我们刚刚部署的版本2 没有任何关系,这是因为我们 Service 对象中通过 label selector 匹配的是version=v1.0.0这个标签,我们可以通过修改 Service 对象的匹配标签,将流量路由到标签version=v2.0.0的 Pod 去:

$ kubectl patch service my-app -p '"spec":"selector":"version":"v2.0.0"'
service "my-app" patched

然后再去访问应用,可以发现现在都是版本2 的信息了:

$ while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
......

如果你需要回滚到版本1,同样只需要更改 Service 的匹配标签即可:

$ kubectl patch service my-app -p '"spec":"selector":"version":"v1.0.0"'

如果新版本已经完全符合我们的需求了,就可以删除版本1 的应用了:

$ kubectl delete deploy my-app-v1

最后,同样,执行如下命令清理上述资源对象:

$ kubectl delete all -l app=my-app

结论:

实时部署/回滚
避免版本问题,因为一次更改是整个应用的改变
需要两倍的资源
在发布到生产之前,应该对整个应用进行适当的测试

金丝雀(Canary) - 让部分用户参与测试

金丝雀部署是让部分用户访问到新版本应用,在 Kubernetes 中,可以使用两个具有相同 Pod 标签的 Deployment 来实现金丝雀部署。新版本的副本和旧版本的一起发布。在一段时间后如果没有检测到错误,则可以扩展新版本的副本数量并删除旧版本的应用。

如果需要按照具体的百分比来进行金丝雀发布,需要尽可能的启动多的 Pod 副本,这样计算流量百分比的时候才方便,比如,如果你想将 1% 的流量发送到版本 B,那么我们就需要有一个运行版本 B 的 Pod 和 99 个运行版本 A 的 Pod,当然如果你对具体的控制策略不在意的话也就无所谓了,如果你需要更精确的控制策略,建议使用服务网格(如 Istio),它们可以更好地控制流量。

在下面的例子中,我们使用 Kubernetes 原生特性来实现一个穷人版的金丝雀发布,如果你想要对流量进行更加细粒度的控制,请使用豪华版本的 Istio。下面是金丝雀发布的应用请求示意图:

接下来我们按照下面的步骤来验证金丝雀策略:

10个副本的版本1 应用提供服务
版本2 应用部署1个副本(意味着小于10%的流量)
等待足够的时间来确认版本2 应用足够稳定没有任何错误信息
将版本2 应用扩容到10个副本
等待所有实例完成
关闭版本1 应用

首先,创建版本1 的应用资源清单,app-v1-canary.yaml,内容如下:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 10
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - nameKubernetes 学习总结(30)—— Kubernetes YAML 最佳实践和策略

Kubernetes 学习总结(30)—— Kubernetes YAML 最佳实践和策略

生产环境中的Kubernetes最佳实践

Kubernetes 部署策略

Kubernetes 部署策略

Kubernetes 部署策略