开源Gloo的两阶段Canary推广

作者: 里克·杜科特| 的GitHub | 推特

每天,我和我的同事都在与正在使用的平台所有者,架构师和工程师进行交谈。 o 作为API网关 向最终用户展示他们的应用程序。这些应用程序可能跨越旧式整体,微服务,托管云服务和Kubernetes 集群。幸运的是,使用Gloo可以轻松设置路由来管理,保护和观察应用程序流量,同时 支持灵活的部署架构,以满足用户不断变化的生产需求。

除了最初的设置外,平台所有者还经常要求我们帮助设计其组织内的操作流程: 我们如何使一个新的应用程序在线?我们如何升级应用程序?我们如何在我们之间划分责任 平台,运营和开发团队?

在本文中,我们将使用Gloo为应用程序升级设计两阶段的Canary推广工作流程:

  • 在第一阶段,我们将通过将一小部分流量转移到新版本进行金丝雀测试。这使您可以安全地执行烟雾和正确性测试。
  • 在第二阶段中,我们将逐步将流量转移到新版本,使我们能够监视负载下的新版本,并最终停用旧版本。

为简单起见,我们将重点放在使用 开源Gloo,我们将部署网关和 应用到Kubernetes。最后,我们将讨论一些扩展和高级主题,这些内容和后续主题可能会很有趣。

初始设置

