NGINX.COM
Web Server Load Balancing with NGINX Plus

NGINX 实操教程:通过灰度部署改善正常运行时间和弹性

注:本教程是 Microservices June 2022 微服务之月项目第三单元“Kubernetes 高级部署策略”的配套实验手册,您可点此报名参与这一免费线上教学项目以获取更多学习资源。

您的企业已经成功在 Kubernetes 中交付应用,现在是时候部署 v2 版 backend 服务了。但是,对于流量中断(又称停机)以及 v2 可能存在的不稳定性,人们也有着一些合理的担忧。作为 Kubernetes 工程师,您需要找到一种方法来确保在几乎不影响客户的情况下测试和部署 v2。

您可以使用流量分割技术“灰度部署”来实施渐进的、可控的迁移,这种部署模式支持安全、敏捷地测试新特性或新版本的稳定性。您的用例涉及在两个 Kubernetes service 之间移动的流量,因此您可以选择使用简单好用、效果可靠的 NGINX Service Mesh。您将 10% 的流量发送到 v2,其余 90% 仍将被路由到 v1。看到稳定性良好,于是您逐步将越来越多的流量发送到 v2,直至全部转移完毕!问题就这样解决了!

 

实验和教程概述

本文是“Microservices June 2022 微服务之月”第三单元“Kubernetes 高级部署策略”的实验配套文档,但您也可以在自己的环境中将其当作教程使用(您可以从我们的 GitHub 仓库中获取示例)。

为了完成实验,您需要一台具有以下配置的电脑:

  • 至少 2 个 CPU
  • 2GB 可用内存
  • 20GB 可用磁盘空间
  • 互联网连接
  • 容器或虚拟机管理器,例如 Docker、HyperKit、Hyper-V、KVM、Parallels、Podman、VirtualBox 或 VMware Fusion/Workstation
  • 装有 minikube
  • 装有 Helm

注意:本文提到的 minikube 需在可以启动浏览器窗口的台式/笔记本电脑上运行。如果您所处的环境无法做到这一点,那么您需要解决如何通过浏览器访问服务的问题。

为了充分利用实验和教程,我们建议您在开始之前:

本教程使用了以下技术:

本教程涉及三个挑战:

  1. 部署集群和 NGINX Service Mesh
  2. 部署两个应用(Frontend 和 Backend)
  3. 使用 NGINX Service Mesh 实施灰度部署

 

挑战 1:部署集群和 NGINX Service Mesh

部署一个 minikube 集群。几秒钟后将出现一条确认部署成功的消息。

$ minikube start \ 
--extra-config=apiserver.service-account-signing-key-file=/var/lib/minikube/certs/sa.key \
  --extra-config=apiserver.service-account-key-file=/var/lib/minikube/certs/sa.pub \
  --extra-config=apiserver.service-account-issuer=kubernetes/serviceaccount \
  --extra-config=apiserver.service-account-api-audiences=api
  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

您是否注意到此 minikube 命令看起来与其他 Microservices March 教程的命令不同?

  • 您需要一些额外的配置来启用 Service Account Token Volume Projection(服务帐户令牌卷投影)——这是使用 NGINX Service Mesh 的必要条件。
  • 启用此功能后,kubelet 将把同一个服务帐户安装到每个 pod 中。
  • 此后,pod 就会在集群中获得唯一标识,kubelet 则负责定期轮换令牌。
  • 如欲了解更多信息,请阅读《使用 Kubernetes 身份在微服务之间进行身份验证》

部署 NGINX Service Mesh

NGINX Service Mesh 由 F5 NGINX 维护,使用 NGINX Plus 作为 sidecar。虽然 NGINX Plus 是商用产品,但您可以通过 NGINX Service Mesh 免费使用这一组件。

我们有两种安装选择:

Helm 是最简单、最快捷的方法,因此本教程选择了使用 Helm。

  1. 下载并安装 NGINX Service Mesh:
  2. helm install nms ./nginx-service-mesh  --namespace nginx-mesh --create-namespace 
  3. 确认 NGINX Service Mesh pod 已经部署成功,如 STATUS 列中的值 Running 所示。
  4. kubectl get pods --namespace nginx-mesh 
    NAME                                  READY   STATUS 
    grafana-7c6c88b959-62r72              1/1     Running 
    jaeger-86b56bf686-gdjd8               1/1     Running 
    nats-server-6d7b6779fb-j8qbw          2/2     Running 
    nginx-mesh-api-7864df964-669s2        1/1     Running 
    nginx-mesh-metrics-559b6b7869-pr4pz   1/1     Running 
    prometheus-8d5fb5879-8xlnf            1/1     Running 
    spire-agent-9m95d                     1/1     Running 
    spire-server-0                        2/2     Running 
    

