Istio-服务网格入门指南-二-

177 阅读33分钟

Istio 服务网格入门指南(二)

原文:Getting Started with Istio Service Mesh

协议:CC BY-NC-SA 4.0

三、安装 Istio

在前一章中,我们介绍了微服务架构及其挑战。您了解了编排引擎如何解决这些挑战,但是为了使解决方案更加优雅和可重用,您需要一个服务网格。在本章中,我们将向您展示如何设置 Istio 环境并了解 Istio 的基础知识。

Istio 服务网格

正如上一章所讨论的,当使用微服务架构时,当您想要提供响应时,通过网络调用服务会有点不太可靠。为了简化流程,客户应该能够动态地发现服务,并保证服务的可用性。服务必须以这样的方式被发现,即调用服务同意被发现服务的 API 版本契约。除此之外,调用服务应该处理网络调用期间的任何错误,重试任何失败的调用,并且在必要时超时。这些步骤是在微服务架构中创建流畅的网络交互所必需的。此外,还需要一个应用来记录呼叫和工具交易,并限制对不同服务的访问。所有这些都在应用内部带来了额外的冗余工作,并且与应用逻辑没有太大关系。

当你仔细观察时,所有这些问题和挑战都与网络通信直接或间接相关。Istio 服务网格为服务到服务的通信提供了一个基础设施层,抽象出了网络复杂性和挑战。以下是 Istio 提供的核心功能:

  • 弹性 : Istio 无需在应用中编写断路器代码。它还负责服务的超时和重试,而应用并不知道。

  • 安全性 : Istio 通过支持包括密钥管理在内的基于 TLS 的加密来负责访问控制。

  • 遥测:由于 Istio 是网络层上的一个抽象,它可以跟踪网络调用,因此可以跟踪从单个源发起的多个服务的调用,它还可以收集调用的度量。

  • 服务发现:在生产环境中运行的应用的主要需求之一是高度可用。这要求人们随着负载的增加而扩大服务规模,当不需要节省成本时则缩小服务规模。服务发现跟踪服务的可用节点,并准备好接受新任务。如果某个节点不可用,服务发现会将其从可用节点列表中删除,并停止向该节点发送新任务/请求。

  • 路由选择:Istio 提供了灵活性,因此可以很好地控制服务的可用节点之间的流量。以下列表强调了 Istio 提供的基本支持:

    1. 负载均衡 : Istio 允许基于不同算法的负载均衡,例如循环、随机、加权和最小请求。

    2. 健康检查 : Istio 不仅关注节点可用性,还关注服务是否启动、运行并仍在响应,然后才将其包含在可用节点中。

    3. 自动部署:根据使用的部署类型,Istio 以加权模式将流量驱动到新节点。

图 3-1 让你一瞥使用 Istio 的服务和它的节点之间发生了什么。

img/483921_1_En_3_Fig1_HTML.jpg

图 3-1

Istio 特征

这些特性是应用必须具备的,将这些功能从应用代码中去掉会使代码更加整洁,并删除不必要的模块和跨多种语言的冗余。让我们看看 Istio 是如何通过浏览其架构来实现这一功能的。

Istio 建筑

如上所述,Istio 特性也可以在应用内部实现。Istio 使用 Sidecar 模式,将这些特性提取出来作为小组件,并包装每个服务,以便观察、验证和处理任何入站和出站请求。所有流量都被定向到代理,代理根据规则或策略来决定如何、何时或是否将流量部署到服务。使用这些规则,它还支持故障注入、电路中断和金丝雀部署等技术,而服务无需担心所有这些问题。

图 3-2 显示了 Istio 架构的不同组件。

img/483921_1_En_3_Fig2_HTML.jpg

图 3-2

Istio 组件

Istio 在逻辑上分为两大部分,数据平面和控制面板。

数据平面

数据平面负责转换、转发和监控流入和流出实例的每个网络数据包。顾名思义,它负责在服务网格中收集元数据。它拥有健康检查、路由、服务发现、负载均衡、安全性和遥测等关键功能。正如上一章所讨论的,sidecar 使用 Envoy 代理来处理所有这些关键特性。从本质上讲,数据平面可以被视为跨服务网格部署的边车代理。图 3-3 显示了数据平面的概况。

img/483921_1_En_3_Fig3_HTML.jpg

图 3-3

带特使边车的数据平面服务

如果数据平面负责所有基本项目,那么控制面板做什么?

制导机

数据平面是一组相互交互的独立节点,控制面板利用它们创建一个分布式系统。当发起网络请求时,代理不知道要连接到哪个其他代理。控制面板是网格中提供此信息的平面。当发现新服务时,控制面板填充现有的服务列表,然后代理使用该列表来判断新服务的存在并引导流量。断路、负载均衡、超时、安全信息等基本配置存储在控制面板中。当新部署发生时,数据平面具有新节点准备好接受请求的信息,但是是进行蓝/绿部署还是逐渐转移流量由控制面板定义。

控制面板向数据平面提供策略和配置,而不接触网格中的任何网络分组。图 3-4 显示了系统中网络数据包和元数据的流向。这些配置是从控制面板引用的,而所有操作都发生在数据平面。

img/483921_1_En_3_Fig4_HTML.jpg

图 3-4

控制面板引导请求并从数据平面接收元数据

在任何架构中,数据层总是存在的。如前一章所述,数据层中陈述的大多数特性在 Nginx、HAproxy 和 Envoy 等流行项目中都可用。但是这些都需要手动设置配置,或者通过自己编写的脚本,或者使用多种其他工具。Istio 将所有这些结合在一起,提供了一个单一的平台,消除了样板配置,并在解决方案中提供了持久性。Istio 使用四个主要组件来简化这些繁琐的任务。

搅拌器

Mixer 是一个独立于平台的组件。它提供了一种为服务收集遥测数据的机制,并强制执行授权策略。它从 Istio 系统中抽象出基础设施后端提供的基本支持功能,如遥测捕获、配额实施、计费系统等。服务通常与基础设施后端紧密绑定,以获取这些细节,这导致遵循特定的协议,并增加了限制和依赖性。

考虑一个服务将日志写入文件系统的例子。由于容器是易变的,服务日志可能会随着时间的推移而丢失。为了解决这个问题,人们开始将日志发送到云服务。几个月后,有人想引入一个新的日志捕获器,它能够搜索日志。传统上,服务将被修改为向这两个日志服务发送日志,但是理想情况下,服务应该只关心它的任务,而不关心日志。Istio 负责收集这些数据,Mixer 为 Istio 提供了统一的抽象,以便与基础设施后端进行交互。

图 3-5 显示了混频器遵循的拓扑结构。所有请求都被发送进行策略检查,然后,请求遥测数据被报告给 Mixer。使用高速缓存和缓冲来优化该过程。

img/483921_1_En_3_Fig5_HTML.jpg

图 3-5

请求已发送,需要用报告的遥测数据进行策略检查

基础设施后端可能依赖于基础设施提供商。要使 Mixer 成为一个模块化和可扩展的组件,将它绑定到一个特定的协议并要求基础设施提供者遵循它是不正确的。相反,Mixer 提供了称为适配器的通用插件。

适配器

适配器允许 Mixer 与基础设施后端进行交互,并保持 Istio 的其余部分从提供者中抽象出来。要使用的适配器通过混合器配置驱动,以在运行时面向任何基础设施后端。下面是一些与不同后端交互的流行适配器:

  • 遥测后端:这有助于处理从吊舱收集的遥测数据。

    1. StatsD :将度量数据传递给 StatsD 监控后端。
  • 授权后端:这有助于授权 Istio 网格内外的任何请求。

    1. List :对 IP 地址或正则表达式模式执行简单的白名单和黑名单检查。
  • 配额后端:这有助于跟踪不同的端点配额。

    1. Redis 配额:支持固定或滚动窗口算法的限速配额。顾名思义,它使用 Redis 存储数据。
  • 日志后端:这个适配器帮助处理和保存来自服务的日志。

    1. CloudWatch :这允许 Mixer 向 Amazon CloudWatch 交付指标,并向 Amazon CloudWatchLogs 发送日志。

    2. Fluentd :这将日志传送到 Fluentd 守护进程。

图 3-6 描述了 Mixer 与基础设施后端的交互。

img/483921_1_En_3_Fig6_HTML.jpg

图 3-6

Istio 通过 Mixer 适配器与后端服务交互

由于 Mixer 与不同的基础设施后端交互,它如何决定向哪个后端发送数据或从哪个后端请求数据?Mixer 本质上依赖于它可用的属性来进行这个调用。

属性

属性是定义请求属性的最小数据块。属性包括请求路径、请求指向的 IP 地址、响应代码、响应大小、请求大小等等。Mixer 处理这些属性,并根据配置触发对不同基础设施后端的调用。如图 3-7 所示,数据流从数据平面开始,通过 Mixer 到达基础设施后端。

img/483921_1_En_3_Fig7_HTML.jpg

图 3-7

数据平面将属性发送到属性处理器或混合器

混合器接收属性并让适配器调用基础设施后端,但是如上所述,存在定义活动适配器的配置,如何将输入属性映射到适配器属性,以及哪些实例属性应该给予哪些适配器。这属于 Mixer 的配置模型。

配置模型

配置模型基于适配器和模板。模板定义了如何将属性输入适配器。结合,他们做三种类型的配置。

  • 处理程序:负责定义适配器的配置。在 StatsD 的情况下,请求计数可以是一个提供给适配器的属性。这需要在配置中定义。

  • 实例:这定义了实例属性应该如何映射到适配器输入。对于请求计数,实例配置度量可以将该值定义为 1(即,对每个请求计数一次)。

  • 规则:现在我们知道了从实例中读取的属性,以及如何将它们映射到适配器属性。规则定义了何时运行此流程。让我们假设我们只想推送一个服务的请求计数;然后,该检查需要放在配置的匹配规范中,如destination.service.name == <Service Name>所示。