首先,我们需要一个Kubernetes集群。此示例未利用任何特定于云的优势 功能,并且可以针对本地测试集群(例如 迷你库 . This post assumes a basic understanding of Kubernetes 和 how to interact 与 it using kubectl.

我们将安装最新的 开源Gloo to the gloo-system namespace 和 deploy version v1 of an example application to the echo namespace. We'll expose this application outside the cluster 通过在Gloo中创建路线,最终得到这样的图片:

Setup

部署Gloo

We'll install gloo 与 the glooctl command line tool, which we can download 和 add to the PATH 与 the following commands:

curl -sL //run.solo.io/gloo/install | sh
export PATH=$HOME/.gloo/bin:$PATH

Now, you should be able to run glooctl version to see that it is installed correctly:

➜ glooctl version
Client: {"version":"1.3.15"}
Server: version undefined, could not find any version of gloo running

现在,我们可以使用简单的命令将网关安装到集群中:

glooctl install gateway

控制台应指示安装成功完成:

Creating namespace gloo-system... Done.
Starting  o  installation...

Gloo was successfully installed!

Before long, we can see all the o pods running in the gloo-system namespace:

➜ kubectl get pod -n gloo-system
NAME                             READY   STATUS    RESTARTS   AGE
discovery-58f8856bd7-4fftg       1/1     Running   0          13s
gateway-66f86bc8b4-n5crc         1/1     Running   0          13s
gateway-proxy-5ff99b8679-tbp65   1/1     Running   0          13s
gloo-66b8dc8868-z5c6r            1/1     Running   0          13s

部署应用程序

Our echo application is a simple container (thanks to our friends at HashiCorp) that will 响应应用程序版本,以帮助演示我们开始测试时的金丝雀工作流程,并 shifting traffic to a v2 version of the application.

Kubernetes在建模该应用程序方面给了我们很大的灵活性。我们将采用以下 conventions:

  • 我们将在部署名称中包含该版本,以便我们可以运行该应用程序的两个版本 并排管理它们的生命周期。
  • We'll label pods 与 an app label (app: echo) 和 a version label (version: v1) to help 与 our canary rollout.
  • We'll deploy a single Kubernetes Service for the application to set up networking. Instead of updating 此操作或使用多种服务来管理到不同版本的路由,我们将使用Gloo配置管理部署。

The following is our v1 echo application:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
      version: v1
  template:
    metadata:
      labels:
        app: echo
        version: v1
    spec:
      containers:
        # Shout out to our friends at Hashi for this useful test server
        - image: hashicorp/http-echo
          args:
            - "-text=version:v1"
            - -listen=:8080
          imagePullPolicy: Always
          name: echo-v1
          ports:
            - containerPort: 8080

And here is the echo Kubernetes Service object:

apiVersion: v1
kind: Service
metadata:
  name: echo
spec:
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: echo

为了方便起见,我们已在仓库中发布了此Yaml,以便可以使用以下命令进行部署:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/echo.yaml

我们应该看到以下输出:

namespace/echo created
deployment.apps/echo-v1 created
service/echo created

And we should be able to see all the resources healthy in the echo namespace:

➜ kubectl get all -n echo
NAME                           READY   STATUS    RESTARTS   AGE
pod/echo-v1-66dbfffb79-287s5   1/1     Running   0          6s

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/echo   ClusterIP   10.55.252.216   <none>        80/TCP    6s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/echo-v1   1/1     1            1           7s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/echo-v1-66dbfffb79   1         1         1       7s

用Gloo暴露在集群外

现在,我们可以使用Gloo在群集之外公开此服务。首先,我们将应用程序建模为Gloo 上游,这是Gloo的抽象 对于交通目的地:

apiVersion: gloo.solo.io/v1
kind: 上游
metadata:
  name: echo
  namespace: gloo-system
spec:
  kube:
    selector:
      app: echo
    serviceName: echo
    serviceNamespace: echo
    servicePort: 8080
    subsetSpec:
      selectors:
        - keys:
            - version

Here, we're setting up subsets based on the version label. We don't have to use this in our routes, but later 我们将开始使用它来支持我们的金丝雀工作流程。

现在,我们可以通过定义一个到Gloo的上游创建路由 虚拟服务:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system

我们可以使用以下命令来应用这些资源:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/upstream.yaml
kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/1-setup/vs.yaml

一旦应用了这两种资源,就可以开始通过Gloo将流量发送到应用程序:

➜ curl $(glooctl proxy url)/
version:v1

我们的设置已完成,并且集群现在如下所示:

Setup

两阶段推出策略

Now we have a new version v2 of the echo application that we wish to roll out. We know that when the 部署已完成,我们将最终得到以下图片:

结束状态

但是,为达到此目的,我们可能需要执行几轮测试以确保应用程序的新版本 符合某些正确性和/或性能接受标准。在本文中,我们将介绍一种两阶段的方法 Gloo的金丝雀推出,可以用来满足绝大多数验收测试。

在第一阶段,我们将通过将一小部分流量路由到新版本来执行冒烟性和正确性测试 of the application. In this demo, we'll use a header stage: canary to trigger routing to the new service, though in 实践中,可能希望根据请求的另一部分(例如,经过验证的JWT中的声明)做出此决定。

在第二阶段,我们已经确定了正确性,因此我们准备将所有流量转移到新 应用程序的版本。我们将配置加权目标,并在监控某些业务的同时转移流量 确保服务质量保持可接受水平的指标。一旦将100%的流量转移到新版本, 旧版本可以退役。

在实践中,可能希望仅使用一个阶段进行测试,在这种情况下,另一阶段可以是 skipped.

阶段1:首次推出v2的canary

In this phase, we'll deploy v2, 和 then use a header stage: canary to start routing a small amount of specific traffic to the new version. We'll use this header to perform some basic smoke testing 和 make sure v2 is working the way we'd expect:

子集路由

设置子集路由

Before deploying our v2 service, we'll update our virtual service to only route to pods that have the subset label version: v1,使用称为的Gloo功能 子集路由.

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v1

我们可以使用以下命令将它们应用于集群:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/vs-1.yaml

该应用程序应继续像以前一样运行:

➜ curl $(glooctl proxy url)/
version:v1

部署echo v2

Now we can safely deploy v2 of the echo application:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
      version: v2
  template:
    metadata:
      labels:
        app: echo
        version: v2
    spec:
      containers:
        - image: hashicorp/http-echo
          args:
            - "-text=version:v2"
            - -listen=:8080
          imagePullPolicy: Always
          name: echo-v2
          ports:
            - containerPort: 8080

我们可以使用以下命令进行部署:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/echo-v2.yaml

Since our gateway is configured to route specifically to the v1 subset, this should have no effect. However, it does enable v2 to be routable from the gateway if the v2 subset is configured for a route.

Make sure v2 is running before moving on:

➜ kubectl get pod -n echo
NAME                       READY   STATUS    RESTARTS   AGE
echo-v1-66dbfffb79-2qw86   1/1     Running   0          5m25s
echo-v2-86584fbbdb-slp44   1/1     Running   0          93s

该应用程序应继续像以前一样运行:

➜ curl $(glooctl proxy url)/
version:v1

添加到v2的路由以进行金丝雀测试

We'll route to the v2 subset when the stage: canary header is supplied on the request. If the header isn't provided, we'll continue to route to the v1 subset as before.

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - headers:
              - name: stage
                value: canary
            prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v2
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v1

我们可以使用以下命令进行部署:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/2-initial-subset-routing-to-v2/vs-2.yaml

金丝雀测试

现在我们有了这条路线,我们可以进行一些测试。首先,请确保现有路由能够按预期运行:

➜ curl $(glooctl proxy url)/
version:v1

现在我们可以开始金丝雀测试我们的新应用程序版本:

➜ curl $(glooctl proxy url)/ -H "stage: canary"
version:v2

子集路由的高级用例

我们可能会决定使用用户提供的请求标头的这种方法过于开放。相反,我们可能 希望将金丝雀测试仅限于已知的授权用户。

我们已经看到的一种常见实现是,黄雀色路线需要包含以下内容的有效JWT 指明受试者已获准进行金丝雀测试的特定声明。企业Gloo已开箱 支持验证JWT,基于JWT声明更新请求标头并重新计算 基于更新的标头的路由目标。我们会将其保存在以后的文章中,以涵盖更高级的用途 金丝雀测试中的案例。

阶段2:将所有流量转移到v2并停用v1

At this point, we've deployed v2, 和 created a route for canary testing. If we are satisfied 与 the results of the testing, we can move on to phase 2 和 start shifting the load from v1 to v2. We'll use 加权目的地 在Gloo中管理迁移期间的负载。

设置加权目的地

我们可以更改Gloo路线以路由到这两个目的地,并通过权重来确定应吸引的流量 go to the v1 versus the v2 subset. To start, we're going to set it up so 100% of the traffic continues to get routed to the v1 subset, unless the stage: canary header was provided as before.

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      # We'll keep our route from before if we want to continue testing  与  this header
      - matchers:
          - headers:
              - name: stage
                value: canary
            prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v2
      # Now we'll route the rest of the traffic to the upstream, load balanced across the two subsets.
      - matchers:
          - prefix: /
        routeAction:
          multi:
            destinations:
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v1
                weight: 100
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v2
                weight: 0

我们可以使用以下命令将此虚拟服务更新应用于群集:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-1.yaml

Now the cluster looks like this, for any request that doesn't have the stage: canary header:

初始化交通转移

With the initial weights, we should see the gateway continue to serve v1 for all traffic.

➜ curl $(glooctl proxy url)/
version:v1

开始推出

To simulate a load test, let's shift half the traffic to v2:

负载测试

这可以通过调整权重在我们的虚拟服务上表示:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - headers:
              - name: stage
                value: canary
            prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v2
      - matchers:
          - prefix: /
        routeAction:
          multi:
            destinations:
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v1
                # Update the weight so 50% of the traffic hits v1
                weight: 50
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v2
                # And 50% is routed to v2
                weight: 50

我们可以使用以下命令将其应用于集群:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-2.yaml

Now when we send traffic to the gateway, we should see half of the requests return version:v1 和 the other half return version:v2.

➜ curl $(glooctl proxy url)/
version:v1
➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v1

实际上,在此过程中,您可能会监视一些性能和业务指标 确保流量转移不会导致整体服务质量下降。我们甚至可以 利用像 旗手 帮助自动化这个Gloo 工作流程。 o Enterprise与您的指标后端集成,并提供开箱即用的动态, 基于上游的仪表板,可用于监视部署的运行状况。 我们将把这些主题保存起来,以备将来在Gloo的高级金丝雀测试用例上发表。

完成部署

We will continue adjusting weights until eventually, all of the traffic is now being routed to v2:

最后的转变

我们的虚拟服务将如下所示:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - headers:
              - name: stage
                value: canary
            prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v2
      - matchers:
          - prefix: /
        routeAction:
          multi:
            destinations:
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v1
                # No traffic will be sent to v1 anymore
                weight: 0
              - destination:
                  upstream:
                    name: echo
                    namespace: gloo-system
                  subset:
                    values:
                      version: v2
                # Now all the traffic will be routed to v2
                weight: 100

我们可以使用以下命令将其应用于集群:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/3-progressive-traffic-shift-to-v2/vs-3.yaml

Now when we send traffic to the gateway, we should see all of the requests return version:v2.

➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v2
➜ curl $(glooctl proxy url)/
version:v2

退役v1

至此,我们已经部署了新版本的应用程序,使用子集路由进行了正确性测试, 通过逐步将流量转移到新版本进行负载和性能测试,并完成 the rollout. The only remaining task is to clean up our v1 resources.

首先,我们将清理路线。我们将在路由上指定子集,以便为将来的升级做好准备。

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: echo
  namespace: gloo-system
spec:
  virtualHost:
    domains:
      - "*"
    routes:
      - matchers:
          - prefix: /
        routeAction:
          single:
            upstream:
              name: echo
              namespace: gloo-system
            subset:
              values:
                version: v2

我们可以使用以下命令来应用此更新:

kubectl apply -f //raw.githubusercontent.com/solo-io/gloo-ref-arch/blog-30-mar-20/platform/prog-delivery/two-phased-with-os-gloo/4-decommissioning-v1/vs.yaml

And we can delete the v1 deployment, which is no longer serving any traffic.

kubectl delete deploy -n echo echo-v1

现在我们的集群如下所示:

结束状态

并向网关发出的请求返回以下内容:

➜ curl $(glooctl proxy url)/
version:v2

现在,我们已经使用Gloo完成了两阶段的Canary首次发布应用程序更新!

其他高级主题

在这篇文章的过程中,我们收集了一些可以作为高级探索的良好起点的主题:

  • 使用 智威汤逊 过滤器以验证JWT,将声明提取到标头上,并根据声明值路由到Canary版本。
  • 看着 普罗米修斯指标Grafana仪表板 由Gloo创建,用于监视部署的运行状况。
  • 通过集成自动化部署 旗手 o .

其他一些值得进一步探讨的主题:

  • 配套 自助服务 通过赋予团队所有权对其上游和路线配置进行升级
  • 利用Gloo的 代表团 功能和Kubernetes RBAC 安全地分散配置管理
  • 通过应用完全自动化连续交付过程 GitOps 原理和使用工具,例如 助焊剂 将配置推送到集群
  • 配套 杂种 要么 非Kubernetes 通过使用其他部署模式设置Gloo来创建应用程序用例
  • 利用 交通阴影 开始用实际数据测试新版本,然后再转移生产流量

参与Gloo社区

除了企业客户群以外,Gloo拥有庞大且不断增长的开源用户社区。要了解更多有关 Gloo:

  • 看看 回购 ,您可以在其中查看代码和文件问题
  • 看看 docs ,其中包含大量指南和示例
  • 加入 松弛通道 并开始与Solo工程团队和用户社区聊天

如果您想与我联系(反馈总是很感激!),您可以在 单身休闲 或给我发电子邮件 rick.ducott@solo.io.