所有 pod 的部署可能共需要 1.5 分钟。除了 NGINX Service Mesh pod 外,还有 Grafana、Jaeger、NATS、Prometheus 和 Spire 的 pod。请查阅文档,了解这些工具如何与 NGINX Service Mesh 协同工作。

 

挑战 2:部署两个应用(Frontend 和 Backend)

应用部署由两个微服务组成:

  • frontend:为访问者提供用户界面的 Web 应用。它先解构复杂的请求,然后向众多 backend 应用发送调用。
  • backend-v1:一个通过 Kubernetes API 为 frontend 提供数据的业务逻辑应用。

安装 Backend-v1 应用

  1. 使用您选择的文本编辑器,创建一个名为 1-backend-v1.yaml 的 YAML 文件,该文件应包含以下内容:
  2. 
    kind: ConfigMap 
    metadata: 
      name: backend-v1 
    data: 
      nginx.conf: |- 
        events {} 
        http { 
            server { 
                listen 80; 
                location / { 
                    return 200 '{"name":"backend","version":"1"}'; 
                } 
            } 
        } 
    --- 
    apiVersion: apps/v1 
    kind: Deployment 
    metadata: 
      name: backend-v1 
    spec: 
      replicas: 1 
      selector: 
        matchLabels: 
          app: backend 
          version: "1" 
      template: 
        metadata: 
          labels: 
            app: backend 
            version: "1" 
          annotations: 
        spec: 
          containers: 
            - name: backend-v1 
              image: "nginx" 
              ports: 
                - containerPort: 80 
              volumeMounts: 
                - mountPath: /etc/nginx 
                  name: nginx-config 
          volumes: 
            - name: nginx-config 
              configMap: 
                name: backend-v1 
    --- 
    apiVersion: v1 
    kind: Service 
    metadata: 
      name: backend-svc 
      labels: 
        app: backend 
    spec: 
      ports: 
        - port: 80 
          targetPort: 80 
      selector: 
        app: backend 
    
  3. 部署 backend-v1:
  4. $ kubectl apply -f 1-backend-v1.yaml 
    configmap/backend-v1 created 
    deployment.apps/backend-v1 created 
    service/backend-svc created 
    
  5. 确认 backend-v1 pod 和 service 已经部署成功,如 STATUS 列中的值 Running 所示。
  6. $ kubectl get pods,services 
    NAME                              READY   STATUS 
    pod/backend-v1-745597b6f9-hvqht   2/2     Running 
    
    NAME                  TYPE        CLUSTER-IP       PORT(S) 
    service/backend-svc   ClusterIP   10.102.173.77    80/TCP 
    service/kubernetes    ClusterIP   10.96.0.1        443/TCP 
    

您可能会纳闷:“为什么 backend-v1 有两个 pod?”

  • NGINX Service Mesh 会将一个 sidecar 代理注入到您的 pod 中。
  • 该 sidecar 容器会拦截进出您 pod 的所有流量。
  • 收集到的所有数据被用于指标监测,但是您也可以使用此代理来决定流量的去向。

部署 Frontend 应用

  1. 创建一个名为 2-frontend.yaml 的 YAML 文件,该文件应包含以下内容:请注意,pod 使用 cURL 每秒向 backend service (backend-svc) 签发一个请求。
  2. apiVersion: apps/v1 
    kind: Deployment 
    metadata: 
      name: frontend 
    spec: 
      selector: 
        matchLabels: 
          app: frontend 
      template: 
        metadata: 
          labels: 
            app: frontend 
        spec: 
          containers: 
          - name: frontend 
            image: curlimages/curl:7.72.0 
            command: [ "/bin/sh", "-c", "--" ] 
            args: [ "sleep 10; while true; do curl -s http://backend-svc/; sleep 1 && echo ' '; done" ] 
    
  3. 部署 frontend:
  4. $ kubectl apply -f 2-frontend.yaml 
    deployment.apps/frontend created 
    
  5. 确认 frontend pod 已经部署成功,如 STATUS 列中的值 Running 所示。请注意,每个应用同样也有两个 pod,因为它们是 NGINX Service Mesh 的一部分。
  6. $ kubectl get pods 
    NAME                         READY   STATUS    RESTARTS 
    backend-v1-5cdbf9586-s47kx   2/2     Running   0 
    frontend-6c64d7446-mmgpv     2/2     Running   0 
    