这为运营商提供了粒度控制,让他们知道使用哪个基础设施后端,并在需要时添加和删除它,而无需对服务进行任何更改,这让开发人员的工作变得更加轻松。

飞行员

飞行员驾驶交通。它找出新的路径,管理交通,并处理死角。换句话说,它执行路由,提供服务发现,并促进超时、重试、断路器等等。Pilot 将特定于平台的服务发现方式从 Istio 中分离出来,从而允许 Istio 在多个环境中运行,如 Kubernetes、Nomad 等。Istio 在所有 pod 的边车中使用 Envoy 代理来处理流量和配置。Pilot 将流量相关的配置转换为特使配置,并在运行时将其推送到边车。图 3-8 显示了 Pilot 的架构及其工作原理。

img/483921_1_En_3_Fig8_HTML.jpg

图 3-8

试点建筑

为了对每个服务进行流量控制,飞行员在网格中为每个服务维护一个模型。副本和服务发现的任何更新都在模型中以服务方式被跟踪。这有助于遵循一致的协议,在跨多个环境的模型中保存数据。这也意味着环境适配器必须对通过其资源获得的数据进行操作,以将其转换为试验服务模型。

让我们考虑一个部署在 Kubernetes 上的网格示例。当 Kubernetes 创建一个新的 pod 时,它会通知它的适配器,适配器会在特定于服务的模型中存储有关服务的新副本的信息。根据网络规则和配置,这创建了特定于特使的配置,并且特使 API 通知侧柜新的服务发现。这里重要的是,由于环境适配器负责服务发现,服务可能位于多个环境中,这意味着 Istio 可以跨多个环境部署网格。图 3-9 显示了元数据从环境到 Istio 的流程。

img/483921_1_En_3_Fig9_HTML.jpg

图 3-9

Istio 中的服务发现

在服务模型中,Pilot 存储副本的数量,并配置 Envoy 以支持不同类型的负载均衡,如循环、加权、随机等,这些都不是环境提供商现成可用的。

这些服务能够相互调用,确保可用性和响应性。但是,所有的服务都应该能够调用其他服务吗?这些服务应该通过未加密的连接进行通信吗?所有这些问题都由 Citadel 处理。

城堡

我们看到了引入微服务是如何改善应用的开发时间和性能的,但它也带来了安全问题,因为网络连接成为了应用的一部分。这种连接必须防范常见的安全问题,如中间人攻击;因此,需要 TLS 支持。Citadel 提供了在 Istio 网格中加密请求的特性。它还为网格中的服务提供基于角色的访问控制。请注意,Citadel 只启用加密(换句话说,提供证书来启用服务之间的安全连接),但这个配置是由 Pilot 推送给 Envoy 的。

活版盘

厨房可以被认为是一个管理平面。它的核心职责是从用户和底层环境中抽象出配置输入的 Istio mesh。Galley 存储用户配置,对其进行验证,然后将其发送给 Pilot 进行进一步操作。

既然对 Istio 架构有了基本的了解,那我们就来看看如何在不同的环境下设置 Istio。

建立 Istio

如前所述,Istio 在 Kubernetes、Nomad 等多种环境中都得到了支持。我们将把我们的设置限制在 Kubernetes。有两种方法可以在机器上安装 Istio 一个使用 Helm 图,另一个是快速演示安装。让我们过一遍这些。

使用 Helm 安装

Helm 是一个运行在 Kubernetes 上的包管理器。它允许我们通过掌 Helm 图来定义应用结构。这种安装应该用于生产环境。它为定制数据平面和控制面板组件提供了灵活性。Helm 帮助我们生成配置文件,然后 Kubernetes 控制器可以使用该文件进行安装。

下载 Istio 版本

从 GitHub 下载 Istio 版本并设置 Istio 路径。

  1. 从 GitHub 中拉出 Istio 并安装。版本 1.2.2 是撰写本文时的最新版本。

    curl -L https://git.io/getLatestIstio | ISTIO_VERSION=1.2.2 sh -
    
    
  2. 将 Istio 路径添加到环境变量。当前的 Istio 文件夹是istio-1.2.2

    export PATH=$PWD/bin:$PATH
    
    

图 3-10 显示在 Mac 机上安装 Istio 的输出。

img/483921_1_En_3_Fig10_HTML.png

图 3-10

安装 Istio 版本

安装 Helm

在不同的平台上设置 Helm 是不同的,但是在所有情况下都很简单。

  1. 在 macOS 上安装:在 Mac 上,可以用自制软件安装 Helm。

    brew install kubernetes-helm
    
    
  2. 在 Ubuntu 上安装:在 Ubuntu 上,使用 Snap 安装 Helm。

    sudo snap install helm --classic
    
    
  3. 将 Helm 初始化为客户端,以获取远程存储库。

    helm init --client-only
    
    

一旦安装了 Helm,我们需要添加istio-release库到 Helm。这将包括 Istio 提供的图表。

  1. 将 Istio 存储库添加到 Helm。

    helm repo add istio.io https://storage.googleapis.com/istio-release/releases/1.2.2/charts/
    
    

图 3-11 显示了安装过程。

img/483921_1_En_3_Fig11_HTML.jpg

图 3-11

安装 Helm

安装 Istio

让我们使用 Helm 安装 Istio。我们将使用在第一章中创建的 Minikube Kubernetes 集群。请确保 Minikube 已启动并正在运行。图 3-12 显示了如何检查状态并在出现故障时启动 Minikube。

img/483921_1_En_3_Fig12_HTML.jpg

图 3-12

宇宙魔方启动

  1. 我们将创建一个名为istio-system的名称空间,在这个名称空间下将部署 Istio 服务。

    kubectl create namespace istio-system
    
    
  2. Istio 附带了 23 个自定义资源定义(CRD ),可以在配置 Istio 时使用。让我们使用kubectl来安装它们。确保你在istio文件夹中,因为install文件夹应该在当前位置。

    helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -
    
    
  3. 验证所有 CRD 是否安装正确。

    kubectl get crds | grep 'istio.io\|certmanager.k8s.io' | wc -l
    
    
  4. install文件夹中有多个配置文件。查看install/kubernetes/helm/istio。我们将使用演示配置文件,它允许我们试验大多数 Istio 特性。

    helm template install/kubernetes/helm/istio --name istio --namespace istio-system \
        --values install/kubernetes/helm/istio/values-istio-demo.yaml | kubectl apply -f -
    
    

图 3-13 显示了安装输出。

img/483921_1_En_3_Fig13_HTML.png

图 3-13

使用 Helm 安装 Istio

不带头盔的演示装置

这种安装更快更容易,并允许使用大多数 istio 功能。这可以在不安装任何其他第三方软件的情况下完成。确保您在istio文件夹中。

  1. 所有 Istio 自定义资源定义都存在于 Istio init文件夹中。

    for i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl apply -f $i; done
    
    
  2. 我们允许 Istio 使用相互 TLS 和非 TLS 模式。istio文件夹内的Istio-demo.yaml允许此设置。

    kubectl apply -f install/kubernetes/istio-demo.yaml
    
    

参见图 3-14 了解此次安装和预期结果。

img/483921_1_En_3_Fig14_HTML.jpg

图 3-14

使用 demo.yaml 设置不带 Helm 的 Istio

GKE 装置

在 GKE 上安装 Istio 与其他两种安装方法类似,只是多了一个工具安装。假设 Kubernetes 已为 GKE 项目启用。请遵循以下步骤:

  1. 在本地机器上安装gcloud。该工具有助于管理 GKE 上的资源。安装步骤在 https://cloud.google.com/sdk/docs/#deb 的 Google cloud 文档中。

  2. 可以使用gcloud安装 Kubectl。

    kubectl get svc -n istio-system
    
    
  3. 设置启用 Kubernetes 和安装 Istio 的项目 ID 和区域。

    gcloud config set project [PROJECT_ID]
    gcloud config set compute/zone [COMPUTE_ENGINE_ZONE]
    
    
  4. 创建新的集群。

    gcloud container clusters create istio-installation --machine-type=n1-standard-2 --num-nodes=2
    
    
  5. 现在可以使用 Kubernetes 集群来安装 Istio。使用这两种方法中的任何一种来设置 Istio。

验证安装

安装后,让我们验证安装是否正确完成,这意味着所有服务都在运行,并且它们的 pod 是活动的。

  1. 在 Istio 名称空间中查找所有正在运行的服务。

    kubectl get svc -n istio-system
    
    

图 3-15 显示了正在运行的服务。

img/483921_1_En_3_Fig15_HTML.png

图 3-15

在 Istio 中运行服务

  1. 确保 Istio 系统中的所有单元都已启动并运行。

    kubectl get pods -n istio-system
    
    

图 3-16 显示了预期的输出。

img/483921_1_En_3_Fig16_HTML.jpg

图 3-16

Istio 的跑步 POD

现在我们有了一个 Istio 环境,可以部署应用了。让我们来看看服务。

服务研究所

大多数服务都很简单,并且可以与本章前面讨论的组件相关联。Citadel、Galley、Pilot、Policy 和 Telemetry 是其中的几个。演示安装提供以下附加服务:

  • Grafana :这将从不同服务收集的数据呈现在一个仪表板中,用于分析和监控。它是监视集群中发生的事情的一个非常好的工具。

  • Kiali :它跟踪服务网格中的服务、它们之间的连接方式、数据流以及它们各自的性能。这是一个很好的工具,可以用来检查微服务何时关闭或影响网格的整体性能。

  • Jaeger :它监控分布式系统中的事务并排除故障。它有助于优化性能和延迟。

  • Prometheus :这是一个流行的基于开源指标的系统监控和警报工具包。它有一个强大的数据模型和查询语言,允许分析应用和基础设施。

  • 跟踪和 Zipkin :这些工具在分布式系统中跟踪请求。

现在我们已经有了一个可以部署应用的 Istio 环境,但是在我们继续创建部署之前,让我们先来看几个重要的 Istio 命令和 CRD。

使用 Istio

