增量式应用 Istio 第一部分,流量管理

如何在不部署 Sidecar 代理的情况下使用 Istio 进行流量管理。

Nov 21, 2018 | By Sandeep Parikh

流量管理是 Istio 提供的重要优势之一。Istio 流量管理的核心是在将通信流量和基础设施的伸缩进行解耦。如果没有 Istio 这样的服务网格,这种流量控制方式是不可能实现的。

例如,您希望执行一次金丝雀发布。当使用 Istio 时,您可以指定 service 的 v1 版本接收 90% 的传入流量,而该 service v2 版本仅接收 10%。如果使用标准的 Kubernetes deployment,实现此目的的唯一方法是手动控制每个版本的可用 Pod 数量,例如使 9 个 Pod 运行 v1 版本,使 1 个 Pod 运行 v2 版本。这种类型的手动控制难以实现,并且随着时间的推移可能无法扩展。有关更多信息,请查看使用 Istio 进行金丝雀发布

部署现有 service 的更新时存在同样的问题。虽然您可以使用 Kubernetes 更新 deployment,但它需要将 v1 Pod 替换为 v2 Pod。使用 Istio,您可以部署 service 的 v2 版本,并使用内置流量管理机制在网络层面将流量转移到更新后的 service,然后删除 v1 版本的 Pod。

除了金丝雀发布和一般流量转移之外,Istio 还使您能够实现动态请求路由(基于 HTTP header)、故障恢复、重试、断路器和故障注入。有关更多信息,请查看流量管理文档

这篇文章介绍的技术重点突出了一种特别有用的方法,可以逐步实现 Istio(在这种情况下,只有流量管理功能),而无需单独更新每个 Pod。

设置:为什么要实施 Istio 流量管理功能?

当然,第一个问题是:为什么要这样做?

如果你是众多拥有大量团队和大型集群的组织中的一员,那么答案是很清楚的。假设 A 团队正在开始使用 Istio,并希望在 service A 上开始一些金丝雀发布,但是 B 团队还没有开始使用 Istio,所以他们没有部署 sidecar。

使用 Istio,A 团队仍然可以让 service B 通过 Istio 的 ingress gateway 调用 service A 来实现他们的金丝雀发布。

背景:Istio 网格中的流量路由

但是,如何在不更新每个应用程序的 Pod 的情况下,使用 Istio 的流量管理功能来包含 Istio sidecar?在回答这个问题之前,让我们以高层视角,快速地看看流量如何进入 Istio 网格以及如何被路由。

Pod 包含一个 sidecar 代理,该代理作为 Istio 网格的一部分,负责协调 Pod 的所有入站和出站流量。在 Istio 网格中,Pilot 负责将高级路由规则转换为配置并将它们传播到 sidecar 代理。这意味着当服务彼此通信时,它们的路由决策是由客户端确定的。

假设您有 service A 和 service B 两个服务,他们是 Istio 网格的一部分。当 A 想要与 B 通信时,Pod A 的 sidecar 代理负责将流量引导到 service B。例如,如果你希望到 service B v1 版本和 v2 版本之间的流量按 50/50 分割,流量将按如下方式流动:

50/50 流量分割
50/50 流量分割

如果 service A 和 B 不是 Istio 网格的一部分,则没有 sidecar 代理知道如何将流量路由到 service B 的不同版本。在这种情况下,您需要使用另一种方法来使 service A 到 service B 的流量遵循您设置的 50/50 规则。

幸运的是,标准的 Istio 部署已经包含了一个 Gateway,它专门处理 Istio 网格之外的入口流量。此 Gateway 用于允许通过外部负载均衡器进入的集群外部入口流量;或来自 Kubernetes 集群,但在服务网格之外的入口流量。网关可以进行配置,对没有 Sidecar 支持的入口流量进行代理,引导流量进入相应的 Pod。这种方法允许您利用 Istio 的流量管理功能,其代价是通过入口网关的流量将产生额外的跃点。

使用 Ingress Gateway 的 50/50 流量分割
使用 Ingress Gateway 的 50/50 流量分割

实践:Istio 流量路由

一种实践的简单方法是首先按照平台设置说明设置 Kubernetes 环境,然后使用 Helm 安装仅包含流量管理组件(ingress gateway、egress gateway、Pilot)的 Istio。下面的示例使用 Google Kubernetes Engine