检查日志

接下来,您需要检查日志,确认流量是否从 frontend 流向了 backend-v1。日志检索命令要求您使用下列格式进行整合:

kubectl logs -c frontend <insert the full pod id displayed in your Terminal>

小贴士:创建此命令后,请将其保存在一个易于检索的地方,因为本教程将重复用到它。

您可以在上一步 (frontend-6c64d7446-mmgpv) 中获得完整的 pod ID,该 ID 在您的部署环境中具有唯一性。提交命令之后,日志应报告所有流量都被路由到 backend-v1,这在意料之中,因为它是您唯一的 backend。

$ kubectl logs -c frontend frontend-6c64d7446-mmgpv 

{"name":"backend","version":"1"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"1"} 

使用 Jaeger 检查依赖关系图

更有趣的是,随两个应用一起部署的 NGINX Service Mesh sidecar 会随着流量的流动收集指标。您可以通过 Jaeger 使用这些数据生成架构的依赖关系图。

  1. 使用 minikube service jaeger 在浏览器中打开 Jaeger 仪表盘。
  2. 点击“System Architecture(系统架构)”选项卡,您将看到一个非常简单的架构(将鼠标悬停在图上即可显示标签)。DAG 选项卡提供了一个放大的视图。想象一下,如果 frontend 访问数十个甚至数百个 backend service,这个图将是多么的精彩!

添加 Backend-v2

您现在需要部署第二个 backend 应用 backend-v2,它是用来服务 frontend 的。正如版本号所示,backend-v2backend-v1 的新版本。

  1. 创建一个名为 3-backend-v2.yaml 的 YAML 文件(包含以下内容)并注意以下两点:
    • 当前部署如何与先前的部署共享 app: backend 标签。
    • 由于 service 选择器为 app: backend,流量应该均匀分布在 backend-v1backend-v2 之间。
    apiVersion: v1 
    kind: ConfigMap 
    metadata: 
      name: backend-v2 
    data: 
      nginx.conf: |- 
        events {} 
        http { 
            server { 
                listen 80; 
                location / { 
                    return 200 '{"name":"backend","version":"2"}'; 
                } 
            } 
        } 
    --- 
    apiVersion: apps/v1 
    kind: Deployment 
    metadata: 
      name: backend-v2 
    spec: 
      replicas: 1 
      selector: 
        matchLabels: 
          app: backend 
          version: "2" 
      template: 
        metadata: 
          labels: 
            app: backend 
            version: "2" 
          annotations: 
        spec: 
          containers: 
            - name: backend-v2 
              image: "nginx" 
              ports: 
                - containerPort: 80 
              volumeMounts: 
                - mountPath: /etc/nginx 
                  name: nginx-config 
          volumes: 
            - name: nginx-config 
              configMap: 
                name: backend-v2 
    
  2. 部署 backend-v2:
  3. $ kubectl apply -f 3-backend-v2.yaml 
    configmap/backend-v2 created 
    deployment.apps/backend-v2 created 
    
  4. 确认 backend-v2 pod 和 services 已经部署成功,如 STATUS 列中的值 Running 所示。

检查日志

使用与上文相同的命令检查日志。您现在应该可以看到响应均匀地来自于两个 backend 版本。

$ kubectl logs -c frontend frontend-6c64d7446-mmgpv 