在安装过程中,我们在 Kubernetes 集群上安装了许多自定义资源定义,我们将在后面的章节中使用这些定义来查看 Istio 的运行情况。让我们去拜访其中的几个,了解一下它们是什么。

  • Virtualservices :这定义了当一个服务调用另一个主机时使用的一组流量规则。这些规则定义了在对呼叫应用规则之前要匹配的标准。

  • DestinationRules :这在路由完成时起作用。它涵盖了负载均衡、连接池大小等基本配置。

  • ServiceEntries :这将额外的条目添加到 Istio 服务注册表中,以便自动发现的服务可以访问手动定义的条目。它配置服务的基本细节,如地址、协议、端口等。当存在服务网格外部的服务时,这是有帮助的。

  • 网关:这可以看作是一个负载均衡器,位于服务网格的入口处,监听特定端口的外部连接,然后在网格内部分配流量。

  • 使用这个工具,用户可以在 Pilot 已经生成的过滤器的基础上定义特定于 Envoy 代理的过滤器。换句话说,它可以在 Istio 无法自动更正错误的情况下修改网格流量;因此,需要小心使用。

  • Policies :这个工具执行一些规则,比如对服务的流量进行速率限制、报头重写、黑名单和白名单服务访问。

在安装步骤中还定义了许多其他 CRD,但这些是最常用和最常用的。我们将在本书后面的章节中用到它们。

使用 Istio CLI

我们现在已经使用 Kubectl 来部署服务,并设置了 Istio 来完成与服务网格相关的所有任务。Istio 自带 CLI,可以灵活配置 Istio 设置。调试应用的第一种方法是查看日志,但是进一步深入,istioctl允许我们调试和诊断网格中的每个部署。让我们来看一些有助于应用调试和设置的istioctl命令。

奥滕

这是一个命令行参数,用于与 Istio 身份验证策略进行交互。例如,让我们检查一个 Istio pods 上的tls-authentication设置。我们将在第十章中讨论更多关于认证的内容。

istioctl authn tls-check <pod-name>

注销

这是一个命令行参数,用于从注册到的服务中取消注册现有 IP 地址。当有人想强行从服务中删除 pod 时,这是必需的。

istioctl deregister <service-name> <ip-to-be-removed>

注册

这是将 pod 注册到服务的命令行参数。

istioctl register <service-name> <ip-to-be-added> <port>

实验的

这允许使用istioctl并生成可以修改或废弃的实验命令。它允许在四个领域进行实验。

实验授权

这允许与网格中的身份验证和授权策略进行交互。

istioctl experimental auth check <pod-name>

实验性转换-入口

这将尽最大努力将 Kubernetes 入口转换为VirtualService配置。结果是 Istio 配置的开始。少数情况下会生成转换可能失败的警告,这可能需要手动干预。考虑清单 3-1 中的入口示例。我们会尽量把它转换成VirtualService

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: gateway
spec:
  rules:
  - http:
      paths:
      - path: /
        backend:
          serviceName: frontendservice
          servicePort: 80

Listing 3-1Sample Ingress Config ingress-smaple.yaml

图 3-17 显示了将入口转换为 Istio VirtualService产生的输出。

img/483921_1_En_3_Fig17_HTML.jpg

图 3-17

将入口配置转换为 Istio 虚拟服务

实验仪表板 grafana

使用istioctl可以轻松查看 Grafana 仪表盘。

istioctl experimental dashboard grafana

这将为 Grafana 服务设置一个代理,并使其可以通过随机端口在 web 浏览器中访问。图 3-18 和图 3-19 显示了命令和仪表板。

img/483921_1_En_3_Fig19_HTML.jpg

图 3-19

Grafana 仪表板在随机端口上可见,此处为 53869

img/483921_1_En_3_Fig18_HTML.jpg

图 3-18

请求显示 Grafana 仪表板

在我们当前的设置中,类似的仪表盘也适用于 Envoy、Jaegar、Kiali、Promethus 和 Zipkin。

实验度量

这将打印 Kubernetes 中指定服务的指标。这取决于 Prometheus。当请求服务指标时,该命令向 Prometheus 发出一系列关于指标的请求,并打印它们。

istioctl experimental metrics <service-name>

kube 注入

该工具通过将 Envoy sidecar 注入任何现有的 Kubernetes 资源,将 Kubernetes 配置转换为 Istio 配置。对于不受支持的资源,配置保持不变。让我们从第一章中选择一个部署,并尝试将 Kubernetes 配置转换为 Istio 配置(参见清单 3-2 )。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-deployment
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: web-app:4.0
        imagePullPolicy: Never
        ports:
        - containerPort: 5000
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 40
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 120

Listing 3-2Sample Ingress Config ingress-smaple.yaml

清单 3-3 显示了转换后的配置。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: webapp
  name: webapp-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  strategy: {}
  template:
    metadata:
      annotations:
        sidecar.istio.io/status: '{"version":"761ebc5a63976754715f22fcf548f05270fb4b8db07324894aebdb31fa81d960","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
      creationTimestamp: null
      labels:
        app: webapp
    spec:
      containers:
      - image: web-app:4.0
        imagePullPolicy: Never
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 120
        name: webapp
        ports:
        - containerPort: 5000
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 40
        resources: {}
      - args:
        - proxy
        - sidecar
        - --domain
        - $(POD_NAMESPACE).svc.cluster.local
        - --configPath
        - /etc/istio/proxy
        - --binaryPath
        - /usr/local/bin/envoy
        - --serviceCluster
        - webapp.$(POD_NAMESPACE)
        - --drainDuration
        - 45s
        - --parentShutdownDuration
        - 1m0s
        - --discoveryAddress
        - istio-pilot.istio-system:15010
        - --zipkinAddress
        - zipkin.istio-system:9411
        - --dnsRefreshRate
        - 300s
        - --connectTimeout
        - 10s
        - --proxyAdminPort
        - "15000"
        - --concurrency
        - "2"
        - --controlPlaneAuthPolicy
        - NONE
        - --statusPort
        - "15020"
        - --applicationPorts
        - "5000"
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: INSTANCE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: ISTIO_META_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: ISTIO_META_CONFIG_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: ISTIO_META_INTERCEPTION_MODE
          value: REDIRECT
        - name: ISTIO_META_INCLUDE_INBOUND_PORTS
          value: "5000"
        - name: ISTIO_METAJSON_LABELS
          value: |
            {"app":"webapp"}
        image: docker.io/istio/proxyv2:1.2.2
        imagePullPolicy: IfNotPresent
        name: istio-proxy
        ports:
        - containerPort: 15090
          name: http-envoy-prom
          protocol: TCP
        readinessProbe:
          failureThreshold: 30
          httpGet:
            path: /healthz/ready
            port: 15020
          initialDelaySeconds: 1
          periodSeconds: 2
        resources:
          limits:
            cpu: "2"
            memory: 1Gi
          requests:
            cpu: 10m
            memory: 40Mi
        securityContext:
          readOnlyRootFilesystem: true
          runAsUser: 1337
        volumeMounts:
        - mountPath: /etc/istio/proxy
          name: istio-envoy
        - mountPath: /etc/certs/
          name: istio-certs
          readOnly: true
      initContainers:
      - args:
        - -p
        - "15001"
        - -u
        - "1337"
        - -m
        - REDIRECT
        - -i
        - '*'
        - -x
        - ""
        - -b
        - "5000"
        - -d
        - "15020"
        image: docker.io/istio/proxy_init:1.2.2
        imagePullPolicy: IfNotPresent
        name: istio-init
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 10m
            memory: 10Mi
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
          runAsNonRoot: false
          runAsUser: 0
      volumes:
      - emptyDir:
          medium: Memory
        name: istio-envoy
      - name: istio-certs
        secret:
          optional: true
          secretName: istio.default
status: {}
---


Listing 3-3Sidecar Injected into Kubenetes Deployment

代理配置引导程序|群集|端点|侦听器|路由

此工具检索关于引导、集群、端点、侦听器或 pod 中特使实例的路由特定配置的信息。

生效

这将在 Istio 配置应用于网格之前对其进行验证。让我们验证清单 3-3 中的输出。图 3-20 显示了从文件中生成的警告,因为我们还没有向部署添加版本。

img/483921_1_En_3_Fig20_HTML.jpg

图 3-20

验证生成的 Istio 配置

这些命令有助于在服务网格中创建、修改、注入和使用 Istio 配置。

摘要

在本章中,我们学习了 Istio 架构,您了解了控制层和数据层的去耦如何帮助组织配置和数据流。我们浏览了负责配置的控制面板组件 Mixer、Pilot、Citadel 和 Galley,并展示了它们如何组织数据流,以及如何将多个 pod 转换为分布式系统。我们还介绍了如何在本地机器和 GKE 上通过 Helm 图设置 Istio,而无需任何第三方软件。我们简要地浏览了 Istio 的几个重要的 CRD。我们还简要介绍了 Istio CLI 工具,它将在后面的章节中广泛使用。在下一章,我们将深入研究 Istio CRD,并提供一些如何创建 Istio 网格的例子。

四、 Istio 虚拟服务

在前一章中,我们详细讨论了 Istio 架构。我们使用控制面板来配置数据平面。这两个组件之间的解耦允许它们独立运行,从而提高了故障处理能力。该架构使集中式操作团队能够使用通用规则来配置基础架构。Istio 针对不同的目的制定了不同类型的规则。在本章中,我们将展示如何使用 Istio 的流量管理规则。具体来说,我们将采用现有的 Kubernetes 服务,并通过它路由流量。

请求路由

在本章中,我们将从第一章开始逐步开发我们的应用,所以让我们回顾一下到目前为止已经完成的工作。在第一章中,我们开发了一个多语言应用,并将其部署到 Kubernetes 集群中。该应用有一个基于 Java 的前端和一个基于 Python 的后端。这两个应用都部署在同一个 Kubernetes 名称空间中。webapp 是使用以下配置部署的:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-deployment
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: web-app:4.0
        imagePullPolicy: Never
        ports:
        - containerPort: 5000