首先,安装并配置 GKE

$ gcloud container clusters create istio-inc --zone us-central1-f
$ gcloud container clusters get-credentials istio-inc
$ kubectl create clusterrolebinding cluster-admin-binding \
   --clusterrole=cluster-admin \
   --user=$(gcloud config get-value core/account)

然后,安装 Helm生成 Istio 最小配置安装 – 只有流量管理组件:

$ helm template install/kubernetes/helm/istio \
  --name istio \
  --namespace istio-system \
  --set security.enabled=false \
  --set galley.enabled=false \
  --set sidecarInjectorWebhook.enabled=false \
  --set mixer.enabled=false \
  --set prometheus.enabled=false \
  --set pilot.sidecar=false > istio-minimal.yaml

然后创建 istio-system namespace 并部署 Istio:

$ kubectl create namespace istio-system
$ kubectl apply -f istio-minimal.yaml

然后,在没有 Istio sidecar 容器的前提下部署 Bookinfo 示例:

Zip
$ kubectl apply -f @samples/bookinfo/platform/kube/bookinfo.yaml@

现在,配置一个新的 Gateway 允许从 Istio 网格外部访问 reviews service;一个新的 VirtualService 用于平均分配到 reviews service v1 和 v2 版本的流量;以及一系列新的、将目标子集与服务版本相匹配的 DestinationRule 资源:

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: reviews-gateway
spec:
  selector:
    istio: ingressgateway # 使用 istio 默认控制器
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - "*"
  gateways:
  - reviews-gateway
  http:
  - match:
    - uri:
        prefix: /reviews
    route:
    - destination:
        host: reviews
        subset: v1
      weight: 50
    - destination:
        host: reviews
        subset: v2
      weight: 50
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
EOF

最后,使用 curl 部署一个用于测试的 Pod(没有 Istio sidecar 容器):

Zip
$ kubectl apply -f @samples/sleep/sleep.yaml@

测试您的部署

现在就可以通过 Sleep pod 使用 curl 命令来测试不同的行为了。

第一个示例是使用标准 Kubernetes service DNS 行为向 reviews service 发出请求(注意:下面的示例中使用了 jq 来过滤 curl 的输出):

$ export SLEEP_POD=$(kubectl get pod -l app=sleep \
  -o jsonpath={.items..metadata.name})
$ for i in `seq 3`; do \
  kubectl exec -it $SLEEP_POD curl http://reviews:9080/reviews/0 | \
  jq '.reviews|.[]|.rating?'; \
  done
{
  "stars": 5,
  "color": "black"
}
{
  "stars": 4,
  "color": "black"
}
null
null
{
  "stars": 5,
  "color": "red"
}
{
  "stars": 4,
  "color": "red"
}

请注意我们是如何从 reviews service 的所有三个版本获得响应(null 来自 reviews v1 版本,它没有评级数据)并且流量没有在 v1 和 v2 版本间平均拆分。这是预期的行为,因为 curl 命令在 reviews service 所有三个版本之间进行 Kubernetes service 负载均衡。为了以 50/50 的流量拆分形式访问 reviews,我们需要通过 ingress Gateway 访问 service:

$ for i in `seq 4`; do \
  kubectl exec -it $SLEEP_POD curl http://istio-ingressgateway.istio-system/reviews/0 | \
  jq '.reviews|.[]|.rating?'; \
  done
{
  "stars": 5,
  "color": "black"
}
{
  "stars": 4,
  "color": "black"
}
null
null
{
  "stars": 5,
  "color": "black"
}
{
  "stars": 4,
  "color": "black"
}
null
null

任务完成!这篇文章展示了如何部署仅包含流量管理组件(Pilot、ingress Gateway)的 Istio 的最小安装,然后使用这些组件将流量定向到特定版本的 reviews service。由于不是必须通过部署 Istio sidecar 代理来获得这些功能,因此几乎没有给现有工作负载或应用程序造成中断。

这篇文章展示了如何利用 Istio 及内置的 ingress Gateway(以及一些 VirtualServiceDestinationRule 资源),轻松实现集群外部入口流量和集群内部服务到服务的流量管理。这种技术是增量式应用 Istio 的一个很好的例子,在 Pod 由不同团队拥有,或部署到不同命名空间的现实案例中尤其有用。