{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 
{"name":"backend","version":"1"} 

返回到 Jaeger

返回到 Jaeger 选项卡,查看 NGINX Service Mesh 能否正确映射两个 backend 版本。

  • Jaeger 可能需要几分钟才能识别添加了第二个 backend
  • 如果一分钟后没有结果显现,请先继续下一个挑战,稍后再回头查看依赖关系图。

 

挑战 3:使用 NGINX Service Mesh 实施灰度部署

按照本教程的节奏,现在您已经部署了两个版本的 backend:v1 和 v2。虽然您可以立即将所有流量转移到 v2,但在将生产流量托付给新版本之前,您最好先测试一下稳定性。在这种情况下,使用灰度部署再合适不过了。

什么是灰度部署?

《如何通过高级流量管理提高 Kubernetes 的弹性》一文曾详细地探讨过,灰度部署是一种流量分割技术,为测试新特性或新版本的稳定性提供了一种安全、敏捷的方法。在常规的灰度部署中,企业会先让绝大多数(比如 99%)用户使用稳定版,并且只将一小部分用户(剩余的 1%)转移到新版本。如果新版本出现问题(例如崩溃或向客户端返回错误),您可以立即将测试用户转移回稳定版。如果新版本顺利运行,您可以一次性或渐进、可控地(后者更为常见)将用户从稳定版迁移到新版本。

下图描述了使用 Ingress controller 分割流量的灰度部署。

Service 之间的灰度部署

在本教程中,您将设置流量分割,让 90% 的流量流向 backend-v1,其余 10% 则流向 backend-v2

Ingress controller 可以分割从客户端流向 Kubernetes service 的流量,但不能分割 service 之间的流量。实施这种类型的灰度部署有两种选择:

选项 1:笨方法
您可以指示 frontend pod 的代理向 backend-v1 发送 9 个请求(共 10 个)。但试想一下,如果您的 frontend 有几十个副本呢?您真的想手动更新所有这些代理吗?当然不是了!这不仅容易出错,而且还耗费时间。

选项 2:聪明方法
可观测性和可控性是使用 service mesh(服务网格)的首要理由。除此还有其他更多好处。service mesh 也是在 service 之间实施流量分割的理想工具,因为您可以为网格服务的所有 frontend 副本应用统一的策略!

使用 NGINX Service Mesh 进行流量分割

NGINX Service Mesh 能够实施 Service Mesh Interface (SMI) — SMI 是一个规范,定义了在 Kubernetes 上运行的 service mesh 的标准接口,具有 TrafficSplitTrafficTargetHTTPRouteGroup等类型化资源。借助这些标准的 Kubernetes 配置,NGINX Service Mesh 和NGINX SMI 扩展程序可简化流量分割策略(如灰度部署)的部署,同时最大限度地减少对生产流量的中断。

下图摘自《API 网关 vs. Ingress Controller vs. Service Mesh,该怎么选?》一文,您可以从中看出 NGINX Service Mesh 是如何使用基于 HTTP/S 标准的条件路由在 service 之间实施灰度部署的。

与所有其他网格一样,NGINX Service Mesh 的架构也有一个数据平面和控制平面。由于 NGINX Service Mesh 利用 NGINX Plus 支持数据平面,它能够执行高级部署方案。

  • 数据平面:由容器化 NGINX Plus 代理(称为 sidecar)组成,这些代理负责卸载网格中所有应用所需的功能,并实施灰度部署等部署模式。
  • 控制平面:负责管理数据平面。sidecar 能够路由应用流量并提供其他数据平面服务,控制平面则可以将 sidecar 注入到 pod 中并执行管理任务,例如更新 mTLS 证书并将其推送到合适的 sidecar 等。

创建灰度部署

NGINX Service Mesh 控制平面可以使用 Kubernetes 自定义资源定义 (CRD) 进行控制。它使用 Kubernetes service 来检索 pod 的 IP 地址和端口列表。然后,它会结合来自 CRD 的指令,通知 sidecar 直接将流量路由到 pod。

  1. 使用您选择的文本编辑器,创建一个名为 5-split.yaml 的 YAML 文件,在其中使用 TrafficSplit CRD 定义流量分割。
  2. apiVersion: split.smi-spec.io/v1alpha3 
    kind: TrafficSplit 
    metadata: 
      name: backend-ts 
    spec: 
      service: backend-svc 
      backends: 
      - service: backend-v1 
        weight: 90 
      - service: backend-v2 
        weight: 10 
    

    请注意 CRD 中定义了三个 service(到目前为止您只创建了一个):

    • backend-svc 是面向所有 pod 的 service。
    • backend-v1 是从 backend-v1 部署中选择 pod 的 service。
    • backend-v2 是从 backend-v2 部署中选择 pod 的 service。
  3. 在实施流量分割之前,您必须先创建缺失的 service。创建一个名为 4-services.yaml 的 YAML 文件,该文件应包含以下内容:
  4. apiVersion: v1 
    kind: Service 
    metadata: 
      name: backend-v1 
      labels: 
        app: backend 
        version: "1" 
    spec: 
      ports: 
        - port: 80 
          targetPort: 80 
      selector: 
        app: backend 
        version: "1" 
    --- 
    apiVersion: v1 
    kind: Service 
    metadata: 
      name: backend-v2 
      labels: 
        app: backend 
        version: "2" 
    spec: 
      ports: 
        - port: 80 
          targetPort: 80 
      selector: 
        app: backend 
        version: "2" 
    
  5. 添加您的 service:
  6. $ kubectl apply -f 4-services.yaml 
    service/backend-v1 created 
    service/backend-v2 created 
    

观察日志

在实施流量分割之前,请先检查日志,查看流量在没有 TrafficSplit CRD 的情况下是如何流动的。您应该可以看到流量在 v1和 v2 之间均匀分配。

$ kubectl logs -c frontend frontend-6c64d7446-mmgpv 

{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 
{"name":"backend","version":"1"} 
{"name":"backend","version":"2"} 

实施灰度部署

  1. 应用 TrafficSplit CRD.
  2. $ kubectl apply -f 5-split.yaml 
    trafficsplit.split.smi-spec.io/backend-ts created 
    
  3. 再次观察日志。现在,您应该看到 90% 的流量被传输到 v1,如 5-split.yaml 所定义的那样。
  4. $ kubectl logs -c frontend frontend-6c64d7446-mmgpv 
    
    {"name":"backend","version":"1"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"1"} 
    {"name":"backend","version":"1"} 
    {"name":"backend","version":"1"} 
    

转移到 V2

在实际应用中,我们很少会先进行 90/10 的流量分割,然后立即将所有流量移至新版本。相反,最好的做法是递增转移流量。例如:0%、5%、10%、25%、50% 和 100%。为了演示实施递增转移的简单性,您可以将权重更改为 20/80,然后再改为 0/100。

  1. 编辑 5-split.yaml,让 backend-v1 获得 20% 的流量、backend-v2 获得剩余 80% 的流量。
  2. apiVersion: split.smi-spec.io/v1alpha3 
    kind: TrafficSplit 
    metadata: 
      name: backend-ts 
    spec: 
      service: backend-svc 
      backends: 
      - service: backend-v1 
        weight: 20 
      - service: backend-v2 
        weight: 80 
    
  3. 应用更改:
  4. $ kubectl apply -f 5-split.yaml 
    trafficsplit.split.smi-spec.io/backend-ts configured 
    
  5. 观察日志,查看实际变化:
  6. $ kubectl logs -c frontend frontend-6c64d7446-mmgpv 
    
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"1"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    
  7. 要完成转移,请编辑 5-split.yaml,让 backend-v1 获得 0% 的流量、backend-v2 获得 100% 的流量。
  8. apiVersion: split.smi-spec.io/v1alpha3 
    kind: TrafficSplit 
    metadata: 
      name: backend-ts 
    spec: 
      service: backend-svc 
      backends: 
      - service: backend-v1 
        weight: 0 
      - service: backend-v2 
        weight: 100 
    
  9. 应用更改:
  10. $ kubectl apply -f 5-split.yaml 
    trafficsplit.split.smi-spec.io/backend-ts configured 
    
  11. 观察日志,查看实际变化。所有响应都已转移到 backend-v2,这意味着您的递增转移已全部完成!
  12. $ kubectl logs -c frontend frontend-6c64d7446-mmgpv 
    
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    {"name":"backend","version":"2"} 
    

     

    后续步骤

    如欲继续深入了解和本实验相关的知识技能,您可以加入到 Microservices June 2022 微服务之月项目中来,并继续浏览本项目第三单元“Kubernetes 高级部署策略”的其他学习资源。

    NGINX Service Mesh 完全免费。您可以使用 Helm(本教程使用的方法)或通过 F5 Downloads下载 NGINX Service Mesh。

Hero image
Kubernetes:
从测试到生产

通过多种流量管理工具提升弹性、可视性和安全性

Tags

No More Tags to display