-------
apiVersion: v1
kind: Service
metadata:
  name: webservice
spec:
  selector:
    app: webapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000

这个配置在 Kubernetes 集群中创建了一个 pod 和一个服务。类似的配置用于部署前端 Java 应用。这两个应用可以通过使用 DNS 名称相互引用,因为它们属于同一个名称空间。类似地,不同名称空间中的服务可以通过使用完全限定的服务名来相互引用。

到目前为止,我们总是用新版本更新我们的 Kubernetes 部署。因此,我们的应用只有一个版本。但是这是一个边缘用例,不符合常规的 Kubernetes 部署。实际上,一个 Kubernetes 集群会运行同一个应用的多个版本。这将有助于我们满足不同的用例,如应用部署、A/B 测试等等。但是一旦我们有了部署的应用的许多版本,我们就会遇到各种各样的请求处理问题。

在扩展我们的示例时,我们需要向应用响应添加一个版本标识符。这可以通过在响应头中添加版本信息或在响应中添加版本前缀来实现。让我们修改我们的 Python 应用,将其版本作为欢迎消息的前缀(因为这很容易识别)。

app = Flask(__name__)
@app.route("/")
def main():
    currentDT = datetime.datetime.now()
    return "[{}]Welcome user! current time is {} ".format(os.environ['VERSION'],str(currentDT))
## removed for Brevity

我们现在需要通过 Dockerfile 设置VERSION环境变量。

FROM python:3.7-alpine
COPY ./requirement.txt /app/requirement.txt
WORKDIR /app
RUN pip install -r requirement.txt
COPY . /app
ENTRYPOINT [ "python" ]
ARG ver=NA
ENV VERSION=$ver
CMD [ "app.py" ]

为了验证前面的行为,我们需要将这个应用的几个版本部署到我们的集群中。因此,首先使用以下命令行为不同版本构建一些 Docker 映像:

$docker build . -t web-app:6.0 --build-arg ver=6.0
$docker build . -t web-app:6.1 --build-arg ver=6.1
$docker build . -t web-app:6.2 --build-arg ver=6.2

现在将所有以前的版本部署到 Kubernetes 集群。这是通过使用前面讨论过的webapp-deployment命令和不同的 Docker 映像来完成的。而且,webapp Kubernetes 服务配置了选择器app: webapp。这将选择所有匹配这些属性的 pod,并将请求路由到其中一个。如果我们请求 webapp 服务,我们会得到来自该服务所有版本的响应。

图 4-1 显示了在 Kubernetes 上运行的 webapp 的多个版本。我们来做一个http://10.152.183.146/的查找,它是前端服务。

img/483921_1_En_4_Fig1_HTML.jpg

图 4-1

Kubernetes 部署服务

图 4-2 ,图 4-3 ,图 4-4 显示了响应。

img/483921_1_En_4_Fig4_HTML.jpg

图 4-4

来自 6.0 的响应

img/483921_1_En_4_Fig3_HTML.jpg

图 4-3

对 6.2 的回应

img/483921_1_En_4_Fig2_HTML.jpg

图 4-2

对 6.1 的回应

在本章的其余部分,我们将为所需的版本配置 Istio 请求路由规则。Istio 可以相当好地处理 TCP 和 HTTP 服务。Istio 还支持请求路由的 L4 和 L7 属性查找。

不可分割的做法

在我们能够配置 Istio 请求路由之前,我们需要确保我们的 Kubernetes 集群遵循下面列出的实践。如果有不满足这些要求的服务,那么对这些服务的调用将不受 Istio 控制。此类请求将由 Kubernetes 组件解决。

命名服务端口

Istio 路由需要在 Kubernetes 服务中以<protocol>[<-suffix>]格式定义的端口名称。以下是 Istio 支持的协议:

  • http

  • http2

  • https

  • grpc

  • mysql

  • mongo

  • redis

  • tcp

  • tls

  • udp

在我们的例子中,我们必须将 web 服务的端口命名为http-webapphttp。不同服务的端口可以有相同的名称,但是同一服务的不同端口不能有相同的名称。值得注意的是,这是 Istio 遵循的一个命名约定,并没有给 Kubernetes 规范增加任何额外的协议值。因此,让我们用以下配置更新我们的 web 服务:

apiVersion: v1
kind: Service
metadata:
  name: webservice
spec:
  selector:
    app: webapp
  ports:
  - name: http-webservice
    protocol: TCP
    port: 80
    targetPort: 5000

在前面的配置中,我们添加了一个值为http-webservicename属性。使用以下命令应用以前的配置:

$kubectl apply -f ../config/webservice.yaml

带有版本标签的窗格

Istio 将根据应用版本进行路由。要选择某个版本的节点,必须对它们进行相应的标记。因此,我们所有的部署和 pod 都必须应用应用和版本标签。Istio 还在度量和遥测数据收集中使用这些标签。现在让我们标记我们的 web 服务。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-deployment-6.2
  labels:
    app: webapp
    version: v6.2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
        version: v6.2
    spec:
      containers:

 # REMOVED FOR BREVITY

在前面的配置中,我们已经将version: v6.2添加到部署和模板中。Kubernetes 标签只支持字符串值;因此,我们的应用版本被定义为 v6.2。最后,使用以下命令应用之前的配置:

$kubectl apply -f ../config/webapp-deployent.yaml

声明的 Pod 端口

只有在部署模板中声明了 pod 公开的端口时,才能应用 Istio 路由。可以将端口声明为部署模板中containerPort字段的列表。根据 Kubernetes 文档,containerPort字段用于提供信息。容器可以运行监听 0.0.0.0 和端口的服务。集群中的所有容器都可以访问该端口。如果 Istio 路由适用于不属于部署模板的端口,则它将被绕过。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-deployment-6.2

# Removed FOR BREVITY
      containers:
      - name: webapp
        image: web-app:6.0
        imagePullPolicy: Never
        ports:
        - containerPort: 5000
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 40

在前面的配置中,我们将 5000 声明为容器公开的端口。使用以下命令应用以前的配置:

$kubectl apply -f ../config/webapp-deployent.yaml

到目前为止,我们已经了解了 Istio 路由的先决条件。在配置它之前,让我们先了解一下它是如何工作的。使用VirtualServiceDestinationRule组件在服务网格中配置请求路由。图 4-5 描述了各种相关组件之间的相互作用。

img/483921_1_En_4_Fig5_HTML.jpg

图 4-5

目的地分辨率

涉及以下交互,如图 4-5 所示:

  1. 服务 X 尝试使用完全限定的域名连接到服务 Y。

  2. 虚拟服务查找服务 Y FQDN 以确定它是否需要被处理。

  3. 如果是,那么匹配DestinationRule以确定最终的 Kubernetes 服务。

  4. 最后,呼叫被转发到所需的服务 y。

目的地规则

DestinationRule将请求目标位置解析为 Kubernetes 集群中的网络地址。在上一节中,您了解到 Istio 规定版本号是 pod 标签的一部分。然后,这些标签可以在DestinationRule中进行匹配,从而为请求处理定义基于版本的服务子集。现在让我们为 web 服务配置一些目的地规则。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: webapp-destination
spec:
  host: webservice
  subsets:
  - name: v1
    labels:
      version: v6.2

前面定义的规则配置了一个简单的目标规则 v1。host: webservice用于选择配置了 web 服务 Kubernetes 服务的 pod。然后,它从这些节点集中选择匹配version: v6.2标签的节点来定义子集 v1。我们可以通过以下方式创建规则:

$kubectl create -f ../config/webapp-destinationrules.yaml

之后,使用以下命令验证创建的目的地规则,如图 4-6 所示:

img/483921_1_En_4_Fig6_HTML.jpg

图 4-6

目的地规则

$istioctl get destinationrules:

在前面的配置中,子集部分可用于定义多个命名子集。每个子集都可以配置不同的VirtualService组件。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: webapp-destination
spec:
  host: webservice
  subsets:
  - name: v1
    labels:
      version: v6.2
  - name: v0
    labels:
      version: v6.0

使用以下命令更新规则,然后使用istioctl进行验证(参见图 4-7 ):

img/483921_1_En_4_Fig7_HTML.png

图 4-7

多个目的地规则

$kubectl apply -f ../config/webapp-destinationrules.yaml

DestinationRule组件应该由服务所有者来配置。服务所有者不仅可以创建不同的子集,还可以为每个子集定义connectionPool、负载均衡和异常值检测属性。这些设置决定了使用者服务如何连接到各个节点。

连接池

不用说,共享连接有好处。在我们启用了 TLS 握手的服务网格中,每个新连接的成本相对较高。传统上,我们在消费者应用中添加了各种连接池驱动程序。但是 Istio 为连接池提供了开箱即用的支持。connectionPool配置决定了消费者服务如何连接到提供者服务。这些设置必须由服务所有者进行微调。连接池设置适用于消费者服务的每个主机。

Istio 连接池支持keepAlive TCP 方法。因此,我们不仅可以使用池,还可以重用未使用的连接。这些设置有一组单独的属性来配置 HTTP 和 TCP 连接池。这些属性使我们能够微调 HTTP 连接重用。以下是最重要的属性。我们不会涵盖所有的属性;请参考 Istio 文档了解更多信息。

  • maxConnections:该设置定义了服务连接数的上限。默认值设置为 1024。此设置适用于 TCP 和 HTTPv1.0 服务。

  • connectionTimeout:该设置定义了 TCP 连接超时。

  • Http2MaxRequets:该设置适用于 HTTPv2.0,在 HTTP 2.0 中我们建立一个连接,并在多个请求中重复使用。这些设置定义了可以通过连接执行的请求数量的上限。

  • Http1MaxPendingRequests:该设置定义了通过连接挂起的 HTTP 请求数量的上限。这也适用于 HTTPv2.0/GRPC 服务。

我们可以为每个定义的子集配置connectionPool属性。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: webapp-destination
spec:
  host: webservice
  subsets:
  - name: v0
    labels:
      version: v6.0
    trafficPolicy:
      connectionPool:
        tcp:
          maxConnections: 100
          connectTimeout: 30ms
          tcpKeepalive:
            time: 7200s
            interval: 75s

在前面的代码中,我们已经为 6.0 版本的服务配置了connectionPool。这些设置配置池化资源的最大数量以及连接和保持活动超时。

注意

值得注意的是,连接池是由特使代理监控的。如果违反了配置的限制,Envoy 将启动以下断路器:

  • upstream_cx_overflow:当集群中的一个服务超过最大连接数时,抛出这个断路器。这通常适用于 TCP 和 HTTP/1 服务。由于 HTTP/2 重用相同的连接,因此该限制不适用于它。

  • upstream_rq_pending_overflow:当集群中的服务发出的 HTTP 请求超过配置的限制时,抛出此断路器。这通常适用于 HTTP/2。

  • upstream_rq_retry_overflow:当集群中的服务发出的 HTTP 请求超过配置的限制时,抛出此断路器。

负载均衡

负载均衡是在选定目的地的不同主机之间分配请求的过程。有各种机制可以实现这一点。可以通过loadBalancer设置进行配置。Istio 支持以下类型的负载均衡器:

  • 循环:随机选择一个主机。如果没有为选定的 pod 启用运行状况检查,这样会更好。

  • 最小连接:该方法执行 O(1)查找来确定两个健康的主机。它会选择连接数量最少的一个。

  • 随机:随机选择一个主机。如果没有为选定的 pod 启用运行状况检查,它的性能会更好。

  • 一致散列:该方法基于请求头或 cookies 配置散列。

因为我们正在运行 web 服务的单个实例,所以我们不会为它配置负载均衡。

离群点检测

异常值检测是确定负载均衡集群中不健康主机的过程。该过程之后是从负载均衡集中删除主机。在 Istio 中,Envoy 断路器用于跟踪由目的主机引起的错误。这些错误可能是由服务或相应的边车引起的。在这两种情况下,主机都将被标记为不健康。

Istio 只记录服务抛出的连续错误。默认值设置为五个连续错误。对于基于 TCP 的服务,连接超时被视为错误。而基于 HTTP 的服务,5xx HTTP 响应也被记录为错误。在记录这些错误时,缺省情况下,Istio 从负载均衡集中驱逐一个服务 30 秒。经过该时间间隔后,主机将回到负载均衡集中,并以 10 秒的间隔重新评估(默认情况下)。可以通过配置各种可用的属性来改变这些计时。

总之,我们已经在DestinationRule中配置了子集。子集通过匹配配置的选择器来选择节点。Istio 然后对它们应用connectionPool、负载均衡和异常值检测设置。这些设置可以在DestinationRule级别进行配置。然后,这些设置将应用于在它下面创建的每个子集。但是如果在子集级别有任何配置,那么它将覆盖DestinationRule级别的配置。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: webapp-destination
spec:
  host: webservice l
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 30ms
  subsets:
  - name: v1
    labels:
      version: v6.2
  - name: v0
    labels:
      version: v6.0

在前面的代码中,我们已经为在webapp-destination DestinationRule组件中定义的所有子集(v0 和 v1)配置了connectionPool。这些设置配置池化资源的最大数量和连接超时。如前所述,DestinationRule组件只有在virtualService向其发送请求时才有效。在下一节中,我们将介绍如何定义一个virtualService并使用它的不同配置。

虚拟服务

Istio VirtualService组件的行为类似于 Kubernetes 中的Service组件。基本上,VirtualService是一种抽象,它将请求映射到服务网格中定义的服务。该服务可以是 Kubernetes 服务,也可以是 Istio 定义的服务。使用DestinationRule执行VirtualService组件的目的地解析。一个VirtualService组件可以执行目的地解析来处理以下用例:

  • 服务的单一版本

  • 基于 HTTP 头的查找以选择服务

  • 一组选定服务版本之间的加权比较

VirtualService抽象将请求路由与应用部署分离开来。因此,在一个集群中,我们可以部署同一服务的许多版本,并以精细控制的方式在它们之间分配负载。

促进

在前面的例子中,我们定义了 v0 和 v1 子集。以下配置将所有请求发送到 v1 子集:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-vs
spec:
  hosts:
    - webservice
  http:
  - route:
    - destination:
        host: webservice
        subset: v1

前面的配置是VirtualService最简单的形式之一。它将所有来自虚拟服务的请求只路由到目标服务的特定版本。见图 4-8 。

img/483921_1_En_4_Fig8_HTML.jpg

图 4-8

单个服务

使用以下属性配置VirtualService行为:

  • hosts属性定义了请求必须匹配的主机名或主机和端口的列表。主机地址可以是使用 DNS 解析成 FQDN 的服务名(在我们的例子中是 Kubernetes DNS)。它也可以是主机 IP 和端口。

  • 请求匹配后,再转发给目的主机的subset v1。

我们可以使用下面的代码创建如图 4-8 所示的虚拟服务:

$kubectl create -f ../config/webapp-simple-vs.yaml

之后,通过使用以下命令验证创建的虚拟服务(参见图 4-9 ):

img/483921_1_En_4_Fig9_HTML.jpg

图 4-9

v1 虚拟服务

$istioctl get virtualservices

前面的配置将所有请求发送到 web 服务的 v6.2。让我们通过多次加载前端 web 服务(http://10.152.183.146/)来验证这一点。我们可以看到我们所有的响应都来自于 webapp 的 6.2 版本(见图 4-10 )。

img/483921_1_En_4_Fig10_HTML.jpg

图 4-10

请求路由到 6.2 版本

重写

在前面的示例中,我们创建了请求转发,但是虚拟服务也能够执行请求重写。此行为使用以下属性进行配置:

  • match属性定义了哪些请求将执行重写。匹配可以基于 URI、HTTP 报头、查询参数、HTTP 方法、方案等。为了执行重写,我们必须指定 URI 以及其他选择器(如果需要的话)。

  • rewrite属性定义了请求需要发送到的新 URI 补丁。根据匹配的类型,重写将只替换匹配的 URI 部分。这意味着如果我们匹配 URI 前缀,那么重写只会改变前缀。如果完整的 URI 匹配,那么重写将改变完整的 URI。

  • subset属性定义了被重写的请求被转发到的目的主机。

以下配置匹配/hello请求,并将其发送到我们的 web 服务版本 6.2 的/路径:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-rewrite-vs
spec:
  hosts:
    - webservice
  http:
  - match:
    - uri:
        prefix: /hello
    rewrite:
      uri: /
    route:
    - destination:
        host: webservice
        subset: v1

我们可以通过以下方式创建虚拟服务:

$kubectl create -f ../config/webapp-rewrite-vs.yaml

现在有两个虚拟服务在处理webservice主机。这将会产生问题,所以让我们首先使用以下命令删除之前创建的虚拟服务:

$kubectl delete -f ../config/webapp-simple-vs.yaml

但是我们如何验证我们的改变呢?我们每次都可以更换我们的前端容器。或者,我们可以使用 Kubernetes 提供的exec命令。exec命令允许我们在一个容器中执行命令。因此,我们可以执行wget命令来验证请求路由。

$kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -- wget -O - http://webservice/hello
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
[6.0]Welcome user! current time is 2019-07-21 07:15:59.395367 Connecting to webservice (10.152.183.230:80)
-               100% |********************************|  62  0:00:00 ETA

HTTP 属性查找

Istio 能够执行 HTTP 属性查找。如前一节所述,match属性支持 URIs、HTTP 头、查询参数、HTTP 方法、方案等。我们可以匹配前面提到的任何属性,并将请求转发给匹配的主机。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-httplookup-vs
spec:
  hosts:
    - webservice
  http:
  - match:
    - headers:
        x-upgrade:
          exact: "TRUE"
    route:
    - destination:
        host: webservice
        subset: v1
  - route :
    - destination:
        host: webservice
        subset: v0

在前面的配置中,我们已经配置了match属性来寻找一个x-upgrade头。如果报头可用,那么它被转发到服务的较新版本。我们还添加了一个默认路由,所有不匹配的请求都会转发到该路由。

注意

Istio 配置为match属性的所有配置部分获取一个字符串。字符串喜欢trueTRUE、25 等。被转换为适当的数据类型,因此不能直接传递。这些值可以通过用双引号括起来转换成字符串,就像我们在前面的配置中所做的那样。

让我们应用配置。

$kubectl create -f code/config/webservice-httplookup-vs.yaml

之后,通过使用以下内容验证创建的虚拟服务(参见图 4-11 ):

img/483921_1_En_4_Fig11_HTML.jpg

图 4-11

基于 HTTP 的虚拟服务

$istioctl get virtualservices

让我们确保没有任何之前创建的虚拟服务。

让我们首先在不设置标题的情况下发出一个请求。

$kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -- wget -O - http://webservice/
Defaulting container name to frontend.
Use 'kubectl describe pod/ frontend-deployment-c9c975b4-p8z2 -n default' to see all of the containers in this pod.
[6.0]Welcome user! current time is 2019-07-21 11:19:35.349452 Connecting to webservice (10.152.183.230:80)

现在在wget命令行中传递适当的头。

$kubectl exec frontend-deployment-c9c975b4-p8z2t -- wget -O - --header='x-upgrade: TRUE' http://webservice/
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
[6.2]Welcome user! current time is 2019-07-21 11:19:28.565401 Connecting to webservice (10.152.183.230:80)

加权分布

Istio 能够按照配置的比例在不同版本的服务中分发请求。该比率由目的地的weight属性决定。见图 4-12 。

img/483921_1_En_4_Fig12_HTML.jpg

图 4-12

重量分布服务

以下配置在 v0 和 v1 子集之间分配流量。对于每四个请求,我们希望将其中三个发送到旧版本,一个发送到新版本。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-wtdist-vs
spec:
  hosts:
    - webservice
  http:
  - route:
    - destination:
        host: webservice
        subset: v1
      weight: 25
    - destination:
        host: webservice
        subset: v0
      weight: 75

使用以下内容应用配置:

$kubectl create -f code/config/webservice-wtdist-vs.yaml

之后,使用以下命令验证创建的虚拟服务:

$istioctl get virtualservices

让我们确保没有任何之前创建的虚拟服务。参见图 4-13 。

img/483921_1_En_4_Fig13_HTML.jpg

图 4-13

加权分布式虚拟服务

现在做几次wget请求。我们可以看到,每四个请求中就有一个被路由到 v6.2,而剩下的请求由 v6.0 提供服务。

     $kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
frontend-deployment-c9c975b4-p8z2t:/#  wget -qO - http://webservice
[6.0]Welcome user! current time is 2019-07-22 17:52:24.164478 frontend-deployment-c9c975b4-p8z2t:/#
frontend-deployment-c9c975b4-p8z2t:/#  wget -qO - http://webservice
[6.0]Welcome user! current time is 2019-07-22 17:52:28.977615 frontend-deployment-c9c975b4-p8z2t:/#
frontend-deployment-c9c975b4-p8z2t:/#  wget -qO - http://webservice
[6.0]Welcome user! current time is 2019-07-22 17:52:33.068721 frontend-deployment-c9c975b4-p8z2t:/#
frontend-deployment-c9c975b4-p8z2t:/#  wget -qO - http://webservice
[6.2]Welcome user! current time is 2019-07-22 17:52:41.291074 frontend-deployment-c9c975b4-p8z2t:/#
frontend-deployment-c9c975b4-p8z2t:/#

在这个例子中,我们有一个交互式会话,不像前面的例子,我们在容器中执行命令。交互式会话允许我们一个接一个地执行多个命令。

注意

到目前为止,我们已经将目的地配置为 Kubernetes 服务和基于版本的子集。但是我们也可以在不同的 Kubernetes 服务之间分配请求,而不需要子集定义。在我们的例子中,host属性可以引用 Kubernetes 服务prod.webservicetest.webservice

金丝雀释放

canary 发布是向一部分用户发布软件的过程。该流程允许开发人员在向整个用户群推广新版本之前,先向一部分用户验证新版本。如果在新版本中发现问题,可以回滚到较小的一组服务器。这有助于减轻影响并提高服务正常运行时间。Kubernetes 还通过管理应用的实例/复制计数来支持 canary 测试。但是这个管理 pod 实例的过程很快变得复杂并且难以支持。另一方面,Istio 对选择请求有丰富的支持,因此可以很容易地完成这项工作。

金丝雀发布是对第一章中讨论的蓝绿色部署的补充。作为一般过程,采取以下步骤:

  1. 使用蓝绿色部署流程,我们在少量容器上部署新版本。

  2. 当服务被标记为健康时,我们不是路由所有请求,而是从一定百分比的请求开始。

  3. 我们继续测试新版本,直到我们对结果满意为止。

  4. 最后,这一变化被部署在为所有用户服务的整个车队上。

在前面的小节中,我们讨论了简单的请求匹配。match方法验证一个简单的属性并请求转发。但是对于 canary release 这样相当高级的用例来说,这还不够好。匹配需要处理用 AND/OR 等操作连接在一起的多个子句。Istio match属性以下列方式支持这两种操作:

  • AND 操作通过在单个match属性下嵌套多个条件来执行。

  • OR 运算是通过在单个match属性下设置不同的条件来执行的。

注意

在 YAML 语法中,列表的值是通过在每个值前加一个连字符(-)来创建的。这意味着带有前缀连字符的条件是不同的值。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-and-or-vs
spec:
  hosts:
    - webservice
  http:
  - match:
    - headers:
        x-upgrade:
          exact: "TRUE"
    - queryParams:
        ver:
          exact: v1
       method:
         exact: GET
    route:
    - destination:
        host: webservice
        subset: v1
  - route :
    - destination:
        host: webservice
        subset: v0

之前的配置会尝试匹配以下两个条件中的任何一个(或操作):

  • HTTP 头具有正确的值x-upgrade

  • queryStringver=v1 HTTP 方法是 GET。

部署并验证之前的配置。

$kubectl exec frontend-deployment-c9c975b4-p8z2t -- wget -O - --header='x-upgrade: TRUE' http://webservice/
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
[6.2]Welcome user! current time is 2019-07-21 18:09:25.296747 Connecting to webservice (10.152.183.230:80)

$kubectl exec frontend-deployment-c9c975b4-p8z2t -- wget -O – http://webservice/?ver=v1
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
[6.2]Welcome user! current time is 2019-07-21 18:10:03.728678 Connecting to webservice (10.152.183.230:80)

因为我们正在制定新的多重规则,所以了解 Istio 如何评估它们是很重要的。Istio 根据规则的声明顺序评估所有匹配规则。首先评估第一个声明的匹配条件。如果条件失败,Istio 将评估下一个条件。在以下配置中,我们在第一个位置添加了默认路由:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-httplookup-vs
spec:
  hosts:
    - webservice
  http:
  - route :
    - destination:
        host: webservice
        subset: v0
  - match:
    - headers:
        x-upgrade:
          exact: "TRUE"
    route:
    - destination:
        host: webservice
        subset: v1

让我们部署并验证之前的配置。

$ kubectl exec frontend-deployment-c9c975b4-p8z2t -- wget -O - --header='x-upgrade: TRUE' http://webservice/
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
[6.0]Welcome user! current time is 2019-07-21 18:19:55.581391 Connecting to webservice (10.152.183.230:80)

这意味着服务所有者必须始终确保首先声明最具体的规则。所有匹配规则都应该从最具体的到一般的进行声明。

生产就绪的 Istio 配置将具有匹配条件、规则优先级和请求的加权分布。因此,对于我们的示例 web 服务,以下配置包括所有这些方面:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: webservice-canary-vs
spec:
  hosts:
    - webservice
  http:
  - match:
    - headers:
        host:
          exact: "user1.com"
    route:
    - destination:
        host: webservice
        subset: v1
      weight: 10
    - destination:
        host: webservice
        subset: v0
      weight: 90
  - route :
    - destination:
        host: webservice
        subset: v0

按照之前的配置,除了来自user1.com的请求之外,所有请求都由 v0 服务提供服务。此外,来自user1.com的 10%的请求由 v1 web 服务提供服务;所有剩余的都被路由到 v0 版本。

摘要

在本章中,我们使用了VirtualServiceDestinationRule来执行请求路由。我们通过将多个版本的 webapp 部署到 Kubernetes 集群来开始讨论。接下来,我们确保我们遵循了 Istio 为端口和 pod 规定的命名约定。之后,我们使用目的地规则定义了版本子集。当通过虚拟服务连接时,评估定义的目的地规则。因此,我们为单一服务路由、HTTP 属性路由和加权路由构建了不同的虚拟服务配置。最后,我们看了金丝雀部署。我们发现了该流程如何帮助减少应用停机时间并提高应用稳定性。我们使用多个匹配条件、目的地优先级和加权请求分布构建了 canary 部署示例。本章的重点是服务请求路由。在下一章中,我们将研究如何配置IngressEgressServiceEntry组件,以便与 Kubernetes 集群之外的世界进行交互。

五、Istio 网关

在前一章中,我们讨论了 Istio 请求路由。我们展示了如何配置虚拟服务和部署规则,以便与应用的不同版本进行交互。到目前为止,我们所有的服务都部署在 Kubernetes 集群中。但是在现实场景中,我们需要与 Kubernetes 集群之外的组件进行交互。有许多适用的用例。例如,在集群内部运行的应用可能会与部署在集群外部的数据库进行交互。类似地,可以在集群中部署用户应用。这些应用需要通过互联网访问。在本章中,我们将展示如何配置 Istio 网关和服务入口,这将使我们能够实现所讨论的行为。

进入

术语入口被定义为入口外观。它是为所有外部发起的请求提供服务访问的位置。使用 Istio 网关配置入口。它是一个边缘组件,用于在集群外部公开服务。它可以用来公开 HTTP 以及 TCP 服务。网关提供 TLS 终止和请求转发等功能。

在大多数生产集群中,网关与 Kubernetes 负载均衡器服务一起配置。在这种情况下,Kubernetes 服务会创建一个基于云的 L4 负载均衡器。负载均衡器有一个公共 IP 地址,Kubernetes 集群外部的世界可以访问该地址。当负载均衡器收到请求时,它会将请求委派给匹配的 Istio 网关。然后,网关使用 Istio 流量路由,并将请求分派给适当的服务版本。Istio 还将必要的遥测技术和安全性应用于网关。见图 5-1 。

Istio 网关可以与 Kubernetes 入口资源相比较,但与入口资源不同,该网关没有配置任何流量路由规则。网关将所有入站流量委托给虚拟服务,并应用相关的路由配置。总之,网关仅在 L4、L5 和 L6 工作。

img/483921_1_En_5_Fig1_HTML.jpg

图 5-1

Istio 入口

现在让我们扩展第四章中的 web 服务例子。之前,在第一章中,我们开发了一个多语言应用,并将其部署到 Kubernetes 集群中。该应用有一个基于 Java 的前端和一个基于 Python 的后端。这两个应用都部署在同一个 Kubernetes 名称空间中。在第四章中,我们扩展了 web 服务应用。我们在 Kubernetes 集群上部署了两个版本的 web 服务。最后,我们使用以下虚拟服务和相关的distinationRules将请求路由到两个版本:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: webservice-wtdist-vs
spec:
 hosts:
 - webservice
 http:
 - route:
 - destination:
 host: webservice
 subset: v1
 weight: 25
 - destination:
 host: webservice
 subset: v0
 weight: 75

前面的配置将四个请求中的一个发送到 v1 版本,其余三个发送到 v0 版本。但这仅适用于 Istio 网格内部的服务。如果我们想从外部世界发送请求,我们需要创建下面的ingress网关:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: webapp-gateway
spec:
 selector:
 istio: ingressgateway
 servers:
 - port:
 number: 80
 name: http
 protocol: HTTP
 hosts:
              - "*.greetings.com"

之前的网关配置了一个负载均衡器,以允许外部 HTTP 流量*.greetings.com进入网状网络。接下来,我们必须配置相关的虚拟服务来处理来自已配置网关的请求。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: webservice-wtdist-vs
spec:
 hosts:
 - webservice
 - webservice.greetings.com
 gateways :
 - webapp-gateway
 http:

##REST REMOVED FOR BREVITY

在前面的配置中,我们修改了虚拟服务webservice-wtdist-vs来处理webapp-gateway。这是通过将网关名称添加到gateways字段来完成的。此外,虚拟服务必须与其配置的主机网关相匹配。它可以是网关支持的通配符的精确匹配或子集。因此,我们将webservice.greetings.com添加到了主机列表中。

现在我们需要测试配置。为此,我们需要负载均衡器的地址。执行以下命令确定地址(参见图 5-2 ):

img/483921_1_En_5_Fig2_HTML.jpg

图 5-2

入口网关地址

$kubectl get svc istio-ingressgateway -n istio-system

EXTERNAL-IP值显示负载均衡器的 IP 地址。这适用于基于云的环境,比如 AWS、Azure 等等。在我们的例子中,因为我们已经将应用部署到 Minikube,所以EXTERNAL-IP列显示为<PENDING>。在这种情况下,我们可以跳过负载均衡器,使用istio-ingressgatewaynodePort地址。在我们的例子中,节点 IP 地址是 Minikube 服务器的地址,端口是 31380。我们可以使用以下方法确定 IP 地址:

$ minikube ip
192.168.1.27

因为我们已经向外界公开了服务,所以我们可以通过在集群外部的主机上执行curl命令来验证它。由于我们不拥有greetings.com,我们将无法处理它的任何子域。但是这不是必需的。网关检查Host报头字段。我们可以使用适当的curl选项来设置标题字段。

$curl -v -HHost:webservice.greetings.com http://192.168.1.27:31380/
* Trying 192.168.1.27...
* TCP_NODELAY set
* Connected to 192.168.1.27 port 31380 (#0)
> GET / HTTP/1.1
> Host:webservice.greetings.com
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/html; charset=utf-8
< content-length: 62
< server: istio-envoy
< date: Sun, 04 Aug 2019 08:04:22 GMT
< x-envoy-upstream-service-time: 8
<
* Connection #0 to host 192.168.1.27 left intact
[6.0]Welcome user! current time is 2019-08-04 08:04:22.383137

在前面的输出中,我们可以看到网关匹配头字段并适当地路由请求。我们可以尝试执行几次curl。我们可以看到,每四个请求中就有一个是由新版本的 web 服务提供的。

[6.0]Welcome user! current time is 2019-08-04 14:18:13.330905
[6.0]Welcome user! current time is 2019-08-04 14:18:13.359514
[6.0]Welcome user! current time is 2019-08-04 14:18:13.381638
[6.2]Welcome user! current time is 2019-08-04 14:18:13.402238

在前一章中,我们为网格中的边车代理配置了一个虚拟服务。但是一旦使用网关配置了虚拟服务,虚拟服务就会从每个 Istio 服务代理中删除。我们可以通过使用第四章中的测试来验证这一点,如下所示:

$kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
Defaulting container name to frontend.
Use 'kubectl describe pod/frontend-deployment-c9c975b4-p8z2t -n default' to see all of the containers in this pod.
frontend-deployment-c9c975b4-p8z2t:/# wget -qO - http://webservice/
[6.2]Welcome user! current time is 2019-08-04 16:55:03.230895
frontend-deployment-c9c975b4-p8z2t:/# wget -qO - http://webservice/
[6.0]Welcome user! current time is 2019-08-04 16:55:07.876481
frontend-deployment-c9c975b4-p8z2t:/# wget -qO - http://webservice/
[6.0]Welcome user! current time is 2019-08-04 16:55:12.130989
frontend-deployment-c9c975b4-p8z2t:/# wget -qO - http://webservice/
[6.2]Welcome user! current time is 2019-08-04 16:55:14.911224

在前面的输出中,我们从前端 pod 执行了wget命令。我们可以看到,在 web 服务的两个版本中,请求是以循环方式处理的。如果我们想要将相同的路由应用到网格中的所有服务代理,我们需要将关键字mesh添加到gateways字段中。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: webservice-wtdist-vs
spec:
 hosts:
 - webservice
 - webservice.example.com
 gateways :
 - webapp-gateway
 - mesh
 http:
# REMOVED for BREVITY

我们可以使用之前执行的命令来验证之前的配置。我们的请求应该按照配置的权重来处理。

mesh是省略gateways属性时的默认行为。

安全套接字层

Istio 网关为 SSL 交换提供了完整的支持。我们可以在网关中设置 SSL 证书交换。或者,网关可以充当传递介质。这样,SSL 终止可以由运行在 Kubernetes 集群中的 HAProxy 或 Nginx 来处理。

在启用 SSL 之前,我们需要一个证书和一个私钥。在本节中,我们将使用自签名证书。可以使用openssl生成自签名证书。我们将介绍几个基本步骤,但是认证生成超出了本书的范围。如果您已经有证书,可以继续下一部分。

我们将使用下面的openssl命令生成一个自签名证书。接下来会出现提示,询问更多的细节。

$openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
Generating a RSA private key
.................................................++++
...........................................................++++
writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:packt
Organizational Unit Name (eg, section) []:istio-book
Common Name (e.g. server FQDN or YOUR name) []:*.greetings.com
Email Address []:email@greetings .com

让我们确保通用名称有一个*.greetings.com通配符,这将允许我们为不同的服务使用证书。还有一点需要注意的是,Kubernetes 将无法读取生成的密钥,因为它受密码保护。我们可以使用以下命令删除密码:

$ openssl rsa -in key.pem -out key2.pem
Enter pass phrase for key.pem:
writing RSA key

现在我们有了所需的cert.pemkey2.pem文件。我们可以使用它们以不同的方式配置 SSL。

配置 istio-Ingres 网关-证书

Kubernetes 为秘密管理提供了很好的支持。我们可以在集群中创建一个命名的秘密。然后可以用.spec.volumes[].secret.secretName属性配置一个 pod。Kubernetes 将在 pod 的指定文件位置挂载命名的机密。

apiVersion: v1
kind: Pod
metadata:
 name: mypod
spec:
 containers:
 - name: mypod
 image: redis
 volumeMounts:
 - name: foo
 mountPath: "/etc/foo"
 readOnly: true
 volumes:
 - name: foo
 secret:
 secretName: mysecret

在我们的例子中,我们不打算配置istio-ingressgateway。或者,Istio 网关已经配置了名为secretistio-ingressgateway-certs。因此,我们所需要的就是用这个名字创建一个 Kubernetes 秘密。

$ kubectl create -n istio-system secret tls istio-ingressgateway-certs --key key2.pem --cert cert.pem
secret "istio-ingressgateway-certs" created

我们可以使用下面的方法来验证这个秘密(见图 5-3 ):

img/483921_1_En_5_Fig3_HTML.jpg

图 5-3

秘密研究所

$kubectl describe secret istio-ingressgateway-certs -n istio-system

已经为/etc/istio/ingressgateway-certs文件路径配置了istio-ingressgateway-certs机密。这意味着 Kubernetes 在前面指定的路径中挂载密钥和证书文件。现在,我们可以按以下方式配置证书:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: webapp-gateway
spec:
 selector:
 istio: ingressgateway
 servers:
 - port:
 number: 443
 name: https
 protocol: HTTPS
 tls:
 mode: SIMPLE
 serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
 privateKey: /etc/istio/ingressgateway-certs/tls.key
 hosts:
 - "*.greetings.com"

在前面的配置中,我们在端口 443 上配置了 HTTPS 协议。我们还提供了带有密钥的证书。密钥和证书分别被命名为tls.keytls.crt。我们已经将tls.mode启用为SIMPLE。这是标准的 SSL 配置,其中网关不验证客户端的身份。这是所有需要的;我们现在可以使用curl来验证网关。

$for i in 1 2 3 4; do curl -HHost:webservice.greetings.com --resolve webservice.greetings.com:31390:127.0.0.1 -k https://webservice.greetings.com:31390/; echo "; done
[6.0]Welcome user! current time is 2019-08-10 18:16:05.814646
[6.0]Welcome user! current time is 2019-08-10 18:16:05.843160
[6.0]Welcome user! current time is 2019-08-10 18:16:05.872700
[6.2]Welcome user! current time is 2019-08-10 18:16:05.901381

在前面的curl命令中,除了-HHost头,我们还使用了几个选项。

  • --resolve webservice.greetings.com:31390:127.0.0.1:这将把webservice.greetings.com:31390:设置为localhost,因为我们正在使用NodePort

  • -k:由于我们已经添加了自签名证书,除非我们启用不安全访问,否则curl将会失败。

配置 istio-Ingres 网关-ca-证书

到目前为止,我们已经配置了服务器端 TLS。这对最终用户应用来说很好。但是通常需要相互 TLS 认证。可以使用客户端 SSL 证书为相互 TLS 配置 Istio 网关。在这里,证书链也可以加载到名为 Kubernetes secretistio-ingressgateway-ca-certs中。

$ kubectl create -n istio-system secret tls istio-ingressgateway-ca-certs --cert cert.pem
secret "istio-ingressgateway-ca-certs" created

已经为/etc/istio/ingressgateway-ca-certs文件路径配置了istio-ingressgateway-ca-certs机密。现在,我们可以按以下方式配置客户端的证书链:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: webapp-gateway

 # REMOVED FOR BREVITY

 tls:
 mode: MUTUAL
 serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
 privateKey: /etc/istio/ingressgateway-certs/tls.key
 caCertificates: /etc/istio/ingressgateway-ca-certs/ca-chain.cert.pem
 hosts:
          - "*.greetings.com"

在之前的配置中,我们启用了MUTUAL TLS 认证。客户端证书链被命名为ca-chain.cert.pem。我们现在可以使用curl来验证网关。我们将分别通过使用cacertskey选项来传递客户端证书和密钥。

$for i in 1 2 3 4; do curl -HHost:webservice.greetings.com --resolve webservice.greetings.com:31390:127.0.0.1 --cacerts client.certs.pem --key client.key.pem https://webservice.greetings.com:31390/; echo "; done
[6.0]Welcome user! current time is 2019-08-10 19:16:05.814646
[6.0]Welcome user! current time is 2019-08-10 19:16:05.843160
[6.0]Welcome user! current time is 2019-08-10 19:16:05.872700
[6.2]Welcome user! current time is 2019-08-10 19:16:05.901381

到目前为止,我们已经在网关中配置了 TLS 终端。但是网关也有一个PASSTHROUGH模式,在该模式下它不执行任何终止。终止的责任被委托给虚拟服务。我们将让您尝试用 Nginx 或 HAProxy 配置 TLS 终端。

外部服务访问

到目前为止,我们已经向外部世界公开了我们的服务。但是,如何使用运行在集群之外的服务呢?在 Istio 网格内部运行的服务可以访问集群外部的服务。默认的 Istio 配置不对外部服务访问施加任何限制。这看起来像是一个简单的默认设置,但它可能不是我们想要的。企业通常需要监控和控制组织外部的流量。以前,我们看到所有流量都流经 sidecar 代理。因此,根据网格的配置方式,可以完成以下任务:

  • 允许/拒绝所有外部访问

  • 允许访问有限的服务

  • 控制权限以允许访问

默认情况下,Istio 配置为ALLOW_ANY模式。对于网格未知的所有服务,该配置将绕过代理。在这种模式下,请求不会被发送到边车。相反,它们由应用 pod 网络直接处理。见图 5-4 。

img/483921_1_En_5_Fig4_HTML.png

图 5-4

边车旁路

我们可以使用以下命令来验证 Istio 模式:

$kubectl get configmap istio -n istio-system -o yaml |grep -o "mode: .*"
mode: ALLOW_ANY\n\nlocalityLbSetting:\n {}\n \n\n# The namespace to treat

我们现在可以尝试从我们的一个网状节点访问维基百科。我们可以执行wget命令来确定行为。

$ kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
frontend-deployment-c9c975b4-p8z2t:/# wget -qSO - http://en.wikipedia.org/ >/dev/null
 HTTP/1.1 301 Moved Permanently
 Date: Sun, 11 Aug 2019 14:36:07 GMT
 Content-Type: text/html; charset=utf-8

我们可以看到,我们能够使用 301 响应代码到达en.wikipedia.org。现在我们把模式改成REGISTRY_ONLY。Istio 配置存储在名为configmapistio中。让我们更新它并检查其行为。

$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
configmap/istio replaced

$ kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
Defaulting container name to frontend.
frontend-deployment-c9c975b4-p8z2t:/# wget -qSO - http://en.wikipedia.org/
 HTTP/1.1 502 Bad Gateway
wget: server returned error: HTTP/1.1 502 Bad Gateway

我们可以看到该位置已无法访问。它返回 502 响应,表示需要查看代理配置。我们需要控制对有限地点的访问。我们可以使用以下组件对此进行配置。

辅助平巷

ServiceEntry是一种可以在 Istio 服务注册中心配置网格外部服务的方式。配置外部运行的业务组件通常很有帮助。配置服务条目后,Istio 将监控服务的所有流量。见图 5-5 。

img/483921_1_En_5_Fig5_HTML.jpg

图 5-5

辅助平巷

让我们继续之前的示例,并启用对en.wikipedia.org的访问。我们可以使用以下配置来配置其服务入口端点:

---

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
 name: wikipedia
spec:
 hosts:
 - en.wikipedia.org
 ports:
 - number: 443
 name: https
 protocol: HTTPS
 resolution: DNS
 location: MESH_EXTERNAL

先前的配置具有以下属性:

  • spec.hosts:该属性定义了使用该服务条目配置的主机列表。

  • spec.ports:该属性定义配置的端口。

  • spec.resolution:该属性定义了需要如何进行地址查找。如果在配置中定义了主机 IP 地址,它可以是基于 DNS 的,也可以是静态的。

  • spec.location:该属性定义了服务所在的位置。服务地点可以定义为INTERNALEXTERNAL。在EXTERNAL服务的情况下,Istio 禁用相互 TLS 行为。

现在将配置应用到 Kubernetes 集群。我们应该被允许访问维基百科。

$ kubectl create -f config/service-entry.yaml
serviceentry.networking.istio.io/wikipedia configured
virtualservice.networking.istio.io/wikipedia configured
$ kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
Defaulting container name to frontend.
frontend-deployment-c9c975b4-p8z2t:/# wget -qSO - https://en.wikipedia.org/
 HTTP/1.1 302 Found
 Date: Sun, 11 Aug 2019 16:44:19 GMT
 Content-Type: text/html; charset=utf-8
 Content-Length: 0
 Connection: close
........

frontend-deployment-c9c975b4-p8z2t:/# wget -qSO - http://en.wikipedia.org/
 HTTP/1.1 502 Bad Gateway
wget: server returned error: HTTP/1.1 502 Bad Gateway

我们可以看到,只允许访问 HTTPS 服务。对 HTTP 服务的访问失败,出现错误。我们可以有以下配置,这将允许访问两种协议:

---

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
 name: wikipedia
spec:
 hosts:
 - en.wikipedia.org
 ports:
 - number: 80
 name: http
 protocol: HTTP
 - number: 443
 name: https
 protocol: HTTPS
 resolution: DNS
 location: MESH_EXTERNAL

出口

在上一节中,我们限制了对一组有限的外部服务的访问。但是网格中运行的所有服务仍然可以连接到可用的外部服务。有时,企业要求必须评估所有外部流量,以确保其符合授权规则。必须对每个请求进行检查,以限制只有经过授权的人才能访问。此外,网状网络外的所有流量都必须从受监控的单一位置通过。Istio 为此定义了一个出口网关。这是一个可以拦截离开服务网格的流量的组件。参见图 5-6 。

img/483921_1_En_5_Fig6_HTML.jpg

图 5-6

出口

在前面的例子中,我们配置了对en.wikipedia.org的直接访问。现在,在以下配置中,我们已经定义了出口主机并截获了其中的所有请求:

---

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
 name: wikipedia-egressgateway
spec:
 selector:
 istio: egressgateway
 servers:
 - port:
 number: 80
 name: http
 protocol: HTTP
 hosts:
 - en.wikipedia.org
---

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: wiki-egress-gateway
spec:
 hosts:
 - en.wikipedia.org
 gateways:
 - wikipedia-egressgateway
 - mesh
 http:
 - match:
 - gateways:
 - mesh
 port: 80
 route:
 - destination:
 host: istio-egressgateway.istio-system.svc.cluster.local
 port:
 number: 80

 weight: 100
 - match:
 - gateways:
 - wikipedia-egressgateway
 port: 80
 route:
 - destination:
 host: en.wikipedia.org
 port:
 number: 80
 weight: 100
---

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
 name: wikipedia
spec:
 hosts:
 - en.wikipedia.org
 ports:
 - number: 80
 name: http
 protocol: HTTP
 resolution: DNS
 location: MESH_EXTERNAL

之前的配置充分利用了 Istio 路由功能。我们在其中定义了以下行为:

  • en.wikipedia.org的服务条目进行配置,以便网格内部的服务可以访问它。

  • 我们定义了wikipedia-egressgateway,它可以处理匹配指定主机和端口的请求。

  • 我们定义了一个wiki-egress-gateway虚拟服务。服务是两个组件之间的粘合剂。它针对以下内容进行了配置:

    • 虚拟服务处理对en.wikipedia.org的所有请求。它适用于网关和所有边车代理。

    • 源自边车的请求被路由至istio-egressgateway。网关部署在istio-system名称空间中。

    • 网关为wikipedia-egressgateway匹配这些传入的请求。然后,虚拟服务将网关发出的请求路由到服务入口主机。

这样,所有发往en.wikipedia.org的流量都会被istio-egress网关捕获。让我们应用配置并使用wget命令进行测试,如下所示:

$ kubectl create -f config/egress-gateway.yaml
gateway.networking.istio.io/wikipedia-egressgateway created
virtualservice.networking.istio.io/wiki-egress-gateway created
serviceentry.networking.istio.io/wikipedia created

$kubectl exec pod/frontend-deployment-c9c975b4-p8z2t -it -- sh -il
frontend-deployment-c9c975b4-p8z2t:/# wget -qSO - http://en.wikipedia.org/
 HTTP/1.1 301 Moved Permanently
 date: Sun, 11 Aug 2019 17:53:52 GMT
 server: envoy
 ......

我们可以查看egressgateway中的日志。它应该已经捕获了请求。

$microk8s.kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system
......
[2019-08-11T17:53:52.214Z] "GET / HTTP/2" 301 - "-" "-" 0 0 395 355 "10.1.1.8" "Wget" "e2766b89-6b38-9744-9b02-fe9a32c6deea" "en.wikipedia.org" "103.102.166.224:80" outbound|80||en.wikipedia.org - 10.1.1.7:80 10.1.1.8:49880 -

摘要

在这一章中,我们向外部世界展示了我们的 Kubernetes 集群。一开始,我们定义了入口网关,以允许外部客户端连接到集群中运行的服务。我们还在边缘网关上配置了 SSL 终端。接下来,我们试图控制对运行在集群之外的服务的访问。我们将默认的ALLOW_ANY策略修改为RESTRICTED_ONLY。接下来,我们使用服务条目配置对 Istio 服务的访问。服务条目有助于监控外部连接。Istio 提供了出口网关,以便对服务条目定义的服务进行日志记录和访问控制。最后,我们使用出口网关来控制服务入口访问。