service mesh:istio全

istio官网学习总结以及实操,同时填补了踩过的坑

文章目录

  • 概念
  • 可观察性
  • 可扩展性
  • istio安全
  • 架构体系与组件概念
  • 环境,部署前提
  • 部署
  • 部署bookinfo云应用示例:
  • 部署 Kiali、Prometheus、Grafana 以及 Jaeger:
  • 流量管理:
    • VirtualService:
    • DestinationRule
    • 不同服务版本访问规则:
    • 基于权重的服务访问规则
    • 基于请求内容的服务访问规则
    • 故障注入
    • 延迟故障注入
    • 中断访问故障注入
    • 外部接入服务治理
    • 用 Egress Gateway 处理 HTTPS 请求
    • TCP 流量转移
    • 熔断
    • 流量镜像
  • istio观察
    • 使用 Kiali 观测微服务
    • 监控指标
    • 使用 Grafana 可视化系统监控
    • 访问日志
    • 分布式追踪
  • 安全
    • 单一主机(顺带总结openssl和CA流程)
    • 多个主机
    • mTLS认证
    • 基于 JWT 的认证和授权
    • CertManager 自动 HTTPS
  • 最后

概念

Istio 的流量管理模型依赖于使envoy与您的服务一起部署的代理。您的网格服务发送和接收的所有流量(数据平面流量)通过 Envoy 代理,从而可以轻松地引导和控制网格周围的流量,而无需对服务进行任何更改。(envoy以sidecar形式部署)
为了在网格中引导流量,Istio 需要知道所有端点在哪里,以及它们属于哪些服务。填充自己的 服务注册, Istio 连接到服务发现系统。如果 Kubernetes 集群上安装了 Istio,那么 Istio 会自动检测该集群中的服务和端点。
使用此服务注册表,Envoy 代理可以将流量引导到相关服务。大多数基于微服务的应用程序都有每个服务工作负载的多个实例来处理服务流量,有时称为负载平衡池(其实就是service对应的pod)。默认情况下,Envoy 代理使用循环模型在每个服务的负载平衡池中分配流量,其中请求依次发送到每个池成员,一旦每个服务实例收到请求,就会返回到池的顶部。

虽然 Istio 的基本服务发现和负载平衡为您提供了一个有效的服务网格,但它远非 Istio 所能做的。在许多情况下,您可能希望对网格流量发生的情况进行更细粒度的控制。作为 A/B 测试的一部分,您可能希望将特定百分比的流量定向到新版本的服务(weight),或者将不同的负载平衡策略应用于特定服务实例子集的流量(subset)。您可能还希望对进出网格的流量应用特殊规则(gateway),或者将网格的外部依赖项添加到服务注册表(service entry)。

virtualservice以及destinationrule是 Istio 流量路由功能的关键构建块。虚拟服务允许你配置如何将请求路由到 Istio 服务网格中的服务,建立在 Istio 和你的平台提供的基本连接和发现之上。每个虚拟服务都包含一组按顺序评估的路由规则,让 Istio 将每个给定的虚拟服务请求匹配到网格中的特定真实目的地。根据你的用例,你的网格可能需要多个虚拟服务或不需要。(destinationrule可以看做对于virtualservice的流量做更加细粒化的路由)
在没有虚拟服务的情况下,Envoy 在所有服务实例之间使用循环负载平衡来分配流量。
使用虚拟服务,您可以为一个或多个主机名指定流量行为。您在虚拟服务中使用路由规则来告诉 Envoy 如何将虚拟服务的流量发送到适当的目的地。路由目的地可以是相同服务的版本,也可以是完全不同的服务。
一个典型的用例是将流量发送到服务的不同版本,指定为服务子集。客户端向虚拟服务主机发送请求,就好像它是一个单一实体一样,然后 Envoy 根据虚拟服务规则将流量路由到不同的版本:由weight控制,例如,这使你可以创建一个金丝雀部署,逐步增加发送到新服务版本的流量百分比(就是柔和发布,设计思维类似灰度发布)。流量路由与实例部署完全分离,这意味着实现新服务版本的实例数量可以根据流量负载进行扩展和缩减,而完全无需参考流量路由。相比之下,像 Kubernetes 这样的容器编排平台只支持基于实例扩展的流量分配,这很快就会变得复杂。
如果您的网格使用 Kubernetes,您可以配置一个虚拟服务来处理特定命名空间中的所有服务。将单个虚拟服务映射到多个“真实”服务(service)对于促进将单一应用程序转变为由不同微服务构建的复合服务特别有用,而无需服务的消费者适应转换

虚拟服务
vituralservice的hosts字段列出了虚拟服务的主机 - 换句话说,这些路由规则适用的用户可寻址目的地或目的地。这是客户端在向服务发送请求时使用的地址。
虚拟服务主机名可以是 IP 地址、DNS 名称,或者,取决于平台,可以是隐式或显式解析为完全限定域名 (FQDN) 的短名称(例如 Kubernetes 服务短名称)。您还可以使用通配符 (“*”) 前缀,让您为所有匹配的服务创建一组路由规则。虚拟服务主机实际上不必是 Istio 服务注册表的一部分,它们只是虚拟目的地。这使您可以对网格内没有可路由条目的虚拟主机的流量进行建模。

路由规则
包含虚拟服务的路由规则,描述了将 HTTP/1.1、HTTP2 和 gRPC 流量路由到 hosts 字段中指定的目标的匹配条件和操作(您还可以使用tcp和 tls部分来配置 TCP的路由规则和未终止 的TLS 流量)。路由规则由您希望流量流向的目的地和零个或多个匹配条件组成,具体取决于您的用例
配置文件主要分匹配条件(match字段)目的地(route字段)
匹配条件有match开始。您可以将多个匹配条件添加到同match一块以 AND 您的条件,或将多个匹配块添加到同一规则以 OR 您的条件。

路由部分的destination字段指定符合此条件的流量的实际目的地。与虚拟服务的主机不同,目的地的主机必须是存在于 Istio 服务注册表中的真实目的地,否则 Envoy 将不知道将流量发送到何处。这可以是带有代理的网格服务,也可以是使用服务条目添加的非网格服务。在这种情况下,我们在 Kubernetes 上运行,主机名是 Kubernetes 服务名
**只有当目标主机和虚拟服务实际上在同一个 Kubernetes 命名空间中时,才可以使用这样的短名称。**因为使用 Kubernetes 短名称可能会导致配置错误,我们建议您在生产环境中指定完全限定的主机名。

kubernetes的fqdn格式一般是servicename.namespace.svc.cluster.local

路由规则优先级
路由规则按从上到下的顺序进行评估,虚拟服务定义中的第一个规则被赋予最高优先级。我们建议提供一个默认的“无条件”或基于权重的规则(较小的权重)作为每个虚拟服务中的最后一条规则,以确保到虚拟服务的流量始终具有至少一个匹配的路由。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
    - bookinfo.com
  http:
  - match:   #多个match块,可以match匹配后,即对hosts的服务请求进行更细致的区分后再route
    - uri:   #同一个match块中要满足全部条件才算匹配成功
        prefix: /reviews
    route:
    - destination:
        host: reviews
  - match:
    - uri:
        prefix: /ratings
    route:
    - destination:
        host: ratings

目的地规则(destinationrule)
与虚拟服务一起, 目标规则 是 Istio 流量路由功能的关键部分。您可以将虚拟服务视为将流量路由到给定目的地的方式,然后使用目的地规则来配置该目的地的流量会发生什么。目标规则在评估虚拟服务路由规则后应用,因此它们适用于流量的“真实”目标。

特别是,您使用目标规则来指定命名的服务子集,例如按版本对所有给定服务的实例进行分组。然后,您可以在虚拟服务的路由规则中使用这些服务子集来控制到不同服务实例的流量。

目标规则还允许您在调用整个目标服务或特定服务子集时自定义 Envoy 的流量策略,例如您首选的负载平衡模型、TLS 安全模式或断路器设置。您可以在目标规则参考中查看目标规则选项的完整列表 。
默认情况下,Istio 使用循环负载均衡策略,实例池中的每个服务实例依次收到请求。Istio 还支持以下模型,您可以在目标规则中为对特定服务或服务子集的请求指定这些模型。

随机:请求随机转发到池中的实例。
加权:根据特定百分比将请求转发到池中的实例。
最少请求:请求被转发到请求数量最少的实例。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-destination-rule
spec:
  host: my-svc
  trafficPolicy:   #定义交通策略
    loadBalancer:   #负载均衡设置
      simple: RANDOM
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN   #覆盖
  - name: v3
    labels:
      version: v3

每个子集都是基于一个或多个定义的labels,在 Kubernetes 中,它们是附加到 Pod 等对象的键/值对。这些标签应用于 Kubernetes 服务的部署,metadata以识别不同的版本。

gateway
您使用网关来管理网格的入站和出站流量,让您指定要进入或离开网格的流量。网关配置应用于在网格边缘运行的独立 Envoy 代理,而不是与服务工作负载一起运行的 Sidecar Envoy 代理。

与控制进入系统的流量的其他机制(例如 Kubernetes Ingress API)不同,Istio 网关让您可以使用 Istio 流量路由的全部功能和灵活性。您可以这样做,因为 Istio 的网关资源只允许您配置 4-6 层负载平衡属性,例如要公开的端口、TLS 设置等。然后,您无需将应用层流量路由 (L7) 添加到同一 API 资源,而是将常规 Istio虚拟服务绑定到网关。这使您可以像管理 Istio 网格中的任何其他数据平面流量一样管理网关流量。

网关主要用于管理入口流量,但您也可以配置出口网关。例如,出口网关允许您为离开网格的流量配置专用出口节点,让您限制哪些服务可以或应该访问外部网络,或者启用 对出口流量的安全控制 以增加网格的安全性。您还可以使用网关来配置纯内部代理。

Istio 提供了一些您可以使用的预配置网关代理部署 (istio-ingressgateway和istio-egressgateway) - 如果您使用我们的演示安装,则会部署两者,而仅使用我们的默认配置文件部署入口网关 。您可以将自己的网关配置应用于这些部署,或者部署和配置自己的网关代理。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: ext-host-gwy
spec:
  selector:
    app: my-gateway-controller
  servers:
  - port:
      number: 443   #此网关配置允许 HTTPS 流量从ext-host.example.com端口 443 进入网格,(或者理解成my-gateway-controller这个service的443端口进入网格)
      name: https
      protocol: HTTPS
    hosts:
    - ext-host.example.com
    tls:
      mode: SIMPLE
      credentialName: ext-host-cert

要指定路由并使网关按预期工作,您还必须将网关绑定到虚拟服务。您可以使用虚拟服务的 gateways字段执行此操作,如以下示例所示:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: virtual-svc
spec:
  hosts:
  - ext-host.example.com
  gateways:
  - ext-host-gwy

服务条目
您可以使用 服务条目向 Istio 内部维护的服务注册表添加一个条目。添加服务条目后,Envoy 代理可以将流量发送到服务,就好像它是网格中的服务一样。配置服务条目允许您管理在网格之外运行的服务的流量,包括以下任务:

重定向和转发外部目的地的流量,例如从 Web 使用的 API,或到旧基础设施中的服务的流量。
为外部目标定义重试、超时和 故障注入策略。
通过将虚拟机添加到网格中,在虚拟机 (VM) 中运行网格服务 。
您无需为希望网格服务使用的每个外部服务添加服务条目。默认情况下,Istio 将 Envoy 代理配置为将请求传递给未知服务(allow_any)。但是,您不能使用 Istio 功能来控制到未在网格中注册的目的地的流量(可以通过service_entry改变)。

serviceentry
将未注册的网格外部服务拉入网格

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: svc-entry
spec:
  hosts:
  - ext-svc.example.com   #您使用该hosts字段指定外部资源。您可以完全限定它或使用通配符前缀的域名
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  location: MESH_EXTERNAL
  resolution: DNS

配置虚拟服务和目标规则,以更精细的方式控制服务条目的流量,就像为网格中的任何其他服务配置流量一样

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: ext-res-dr
spec:
  host: ext-svc.example.com
  trafficPolicy:   #交通规则
    tls:   #配置tls
      mode: MUTUAL   #双向通信,安全性更高
      clientCertificate: /etc/certs/myclientcert.pem
      privateKey: /etc/certs/client_private_key.pem
      caCertificates: /etc/certs/rootcacerts.pem

sidecar
默认情况下,Istio 将每个 Envoy 代理配置为接受其相关工作负载的所有端口上的流量,并在转发流量时到达网格中的每个工作负载。您可以使用sidecar配置执行以下操作:

微调 Envoy 代理接受的端口和协议集。
限制 Envoy 代理可以访问的服务集。
您可能希望在大型应用程序中像这样限制 Sidecar 的可访问性,其中将每个代理配置为访问网格中的所有其他服务可能会由于高内存使用率而影响网格性能。

apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: default
  namespace: bookinfo
spec:
  egress:
  - hosts:
    - "./*"
    - "istio-system/*"

超时

超时是 Envoy 代理应该等待来自给定服务的回复的时间量,确保服务不会无限期地等待回复,并且调用在可预测的时间范围内成功或失败。默认情况下,Istio 中禁用了 HTTP 请求的 Envoy 超时(即默认会一直等待该服务的调用)。

对于某些应用程序和服务,Istio 的默认超时可能不合适。例如,过长的超时可能会导致等待来自失败服务的回复的延迟过长,而过短的超时可能会导致调用在等待涉及多个服务的操作返回时不必要地失败。为了找到并使用您的最佳超时设置,Istio 允许您使用虚拟服务轻松地在每个服务的基础上动态调整超时,而无需编辑您的服务代码。下面是一个虚拟服务,它为调用 rating 服务的 v1 子集指定了 10 秒的超时时间:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
    timeout: 10s   #该服务的超时时间10秒,超过10秒视为错误,在spec.http下

重试

重试设置指定 Envoy 代理在初始调用失败时尝试连接服务的最大次数。重试可以通过确保调用不会因为临时性问题(例如临时过载的服务或网络)而永久失败,从而提高服务可用性和应用程序性能。重试间隔(25ms+)是可变的,由 Istio 自动确定,防止被调用的服务被请求淹没。HTTP 请求的默认重试行为是在返回错误之前重试两次。

与超时一样,Istio 的默认重试行为可能不适合您的应用程序在延迟(失败的服务重试次数过多可能会减慢速度)或可用性方面的需求。也像超时一样,您可以在虚拟服务中基于每个服务调整重试设置,而无需接触您的服务代码。您还可以通过添加每次重试超时来进一步优化您的重试行为,指定您希望等待每次重试尝试成功连接到服务的时间量。以下示例在初始调用失败后配置最多 3 次重试以连接到此服务子集,每次重试时间为 2 秒。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
    retries:   #定义重试操作
      attempts: 3   #尝试,连续3次失败才视为失败(与探针类似)
      perTryTimeout: 2s   #重试的的间隔

断路器(熔断)
断路器是 Istio 提供的另一种有用的机制,用于创建基于微服务的弹性应用程序。在断路器中,您可以设置对服务中各个主机的调用限制,例如并发连接数或对该主机的调用失败的次数。一旦达到该限制,断路器就会“跳闸”并停止与该主机的进一步连接。使用断路器模式可以实现快速故障,而不是客户端尝试连接到过载或故障主机。

由于断路器适用于负载平衡池中的“真实”网格目标,您可以在目标规则中配置断路器阈值 ,并将设置应用于服务中的每个单独的主机。以下示例reviews将 v1 子集的服务工作负载的并发连接数限制为 100:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
    trafficPolicy:
      connectionPool:   #定义连接池
        tcp:
          maxConnections: 100   #最大tcp连接数

故障注入

配置好网络(包括故障恢复策略)后,您可以使用 Istio 的故障注入机制来测试整个应用程序的故障恢复能力。故障注入是一种将错误引入系统以确保系统能够承受并从错误条件中恢复的测试方法。使用故障注入对于确保您的故障恢复策略不会不兼容或限制过多,可能会导致关键服务不可用特别有用。

与其他引入错误的机制(例如延迟数据包或在网络层杀死 pod)不同,Istio’ 允许您在应用程序层注入故障。这使您可以注入更多相关故障,例如 HTTP 错误代码,以获得更多相关结果。

您可以注入两种类型的故障,均使用 虚拟服务进行配置:

延迟:延迟是计时失败。它们模仿增加的网络延迟或过载的上游服务。
中止:中止是崩溃失败。它们模仿上游服务中的故障。中止通常以 HTTP 错误代码或 TCP 连接失败的形式出现。
例如,此虚拟服务对ratings服务的每 1000 个请求中的 1 个引入了 5 秒的延迟。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - fault:   #注入故障
      delay:   #延迟
        percentage:   #全部请求中故障注入的请求占比
          value: 0.1
        fixedDelay: 5s
    route:
    - destination:
        host: ratings
        subset: v1

可观察性

istio为网格内所有服务生成遥测数据–》遥测提供服务可观测性–》便于服务的故障维护和优化–》全面了解服务间预计与istio组件间的通信
Istio 生成以下类型的遥测数据以提供整体服务网格可观察性:

指标。Istio 根据监控的四个“黄金信号”**(延迟、流量、错误和饱和度)**生成一组服务指标。Istio 还提供了网格控制平面的详细指标。还提供了基于这些指标构建的一组默认网格监控仪表板。
分布式跟踪。Istio 为每个服务生成分布式跟踪跨度,使操作员能够详细了解网格内的调用流和服务依赖关系。
访问日志。当流量流入网格内的服务时,Istio 可以生成每个请求的完整记录,包括源和目标元数据。此信息使操作员能够审计服务行为,直至单个 工作负载实例级别。
service mesh:istio全_第1张图片

metrics交给prometheus处理

度量标准(metrics)提供了一种监控和理解总体行为的方法。 为了监控服务行为,Istio 为 Istio
服务网格内的所有服务流量生成指标。这些指标提供有关行为的信息,例如总流量、流量中的错误率以及请求的响应时间。
除了监控网格内服务的行为外,监控网格本身的行为也很重要。Istio
组件根据自己的内部行为导出指标,以提供对网格控制平面的运行状况和功能的洞察。

代理级指标
Istio 指标收集从 sidecar 代理 (Envoy)
开始。每个代理都会生成一组关于通过代理的所有流量(入站和出站)的丰富指标。代理还提供有关代理本身管理功能的详细统计信息,包括配置和健康信息。

Envoy 生成的指标以 Envoy 资源(例如侦听器和集群)的粒度提供对网格的监控。因此,监控 Envoy 指标需要了解网格服务和
Envoy 资源之间的连接。

Istio 使操作员能够选择在每个工作负载实例上生成和收集哪些 Envoy 指标。默认情况下,Istio 仅启用 Envoy
生成的统计信息的一小部分,以避免过多的指标后端并减少与指标收集相关的 CPU
开销。但是,运营商可以在需要时轻松扩展收集的代理指标集。这可以有针对性地调试网络行为,同时降低跨网状网络监控的总体成本。
代理级别指标示例:

envoy_cluster_internal_upstream_rq{response_code_class=“2xx”,cluster_name=“xds-grpc”}
7163

envoy_cluster_upstream_rq_completed{cluster_name=“xds-grpc”} 7164

envoy_cluster_ssl_connection_error{cluster_name=“xds-grpc”} 0

envoy_cluster_lb_subsets_removed{cluster_name=“xds-grpc”} 0

envoy_cluster_internal_upstream_rq{response_code=“503”,cluster_name=“xds-grpc”}
1

服务级别指标
除了代理级别的指标之外,Istio 还提供了一组面向服务的指标来监控服务通信。这些指标涵盖了四个基本的服务监控需求:延迟、流量、错误和饱和度。Istio 附带一组默认 仪表板,用于根据这些指标监控服务行为。

默认情况下,标准 Istio 指标会导出到Prometheus。

服务级别指标的使用完全是可选的。运营商可以选择关闭这些指标的生成和收集以满足他们的个人需求。

服务级别指标示例:

istio_requests_total{ connection_security_policy=“mutual_tls”,
destination_app=“details”, destination_canonical_service=“details”,
destination_canonical_revision=“v1”,
destination_principal=“cluster.local/ns/default/sa/default”,
destination_service=“details.default.svc.cluster.local”,
destination_service_name=“details”,
destination_service_namespace=“default”, destination_version=“v1”,
destination_workload=“details-v1”,
destination_workload_namespace=“default”, reporter=“destination”,
request_protocol=“http”, response_code=“200”, response_flags=“-”,
source_app=“productpage”, source_canonical_service=“productpage”,
source_canonical_revision=“v1”,
source_principal=“cluster.local/ns/default/sa/default”,
source_version=“v1”, source_workload=“productpage-v1”,
source_workload_namespace=“default” } 214

控制平面指标
Istio 控制平面还提供了一组自我监控指标。这些指标允许监控 Istio 本身的行为(与网格中的服务不同)。

分布式跟踪
就是跟踪流量
分布式跟踪提供了一种通过在单个请求流经网格时监控它们来监控和理解行为的方法。跟踪使网格运营商能够了解服务依赖关系以及其服务网格中的延迟来源

Istio 支持通过 Envoy 代理进行分布式跟踪。代理会代表它们代理的应用程序自动生成跟踪跨度,只需要应用程序转发适当的请求上下文。

Istio 支持许多跟踪后端,包括Zipkin、 Jaeger、Lightstep和
Datadog。操作员控制跟踪生成的采样率(即每个请求生成跟踪数据的速率)。这允许操作员控制为其网格生成的跟踪数据的数量和速率。

访问日志
与nginx日志类似
访问日志提供了一种从单个工作负载实例的角度监控和理解行为的方法。

Istio 可以以一组可配置的格式为服务流量生成访问日志,使操作员可以完全控制日志记录的方式、内容、时间和地点(日志格式)。有关更多信息,请参阅获取
Envoy 的访问日志。

可扩展性

WebAssembly 是一种沙盒技术,可用于扩展 Istio 代理(Envoy)。Proxy-Wasm 沙箱 API 取代 Mixer 作为 Istio 中的主要扩展机制。

WebAssembly 沙箱目标:

效率- 扩展增加了低延迟、CPU 和内存开销。 功能- 扩展可以强制执行策略、收集遥测数据并执行有效负载突变。 隔离-
一个插件中的编程错误或崩溃不会影响其他插件。 配置- 使用与其他 Istio API 一致的 API 配置插件。可以动态配置扩展。
operator- 扩展可以以仅日志、故障打开或故障关闭的形式进行部署

高层架构
Istio 扩展(Proxy-Wasm 插件)有几个组件:

过滤器服务提供者接口 (SPI),用于为过滤器构建 Proxy-Wasm 插件。 嵌入在 Envoy 中的Sandbox V8 Wasm
运行时。 用于标头、预告片和元数据的主机 API。 调出用于 gRPC 和 HTTP 调用的 API 。 用于度量和监控的统计和日志 API

在这里插入图片描述

istio安全

将单体应用程序分解为原子服务提供了各种好处,包括更好的敏捷性、更好的可扩展性和更好的服务重用能力。但是,微服务也有特殊的安全需求:

为了防御中间人攻击,他们需要流量加密。
为了提供灵活的服务访问控制,他们需要双向 TLS 和细粒度的访问策略。
为了确定谁在什么时间做了什么,他们需要审计工具。
Istio Security 提供了一个全面的安全解决方案来解决这些问题。此页面概述了如何使用 Istio 安全功能来保护您的服务,无论您在何处运行它们。特别是,Istio 安全性可以缓解针对您的数据、端点、通信和平台的内部和外部威胁。
service mesh:istio全_第2张图片

Istio 安全功能提供强大的身份、强大的策略、透明的 TLS 加密以及身份验证、授权和审计 (AAA)
工具来保护您的服务和数据。Istio 安全的目标是:

默认安全性:无需更改应用程序代码和基础架构 纵深防御:与现有安全系统集成,提供多层防御 零信任网络:在不信任网络上构建安全解决方案

Istio 中的安全性涉及多个组件:

用于密钥和证书管理的证书颁发机构 (CA)

配置 API 服务器分发给代理:
1.身份验证策略
2.授权策略
3.安全命名信息
Sidecar 和外围代理作为策略执行点 (PEP) 来保护客户端和服务器之间的通信。
一组 Envoy 代理扩展来管理遥测和审计
(Policy Enforcement Points)

Istio 身份
身份是任何安全基础设施的基本概念。在工作负载到工作负载的通信开始时,双方必须用他们的身份信息交换凭证以进行相互身份验证。在客户端,根据 安全命名 信息检查服务器的身份,以查看它是否是工作负载的授权运行者。在服务器端,服务器可以根据 授权策略确定客户端可以访问哪些信息,审计谁在什么时间访问了什么,根据客户端使用的工作负载向客户端收费,拒绝任何未能支付账单的客户端访问工作量。

Istio 身份模型使用第一类service
identity来确定请求来源的身份。此模型允许服务身份具有极大的灵活性和粒度,以代表人类用户、单个工作负载或一组工作负载。在没有服务身份的平台上,Istio
可以使用可以对工作负载实例进行分组的其他身份,例如服务名称。

以下列表显示了您可以在不同平台上使用的服务标识示例:

Kubernetes:Kubernetes 服务帐号 GCE:GCP 服务帐号 本地(非
Kubernetes):用户帐户、自定义服务帐户、服务名称、Istio 服务帐户或 GCP
服务帐户。自定义服务帐户是指现有服务帐户,就像客户的身份目录管理的身份一样。

身份和证书管理
Istio 使用 X.509 证书安全地为每个工作负载提供强身份。Istio 代理与每个 Envoy
代理一起运行,协同工作istiod以大规模自动化密钥和证书轮换。下图显示了身份配置流程。

service mesh:istio全_第3张图片

Istio 通过以下流程提供密钥和证书:
istiod提供 gRPC 服务来接受证书签名请求(CSR)。
启动时,Istio代理会创建私钥和 CSR,然后将 CSR 及其凭据发送给istiod进行签名。
CAistiod验证 CSR中携带的凭证。成功验证后,它会签署 CSR 以生成证书。
当工作负载启动时,Envoy 通过Envoy 秘密发现服务 (SDS)API从同一容器中的 Istio 代理请求证书和密钥 。 Istio 代理通过 Envoy SDS API将收到的证书istiod和私钥发送给 Envoy。
Istio 代理监控工作负载证书的到期情况。上述过程周期性地重复证书和密钥轮换

验证
Istio 提供两种类型的身份验证:
对等身份验证:用于服务到服务的身份验证,以验证建立连接的客户端。Istio 提供双向TLS作为传输身份验证的全栈解决方案,无需更改服务代码即可启用。这个解决方案:
为每个服务提供代表其角色的强大身份,以实现跨集群和云的互操作性。
保护服务到服务的通信。
提供密钥管理系统以自动生成、分发和轮换密钥和证书。
请求身份验证:用于最终用户身份验证,以验证附加到请求的凭据。Istio 通过 JSON Web Token (JWT) 验证启用请求级身份验证,并使用自定义身份验证提供程序或任何 OpenID Connect 提供程序简化开发人员体验,例如:
ORY Hydra
Keycloak
Auth0
Firebase Auth
Google Auth
在所有情况下,Istio 都Istio config store通过自定义 Kubernetes API 将身份验证策略存储在 Kubernetes 中。伊斯蒂奥德使它们为每个代理保持最新,以及适当的密钥。此外,Istio 支持许可模式下的身份验证,以帮助您了解策略更改如何在强制执行之前影响您的安全状况。

双向 TLS 身份验证
Istio 通过客户端和服务器端 PEP 建立服务到服务通信的隧道,这些 PEP 被实现为Envoy 代理。当一个工作负载使用双向 TLS 身份验证向另一个工作负载发送请求时,该请求的处理方式如下:

Istio 将来自客户端的出站流量重新路由到客户端的本地 Sidecar Envoy。
客户端 Envoy 开始与服务器端 Envoy 进行双向 TLS 握手。在握手期间,客户端 Envoy 还会进行 安全命名 检查,以验证服务器证书中提供的服务帐户是否被授权运行目标服务。
客户端 Envoy 和服务端 Envoy 建立相互的 TLS 连接,Istio 将来自客户端 Envoy 的流量转发到服务端 Envoy。
服务器端 Envoy 对请求进行授权。如果获得授权,它将通过本地 TCP 连接将流量转发到后端服务。
IstioTLSv1_2使用以下密码套件配置为客户端和服务器的最低 TLS 版本:

ECDHE-ECDSA-AES256-GCM-SHA384

ECDHE-RSA-AES256-GCM-SHA384

ECDHE-ECDSA-AES128-GCM-SHA256

ECDHE-RSA-AES128-GCM-SHA256

AES256-GCM-SHA384

AES128-GCM-SHA256

许可模式
Istio 双向 TLS 有一种许可模式,允许服务同时接受明文流量和双向 TLS 流量。此功能极大地改善了双向 TLS 登录体验。

许多非 Istio 客户端与非 Istio 服务器通信会给想要将该服务器迁移到启用双向 TLS 的 Istio 的操作员提出问题。通常,运营商无法同时为所有客户端安装 Istio sidecar,或者甚至没有权限在某些客户端上这样做。即使在服务器上安装了 Istio sidecar 之后,运营商也无法在不中断现有通信的情况下启用双向 TLS。

启用许可模式后,服务器接受明文和双向 TLS 流量。该模式为入职流程提供了更大的灵活性。服务器安装的 Istio sidecar 立即获取双向 TLS 流量,而不会破坏现有的明文流量。因此,运营商可以逐步安装和配置客户端的 Istio sidecars 以发送双向 TLS 流量。客户端配置完成后,操作员可以将服务器配置为仅双向 TLS 模式。有关更多信息,请访问 相互 TLS 迁移教程。

安全命名
服务器身份编码在证书中,但服务名称是通过发现服务或 DNS 检索的。安全命名信息将服务器身份映射到服务名称。身份A到服务名称的映射B意味着“A被授权运行服务B”。控制平面监视apiserver,生成安全命名映射,并将它们安全地分发给 PEP。以下示例解释了为什么安全命名在身份验证中至关重要。

假设运行服务的合法服务器datastore只使用该 infra-team身份。恶意用户拥有 test-team身份的证书和密钥。恶意用户打算冒充该服务来检查从客户端发送的数据。test-team恶意用户使用证书和身份密钥部署伪造的服务器。假设恶意用户成功劫持(通过 DNS 欺骗、BGP/路由劫持、ARP 欺骗等)发送到的流量并将其datastore重定向到伪造的服务器。

当客户端调用datastore服务时,它会从服务器的证书中提取test-team 身份,并检查是否test-team允许datastore使用安全命名信息运行。客户端检测到test-team不允许运行datastore服务,认证失败。

请注意,对于非 HTTP/HTTPS 流量,安全命名不能防止 DNS 欺骗,在这种情况下,攻击者会修改服务的目标 IP。由于 TCP 流量不包含Host信息,并且 Envoy 只能依赖目标 IP 进行路由,因此 Envoy 可能会将流量路由到被劫持 IP 上的服务。这种 DNS 欺骗甚至可能在客户端 Envoy 接收到流量之前发生。

认证架构
您可以使用对等和请求身份验证策略为在 Istio 网格中接收请求的工作负载指定身份验证要求。网格操作员使用.yaml文件来指定策略。部署后,策略将保存在 Istio 配置存储中。Istio 控制器监视配置存储。

在任何策略更改时,新策略都会转换为适当的配置,告诉 PEP 如何执行所需的身份验证机制。控制平面可以获取公钥并将其附加到配置以进行 JWT 验证。或者,Istiod 提供 Istio 系统管理的密钥和证书的路径,并将它们安装到应用程序 pod 以实现双向 TLS。您可以在身份和证书管理部分找到更多信息。

Istio 将配置异步发送到目标端点。代理收到配置后,新的身份验证要求会立即在该 pod 上生效。

发送请求的客户端服务负责遵循必要的身份验证机制。对于请求身份验证,应用程序负责获取 JWT 凭证并将其附加到请求中。对于对等身份验证,Istio 会自动将两个 PEP 之间的所有流量升级为双向 TLS。如果身份验证策略禁用双向 TLS 模式,Istio 将继续在 PEP 之间使用纯文本。要覆盖此行为,请使用目标规则显式禁用双向 TLS 模式 。
service mesh:istio全_第4张图片
Istio 将具有两种身份验证类型的身份以及凭证中的其他声明(如果适用)输出到下一层: 授权。

身份验证策略
本节提供有关 Istio 身份验证策略如何工作的更多详细信息。正如您在 架构部分中所记得的那样,身份验证策略适用于服务接收到的请求。要在双向 TLS 中指定客户端身份验证规则,您需要 TLSSettings在DestinationRule. 您可以在我们的TLS 设置参考文档中找到更多信息 。

与其他 Istio 配置一样,您可以在 .yaml文件中指定身份验证策略。您使用kubectl. 以下示例身份验证策略指定带有app:reviews标签的工作负载的传输身份验证必须使用双向 TLS:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "example-peer-policy"
  namespace: "foo"
spec:
  selector:
    matchLabels:
      app: reviews
  mtls:
    mode: STRICT

策略存储
Istio 将网格范围策略存储在根命名空间中。这些策略有一个空选择器,适用于网格中的所有工作负载。具有命名空间范围的策略存储在相应的命名空间中。它们仅适用于其命名空间内的工作负载。如果您配置selector字段,则身份验证策略仅适用于与您配置的条件匹配的工作负载。

对等和请求认证策略按种类分开存储, PeerAuthentication分别存储RequestAuthentication。

选择器字段
对等和请求身份验证策略使用selector字段来指定策略应用到的工作负载的标签。以下示例显示了适用于具有 app:product-page标签的工作负载的策略的选择器字段:

selector:
  matchLabels:
    app: product-page

如果您没有为该selector字段提供值,Istio 会将策略匹配到策略存储范围内的所有工作负载。因此,这些selector字段可帮助您指定策略的范围:

Mesh-wide policy:为根命名空间指定的策略,不带或带空selector字段。
selector命名空间范围的策略:为不带或带空字段的非根命名空间指定的策略。
特定于工作负载的策略:在常规命名空间中定义的策略,具有非空选择器字段。
对等和请求身份验证策略遵循相同的selector字段层次结构原则,但 Istio 以稍微不同的方式组合和应用它们。

每个命名空间只能有一个网格范围的对等身份验证策略,并且只能有一个命名空间范围的对等身份验证策略。当您为同一个网格或命名空间配置多个网格或命名空间范围的对等身份验证策略时,Istio 会忽略较新的策略。当多个特定于工作负载的对等身份验证策略匹配时,Istio 会选择最旧的一个。

Istio 使用以下顺序为每个工作负载应用最窄匹配策略:

特定于工作负载
命名空间范围
全网
Istio 可以组合所有匹配的请求身份验证策略,就像它们来自单个请求身份验证策略一样。因此,您可以在网格或命名空间中拥有多个网格范围或命名空间范围的策略。但是,避免使用多个网格范围或命名空间范围的请求身份验证策略仍然是一个好习惯。

对等身份验证
对等身份验证策略指定 Istio 对目标工作负载实施的双向 TLS 模式。支持以下模式:

PERMISSIVE:工作负载接受双向 TLS 和纯文本流量。当没有 sidecar 的工作负载无法使用双向 TLS 时,此模式在迁移期间最有用。使用 sidecar 注入迁移工作负载后,您应该将模式切换为 STRICT。
严格:工作负载只接受双向 TLS 流量。
禁用:禁用双向 TLS。从安全角度来看,除非您提供自己的安全解决方案,否则不应使用此模式。
未设置模式时,将继承父范围的模式。未设置模式的全网对等体认证策略默认使用该PERMISSIVE模式。

以下对等身份验证策略要求命名空间 foo中的所有工作负载都使用双向 TLS:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "example-policy"
  namespace: "foo"
spec:
  mtls:
    mode: STRICT

使用特定于工作负载的对等身份验证策略,您可以为不同的端口指定不同的双向 TLS 模式。您只能使用工作负载为端口范围的双向 TLS 配置声明的端口。以下示例为工作负载禁用端口80上app:example-app的双向 TLS,并为所有其他端口使用命名空间范围的对等身份验证策略的双向 TLS 设置:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "example-workload-policy"
  namespace: "foo"
spec:
  selector:
     matchLabels:
       app: example-app
  portLevelMtls:
    80:
      mode: DISABLE

上面的对等身份验证策略之所以有效,是因为下面的服务配置将来自工作负载的请求绑定到了以下example-app端口 :80example-service

apiVersion: v1
kind: Service
metadata:
  name: example-service
  namespace: foo
spec:
  ports:
  - name: http
    port: 8000
    protocol: TCP
    targetPort: 80
  selector:
    app: example-app

请求认证
请求身份验证策略指定验证 JSON Web 令牌 (JWT) 所需的值。这些值包括以下内容:

请求中令牌的位置
发行人或请求
公共 JSON Web 密钥集 (JWKS)
如果根据请求身份验证策略中的规则提供令牌,Istio 会检查提供的令牌,并拒绝带有无效令牌的请求。当请求不携带令牌时,默认接受。要拒绝没有令牌的请求,请提供指定特定操作限制的授权规则,例如路径或操作。

如果每个 JWT 都使用唯一的位置,则请求身份验证策略可以指定多个 JWT。当多个策略与工作负载匹配时,Istio 会组合所有规则,就好像它们被指定为单个策略一样。此行为对于对工作负载进行编程以接受来自不同提供商的 JWT 很有用。但是,不支持具有多个有效 JWT 的请求,因为此类请求的输出主体未定义。

校长
当您使用对等身份验证策略和双向 TLS 时,Istio 会将身份从对等身份验证中提取到source.principal. 同样,当您使用请求身份验证策略时,Istio 会将身份从 JWT 分配给request.auth.principal. 使用这些主体设置授权策略并作为遥测输出。

更新身份验证策略
您可以随时更改身份验证策略,Istio 几乎实时地将新策略推送到工作负载。但是,Istio 不能保证所有工作负载同时收到新策略。以下建议有助于在更新身份验证策略时避免中断:

将模式从toPERMISSIVE更改为使用模式时,使用中间对等身份验证策略,反之亦然。当所有工作负载成功切换到所需模式时,您可以将策略应用到最终模式。您可以使用 Istio 遥测来验证工作负载是否已成功切换。DISABLESTRICT
将请求身份验证策略从一个 JWT 迁移到另一个 JWT 时,将新 JWT 的规则添加到策略中,而不删除旧规则。然后工作负载接受这两种类型的 JWT,当所有流量切换到新的 JWT 时,您可以删除旧规则。但是,每个 JWT 必须使用不同的位置。

授权
Istio 的授权功能为网格中的工作负载提供网格、命名空间和工作负载范围的访问控制。这种级别的控制提供了以下好处:

工作负载到工作负载和最终用户到工作负载授权。
一个简单的 API:它包含一个易于使用和维护的AuthorizationPolicyCRD 。
灵活的语义:操作员可以在 Istio 属性上定义自定义条件,并使用 CUSTOM、DENY 和 ALLOW 操作。
高性能:Istio 授权(ALLOW和DENY)在 Envoy 上本地强制执行。
高兼容性:原生支持 gRPC、HTTP、HTTPS 和 HTTP/2,以及任何普通的 TCP 协议。

授权架构
授权策略强制对服务器端 Envoy 代理中的入站流量进行访问控制。每个 Envoy 代理运行一个授权引擎,在运行时授权请求。当请求到达代理时,授权引擎根据当前授权策略评估请求上下文,并返回授权结果,要么ALLOW要么DENY。.yaml操作员使用文件指定 Istio 授权策略。
service mesh:istio全_第5张图片
隐式启用
您不需要显式启用 Istio 的授权功能;它们在安装后可用。要对您的工作负载实施访问控制,您需要应用授权策略。

对于未应用授权策略的工作负载,Istio 允许所有请求。

授权策略支持ALLOW和DENY操作CUSTOM。您可以根据需要应用多个策略,每个策略都有不同的操作,以保护对工作负载的访问。

Istio 按以下顺序检查层中的匹配策略:CUSTOM、DENY、 然后ALLOW。对于每种类型的操作,Istio 首先检查是否有应用了该操作的策略,然后检查请求是否符合策略的规范。如果请求与其中一个层中的策略不匹配,则检查将继续到下一层。

下图详细显示了策略优先级:
service mesh:istio全_第6张图片
当您将多个授权策略应用于同一工作负载时,Istio 会附加地应用它们。

授权政策
要配置授权策略,您需要创建 AuthorizationPolicy自定义资源。授权策略包括选择器、操作和规则列表:

该selector字段指定策略的目标
该action字段指定是允许还是拒绝请求
指定rules何时触发动作
中的from字段rules指定请求的来源
中的to字段rules指定请求的操作
该when字段指定应用规则所需的条件
以下示例显示了一个授权策略, 当发送的请求具有有效的 JWT 令牌时,该策略允许cluster.local/ns/default/sa/sleep服务帐户和dev命名空间这两个源访问具有命名空间中的app: httpbin和version: v1标签 的工作负载。foo

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: httpbin
 namespace: foo
spec:
 selector:
   matchLabels:
     app: httpbin
     version: v1
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["cluster.local/ns/default/sa/sleep"]
   - source:
       namespaces: ["dev"]
   to:
   - operation:
       methods: ["GET"]
   when:
   - key: request.auth.claims[iss]
     values: ["https://accounts.google.com"]

以下示例显示了在源不是foo命名空间时拒绝请求的授权策略:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: httpbin-deny
 namespace: foo
spec:
 selector:
   matchLabels:
     app: httpbin
     version: v1
 action: DENY
 rules:
 - from:
   - source:
       notNamespaces: ["foo"]

拒绝策略优先于允许策略。如果与拒绝策略匹配,则可以拒绝与允许策略匹配的请求。Istio 首先评估拒绝策略,以确保允许策略无法绕过拒绝策略。

政策目标
metadata/namespace您可以使用字段和可选字段指定策略的范围或目标 selector。策略适用于metadata/namespace字段中的命名空间。如果将其值设置为根命名空间,则该策略适用于网格中的所有命名空间。根命名空间的值是可配置的,默认为 istio-system. 如果设置为任何其他命名空间,则该策略仅适用于指定的命名空间。

您可以使用selector字段进一步限制策略以应用于特定工作负载。使用selector标签来选择目标工作负载。选择器包含一个{key: value}对列表,其中key是标签的名称。如果未设置,授权策略将应用于与授权策略相同命名空间中的所有工作负载。

例如,该allow-read策略允许"GET"和"HEAD"访问具有名称空间中app: products标签的工作负载。default

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-read
  namespace: default
spec:
  selector:
    matchLabels:
      app: products
  action: ALLOW
  rules:
  - to:
    - operation:
         methods: ["GET", "HEAD"]

价值匹配
授权策略中的大多数字段都支持以下所有匹配模式:

精确匹配:精确的字符串匹配。
前缀匹配:以 . 结尾的字符串""。例如,"test.abc." 匹配"test.abc.com"、“test.abc.com.cn”、“test.abc.org"等。
后缀匹配:以”“. 例如,”.abc.com" 匹配"eng.abc.com"、“test.eng.abc.com"等。
存在匹配:用于指定任何内容,但不能为空。要指定必须存在的字段,请使用fieldname: ["”]格式。这与未指定字段不同,这意味着匹配任何内容,包括空的。
有几个例外。例如,以下字段仅支持完全匹配:

部分下的key字段when
该部分ipBlocks下source
部分下的ports字段to
以下示例策略允许访问带有/test/前缀或/info后缀的路径。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: tester
  namespace: default
spec:
  selector:
    matchLabels:
      app: products
  action: ALLOW
  rules:
  - to:
    - operation:
        paths: ["/test/*", "*/info"]

排除匹配

为了匹配字段中、字段中、字段中等否定条件, Istio支持notValues排除匹配。如果请求路径不是 ,则以下示例需要有效的请求主体,该主体源自 JWT 身份验证。因此,该策略从 JWT 身份验证中排除对路径的请求:whennotIpBlockssourcenotPortsto/healthz/healthz

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: disable-jwt-for-healthz
  namespace: default
spec:
  selector:
    matchLabels:
      app: products
  action: ALLOW
  rules:
  - to:
    - operation:
        notPaths: ["/healthz"]
    from:
    - source:
        requestPrincipals: ["*"]

以下示例拒绝对/admin没有请求主体的请求的路径的请求:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: enable-jwt-for-admin
  namespace: default
spec:
  selector:
    matchLabels:
      app: products
  action: DENY
  rules:
  - to:
    - operation:
        paths: ["/admin"]
    from:
    - source:
        notRequestPrincipals: ["*"]

allow-nothing,deny-all和allow-all政策
以下示例显示了一个ALLOW不匹配的策略。如果没有其他ALLOW策略,由于“默认拒绝”行为,请求将始终被拒绝。

请注意,“默认拒绝”行为仅适用于工作负载具有至少一个授权策略的ALLOW操作。

allow-nothing从策略开始并逐步添加更多ALLOW策略以打开对工作负载的更多访问权限是一种良好的安全实践。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-nothing
spec:
  action: ALLOW
  # the rules field is not specified, and the policy will never match.

以下示例显示了DENY明确拒绝所有访问的策略。即使有另一个ALLOW策略允许该请求,它也将始终拒绝该请求,因为该DENY策略优先于该ALLOW策略。如果您想暂时禁用对工作负载的所有访问,这很有用。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
spec:
  action: DENY
  # the rules field has an empty rule, and the policy will always match.
  rules:
  - {}

以下示例显示了ALLOW允许完全访问工作负载的策略。它将使其他ALLOW策略无用,因为它将始终允许请求。如果您想暂时公开对工作负载的完全访问权限,这可能会很有用。CUSTOM请注意,由于和DENY政策,该请求仍可能被拒绝。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-all
spec:
  action: ALLOW
  # This matches everything.
  rules:
  - {}

自定义条件
您还可以使用该when部分指定其他条件。例如,下面的AuthorizationPolicy定义包括一个条件request.headers[version]是"v1"or “v2”。在这种情况下,键是request.headers[version],它是 Istio 属性中的一个条目 request.headers,它是一个映射。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: httpbin
 namespace: foo
spec:
 selector:
   matchLabels:
     app: httpbin
     version: v1
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["cluster.local/ns/default/sa/sleep"]
   to:
   - operation:
       methods: ["GET"]
   when:
   - key: request.headers[version]
     values: ["v1", "v2"]

条件页面key上列出了条件支持的值。

已认证和未认证的身份
如果要使工作负载可公开访问,则需要将该 source部分留空。这允许来自所有(经过身份验证和未经身份验证的)用户和工作负载的来源,例如:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: httpbin
 namespace: foo
spec:
 selector:
   matchLabels:
     app: httpbin
     version: v1
 action: ALLOW
 rules:
 - to:
   - operation:
       methods: ["GET", "POST"]

要仅允许经过身份验证的用户,请改为设置principals为"*",例如:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: httpbin
 namespace: foo
spec:
 selector:
   matchLabels:
     app: httpbin
     version: v1
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["*"]
   to:
   - operation:
       methods: ["GET", "POST"]

在纯 TCP 协议上使用 Istio 授权
Istio 授权支持使用任何普通 TCP 协议的工作负载,例如 MongoDB。在这种情况下,您可以按照与 HTTP 工作负载相同的方式配置授权策略。不同之处在于某些字段和条件仅适用于 HTTP 工作负载。这些字段包括:

request_principals授权策略对象的源部分中的字段
授权策略对象的操作部分中的,hosts和methods字段paths
支持的条件列在 条件页面中。如果您对 TCP 工作负载使用任何仅 HTTP 字段,Istio 将忽略授权策略中的仅 HTTP 字段。

假设您在 port 上有一个 MongoDB 服务27017,以下示例配置一个授权策略以仅允许bookinfo-ratings-v2 Istio 网格中的服务访问 MongoDB 工作负载。

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: mongodb-policy
  namespace: default
spec:
 selector:
   matchLabels:
     app: mongodb
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["cluster.local/ns/default/sa/bookinfo-ratings-v2"]
   to:
   - operation:
       ports: ["27017"]

对双向 TLS 的依赖
Istio 使用双向 TLS 将一些信息从客户端安全地传递到服务器。在授权策略中使用以下任何字段之前,必须启用双向 TLS:

部分下的principalsandnotPrincipals字段source
部分下的namespacesandnotNamespaces字段source
自source.principal定义条件
自source.namespace定义条件
请注意,强烈建议始终将这些字段与严格的双向TLS 模式一起使用,PeerAuthentication以避免当纯文本流量与许可的双向 TLS 模式一起使用时潜在的意外请求拒绝或策略绕过。

架构体系与组件概念

Istio 服务网格在逻辑上分为数据平面和控制平面。

数据平面由一组部署为sidecar的智能代理 (Envoy)组成。这些代理调解和控制微服务之间的所有网络通信。他们还收集和报告所有网状流量的遥测数据。
控制平面管理和配置代理以路由流量。

service mesh:istio全_第7张图片
其中的pilot是istio与kubernetes的唯一结合点,更加强化了kubernetes的服务,流量和全等功能。pilot连接kubernetes的apiserver,监听用户等输入的各种规则,生成对应的代理规则下发到各个envoy。

Istio 使用 Envoy代理的扩展版本。Envoy 是用 C++ 开发的高性能代理,用于调解服务网格中所有服务的所有入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。

Envoy 代理被部署为服务的 sidecar,在逻辑上使用 Envoy 的许多内置功能增强服务,例如:

动态服务发现
负载均衡
TLS 终止
HTTP/2 和 gRPC 代理
断路器
健康检查
基于百分比的流量拆分的分阶段部署
故障注入
丰富的指标
这种 sidecar 部署允许 Istio 执行策略决策并提取丰富的遥测数据,这些遥测数据可以发送到监控系统以提供有关整个网格行为的信息。
Sidecar 代理模型还允许您将 Istio 功能添加到现有部署中,而无需重新架构或重写代码。
Envoy 代理支持的一些 Istio 功能和任务包括:
流量控制功能:通过丰富的 HTTP、gRPC、WebSocket 和 TCP 流量路由规则来实施细粒度的流量控制。
网络弹性功能:设置重试、故障转移、断路器和故障注入。
安全和身份验证功能:强制执行安全策略并强制执行通过配置 API 定义的访问控制和速率限制。
基于 WebAssembly 的可插拔扩展模型,允许自定义策略实施和网状流量遥测生成。
Istiod 提供服务发现、配置和证书管理。

Istiod 将控制流量行为的高级路由规则转换为特定于 Envoy 的配置,并在运行时将它们传播到 Sidecar。Pilot 抽象了特定于平台的服务发现机制,并将它们合成为任何符合 Envoy API的 Sidecar都可以使用的标准格式。

Istio 可以支持多种环境的发现,例如 Kubernetes 或 VM。

您可以使用 Istio 的 流量管理 API 来指示 Istiod 改进 Envoy 配置,以对服务网格中的流量进行更精细的控制。

Istiod安全性通过内置身份和凭证管理实现强大的服务到服务和最终用户身份验证。您可以使用 Istio 升级服务网格中的未加密流量。使用 Istio,运营商可以基于服务身份而不是相对不稳定的第 3 层或第 4 层网络标识符来执行策略。此外,您可以使用Istio 的授权功能 来控制谁可以访问您的服务。

Istiod 充当证书颁发机构 (CA) 并生成证书以允许在数据平面中进行安全的 mTLS 通信。

集群模型

您的应用程序的工作负载实例(pod)在一个或多个集群中运行. 为了隔离、性能和高可用性,您可以将集群限制在可用区和区域中。
生产系统,根据他们的要求,可以在跨越多个区域或区域的多个集群中运行,利用云负载均衡器来处理诸如局部性和区域性或区域性故障转移之类的事情。
在大多数情况下,集群代表配置和端点发现的边界。例如,每个 Kubernetes 集群都有一个 API Server,它管理集群的配置以及提供服务和服务端点pod 启动或关闭时的信息。由于 Kubernetes 在每个集群的基础上配置此行为,此方法有助于限制由错误配置引起的潜在问题。
在 Istio 中,您可以配置单个服务网格以跨越任意数量的集群。

单集群
在最简单的情况下,您可以将 Istio 网格限制为单个cluster集群通常在 单个网络上运行,但它因基础设施提供商而异。单个集群和单个网络模型包括一个控制平面,这形成了最简单的 Istio 部署。

iaas:基础设置即服务,基础设置提供商,提供cpu内存等资源,操作系统等我们自己规划
paas: 平台即服务,提供操作系统,各种语言开发环境,机子来开发,不用调环境
saas: 软件即服务,直接给你提供开发好的软件,当然你更改不了软件功能

service mesh:istio全_第8张图片

多个集群
您可以将单个网格配置为包含多个集群. 用一个 多集群在单个网格内部署提供了超出单个集群部署的以下功能:
故障隔离和故障转移:cluster-1出现故障,故障转移到cluster-2.
位置感知路由和故障转移:将请求发送到最近的服务。
各种控制平面模型:支持不同级别的可用性。
团队或项目隔离:每个团队运行自己的一组集群。
service mesh:istio全_第9张图片
多集群部署为您提供更高程度的隔离和可用性,但增加了复杂性。如果您的系统具有高可用性要求,您可能需要跨多个专区和区域的集群。您可以在单个集群中进行金丝雀配置更改或新的二进制版本,其中配置更改只会影响少量用户流量。此外,如果集群出现问题,您可以暂时将流量路由到附近的集群,直到您解决问题为止。
您可以根据网络和云提供商支持的选项配置集群间通信。例如,如果两个集群位于同一个底层网络上,您只需配置防火墙规则即可启用跨集群通信。
在多集群网格中,所有服务默认共享,根据概念命名空间相同. 流量管理规则 提供对多集群流量行为的细粒度控制。

具有多个集群的 DNS
当客户端应用程序向某个主机发出请求时,它必须首先对主机名执行 DNS 查找以获取 IP 地址,然后才能继续请求。在 Kubernetes 中,驻留在集群中的 DNS 服务器通常会根据配置的Service定义处理此 DNS 查找。

Istio 使用 DNS 查找返回的虚拟 IP 在请求服务的活动端点列表中进行负载平衡,同时考虑到任何 Istio 配置的路由规则。Istio 使用 Kubernetes Service/Endpoint或 IstioServiceEntry来配置其主机名到工作负载 IP 地址的内部映射。(istio根据各路由规则和kubernetes的dns解或istio的serviceentry来配置请求的主机名与工作服在实例的映射)

当您有多个集群时,这种两层命名系统会变得更加复杂。**Istio 本质上是多集群感知的,但 Kubernetes 不是(今天)。**因此,客户端集群必须具有服务的 DNS 条目才能成功进行 DNS 查找,并成功发送请求。即使客户端集群中没有运行该服务的 pod 实例也是如此。

为确保 DNS 查找成功,您必须将 Kubernetes 部署Service到使用该服务的每个集群。这确保无论请求来自何处,它都会通过 DNS 查找并交给 Istio 以进行正确的路由。这也可以通过 IstioServiceEntry而不是 Kubernetes 来实现Service。但是,ServiceEntry没有配置 Kubernetes DNS 服务器。这意味着 DNS 需要手动配置或使用自动化工具(例如 Istio CoreDNS 插件)进行配置。

正在进行一些有助于简化 DNS 故事的工作:

DNS sidecar 代理 支持在 Istio 1.8 中可供预览。这通过 sidecar 为所有工作负载提供 DNS 拦截,允许
Istio 代表应用程序执行 DNS 查找。

Admiral是一个 Istio
社区项目,提供了许多多集群功能。如果您需要支持多网络拓扑,则跨多个集群大规模管理此配置具有挑战性。Admiral
对此配置持固执己见,并提供跨集群的自动配置和同步。

Kubernetes 多集群服务 是一个 Kubernetes 增强提案 (KEP),它定义了一个用于将服务导出到多个集群的
API。这有效地将整个服务可见性和 DNS 解析的责任推clusterset到了 Kubernetes 上。在 Istio
中构建支持层的工作也在进行中MCS,这将允许 Istio 与任何云供应商MCS 控制器一起工作,甚至充当MCS整个网格的控制器。

网络模型
Istio 使用了一个简化的定义网络参考工作负载实例具有直接可达性。例如,默认情况下,单个集群中的所有工作负载实例都在同一网络上。
许多生产系统需要多个网络或子网来实现隔离和高可用性。Istio 是支持跨越各种网络拓扑的服务网格。这种方法允许您选择适合您现有网络拓扑的网络模型。

单一网络
在最简单的情况下,服务网格在单个完全连接的网络上运行。在单一网络模型中,所有工作负载实例无需 Istio 网关即可直接相互访问。
单个网络允许 Istio 以统一的方式跨网格配置服务消费者,并能够直接处理工作负载实例。
service mesh:istio全_第10张图片
(记住service也是逻辑概念,逻辑上将一组pod归类,endpoint,实际上与envoy部署一块的是pod的主容器)

多个网络
您可以部署跨越多个网络的单个服务网格;这种配置称为多网络。

多个网络提供了超出单个网络的以下功能:

服务端点的重叠 IP 或 VIP 范围
跨越行政边界
容错
网络地址的缩放
符合需要网络分段的标准

在此模型中,不同网络中的工作负载实例只能通过一个或多个Istio 网关相互访问。Istio 使用分区服务发现为消费者提供不同的视图服务端点s。视图取决于消费者的网络。
service mesh:istio全_第11张图片
此解决方案需要通过网关公开所有服务(或子集)。云供应商可能会提供不需要在公共互联网上公开服务的选项。如果这样的选项存在并且满足您的要求,那么它可能是最佳选择。

为了确保多网络场景下的安全通信,Istio 仅支持使用 Istio 代理与工作负载进行跨网络通信。这是因为 Istio 通过 TLS 传递在 Ingress Gateway 上公开服务,这使得 mTLS 可以直接用于工作负载。但是,没有 Istio 代理的工作负载可能无法参与与其他工作负载的相互身份验证。出于这个原因,Istio 过滤了无代理服务的网络外端点。

控制平面模型
Istio 网格使用控制平面配置网格内工作负载实例之间的所有通信。工作负载实例连接到控制平面实例以获取其配置。
在最简单的情况下,您可以在单个集群上使用控制平面运行网格。
service mesh:istio全_第12张图片
像这样的集群,有自己的本地控制平面,被称为主集群.
多集群部署还可以共享控制平面实例。在这种情况下,控制平面实例可以驻留在一个或多个主集群中。没有自己的控制平面的集群称为远程集群.
service mesh:istio全_第13张图片
为了支持多集群网格中的远程集群,主集群中的控制平面必须可以通过稳定的 IP(例如,集群 IP)访问。**对于跨网络的集群,这可以通过通过 Istio 网关公开控制平面来实现。云供应商可能会提供内部负载平衡器等选项来提供此功能,而不会将控制平面暴露在公共互联网上。**如果这样的选项存在并且满足您的要求,那么它可能是最佳选择。

在具有多个主集群的多集群部署中,每个主集群从驻留在同一集群中的 Kubernetes API Server接收其配置(即Service和ServiceEntry、 等)。DestinationRule因此,每个主集群都有一个独立的配置源。这种跨主集群的重复配置在推出更改时确实需要额外的步骤。大型生产系统可以使用工具(例如 CI/CD 系统)自动执行此过程,以管理配置推出。

无需在网格内的主集群中运行控制平面,完全由远程集群组成的服务网格可以由一个 外部控制平面. 这提供了控制平面部署与构成网格的数据平面服务的隔离管理和完全分离。
service mesh:istio全_第14张图片
云供应商的托管控制平面是外部控制平面的典型示例。
为了实现高可用性,您应该跨集群、专区或区域部署多个控制平面。
service mesh:istio全_第15张图片
该模型具有以下优点:

提高可用性:如果控制平面变得不可用,则中断范围仅限于由该控制平面管理的集群中的工作负载。

配置隔离:您可以在一个集群、专区或区域中进行配置更改,而不会影响其他。

受控推出:您对配置推出有更细粒度的控制(例如,一次一个集群)。您还可以在由给定主集群控制的网格的子部分中进行金丝雀配置更改。

选择性服务可见性:您可以将服务可见性限制在网格的一部分,帮助建立服务级别隔离。例如,管理员可以选择将HelloWorld服务部署到集群 A,而不是集群 B。任何从集群 B 调用的尝试HelloWorld都将导致 DNS 查找失败。

以下列表按可用性对控制平面部署示例进行排名:

每个区域一个集群(最低可用性)
每个区域多个集群
每个区域一个集群
每个区域多个集群
每个集群(最高可用性)

具有多个控制平面的端点发现

Istio 控制平面通过为每个代理提供服务端点列表来管理网格内的流量。为了在多集群场景中进行这项工作,每个控制平面都必须观察每个集群中 API 服务器的端点。

要为集群启用端点发现,管理员会生成一个 remote secret并将其部署到网格中的每个主集群。remote secret包含凭据,授予对集群中 API 服务器的访问权限。 然后,控制平面将连接并发现集群的服务端点,从而为这些服务启用跨集群负载平衡。
service mesh:istio全_第16张图片
默认情况下,Istio 会在每个集群的端点之间平均负载均衡请求。在跨越地理区域的大型系统中,可能需要使用局部负载平衡 来使流量保持在同一区域或区域中。
在某些高级场景中,可能不需要跨集群进行负载平衡。例如,在蓝/绿部署中,您可以将不同版本的系统部署到不同的集群。在这种情况下,每个集群都有效地作为一个独立的网格运行。这种行为可以通过以下几种方式实现:
不要在集群之间交换远程机密。这提供了集群之间最强的隔离。
使用VirtualService和DestinationRule禁止在两个版本的服务之间进行路由。
在任何一种情况下,都会阻止跨集群负载平衡。外部流量可以使用外部负载均衡器路由到一个集群或另一个集群。
service mesh:istio全_第17张图片
身份和信任模型
在服务网格中创建工作负载实例时,Istio 会为工作负载分配一个身份.
证书颁发机构 (CA) 创建并签署用于验证网格中使用的身份的证书。您可以使用为该身份创建并签署证书的 CA 的公钥来验证消息发送者的身份。信任包是 Istio 网格使用的所有 CA 公钥的集合。使用网格的信任包,任何人都可以验证来自该网格的任何消息的发送者。

网格内的信任
在单个 Istio 网格中,Istio 确保每个工作负载实例都有一个代表其自身身份的适当证书,以及识别网格和任何联合网格中的所有身份所必需的信任包。CA 为这些身份创建并签署证书。该模型允许网格中的工作负载实例在通信时相互验证。
service mesh:istio全_第18张图片
网格之间的信任
要启用具有不同 CA 的两个网格之间的通信,您必须交换网格的信任包。Istio 不提供任何工具来跨网格交换信任包。您可以手动或使用诸如SPIFFE Trust Domain Federation 之类的协议自动交换信任包。将信任包导入网格后,您可以为这些身份配置本地策略。
service mesh:istio全_第19张图片
网格模型
Istio 支持将您的所有服务放在一个 网,或将多个网格联合在一起,也称为多网格.
单网
最简单的 Istio 部署是单个网格。在网格中,服务名称是唯一的。例如,命名空间中只能有一项服务mysvc的foo 名称。此外,工作负载实例共享一个共同的身份,因为服务帐户名称在命名空间中是唯一的,就像服务名称一样。

单个网格可以跨越一个或多个集群和 一个或多个网络。在网格中, 命名空间用于租赁。

多个网格
多个网格部署源于网状联邦.
多个网格提供了超出单个网格的以下功能:

组织边界:业务线
default 服务名称或命名空间重用:命名空间的多种不同用途
更强的隔离:将测试工作负载与生产工作负载隔离开来
您可以启用网格间通信网状联邦. 联合时,每个网格都可以公开一组服务和身份,所有参与的网格都可以识别。
service mesh:istio全_第20张图片
为避免服务命名冲突,您可以为每个网格分配一个全局唯一的 网格 ID,以确保每个服务的完全限定域名 (FQDN) 是不同的。

联合两个不共享相同网格的网格时 信任域, 你必须联合 身份并信任它们之间的捆绑。有关更多详细信息,请参阅网格之间的信任部分。

租赁模式
在 Istio 中,租户是一组用户,它们共享一组已部署工作负载的公共访问权限和特权。租户可用于在不同团队之间提供一定程度的隔离。

您可以配置租户模型以满足以下组织隔离要求:

安全
政策
容量
成本
表现
Istio 支持三种类型的租户模型:

命名空间租用
集群租用
网状租赁
命名空间租用
一个集群可以在多个团队之间共享,每个团队使用不同的命名空间。您可以授予团队仅将其工作负载部署到给定命名空间或一组命名空间的权限。

默认情况下,来自多个命名空间的服务可以相互通信,但您可以通过有选择地选择哪些服务公开给其他命名空间来增加隔离。您可以为公开的服务配置授权策略,以将访问权限仅限于适当的调用者。
service mesh:istio全_第21张图片
命名空间租用可以扩展到单个集群之外。使用多个集群时,默认情况下,每个集群中同名的命名空间被认为是同一个命名空间。例如,Service B在集群的Team-1命名空间中West和Service B在集群的 Team-1命名空间中East引用同一个服务,Istio 合并它们的端点以进行服务发现和负载均衡。
service mesh:istio全_第22张图片
集群租用
Istio 支持使用集群作为租户单元。在这种情况下,您可以为每个团队提供一个专用集群或一组集群来部署他们的工作负载。集群的权限通常仅限于拥有它的团队成员。您可以为更精细的控制设置各种角色,例如:

集群管理员
开发商
要将集群租用与 Istio 一起使用,您需要为每个团队的集群配置自己的集群控制平面,允许每个团队管理自己的配置。或者,您可以使用 Istio 将一组集群实现为单个租户,使用远程集群或多个同步主集群. 有关详细信息,请参阅控制平面模型。

网状租赁
在多网格部署中网状联邦,每个网格都可以作为隔离单位。
service mesh:istio全_第23张图片
由于不同的团队或组织操作每个网格,服务命名很少是不同的。例如,集群命名空间中的 aService C和foo集群命名空间中的 Team-1服务Service C将不会引用同一个服务。最常见的示例是 Kubernetes 中的场景,其中许多团队将他们的工作负载部署到 命名空间。fooTeam-2default

当每个团队都有自己的网格时,跨网格通信遵循多网格模型中描述的概念。

虚拟机架构
Istio 的虚拟机支持允许将 Kubernetes 集群外部的工作负载连接到网格。这使得遗留应用程序或不适合在容器化环境中运行的应用程序能够获得 Istio 为在 Kubernetes 内运行的应用程序提供的所有好处。

对于在 Kubernetes 上运行的工作负载,Kubernetes 平台本身提供了各种功能,例如服务发现、DNS 解析和健康检查,这些功能在虚拟机环境中经常缺失。Istio 为在虚拟机上运行的工作负载启用了这些功能,此外还允许这些工作负载利用 Istio 功能,例如双向 TLS (mTLS)、丰富的遥测和高级流量管理功能。
service mesh:istio全_第24张图片
service mesh:istio全_第25张图片
Service association
Istio 提供了两种机制来表示虚拟机工作负载:

WorkloadGroup表示共享公共属性的一组逻辑虚拟机工作负载。这类似于DeploymentKubernetes 中的 a。
WorkloadEntry表示虚拟机工作负载的单个实例。这类似于PodKubernetes 中的 a。
创建这些资源 (WorkloadGroup和WorkloadEntry) 不会导致配置任何资源或运行任何虚拟机工作负载。相反,这些资源只是引用这些工作负载并告知 Istio 如何正确配置网格。

将虚拟机工作负载添加到网格时,您需要创建一个WorkloadGroup充当每个WorkloadEntry实例的模板:

apiVersion: networking.istio.io/v1alpha3
kind: WorkloadGroup
metadata:
  name: product-vm
spec:
  metadata:
    labels:
      app: product
  template:
    serviceAccount: default
  probe:
    httpGet:
      port: 8080

配置虚拟机并将其添加到网格WorkloadEntry后, Istio 控制平面将自动创建相应的虚拟机。例如:

apiVersion: networking.istio.io/v1beta1
kind: WorkloadEntry
metadata:
  annotations:
    istio.io/autoRegistrationGroup: product-vm
  labels:
    app: product
  name: product-vm-1.2.3.4
spec:
  address: 1.2.3.4
  labels:
    app: product
  serviceAccount: default

此WorkloadEntry资源描述了工作负载的单个实例,类似于 Kubernetes 中的 pod。当工作负载从网格中移除时,WorkloadEntry资源将被自动移除。此外,如果在资源中配置了任何探针WorkloadGroup,Istio 控制平面会自动更新关联WorkloadEntry实例的健康状态。

为了让消费者可靠地调用您的工作负载,建议声明一个Service关联。这允许客户端访问稳定的主机名,例如product.default.svc.cluster.local,而不是临时 IP 地址。DestinationRule这也使您能够通过 API和API在 Istio 中使用高级路由功能VirtualService。

任何 Kubernetes 服务都可以通过分别与 pod 和WorkloadEntry标签匹配的 selector 字段,透明地跨 pod 和虚拟机选择工作负载。

例如,Service命名product由 aPod和 a组成WorkloadEntry:
service mesh:istio全_第26张图片
使用此配置,请求product将在 Pod 和虚拟机工作负载实例之间进行负载平衡。

域名系统
Kubernetes 在 Pod 中为Service名称提供 DNS 解析,允许 Pod 通过稳定的主机名轻松地相互通信。

对于虚拟机扩展,Istio 通过DNS 代理提供了类似的功能。此功能将所有 DNS 查询从虚拟机工作负载重定向到 Istio 代理,该代理维护主机名到 IP 地址的映射。

因此,运行在虚拟机上的工作负载可以透明地调用Services(类似于 pod),而无需任何额外的配置。

性能和可扩展性

Istio 可以轻松创建具有丰富路由、负载平衡、服务到服务身份验证、监控等的已部署服务网络 - 所有这些都无需更改应用程序代码。Istio 致力于以最小的资源开销提供这些优势,并旨在支持具有高请求率的超大型网格,同时增加最小的延迟。

Istio 数据平面组件 Envoy 代理处理流经系统的数据。Istio 控制平面组件 Istiod 配置数据平面。数据平面和控制平面具有不同的性能问题。

Istio 1.13.2 性能总结
Istio负载测试网格由1000 个服务和2000个 sidecar 组成,每秒有 70,000 个网格范围的请求。使用 Istio 1.13.2 运行测试后,我们得到以下结果:

Envoy 代理每秒通过代理的每 1000 个请求使用0.35 个 vCPU和40 MB 内存。
Istiod 使用1 个 vCPU和 1.5 GB 内存。
Envoy 代理在第 90 个百分位延迟上增加了 2.65 毫秒。

控制平面性能
Istiod 根据用户编写的配置文件和系统的当前状态配置 sidecar 代理。在 Kubernetes 环境中,自定义资源定义 (CRD) 和部署构成了系统的配置和状态。Istio 配置对象(如网关和虚拟服务)提供用户编写的配置。为了生成代理的配置,Istiod 处理来自 Kubernetes 环境的组合配置和系统状态以及用户编写的配置。

控制平面支持数千种服务,分布在数千个具有相似数量的用户创作的虚拟服务和其他配置对象的 Pod 中。Istiod 的 CPU 和内存需求随配置数量和可能的系统状态而变化。CPU 消耗与以下因素成比例:

部署速度发生变化。
配置更改的速率。
连接到 Istiod 的代理数量。
然而,这部分本质上是水平可扩展的。

启用命名空间隔离后,单个 Istiod 实例可以支持 1000 个服务、2000 个 Sidecar、1 个 vCPU 和 1.5 GB 内存。您可以增加 Istiod 实例的数量,以减少配置到达所有代理所需的时间。

数据平面性能
数据平面性能取决于许多因素,例如:

客户端连接数
目标请求率
请求大小和响应大小
代理工作线程数
协议
CPU内核
代理过滤器的数量和类型,特别是遥测 v2 相关过滤器。
延迟、吞吐量以及代理的 CPU 和内存消耗是作为上述因素的函数来测量的。

CPU和内存
由于 sidecar 代理在数据路径上执行额外的工作,因此会消耗 CPU 和内存。在 Istio 1.13.2 中,代理每秒每 1000 个请求消耗大约 0.5 个 vCPU。

代理的内存消耗取决于代理持有的总配置状态。大量的侦听器、集群和路由会增加内存使用量。在启用了命名空间隔离的大型命名空间中,代理消耗大约 50 MB 的内存。

由于代理通常不会缓冲通过的数据,因此请求率不会影响内存消耗。

潜伏
由于 Istio 在数据路径上注入了一个 sidecar 代理,因此延迟是一个重要的考虑因素。Istio 添加的每个功能也会增加代理内部的路径长度,并可能影响延迟。

Envoy 代理在向客户端发送响应后收集原始遥测数据。为请求收集原始遥测数据所花费的时间不计入完成该请求所花费的总时间。但是,由于工作人员正忙于处理请求,工作人员不会立即开始处理下一个请求。此过程会增加下一个请求的队列等待时间,并影响平均延迟和尾延迟。实际的尾部延迟取决于流量模式。

Istio 1.13.2 的延迟
在网格内部,请求会遍历客户端代理,然后是服务器端代理。在 Istio 1.13.2 的默认配置(即带有遥测 v2 的 Istio)中,两个代理分别比基线数据平面延迟增加了大约 1.7 ms 和 2.7 ms 到第 90 和第 99 个百分位数的延迟。 我们使用该协议的Istio 基准测试获得了这些结果,使用 16 个客户端连接、2 个代理工作者和启用了双向 TLS,每秒 1000 个请求的负载为 1 kB。http/1.1

申请要求

Istio 为应用程序提供了大量功能,而对应用程序代码本身几乎没有影响。许多 Kubernetes 应用程序可以部署在支持 Istio 的集群中,而无需进行任何更改。但是,在部署支持 Istio 的应用程序时,可能需要特别考虑 Istio 的 sidecar 模型的一些含义。本文档描述了 Istio 启用的这些应用注意事项和具体要求。

吊舱要求
要成为网格的一部分,Kubernetes pod 必须满足以下要求:

服务关联:一个 pod 必须至少属于一个 Kubernetes 服务,即使该 pod 没有暴露任何端口。如果一个 pod 属于多个Kubernetes 服务,则这些服务不能为不同的协议使用相同的端口号,例如 HTTP 和 TCP。

应用程序 UID:确保您的 pod不会以用户 ID (UID) 值的用户身份运行应用程序,1337因为1337它是为边车代理保留的。

NET_ADMIN和NET_RAW功能:如果在您的集群 中强制执行pod 安全策略 ,并且除非您使用Istio CNI 插件,否则您的 pod 必须具有 允许的和功能。Envoy 代理的初始化容器需要这些功能。NET_ADMINNET_RAW

要检查您的 pod 是否允许NET_ADMINandNET_RAW功能,您需要检查他们的 服务帐户是否 可以使用允许NET_ADMINandNET_RAW功能的 pod 安全策略。如果您尚未在 pod 的部署中指定服务帐户,则 pod 将使用default其部署命名空间中的服务帐户运行。

要列出服务帐户的功能,请将和替换 为以下命令中的值:

for psp in $(kubectl get psp -o jsonpath="{range .items[*]}{@.metadata.name}{'\n'}{end}"); do if [ $(kubectl auth can-i use psp/$psp --as=system:serviceaccount:<your namespace>:<your service account>) = yes ]; then kubectl get psp/$psp --no-headers -o=custom-columns=NAME:.metadata.name,CAPS:.spec.allowedCapabilities; fi; done

例如,要检查命名空间default中的服务帐户default,请运行以下命令:
for psp in $(kubectl get psp -o jsonpath="{range .items[*]}{@.metadata.name}{'\n'}{end}"); do if [ $(kubectl auth can-i use psp/$psp --as=system:serviceaccount:default:default) = yes ]; then kubectl get psp/$psp --no-headers -o=custom-columns=NAME:.metadata.name,CAPS:.spec.allowedCapabilities; fi; done

如果您在服务帐户的允许策略之一的功能列表中看到NET_ADMIN和NET_RAW/或*,则您的 pod 有权运行 Istio init 容器。否则,您将需要提供权限。

带有应用和版本标签的 Pod :我们建议 在使用 Kubernetes 部署的 Pod 的规范中添加显式app标签和标签。和标签将上下文信息添加到 Istio 收集的指标和遥测数据中。versionDeploymentappversion

app标签:每个部署都应该有一个 app具有有意义值的不同标签。app标签用于在分布式跟踪中添加上下文信息。

标签:该version标签表示与特定部署对应的应用程序的版本。

命名服务端口:可以选择命名服务端口以明确指定协议。有关详细信息,请参阅协议选择。
Istio 使用的端口
Istio sidecar 代理(Envoy)使用以下端口和协议。

为避免与 sidecar 发生端口冲突,应用程序不应使用 Envoy 使用的任何端口
service mesh:istio全_第27张图片
Istio 控制平面 (istiod) 使用以下端口和协议。
service mesh:istio全_第28张图片
服务器优先协议
一些协议是“服务器优先”协议,这意味着服务器将发送第一个字节。这可能会对 PERMISSIVEmTLS 和自动协议选择产生影响。

这两个功能都通过检查连接的初始字节来确定协议,这与服务器优先协议不兼容。

为了支持这些情况,请按照显式协议选择步骤将应用程序的协议声明为TCP.

已知以下端口通常承载服务器优先协议,并自动假定为TCP:
service mesh:istio全_第29张图片
因为 TLS 通信不是服务器优先的,所以 TLS 加密的服务器优先流量将与自动协议检测一起使用,只要您确保所有经过 TLS 嗅探的流量都已加密:

为服务器配置mTLS模式STRICT。这将对所有请求强制执行 TLS 加密。
为服务器配置mTLS模式DISABLE。这将禁用 TLS 嗅探,允许使用服务器优先协议。
配置所有客户端发送TLS流量,通常通过DestinationRule或依赖自动 mTLS。
将您的应用程序配置为直接发送 TLS 流量。
出站流量
为了支持 Istio 的流量路由功能,离开 Pod 的流量可能与未部署 Sidecar 时的流量不同。

对于基于 HTTP 的流量,流量根据Host标头进行路由。Host如果目标 IP 和标头未对齐,这可能会导致意外行为。例如,请求 likecurl 1.2.3.4 -H "Host: httpbin.default"将被路由到httpbin服务,而不是1.2.3.4.

对于非基于 HTTP 的流量(包括 HTTPS),Istio 无法访问Host标头,因此路由决策基于服务 IP 地址。

这意味着直接调用 pod(例如,curl ),而不是服务,将不匹配。虽然流量可以通过,但它不会获得完整的 Istio 功能,包括 mTLS 加密、流量路由和遥测。

环境,部署前提

一个k8s集群(kubeadm方式),版本1.20.10,三台centos7,内核3.10,域名解析,istio版本1.13.2,cri采用docker

部署潜规则:
部署更少的集群
跨少量大型集群部署 Istio,而不是大量小型集群。最好的做法是使用命名空间租用 来管理大型集群,而不是将集群添加到您的部署中。按照这种方法,您可以在每个专区或区域的一个或两个集群中部署 Istio。然后,您可以在每个区域或专区的一个集群上部署一个控制平面,以提高可靠性。
在您的用户附近部署集群
在全球范围内的部署中包括集群,以便在地理上接近最终用户。邻近性可帮助您的部署具有低延迟。
跨多个可用区部署
在您的部署中包括跨多个可用区域和每个地理区域内的专区的集群。这种方法限制了 故障域您的部署,并帮助您避免全局故障。

部署

service mesh:istio全_第30张图片

**下载指定版本:
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.13.2 sh -**

或者wget下载:
wget https://github.com/istio/istio/releases/download/1.13.2/istio-1.13.2-linux-amd64.tar.gz
(无非就是下载下这个tar.gz)(架构一般都是用amd的64位)
tar -xz -f istioctl-1.13.2-linux-amd64.tar.gz
cd istio-1.13.2

在这里插入图片描述

cp bin/istioctl /usr/local/bin/istioctl

在这里插入图片描述

这里使用istioctl方式部署istio:

istioctl install --set profile=demo -y

这里一般下载比较慢,下载失败可以:
kubectl get pod -n istio-sytem
如果错误是因为镜像拉去,可以自己kubectl describe pod,找到image,手动下载。
如果是readline等探针的错误,可以考虑删除那个pod让自己重启
最好网络环境好点,不然会有各类探针超时和mount失败等问题
其次探针的问题,可以自己考虑它是否对业务容器有影响,实在不行就拿掉探针。
(出错重来可以删除命名空间)

方便修改你也可以(提前准备名称空间istio-system):

istioctl manifest generate --set profile=demo  > generated-manifest.yaml
vim 该yaml,修改ingress和engress的探针延迟长一点,或者直接注释掉探针)
kubectl apply -f generated-manifest.yaml

在这里插入图片描述
不仅要是running,还得是1/1

卸载istio命令istioctl manifest generate --set profile=demo | kubectl delete -f -
default命名空间下的pod会自动注入sidecar
kubectl label namespace default istio-injection=enabled

部署bookinfo云应用示例:

service mesh:istio全_第31张图片
多语言,多服务,服务网格,微服务架构
在istio中运行这个微服务架构的bokkinfo应用,不用对应用本身进行修改,直接配置部署即可
注入envoy sidecar:
service mesh:istio全_第32张图片
所有的微服务与exvoy sidecar集成在一起,进出应用的流量会被exvoy sidecar劫持,即微服务均放在了istio的数据面,可以使用istio的控制面来对应用进行服务路由,遥测数据采集,策略实施

先安装metrics-server:

wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.0/components.yaml
 vim` components.yaml
 kubectl apply -f components.yaml
 

在这里插入图片描述
service mesh:istio全_第33张图片
在这里插入图片描述
修改api-server参数:

[root@m1 ~]# cd /etc/kubernetes/manifests/
[root@m1 manifests]# ls
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml
[root@m1 manifests]# vim kube-apiserver.yaml
[root@m1 manifests]# vim kube-apiserver.yaml
[root@m1 manifests]# mv kube-apiserver.yaml /tmp
[root@m1 manifests]# mv /tmp/kube-apiserver.yaml .

service mesh:istio全_第34张图片
注意重启api静态pod,也会连带重启controller,etcd,schduler等,需等待一会
(遇到问题的pod可以kubectl delete pod --force --grace-period=0删除)

service mesh:istio全_第35张图片
(安装metrics-server和修改api参数最好在部署istio之前就准备好,网上不少朋友反映bookinfo部署出错,一般是这两个原因,所以这里在提一下)

部署bookinfo应用
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
实在不行就手动注入,同时可以指定命名空间,其实就是在命令行中覆盖yaml的(默认)命名空间,命名空间最好提前配置好:kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml)
无非就是加入sidecar,另外istio的各种注入方式后续会单独做个详细的解释总结

kubectl get pod

[root@master istio-1.13.2]# kubectl get svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
details       ClusterIP   10.109.98.199    <none>        9080/TCP   56m
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP    5h54m
productpage   ClusterIP   10.109.236.5     <none>        9080/TCP   56m
ratings       ClusterIP   10.100.1.99      <none>        9080/TCP   56m
reviews       ClusterIP   10.104.135.222   <none>        9080/TCP   56m

注意是2/2
service mesh:istio全_第36张图片

(自动注入不了也可以考虑手动注入)
(bookinfo删除或清理重新安装可以执行samples/bookinfo/platform/kube/cleanup.sh)

现在应用的服务都部署成功并启动了,如果我们需要在集群外部访问,就需要添加一个 istio gateway,gateway 相当于 k8s 的 ingress controller 和 ingress,它为 HTTP/TCP 流量配置负载均衡,通常在服务网格边缘作为应用的 ingress 流量管理。(gateway这里可以理解成一种规则,交由网格的envoy来实现)
(网格而言进出门户一般是ingressgateway服务和engressgateway服务对应的pod)

[root@master istio-1.13.2]# kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created

[root@master istio-1.13.2]# kubectl get gateway
NAME               AGE
bookinfo-gateway   21s
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   46s

cat samples/bookinfo/networking/bookinfo-gateway.yaml

kind: Gateway
metadata:
  name: bookinfo-gateway   #这个gateway规则有用户写入到k8s,pilot会根据这个规则生成代理规则下发到各个envoy
spec:
  selector:
    istio: ingressgateway # use istio default controller,其实就是选择哪个ingressgateway_pod,通过label选择
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:   #对应要被访问的服务,ip或servicename或FQDN完整域名,多数情况最后映射成k8s的cluster ip。的任意服务的80端口的http请求,即客户端要访问的服务,可以是还不在istio里注册的服务,k8s的service,k8s外部的服务应该也行,后续验证下,进入的流量一般访问网格内或k8s内的service就够了
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService   ##这个virtualservice由用户写入到k8s,pilot会根据这个virtaulservice生成代理规则下发到各个envoy
metadata:
  name: bookinfo
spec:
  hosts:   #任意的服务,同样,跟gateway的host的概念一样,一个虚拟服务包括了这些服务
  - "*"   **#hosts 字段列举虚拟服务的主机——即用户指定的目标或是路由规则设定的目标,这是客户端向服务发送请求时使用的一个或多个地址。**
  #简单理解就是这些路由规则对应的service,可以是不在网格中注册的服务,用service的servicename区访问service流量应该也会被envoy劫持,所以访问网格内或引入到网格内的服务的流量都会有网格来转发
  gateways:
  - bookinfo-gateway   #使用这个gateway,virtualservice将流量转到bookinfo-gateway这个gateway规则
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products   #前缀
    route:   #这里没有设置subnet,即destinationroute,也是可以转发流量的,destinationroute一般理解成对virtualservice的转发的流量进行更
细粒化的控制
    - destination:
        host: productpage   #因为绑定了gateway,所以是有gateway_pod将流量转到productpage这个svc对应的pod,注意这里是不经过svc,是直接podip访问到提供服务的pod(这里gateway和inress和很相似)
        port:
          number: 9080   #转发到服务的这个端口,这里的svc在default下,是在istio里注册了的

#浏览器访问istio-ingressgateway这个svc的nodeport31225,即svc的port(clusterport80),在由kube-proxy转发到到pod的targetport8080,在到容器的containerport(一般和targetport一样) ---->  此时到了ingressgateway的pod,这个pod也是service mesh的一个特殊的proxy,envoy以sidecar形式部署的proxy,pilot是istio架构和kubernetes架构的结合点,pilot会根据从kubernetes那得到的相关的元数据和>用户用kubectl,istioctl创建的virtualservice和gateway等规则生成代理规则,下发到各个的proxy,所以ingressgateway-pod会根据刚刚>上面创建的virtualservice和gateway生成路由规则   ---->   gateway-pod将流量转发到如productpage这个svc的实际提供服务的pod

vim黏贴是为避免错误,可以先set paste
yaml中的-只是列表声明效果,层级主要是通过对齐缩进来体现

service mesh:istio全_第37张图片

这是istio这个service mesh的进出口:
在这里插入图片描述

[root@master istio-1.13.2]# kubectl describe svc istio-ingressgateway -n istio-system
Name:                     istio-ingressgateway
Namespace:                istio-system
Labels:                   app=istio-ingressgateway
                          install.operator.istio.io/owning-resource=unknown
                          install.operator.istio.io/owning-resource-namespace=istio-system
                          istio=ingressgateway
                          istio.io/rev=default
                          operator.istio.io/component=IngressGateways
                          operator.istio.io/managed=Reconcile
                          operator.istio.io/version=1.13.2
                          release=istio
Annotations:              <none>
Selector:                 app=istio-ingressgateway,istio=ingressgateway
Type:                     LoadBalancer
IP Families:              <none>
IP:                       10.100.81.91
IPs:                      10.100.81.91
Port:                     status-port  15021/TCP
TargetPort:               15021/TCP
NodePort:                 status-port  31555/TCP
Endpoints:                10.244.2.2:15021
Port:                     http2  80/TCP
TargetPort:               8080/TCP
NodePort:                 http2  31060/TCP
Endpoints:                10.244.2.2:8080
Port:                     https  443/TCP
TargetPort:               8443/TCP
NodePort:                 https  31232/TCP
Endpoints:                10.244.2.2:8443
Port:                     tcp  31400/TCP
TargetPort:               31400/TCP
NodePort:                 tcp  30799/TCP
Endpoints:                10.244.2.2:31400
Port:                     tls  15443/TCP
TargetPort:               15443/TCP
NodePort:                 tls  32342/TCP
Endpoints:                10.244.2.2:15443
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

底下对应的endpoint列表,有pod提供实际的服务,也是envoy,可以理解成整个service mesh中特殊的代理sidecar

service mesh:istio全_第38张图片
LoadBalancer 类型的服务,实际上是用来对接云服务厂商的,如果我们没有对接云服务厂商的话,可以将这里类型改成 NodePort,但是这样当访问我们的服务的时候就需要加上 nodePort 端口了:

kubectl edit svc istio-ingressgateway -n istio-system

selector:
    app: istio-ingressgateway
    istio: ingressgateway
  sessionAffinity: None
  type: NodePort

[root@master istio-1.13.2]# kubectl get svc -n istio-system
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
istio-egressgateway    ClusterIP   10.96.74.49      <none>        80/TCP,443/TCP                                                               136m
istio-ingressgateway   NodePort    10.100.81.91     <none>        15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   136m
istiod                 ClusterIP   10.110.124.198   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP

通过 http://:/productpage 从集群外部访问 Bookinfo 应用:
service mesh:istio全_第39张图片
刷新页面可以看到 Book Reviews 发生了改变(红色、黑色的星形或者没有显示),因为每次请求会被路由到到了不同的 Reviews 服务版本去。

上面我们是安装的 Istio 的核心组件,此外 Istio 还和一些遥测应用做了集成,遥测能帮助我们了解服务网格的结构、展示网络的拓扑结构、分析网格的健康状态等,对于分布式微服务应用也是非常重要的。

部署 Kiali、Prometheus、Grafana 以及 Jaeger:

kubectl apply -f samples/addons

[root@master istio-1.13.2]# kubectl get pod -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
grafana-69ccf87b97-pdbqv                1/1     Running   0          7m6s
istio-egressgateway-7967d4d9d9-z86qm    1/1     Running   0          168m
istio-ingressgateway-5464d78db9-mn94n   1/1     Running   0          168m
istiod-56dbb6c987-dz9gt                 1/1     Running   0          169m
jaeger-648f4f4ddb-9m9b4                 1/1     Running   0          7m5s
kiali-5ccffdfd97-t9ptn                  1/1     Running   0          7m5s
prometheus-f675ff955-h7wgv              2/2     Running   0          7m4s

开启kiali,使用个组件遥测分布式微服务
istioctl dashboard kiali(也可以修改kiali为nodeport类型)
http://localhost:20001/kiali

service mesh:istio全_第40张图片
service mesh:istio全_第41张图片
service mesh:istio全_第42张图片
导入些流量,这里去访问k8s的svc,这个svc刚好又是注册在istio下的(前面的default命名空间的自动注入)

[root@master ~]# kubectl get svc -o wide
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE     SELECTOR
details       ClusterIP   10.109.98.199    <none>        9080/TCP   175m    app=details
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP    7h53m   <none>
productpage   ClusterIP   10.109.236.5     <none>        9080/TCP   175m    app=productpage
ratings       ClusterIP   10.100.1.99      <none>        9080/TCP   175m    app=ratings
reviews       ClusterIP   10.104.135.222   <none>        9080/TCP   175m    app=reviews
[root@master ~]# while true ; do curl http://10.109.236.5:9080/productpage; done

service mesh:istio全_第43张图片
service mesh:istio全_第44张图片
网格内,不用经过istio的ingressgateway,直接流量转发到对应的pod

samples/bookinfo/platform/kube/bookinfo.yaml:
通常的 Kubernetes 定义的
Deployment 和 Service 的资源清单文件,只是在部署时使用 istioctl
kube-inject(或者通过对命名空间打上自动注入的标签)对这个文件定义的 Pod 注入了 sidecar 代理

samples/bookinfo/networking/bookinfo-gateway.yaml:
定义了这个bookinfo应用的外部访问入口 gateway,以及应用内部 productpage 服务的 VirtualService
规则,而其他内部服务的访问规则还没有被定义。(对于入口流量,可以将virtualservice理解成这个应用的虚拟的最外层比较好,virtualservice再讲流量转发到gateway这个路由规则(其实就是envoy)

现在访问应用界面并刷新,会看到 Reviews 有时不会显示评分,有时候会显示不同样式的评分,这是因为后面有3个不同的 Reviews 服务版本,而没有配置该服务的更细粒化的路由规则的情况下,该服务的几个实例会被随机访问到,有的版本服务会进一步调用 Ratings 服务,有的不会。

流量管理:

流量管理的两个资源对象:
VirtualService(虚拟服务):用来在 Istio 中定义路由规则,控制流量路由到服务上的各种行为。

DestinationRule(目标规则):虚拟服务视定义将流量如何路由到指定目标地址,然后使用目标规则来配置该目标的流量,在评估虚拟服务路由规则之后,目标规则将应用于流量的真实目标地址。

上面的概念简述比较官方化,晦涩,下面从实操中来理解,这里秩序大概的概念就是virtualservice可以将流量转发到destinationrule,由destinationrulej进行更加细粒化的流量管理

VirtualService:

虚拟服务(VirtualService)和目标规则(Destination Rule)是 Istio
流量路由功能的关键对象,虚拟服务配置如何在 Istio 内将请求路由到服务,每个虚拟服务包含一组路由规则,Istio
会按定义的顺序来评估它们,Istio 将每个指定的请求匹配到虚拟服务指定的实际目标地址。在网格中可以有多个虚拟服务,也可以没有
使用虚拟服务,你可以为一个或多个主机名指定流量行为,在虚拟服务中使用路由规则,告诉 Envoy如何发送虚拟服务的流量到合适的目标,路由目标地址可以是同一服务的不同版本,也可以是完全不同的服务。

一个典型的使用场景是将流量发送到指定服务的不同版本。客户端会将虚拟服务视为一个单一实体,将请求发送至虚拟服务主机,然后 Envoy
根据虚拟服务规则把流量路由到不同的版本。比如把 20% 的流量路由到新版本 或 将这些用户的请求路由到版本
2,可以创建一个金丝雀发布,然后逐步增加发送到新版本服务的流量百分比。流量路由完全独立于实例部署,所以实现新版本服务的实例可以根据流量的负载来伸缩,完全不影响流量路由。相比之下,Kubernetes则只支持基于实例缩放的流量分发,这会更复杂。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews   #virtualservice的名称
spec:
  hosts: # 列出VirtualService的hosts,可以是IP、DNS名称、FQDN或*,叫虚拟主机,其实就是提供的服务,与nginx的虚拟主机概念,一级多服务器的概念类似,无非就是请求流量要请求的服务,也可以使用通配符(“*”)前缀,创建一组匹配所有服务的路由规则。虚拟服务的 hosts 字段实际上不必是 Istio 服务注册的一部分,它只是虚拟的目标地址,这样可以为没有路由到网格内部的虚拟主机建模。
  - reviews
  http: # 在下面配置VirtualService的路由规则,指定符合哪些规则的流量打到哪些Destination,支持HTTP/1.1,HTTP2,及gRPC等协议
  - match: # 指定具体的匹配规则,可以有0个或多个匹配条件
    - headers:
        end-user:   #匹配http请求头的内容
          exact: jason   #exact,精确匹配,end-user=jason
    route:   #将符合上述的匹配规则的流量进行路由
    - destination: # 指定满足规则后将流量打到哪个具体的Destination
        host: reviews   #符合该条件的流量的实际目标地址,与虚拟服务的 hosts 不同,destination 的 host 必须是存在于 Istio 服务注册中心的实际目标地址,否则 Envoy 不知道该将请求发送到哪里,但实际上,对于非网格内的服务,可以通过服务入口(比如engressgateway)将流量转发到网格外的服务,这里实例因为运行在k8s的环境,所以hosts一般都是k8s的服务(reviews就是网格自动注入的default命名空间下)
        subset: v2   #subset用来关联virtualservice和destinationrule
  - route:  # 流量规则按从上到下的优先级去匹配,若不满足上述规则时,进入该默认规则
    - destination:
        host: reviews
        subset: v3

#路由规则是按从上到下的顺序选择的,虚拟服务中定义的第一条规则有最高优先级
#一般建议提供一个默认的无条件或基于权重的规则作为每一个虚拟服务的最后一条规则,从而确保流经虚拟服务的流量至少能够匹配到一条路由规则。

DestinationRule

与虚拟服务一样,DestinationRule(目标规则)也是 Istio流量路由功能的关键部分,我们可以将虚拟服务看成将流量如何路由到指定目标地址,然后使用目标规则来配置该目标的流量。在评估虚拟服务路由规则之后,目标规则将应用于流量的“真实”目标地址。
可以使用目标规则来指定命名的服务子集,例如按版本为所有指定服务的实例分组,然后可以在虚拟服务的路由规则中使用这些服务子集来控制到服务不同实例的流量。目标规则还允许你在调用整个目的服务或特定服务子集时定制Envoy 的流量策略,比如负载均衡模型、TLS 安全模式或熔断器设置。

默认情况下,Istio 使用轮询的负载均衡策略,实例池中的每个实例依次获取请求。Istio 同时支持如下的负载均衡模型,可以在
DestinationRule 中为流向某个特定服务或服务子集的流量指定这些模型。
随机:请求以随机的方式转到池中的实例。
权重:请求根据指定的百分比转到实例。
最少请求:请求被转到最少被访问的实例。

负载均衡策略,熔断设置主要在destinationrule中配置

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-destination-rule
spec:
  host: my-svc
  trafficPolicy:
    loadBalancer:
      simple: RANDOM  # 随机的策略
  subsets:
  - name: v1
    labels:
      version: v1   #匹配pod的标签用
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN  # 轮询,除了定义子集之外,目标规则对于所有子集都有默认的流量策略,而对于具体的子集,则可以使用特定于子集的策略来覆盖它。
  - name: v3
    labels:
      version: v3

#创建了3个子集

service mesh:istio全_第45张图片
service mesh:istio全_第46张图片

逻辑理清

不同服务版本访问规则:

samples/bookinfo/networking/virtual-service-reviews-v3.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews   #意思是将流量转发到reviews服务的第三个版本的子集,用后续的destination来引流
        subset: v3


[root@master istio-1.13.2]# kubectl apply -f  samples/bookinfo/networking/virtual-service-reviews-v3.yaml
virtualservice.networking.istio.io/reviews created
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS         AGE
bookinfo   ["bookinfo-gateway"]   ["*"]         25h
reviews                           ["reviews"]   37s

service mesh:istio全_第47张图片
此时我们去刷新应用的页面,发现访问 Reviews 失败了,因为没有还没创建destinationrule

搭配有: virtualserive+destinationtrule
virtualservice+gateway
virtualservice+gateway+destinationrule

DestinationRule 对象是 VirtualService 路由生效后,配置应用与请求的策略集,用来将 VirtualService 中指定的 subset 与对应的 Pod 关联起来。
samples/bookinfo/networking/destination-rule-all.yaml:

apiVersion: networking.istio.io/v2alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews   #destinationrule的名称
spec:
  host: reviews   #reviews这个服务
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3   #第三子集
    labels:
      version: v3   #匹配提供reviews服务的实际的对应版本的pod
---
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: ratings
spec:
  host: ratings
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v2-mysql
    labels:
      version: v2-mysql
  - name: v2-mysql-vm
    labels:
      version: v2-mysql-vm
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: details

spec:
  host: details
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

DestinationRule 中定义了 subsets 集合,其中 labels 就和我们之前 Service 的 labelselector 一样是去匹配 Pod 的 labels 标签的,比如我们这里 subsets 中就包含一个名为 v3 的 subset,而这个 subset 匹配的就是具有 version=v3 这个 label 标签的 Pod 集合,前面我们创建的 Bookinfo 中也有这个标签的 Pod(同一个名称空间)

[root@master istio-1.13.2]# kubectl get pod -l version=v3
NAME                          READY   STATUS    RESTARTS   AGE
reviews-v3-84779c7bbc-wzksq   2/2     Running   0          27h
[root@master istio-1.13.2]# kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml
destinationrule.networking.istio.io/productpage created
destinationrule.networking.istio.io/reviews created
destinationrule.networking.istio.io/ratings created
destinationrule.networking.istio.io/details created
[root@master istio-1.13.2]# kubectl get destinationrule
NAME          HOST          AGE
details       details       6m33s
productpage   productpage   6m34s
ratings       ratings       6m33s
reviews       reviews       6m33s

此时再访问应用就成功了,多次刷新页面发现 Reviews 始终都展示的是 v3 版本(带红色星的)的 Ratings
service mesh:istio全_第48张图片

基于权重的服务访问规则

[root@master istio-1.13.2]# kubectl delete virtualservice reviews
virtualservice.networking.istio.io "reviews" deleted
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   25h

samples/bookinfo/networking/virtual-service-reviews-80-20.yaml:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 80   #为不同的路由的设置权重,路由到不同的subset子集,注意百分比不是绝对准确
    - destination:
        host: reviews
        subset: v2
      weight: 20

[root@master istio-1.13.2]# kubectl apply -f  samples/bookinfo/networking/virtual-service-reviews-80-20.yaml
virtualservice.networking.istio.io/reviews created
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS         AGE
bookinfo   ["bookinfo-gateway"]   ["*"]         26h
reviews                           ["reviews"]   9s

多次刷新浏览器页面体验权重带来的流量路由

基于请求内容的服务访问规则

[root@master istio-1.13.2]# kubectl delete virtualservice reviews
virtualservice.networking.istio.io "reviews" deleted
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   26h

samples/bookinfo/networking/virtual-service-reviews-jason-v2-v3.yaml:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - match:   #定义个match匹配规则
    - headers:   #这里匹配http的请求头的内容
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:   #默认路由
    - destination:
        host: reviews
        subset: v3

这个 VirtualService 对象定义了对 reviews 服务访问的 match 规则,意思是如果当前请求的 header 中包含 jason 这个用户信息,则只会访问到 v2 的 reviews 这个服务版本,即都带黑星的样式,如果不包含该用户信息,则都直接将流量转发给 v3 这个 reviews 的服务。

service mesh:istio全_第49张图片
密码可以为空
多次刷新测试流量的转发情况(可以搭配kiali查看)
此时再回去刷新页面,发现一直都是黑星的 Reviews 版本(v2)被访问到了,注销退出后再访问,此时又一直是红星的版本(v3)被访问了。
说明我们基于 headers->end-user->exact:jason 的控制规则生效了。在 productpage 服务调用 reviews 服务时,登录的情况下会在 header 中带上用户信息,通过 exact 规则匹配到相关信息后,流量被引向了上面配置的 v2 版本中。

match 块里的条件是需要同时满足才算匹配成功的,如下面是 url 前缀和端口都必须都满足才算成功:
match列表匹配任意一个即可

- match:
    - uri:
        prefix: "/wpcatalog"
      port: 443
来理清网格流量抓发的过程,用最简单的来讲解,其他家进来的virtualservice,destination,gateway都一样的套路

[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS   AGE
bookinfo   ["bookinfo-gateway"]   ["*"]   26h
[root@master istio-1.13.2]# kubectl get gateway
NAME               AGE
bookinfo-gateway   26h

[root@master istio-1.13.2]# kubectl get svc -n istio-system istio-ingressgateway
NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   NodePort   10.100.81.91   <none>        15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   28h
当浏览器输入节点ip:31060/productpage时,就是访问istio-ingressgateway这个service对应的clusterip的80端口

kubectl describe svc -n istio-system istio-ingressgateway:
Endpoints:                10.244.2.2:15443   #对应的pod列表,就是转发到这个pod

kubectl get pod -n istio-system -o wide:
istio-ingressgateway-5464d78db9-mn94n   1/1     Running   0          29h   10.244.2.2    node2   <none>           <none>




kubectl get svc -n istio-system istio-ingressgateway -o yaml:
ports:
  - name: status-port
    nodePort: 31555
    port: 15021
    protocol: TCP
    targetPort: 15021
  - name: http2
    nodePort: 31060
    port: 80  #clusterip 的端口
    protocol: TCP
    targetPort: 8080   #pod的端口
kube-proxy将流量转发到pod的targetport上

kubectl get pod -n istio-system istio-ingressgateway-5464d78db9-mn94n -o yaml:
ports:
    - containerPort: 15021
      protocol: TCP
    - containerPort: 8080   #pod将流量转发到容器的这个端口,containerPort不设置的话默认和targetPort一样
      protocol: TCP
    - containerPort: 8443
      protocol: TCP
    - containerPort: 31400
      protocol: TCP
    - containerPort: 15443
      protocol: TCP
    - containerPort: 15090
(大体上看通过ingreassgateway这个大门进入网格的流量通过ingressgateway pod来转发给下一个envoy)

kubectl get virtualservice bookinfo -o yaml:
spec:
  gateways:
  - bookinfo-gateway   #使用这个gateway,virtualservice的默认gateway(如果这里不指定gateway,默认回去使用存在的唯一一个gateway规则)
  hosts:
  - '*'   #包含任意service意思
  http:
  - match:
    - uri:
        exact: /productpage   #节点ip:31060/productpage匹配这里
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage   #将流量发送到这个productpage服务
        port:
          number: 9080   #发送到这个productpage的9080端口

kubectl get gateway bookinfo-gateway -o yaml:
spec:
  selector:
    istio: ingressgateway   #使用这个标签的ingressgateway pod
  servers:   #开放的服务列表
  - hosts:
    - '*'   #包含任意的service,fqdn,gateway的发布地址,设置一个或多个通过这个gateway暴露的服务service
    port:
      name: http
      number: 80   #服务对外监听的port,此网关通过80端口接收http流量(ingressgateway这个service的80端口)
      protocol: HTTP
#不同的服务或不同的协议流量可以设置不同的ingressgateway的port

即这里是任意servicename:80
virtualservice使用这个gateway,流量由这个gateway转发进网格,gateway类似ingress,最后是由envoy将流量直接转发到对应的productpage的对应的pod

[root@master istio-1.13.2]# kubectl get svc productpage
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
productpage   ClusterIP   10.109.236.5   <none>        9080/TCP   29h
理论上gateway会将流量转发到productpage服务的这个端口,实际上不是,gateway就像ingress,直接将流量转发到productpage这个端口对应的pod

[root@master istio-1.13.2]# kubectl get svc -n istio-system --show-labels istio-ingressgateway
NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE   LABELS
istio-ingressgateway   NodePort   10.100.81.91   <none>        15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   29h   app=istio-ingressgateway,install.operator.istio.io/owning-resource-namespace=istio-system,install.operator.istio.io/owning-resource=unknown,istio.io/rev=default,istio=ingressgateway,operator.istio.io/component=IngressGateways,operator.istio.io/managed=Reconcile,operator.istio.io/version=1.13.2,release=istio
注意istio那个标签

kubectl get pod -n istio-system istio-ingressgateway-5464d78db9-mn94n --show-labels
NAME                                    READY   STATUS    RESTARTS   AGE   LABELS
istio-ingressgateway-5464d78db9-mn94n   1/1     Running   0          29h   app=istio-ingressgateway,chart=gateways,heritage=Tiller,install.operator.istio.io/owning-resource=unknown,istio.io/rev=default,istio=ingressgateway,operator.istio.io/component=IngressGateways,pod-template-hash=5464d78db9,release=istio,service.istio.io/canonical-name=istio-ingressgateway,service.istio.io/canonical-revision=latest,sidecar.istio.io/inject=false

virtualservice定义了初步规则没使用指定的gateway,这gateway就像是进入服务或者说进入网格的第一扇大门,所以直接servicename访问注册在网格内和被拉入网格内的service也一样经过virtualservice,destination,gateway(外部进入网格的流量)来进入网格,流量被网格劫持。
时刻留意所有的envoy依靠上面这三样东西生成规则(pilot生成)

浏览器31060映射成ingressgateway_servicename:80,会转发ingressgateway的pod(envoy)->这是会匹配virtualservice规则,virtualservice(可能也有destinationrule进行很细粒化的流量规则指定)规定使用bookinfo-gateway这个gateway规则,并指定了路由规则,指定匹配的productpage转发到productpage这个serive->gateway的hosts匹配该流量要访问的service(这里是*)和port匹配上ingressgateway_servicename:80,由这个服务的80端口来接收流量,kube-proxy转发到自己的pod.由这个网关的大门exvoy进行下一步转发,gateway指定使用的ingressgateway的service或envoy的标签->由ingressgateway的pod进行转发
重点:看待整个流量转发过程时,最好先第一步将virtualservice看成网格最外层,容易理解,virtualservice在将流量引流到gateway规则,该规则制定了ingressgateway

另外实践发现,如果没有gateway规则,至少外部通过访问ingressgateway大门来范文微服务会有问题,vitualservice不指定gateway,gateway规则有刚好一个,应该会默认使用这个gateway规则。
对于不是直接访问ingressgateway的ip或servicename加port的流量,但访问的也是网格内注册的或被拉入网格管理范围的service,会与virtualservice的hosts列表进行匹配,这么看来还是一样会经过ingressgateway进入网格

故障注入

对于一个系统,尤其是一个复杂的系统,重要的不是故障会不会发生,而是什么时候发生。故障处理对于开发人员和测试人员来说都特别耗费时间和精力:对于开发人员来说,他们在开发代码时需要用20%的时间写80%的主要逻辑,然后留出80%的时间处理各种非正常场景;对于测试人员来说,除了需要用80%的时间写20%的异常测试项,更要用超过80%的时间执行这些异常测试项,并构造各种故障场景,尤其是那种理论上才出现的故障,让人苦不堪言。

故障注入是一种评估系统可靠性的有效方法,例如异常处理、故障恢复等。只有当系统的所有服务都经过故障测试且具备容错能力时,整个应用才健壮可靠。故障注入从方法上来说有编译期故障注入和运行期故障注入,前者要通过修改代码来模拟故障,后者在运行阶段触发故障。Istio
的故障注入就是在网格中对特定的应用层协议进行故障注入,这样,基于 Istio 的故障注入就可以模拟出应用的故障场景了。

延迟故障注入

[root@master ~]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS         AGE
bookinfo   ["bookinfo-gateway"]   ["*"]         38h
reviews                           ["reviews"]   9h
[root@master ~]# kubectl delete virtualservice reviews
virtualservice.networking.istio.io "reviews" deleted

samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v1

samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    fault:   #对这个符合条件http流量进行故障注入
      delay:   #延迟故障
        percentage:
          value: 100.0   #百分比,这里是对这个http流量百分百注入故障
        fixedDelay: 7s   #延迟7秒
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

这个 VirtualService 定义了一个在 jason 登录的情况下,访问 ratings 服务的 100% 的 7s 访问延迟。
reviews服务会调用ratings服务,这里又对ratings的符合条件流量进行故障注入

前面我们知道,Bookinfo 这个示例 productpage 服务调用 reviews,reviews 的不同版本会对 ratings 进行不同的调用,其中 reviews-v1 不调用 ratings,reviews-v2 和 reviews-v3 会调用 ratings,并做不同样式的渲染。注意 reviews:v2 服务对 ratings 服务的调用具有 10 秒的硬编码连接超时。因此,尽管引入了 7 秒的延迟,我们仍然期望端到端的流程是没有任何错误的。

[root@master istio-1.13.2]# kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
virtualservice.networking.istio.io/reviews created
[root@master istio-1.13.2]# kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
virtualservice.networking.istio.io/ratings created
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS         AGE
bookinfo   ["bookinfo-gateway"]   ["*"]         38h
ratings                           ["ratings"]   12s
reviews                           ["reviews"]   23s

通过浏览器打开 Bookinfo 应用,使用用户 jason 登录到 /productpage 页面。我们期望的是 Bookinfo 主页在大约 7 秒钟加载完成并且没有错误,但是 Reviews 部分显示了一个错误消息:Sorry, product reviews are currently unavailable for this book.
service mesh:istio全_第50张图片
F12查看详情:
看到页面加载实际上用了大约6s,按照预期,我们引入的 7s 延迟不会影响到 reviews 服务,因为 reviews 和 ratings 服务间的超时被硬编码为 10 秒,但实际上在 productpage 和 reviews 服务之间也有一个 3s 的硬编码的超时,再加 1 次重试,一共 6s,所以 productpage 对 reviews 的调用在 6s 后提前超时并抛出错误了。(简单地说就是product在3秒的硬编码延迟后需要调用revies,当由于对ratings延迟故障的注入reviews服务是失败的,所以product服务会报错,这里是product应用的代码实现将异常抛出,显示给用户)(硬编码延迟可以参考探针的延迟来理解,主要是为了保证以来服务的已经启动)
这种类型的错误在不同的团队独立开发不同的微服务的企业应用程序中是可能会出现的,Istio 的故障注入规则可以帮助我们识别此类异常,而不会影响最终用户。
请注意,此次故障注入限制为仅影响用户 jason,如果你以任何其他用户身份登录,则不会遇到任何延迟。

插入下软件架构的上下游方便理解:
水流也分上下游,水有上游流到下游,下游的水依赖上游,下游出问题对上游水不影响,但上游水出问题比如水被地形截断了,那下游的水就受影响了,
软件应用架构也可以这样看待,就是判断出应用间谁对谁有重要性的依赖

中断访问故障注入

测试微服务弹性的另一种方法是引入 HTTP abort 故障

samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    fault:
      abort:
        percentage:
          value: 100.0
        httpStatus: 500   #符合条件的被路由到v1,并对百分百的http请求流量注入中断故障,并返回一个500的错误
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

[root@master istio-1.13.2]# kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
virtualservice.networking.istio.io/ratings configured   #直接对ratings进行修改
[root@master istio-1.13.2]# kubectl get virtualservice
NAME       GATEWAYS               HOSTS         AGE
bookinfo   ["bookinfo-gateway"]   ["*"]         41h
ratings                           ["ratings"]   141m
reviews                           ["reviews"]   141m

现在我们回到 BookInfo 应用,登录 jason,刷新页面,有时候可以很快就看到 Rating 服务不可用的提示信息

如果注销用户 jason,我们将看到 /productpage 为除 jason 以外的其他用户调用了 reviews:v1(完全不调用 ratings),因此,不会看到任何错误消息,不会显示星标的图形。
service mesh:istio全_第51张图片

外部接入服务治理

随着系统越来越复杂,服务间的依赖也越来越多,当实现一个完整的功能时,只靠内部的服务是无法支撑的。且不说当前云原生环境下的复杂应用,就是在多年前的企业软件开发环境下,自己开发的程序也需要搭配若干中间件才能完成。

如下图所示,4个服务组成一个应用,后端依赖一个数据库服务,这就需要一种机制能将数据库服务接入并治理。在当前的云环境下,这个数据库可以是部署的一个外部服务,也可以是一个 RDS 的云服务。在托管的云平台上搭建的应用一般都会访问数据库、分布式缓存等中间件服务。

云关系型数据库(RDS)是一种稳定可靠、可弹性伸缩的在线数据库服务,支持MySQL、SQL Server、PostgreSQL、PPAS(Postgre Plus Advanced Server,高度兼容Oracle数据库)、MariaDB等引擎,并且提供了容灾、备份、恢复、监控、迁移等方面的全套解决方案。
service mesh:istio全_第52张图片
关于这种第三方服务的管理,专门有一种 Open Service Broker API 来实现第三方软件的服务化,这种 API 通过定义 Catalog、Provisioning、Updating、Binding、Unbinding 等标准接口接入服务,在和 Kubernetes 结合的场景下,使用 Service Catalog 的扩展机制可以方便地在集群中管理云服务商提供的第三方服务,如下图所示。(catalog目录)
service mesh:istio全_第53张图片
数据库可以是一个普通的外部服务部署,也可以是云服务商提供的RDS的云服务

Istio 可以方便地对网格内的服务访问进行治理,那么如何对这种网格外的服务访问进行治理呢?从实际需求上看,对一个数据库访问进行管理,比对两个纯粹的内部服务访问进行管理更重要。在 Istio 中是通过一个 ServiceEntry 的资源对象将网格外的服务注册到网格上,然后像对网格内的普通服务一样对网格外的服务访问进行治理的。

使用 istio 样例中的 sleep 应用来验证改功能:

##################################################################################################
# Sleep service
##################################################################################################
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sleep
---
apiVersion: v1
kind: Service
metadata:
  name: sleep
  labels:
    app: sleep
    service: sleep
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /etc/sleep/tls
          name: secret-volume
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true
---
这其实就是一个简单的应用,通过 Deployment 进行控制,通过 Service 暴露服务

[root@master istio-1.13.2]# kubectl apply -f samples/sleep/sleep.yaml
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created

Istio 有一个安装选项,global.outboundTrafficPolicy.mode,它配置 sidecar 对外部服务(那些没有在 Istio 的内部服务注册中定义的服务)的处理方式。如果这个选项设置为 ALLOW_ANY,Istio 代理允许调用未知的服务,如果这个选项设置为 REGISTRY_ONLY,那么 Istio 代理会阻止任何没有在网格中定义的 HTTP 服务或 service entry 的主机,ALLOW_ANY 是默认值,不控制对外部服务的访问。但是这种方式有一个缺点,即丢失了对外部服务流量的 Istio 监控和控制。

运行以下命令以确认 meshConfig.outboundTrafficPolicy.mode 设置为 ALLOW_ANY 或被省略:
[root@master istio-1.13.2]# kubectl get istiooperator installed-state -n istio-system -o jsonpath='{.spec.meshConfig.outboundTrafficPolicy.mode}'
[root@master istio-1.13.2]#
kubectl get istiooperator installed-state -n istio-system -o yaml:
查看global.outboundTrafficPolicy.mode字段的设置

正常上面的命令应该输出 ALLOW_ANY 或没有任何输出,这里为了测试 ServiceEntry 功能,我们将其更改为 REGISTRY_ONLY 模式,直接使用 istioctl install 命令指定配置重新安装即可:

istioctl install --set profile=demo --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY -y

[root@master istio-1.13.2]# istioctl install --set profile=demo --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY -y
✔ Istio core installed
✔ Istiod installed
✔ Egress gateways installed
✔ Ingress gateways installed
✔ Installation complete                                                                       Making this installation the default for injection and validation.

Thank you for installing Istio 1.13.  Please take a few minutes to tell us about your install/upgrade experience!  https://forms.gle/pzWZpAvMVBecaQ9h9
[root@master istio-1.13.2]# kubectl get istiooperator installed-state -n istio-system -o jsonpath='{.spec.meshConfig.outboundTrafficPolicy.mode}'
REGISTRY_ONLY[root@master istio-1.13.2]#

global.outboundTrafficPolicy.mode字段设置了istio的sidecar对于外部服务(那些没有在 Istio 的内部服务注册中定义的服务)的处理方式,ALLOW_ANY,Istio 代理允许调用未知的服务,不控制对外部服务的访问,但丢失了对外部服务流量的 Istio 监控和控制,设置为 REGISTRY_ONLY,那么 Istio 代理会阻止任何没有在网格中定义的 HTTP 服务或 service entry 的主机(重点,未在网格中注册)

修改完成后我们就可以接管外部 URL 的流量了。现在我们进入上面创建的 sleep 应用容器内部执行一些测试操作:
service mesh:istio全_第54张图片
可以看到会返回 502 信息,因为该域名不在当前的服务网格中,因为现在我们不允许访问服务网格外部的 URL,服务网格中对未知的服务请求会丢弃。这就需要我们来创建一个 ServiceEntry 对象,将外部的访问服务引入到服务网格中来。
(这是网格内的应用去访问网格外部的情况)

下面的规则定义了一个允许访问 edition.cnn.com 外部服务的 ServiceEntry(就是将外部服务拉入网格内):
# vim cnn-service-entry.yaml
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: cnn
spec:
  hosts:
  - edition.cnn.com
  ports:
  - number: 80
    name: http-port
    protocol: HTTP
  - number: 443
    name: https
    protocol: HTTPS
  resolution: DNS


[root@master istio-1.13.2]# kubectl apply -f  cnn-service-entry.yaml
serviceentry.networking.istio.io/cnn created
[root@master istio-1.13.2]# kubectl get serviceentry
NAME   HOSTS                 LOCATION   RESOLUTION   AGE
cnn    ["edition.cnn.com"]              DNS          9s
[root@master istio-1.13.2]# kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com
HTTP/1.1 301 Moved Permanently
server: envoy
retry-after: 0
content-length: 0
cache-control: public, max-age=300
location: https://edition.cnn.com/
accept-ranges: bytes
date: Tue, 05 Apr 2022 06:40:44 GMT
via: 1.1 varnish
set-cookie: countryCode=CN; Domain=.cnn.com; Path=/; SameSite=Lax
set-cookie: stateCode=GD; Domain=.cnn.com; Path=/; SameSite=Lax
set-cookie: geoData=jiangmen|GD|529000|CN|AS|800|broadband|22.570|113.080; Domain=.cnn.com; Path=/; SameSite=Lax
x-served-by: cache-nrt18336-NRT
x-cache: HIT
x-cache-hits: 0
x-envoy-upstream-service-time: 206

HTTP/2 200
content-type: text/html; charset=utf-8
x-servedbyhost: ::ffff:127.0.0.1
access-control-allow-origin: *
cache-control: max-age=60
content-security-policy: default-src 'self' blob: https://*.cnn.com:* http://*.cnn.com:* *.cnn.io:* *.cnn.net:* *.turner.com:* *.turner.io:* *.ugdturner.com:* courageousstudio.com *.vgtf.net:*; script-src 'unsafe-eval' 'unsafe-inline' 'self' *; style-src 'unsafe-inline' 'self' blob: *; child-src 'self' blob: *; frame-src 'self' *; object-src 'self' *; img-src 'self' data: blob: *; media-src 'self' data: blob: *; font-src 'self' data: *; connect-src 'self' data: *; frame-ancestors 'self' https://*.cnn.com:* http://*.cnn.com:* https://*.cnn.io:* http://*.cnn.io:* *.turner.com:* https://www.google.com https://news.google.com https://www.google.co.uk https://amp-cnn-com.cdn.ampproject.org courageousstudio.com;
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
via: 1.1 varnish, 1.1 varnish
accept-ranges: bytes
date: Tue, 05 Apr 2022 06:40:46 GMT
age: 1319
set-cookie: countryCode=CN; Domain=.cnn.com; Path=/; SameSite=None; Secure
set-cookie: stateCode=GD; Domain=.cnn.com; Path=/; SameSite=None; Secure
set-cookie: geoData=jiangmen|GD|529000|CN|AS|800|broadband|22.570|113.080; Domain=.cnn.com; Path=/; SameSite=None; Secure
set-cookie: FastAB=0=5468,1=6327,2=0580,3=2527,4=3191,5=8795,6=4475,7=8380,8=5241,9=4464; Domain=.cnn.com; Path=/; Expires=Sat Jul 01 2023 00:00:00 GMT; SameSite=Lax
x-served-by: cache-iad-kiad7000173-IAD, cache-nrt18326-NRT
x-cache: HIT, HIT
x-cache-hits: 2, 3
x-timer: S1649140846.281396,VS0,VE0
vary: , Accept-Encoding
content-length: 1123838

返回值200,正常访问
-L 参数让 curl 跟随连接进行重定向。这里服务器直接返回的 301 重定向响应,要求客户端再使用 HTTPS 的方式对 https://edition.cnn.com/politics 地址进行访问,第二次访问才返回了200的成功码。

除此之外,我们还可以进一步配置 egress gateway,使这些对外部的流量访问经由 egress 去到外部。
未配置egress gateay,流量是直接由应用的envoy转发到“外部服务”(虽然已经用service entry拉入网格内),你也可以当成是网格内的两个应用在进行流量转发。
service mesh:istio全_第55张图片
两种转发路径(还是将外部服务与网格区分开比较好,尽管service entry将外部服务拉入了网格,由网格管理)

现在为 edition.cnn.com创建 egress gateway,并为指向 egress gateway 的流量创建一个 destination rule:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway   #使用engressgateway这个service与pod
  servers:
  - port:
      number: 80   #edition.cnn.com的http流量由此网关使用的service的80端口转发出去
      name: http
      protocol: HTTP
    hosts:
    - edition.cnn.com   #gateway包含的服务,即有gateway负责转发的服务
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-cnn
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn

除了上面的 engress gateway 对象之外,我们还需要创建 VirtualService 资源对象,将流量从 sidecar 引导至 egress gateway,再从 egress gateway 引导至外部服务:

# vim cnn-virtual.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: direct-cnn-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com   #包含的服务
  gateways:
  - istio-egressgateway
  - mesh  # mesh 表示网格中的所有 Sidecar,如果没有指定 gateways,则默认为 mesh(但时间发现,就算不指定gateway,同样至少得设置一个gateway规则,否则流量转发会出错
  http:
  - match:
    - gateways:   #匹配使用的gateway是mesh
      - mesh
      port: 80   #访问的服务端口是80
    route:
    - destination:  # 将服务路由到什么地方去
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: cnn  # subset 配置流量目的地的子集
        port:
          number: 80
      weight: 100   #权重一样
  - match:
    - gateways:
      - istio-egressgateway
      port: 80
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 80
      weight: 100

service mesh:istio全_第56张图片

网格的进出口流量配置还是有较大区别的,但套路基本固定
对于网格内的流量转发,普通的vitualservice和destinationrule就能实现

用 Egress Gateway 处理 HTTPS 请求

kubectl delete gateway istio-egressgateway
kubectl delete virtualservice direct-cnn-through-egress-gateway
kubectl delete destinationrule egressgateway-for-cnn
[root@master istio-1.13.2]# kubectl exec -it sleep-557747455f-nfwng -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com
HTTP/2 200
content-type: text/html; charset=utf-8
x-servedbyhost: ::ffff:127.0.0.1

可以看到直接访问 https 地址可以得到正确的结果了

然后同样为 edition.cnn.com 创建一个 egress Gateway。除此之外还需要创建一个 DestinationRule 和一个 VirtualService,用来引导流量通过 egress gateway,并通过 egress gateway 与外部服务通信

# vim cnn-https-service-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: 443   #443,https用
      name: tls
      protocol: TLS
    hosts:
    - edition.cnn.com
    tls:  # PASSTHROUGH 表示 gateway 按原样通过入口流量,而不校验 TLS
      mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: egressgateway-for-cnn
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: direct-cnn-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com
  gateways:
  - mesh
  - istio-egressgateway
  tls:
  - match:
    - gateways:
      - mesh
      port: 443
      sniHosts:
      - edition.cnn.com
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: cnn
        port:
          number: 443
  - match:
    - gateways:
      - istio-egressgateway
      port: 443
      sniHosts:
      - edition.cnn.com
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 443
      weight: 100

创建这个yaml,然后
kubectl exec -it $SLEEP_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com

就可以看到 https://edition.cnn.com 的请求日志了。不过需要注意,Istio 中定义的 Egress Gateway 本身并没有为其所在的节点提供任何特殊处理。集群管理员或云提供商可以在专用节点上部署 Egress gateway,并引入额外的安全措施,从而使这些节点比网格中的其他节点更安全。

由于 Istio 无法强制让所有出站流量都经过 egress gateway,Istio 只是通过 sidecar 代理实现了这种流向。攻击者只要绕过 sidecar 代理,就可以不经 egress gateway 直接与网格外的服务进行通信,从而避开了 Istio 的控制和监控。出于安全考虑,集群管理员和云供应商必须确保网格所有的出站流量都要经过 egress gateway。这需要通过 Istio 之外的机制来满足这一要求。例如,集群管理员可以配置防火墙,拒绝 egress gateway 以外的所有流量。Kubernetes 网络策略 也能禁止所有不是从 egress gateway 发起的出站流量。此外,集群管理员和云供应商还可以对网络进行限制,让运行应用的节点只能通过 gateway 来访问外部网络。要实现这一限制,可以只给 gateway Pod 分配公网 IP,并且可以配置 NAT 设备,丢弃来自 egress gateway pod 之外的所有流量。

TCP 流量转移

上面展示了多种流量管理的方式,该部分主要讲解在 Istio 中如何将 TCP 流量从一个版本迁移到另一个版本。在 Istio 中,我们可以通过配置规则来实现该功能,可以按指定的百分比将流量路由到不同的服务。和前面我们使用的 HTTP 服务非常类似。

这里我们先将所有的 TCP 请求路由到 tcp-echo:v1 这个服务,然后在通过配置 Istio 的路由权重把 20% 的流量分配到 tcp-echo:v2 这个版本的服务上。

samples/tcp-echo/tcp-echo-services.yaml:

apiVersion: v1
kind: Service
metadata:
  name: tcp-echo
  labels:
    app: tcp-echo
    service: tcp-echo
spec:
  ports:
  - name: tcp
    port: 9000
  - name: tcp-other
    port: 9001
  # Port 9002 is omitted intentionally for testing the pass through filter chain.
  selector:
    app: tcp-echo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tcp-echo-v1
  labels:
    app: tcp-echo
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tcp-echo
      version: v1
  template:
    metadata:
      labels:
        app: tcp-echo
        version: v1
    spec:
      containers:
      - name: tcp-echo
        image: docker.io/istio/tcp-echo-server:1.2
        imagePullPolicy: IfNotPresent
        args: [ "9000,9001,9002", "one" ]
        ports:
        - containerPort: 9000
        - containerPort: 9001
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tcp-echo-v2
  labels:
    app: tcp-echo
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tcp-echo
      version: v2
  template:
    metadata:
      labels:
        app: tcp-echo
        version: v2
    spec:
      containers:
      - name: tcp-echo
        image: docker.io/istio/tcp-echo-server:1.2
        imagePullPolicy: IfNotPresent
        args: [ "9000,9001,9002", "two" ]
        ports:
        - containerPort: 9000
        - containerPort: 9001
[root@master istio-1.13.2]# kubectl apply -f samples/tcp-echo/tcp-echo-services.yaml
service/tcp-echo created
deployment.apps/tcp-echo-v1 created
deployment.apps/tcp-echo-v2 created
[root@master istio-1.13.2]# kubectl get pods -l app=tcp-echo
NAME                           READY   STATUS            RESTARTS   AGE
tcp-echo-v1-7dd5c5dcfb-2lj45   2/2     Running           0          10s
tcp-echo-v2-56cd9b5c4f-wmdq9   0/2     PodInitializing   0          11s

微服务 tcp-echo 的 TCP 流量全部路由到 v1 版本上
# vim samples/tcp-echo/tcp-echo-all-v1.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: tcp-echo-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 31400   #ingressgateway的31400端口
      name: tcp
      protocol: TCP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: tcp-echo-destination
spec:
  host: tcp-echo
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: tcp-echo
spec:
  hosts:
  - "*"   #包含所有的service
  gateways:
  - tcp-echo-gateway
  tcp:
  - match:
    - port: 31400   #匹配service的31400
    route:
    - destination:
        host: tcp-echo
        port:
          number: 9000
        subset: v1
[root@master istio-1.13.2]# kubectl get svc -n istio-system istio-ingressgateway -o wide
NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE   SELECTOR
istio-ingressgateway   NodePort   10.100.81.91   <none>        15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   2d    app=istio-ingressgateway,istio=ingressgateway
31400端口


kubectl apply -f samples/tcp-echo/tcp-echo-all-v1.yaml

创建完成后我们就可以通过 istio-ingressgateway 去访问上面的 TCP 服务了,要在外部访问该服务,同样我们可以通过 istio-ingressgateway 的 NodePort 来访问,这里的 TCP 对应的 nodePort 端口是30799,这里可以使用如下所示的命令来测试:

export INGRESS_PORT=31175 # TCP 服务的 nodePort 端口
export INGRESS_HOST=192.168.31.30 # 任意一个节点的 IP
然后向微服务 tcp-echo 发送一些 TCP 请求:

for i in {1..10}; do \
docker run -e INGRESS_HOST=$INGRESS_HOST -e INGRESS_PORT=$INGRESS_PORT -it --rm busybox sh -c "(date; sleep 1) | nc $INGRESS_HOST $INGRESS_PORT"; \
done

从上面的日志可以看出,所有时间戳的前缀都是 one,这意味着所有流量都被路由到了 tcp-echo 服务的 v1 版本中。

然后使用新的路由规则将 20% 的流量从 tcp-echo:v1 转移到 tcp-echo:v2:

# vim samples/tcp-echo/tcp-echo-20-v2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: tcp-echo
spec:
  hosts:
  - "*"
  gateways:
  - tcp-echo-gateway
  tcp:
  - match:
    - port: 31400
    route:
    - destination:
        host: tcp-echo
        port:
          number: 9000
        subset: v1
      weight: 80
    - destination:
        host: tcp-echo
        port:
          number: 9000
        subset: v2
      weight: 20

kubectl apply -f samples/tcp-echo/tcp-echo-20-v2.yaml

同样上述方式测试

熔断

熔断是创建弹性微服务应用程序的重要模式,熔断能够使我们的应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力,熔断在微服务框架中也是必备的一个功能。

同样在 Istio 中也是原生就支持熔断功能的,首先部署示例服务 samples/httpbin/httpbin.yaml:

##################################################################################################
# httpbin service
##################################################################################################
apiVersion: v1
kind: ServiceAccount
metadata:
  name: httpbin
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
    service: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      serviceAccountName: httpbin
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
[root@master istio-1.13.2]# kubectl apply -f samples/httpbin/httpbin.yaml
serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created
[root@master istio-1.13.2]# kubectl get pods -l app=httpbin
NAME                       READY   STATUS            RESTARTS   AGE
httpbin-74fb669cc6-vwlqk   0/2     PodInitializing   0          13s

应用部署完成后,接着创建一个目标规则,在调用 httpbin 服务时配置熔断设置。这里通过一个 DestinationRule 对象来创建一个如下所示的资源对象

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin   #包含这个服务
  trafficPolicy:   #定义交通策略
    connectionPool:   #连接池,又称限流
      tcp:
        maxConnections: 1  # 最大连接数为1(建立的一个tcp连接,可以有多个http请求发出,即最大的并发连接数
      http:
        http1MaxPendingRequests: 1  # 最大请求数为1。即http最大请求数
        maxRequestsPerConnection: 1
    outlierDetection:   #异常检测
      consecutiveErrors: 1   #连续的错误,表示连续多少个错误才视为错误
      interval: 1s   #检测时间间隔
      baseEjectionTime: 3m   #基础驱逐时间
      maxEjectionPercent: 100   #最大驱逐百分比
#这里的意思是每秒进行调用服务的检查,有一次错误即视为异常,将该服务驱驱逐出负载均衡池至少3分钟,最多可以驱逐出所有的服务实例)
EOF

目标规则创建完成后,接着我们来创建一个客户端应用来发送流量请求到 httpbin 服务,这里我们使用一个名为 Fortio 的测试客户端应用,该应用可以控制连接数、并发数及发送 HTTP 请求的延迟,通过 Fortio 能够有效的触发前面在 DestinationRule 中设置的熔断策略。

# vim samples/httpbin/sample-client/fortio-deploy.yaml
apiVersion: v1
kind: Service
metadata:
  name: fortio
  labels:
    app: fortio
    service: fortio
spec:
  ports:
  - port: 8080
    name: http
  selector:
    app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fortio-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fortio
  template:
    metadata:
      annotations:
        # This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
        # in addition to the stats normally served by Istio.  The Circuit Breaking example task
        # gives an example of inspecting Envoy stats.
        sidecar.istio.io/statsInclusionPrefixes: cluster.outbound,cluster_manager,listener_manager,http_mixer_filter,tcp_mixer_filter,server,cluster.xds-grpc
      labels:
        app: fortio
    spec:
      containers:
      - name: fortio
        image: fortio/fortio:latest_release
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http-fortio
        - containerPort: 8079
          name: grpc-ping

kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml
kubectl get pods -l app=fortio

部署完成后我们可以进入到客户端应用中使用 Fortio 工具调用 httpbin 服务。-curl 参数表示发送一次请求

FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }')
kubectl exec -it $FORTIO_POD  -c fortio -- /usr/bin/fortio load -curl  http://httpbin:8000/get

可以看到调用后端服务的请求是成功的,接下来测试熔断

这里我们发送并发数为 2 的连接(-c 2),请求 20 次(-n 20)来观察下现象:
kubectl exec -it $FORTIO_POD  -c fortio /usr/bin/fortio -- load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get

可以看到大部分请求还是都完成了,但是并不是一半的请求失败,这是因为 istio-proxy 并不是100%准确的
将并发连接数提高到 3 个测试

可以通过 istio-proxy 状态来了解更多关于熔断的详情
kubectl exec $FORTIO_POD -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending

其中的upstream_rq_pending_overflow字段记录有多少个调用被标记为熔断

上面创建了一个网格内的service,定义了一个destinationrule规则来路由流量。

流量镜像

流量镜像也称为影子流量,是一个以尽可能低的风险为生产带来变化的强大的功能,镜像会将实时流量的副本发送到镜像服务。前面我们在学习 Traefik 的时候就测试过类似的功能。

这里我们先把流量全部路由到 v1 版本的测试服务中,然后添加路由规则将一部分流量镜像到 v2 版本中去,来测试流量镜像功能。

首先部署 v1 版本的 httpbin 服务:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-v1
spec:
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80
EOF

然后部署 v2 版本的 httpbin 服务:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-v2
spec:
  selector:
    matchLabels:
      app: httpbin
      version: v2
  template:
    metadata:
      labels:
        app: httpbin
        version: v2
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80
EOF

然后创建一个 httpbin 的 Service 对象,关联上面的两个版本服务:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
EOF

都是常见的服务

然后在部署一个 sleep 服务,这样就可以使用 curl 来提供负载了:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: tutum/curl
        command: ["/bin/sleep","infinity"]
        imagePullPolicy: IfNotPresent
EOF

默认情况下,Kubernetes 在 httpbin 服务的两个版本之间进行负载均衡,这里我们首先创建一个默认路由规则,将所有流量路由到服务的 v1 版本中去:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - httpbin
  http:
  - route:
    - destination:
        host: httpbin
        subset: v1
      weight: 100   #就哟个路由规则所以这里的权重没意义
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
EOF

上面对象创建完成后,所有流量都会转到 httpbin:v1 服务下面去

export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl  http://httpbin:8000/headers' | python -m json.tool

(| python -m json.tool处理json文件)

然后可以分别查看 httpbin 服务 v1 和 v2 两个 Pods 的日志,正常情况下只会看到 v1 会产生日志,v2 中没有:

export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
kubectl logs -f $V1_POD -c httpbin
127.0.0.6 - - [29/Jul/2021:10:51:59 +0000] "GET /headers HTTP/1.1" 200 523 "-" "curl/7.35.0"
export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
kubectl logs -f $V2_POD -c httpbin(指定容器名)

这是因为上面我们创建的路由规则是将所有的请求都路由到了 v1 这个版本的服务中去。接下来我们更改下流量规则,将流量镜像到 v2 版本的服务中去:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - httpbin
  http:
  - route:
    - destination:
        host: httpbin
        subset: v1
      weight: 100
    mirror:
      host: httpbin
      subset: v2
    mirror_percent: 100   #发送 100% 流量到 v1 这个子集服务,然后通过 mirror 属性配置了将流量也 100% 镜像到了 httpbin:v2 服务。当流量被镜像时,请求将发送到镜像服务中,并在 headers 中的 Host/Authority 属性值上追加 -shadow(用来区分请求)。
EOF

其实就是复制请求
被镜像的请求是即发即弃的。就是不会接受服务对这些镜像请求的响应
同样的方式访问上面的日志

kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name}
过滤出pod的name当然前提要用标签过滤,最好是过滤过唯一pod
kubectl log -f pod_name -c container_name
-f follow持续输出

(cat << EOF | kubectl apply -f -
< (cat >>test < <

istio观察

使用 Kiali 观测微服务

由于微服务之间的调用关系错综复杂,排查问题就更加困难了,为了使服务之间的关系更加清晰明了,了解应用的行为和状态,我们有必要使用一些可视化的方案来观测我们的微服务应用,其中 Kiali 就是这样的一个工具。

Kiali 是 Istio
的一个可观测工具,提供服务拓扑展示服务网格的结构,提供网格的健康状态视图,配置信息验证功能,此外还具有服务网格配置的功能。

[root@master ~]# kubectl get svc kiali -n istio-system
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
kiali   ClusterIP   10.97.200.105   <none>        20001/TCP,9090/TCP   4d18h

安装完成后我们可以通过 istioctl 命令来访问,比如我们可以使用如下命令来打开 kiali 的 web UI:

istioctl dashboard kiali
http://localhost:20001/kiali

就可以通过localhost:20001访问kiali

此外我们也可以通过修改 Kiali 的服务为 NodePort 类型来访问:
service mesh:istio全_第57张图片

[root@master ~]# kubectl edit svc kiali -n istio-system
service/kiali edited
[root@master ~]# kubectl get svc -n istio-system kiali
NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGE
kiali   NodePort   10.97.200.105   <none>        20001:30977/TCP,9090:32539/TCP   4d18h

然后我们可以通过 http://:31617 来访问 Kiali
查看整个微服务的调用链
service mesh:istio全_第58张图片

监控指标

此外我们还部署了用于收集监控指标的 Prometheus 应用:

[root@master ~]# kubectl get svc  -n istio-system -l app=prometheus
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
prometheus   ClusterIP   10.108.34.247   <none>        9090/TCP   4d23h
[root@master ~]# kubectl get pod -n istio-system -l app=prometheus
NAME                         READY   STATUS    RESTARTS   AGE
prometheus-f675ff955-h7wgv   2/2     Running   2          4d23h
kubectl edit svc prometheus -n istio-system
修改service的类型为nodeport
[root@master ~]# kubectl get svc -n istio-system -l app=prometheus
NAME         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
prometheus   NodePort   10.108.34.247   <none>        9090:31960/TCP   4d23h

默认就已经收集了很多网格的指标,包括服务级别和代理级别(sidecar)的数据。
service mesh:istio全_第59张图片

使用 Grafana 可视化系统监控

上面我们了解到了 Istio 网格通过 Prometheus 收集了很多服务和代理相关的指标数据,此外我们还可以通过 Grafana 来可视化查看网格的监控状态。

[root@master ~]# kubectl get pods -n istio-system -l app=grafana
NAME                       READY   STATUS    RESTARTS   AGE
grafana-69ccf87b97-pdbqv   1/1     Running   1          4d23h

虚拟机的火狐浏览器访问grafana失败,所以我用宿主机访问,现在C:\Windows\System32\drivers\etc\hosts加入跟虚拟机/etc/hosts相同的域名解析,这里是192.168.23.172 master,hosts文件写不进就邮件属性将只读关了
ip:port

service mesh:istio全_第60张图片
service mesh:istio全_第61张图片
默认情况下 Grafana 中就已经导入了 Istio 的几个 Dashboard:

Mesh Dashboard:主要是查看应用的数据,包括网格数据总览、服务视图、工作负载视图等
Performance Dashboard:主要是用于查看 Istio 本身的监控数据。
service mesh:istio全_第62张图片

访问日志

日志也是应用可观测性中一个非常重要的方式,也是我们传统调试应用非常重要的手段,在 Istio 中使用 Sidecar 容器对请求进行了拦截,无疑也增大了调试难度,但是同时 Istio 也可以监测到网格内的服务通信的流转情况,并生成详细的遥测日志数据,任何请求与事件的元信息都可以获取到,所以我们也非常有必要来查看下 Istio 中的代理日志数据。Istio 最简单的日志类型是 Envoy 的访问日志Envoy 代理打印访问日志信息到标准输出,然后我们就可以通过 kubectl logs 命令打印出来查看了。
(注意日志的标准以容器为单位,所以注意区分pod的容器)

默认情况下 Istio 已经开启了 Envoy 访问日志,我们也可以通过 istioctl install 命令来配置日志:

istioctl install --set profile=demo --set meshConfig.accessLogFile=/dev/stdout --set  meshConfig.accessLogEncoding=JSON -y

默认情况下日志就是输出到 stdout 上的 TEXT 文本格式,为了方便显示,这里我们将其设置为 JSON 格式,如果要想修改访问日志的格式可以设置 accessLogFormat 属性

kubectl logs productpage-v1-6b746f74dc-9mk2r -c istio-proxy
指定容器

service mesh:istio全_第63张图片
这是text形式
这里我们查询的容器名 istio-proxy 其实就是 Envoy sidecar 代理,Envoy 将请求和响应日志都进行了打印并输出至 stdout ,所以可以通过 kubectl logs 查询。

[root@master istio-1.13.2]# kubectl get svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
details       ClusterIP   10.109.98.199    <none>        9080/TCP            5d5h
httpbin       ClusterIP   10.109.20.28     <none>        8000/TCP            3d3h
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP             5d10h
productpage   ClusterIP   10.109.236.5     <none>        9080/TCP            5d5h
ratings       ClusterIP   10.100.1.99      <none>        9080/TCP            5d5h
reviews       ClusterIP   10.104.135.222   <none>        9080/TCP            5d5h
sleep         ClusterIP   10.106.150.84    <none>        80/TCP              3d10h
tcp-echo      ClusterIP   10.106.154.67    <none>        9000/TCP,9001/TCP   3d5h
[root@master istio-1.13.2]# curl 10.109.236.5:9080

在查看下对应的pod中的容器的日志

kubectl logs productpage-v1-6b746f74dc-9mk2r -c istio-proxy

service mesh:istio全_第64张图片
百度任意一个json的在线解析网站将json日志解析
service mesh:istio全_第65张图片

{
	"start_time": "2022-04-08T16:45:35.746Z",
	"route_name": "default",
	"bytes_sent": 1683,
	"duration": 326,
	"x_forwarded_for": null,
	"path": "/",
	"request_id": "8e178f0c-12c4-945f-b618-a8f1b3ccc49f",
	"protocol": "HTTP/1.1",
	"upstream_local_address": "127.0.0.6:37229",
	"requested_server_name": null,
	"response_flags": "-",
	"upstream_service_time": "300",
	"connection_termination_details": null,
	"response_code": 200,
	"upstream_host": "10.244.1.16:9080",
	"downstream_local_address": "10.244.1.16:9080",
	"authority": "10.109.236.5:9080",
	"method": "GET",
	"user_agent": "curl/7.29.0",
	"downstream_remote_address": "10.244.0.0:55449",
	"upstream_cluster": "inbound|9080||",
	"response_code_details": "via_upstream",
	"bytes_received": 0,
	"upstream_transport_failure_reason": null
}

这里面的日志信息其实是一个标准的 Envoy 流量模型,所以如果我们要深入研究 Istio,对于 Envoy 的了解是必不可少的,其中最重要的几个字段就是 Envoy 流量五元组中的几个信息。

Envoy 中发送请求流量叫做 Downstream(下游),Envoy 接收请求流量叫做 Upstream(上游),在处理 Downstream 和 Upstream 过程中,分别会涉及2个流量端点,即请求的发起端和接收端:

downstream:下游主机连接到envoy发送请求并获得响应
upstream:上游主机获取来自envoy的请求或响应(这里的响应可以理解成上游主机对envoy发送请求时)
cluster:集群是envoy连接的一组逻辑上相似的上游主机。envoy通过服务发现发现集群中的成员,也可以通过主动运行健康检查来确定集群中成员的情况,按照负载均衡策略将请求路由到集群的成员

service mesh:istio全_第66张图片
在这个过程中,Envoy 会根据用户规则,计算出符合条件的转发目的主机集合,这个集合叫做 UPSTREAM_CLUSTER(一般就是微服务上游的service), 并根据负载均衡规则,从这个集合中选择一个 host 作为流量转发的接收端点,这个 host 就是 UPSTREAM_HOST。这就是 Envoy 请求处理的流量五元组信息,这是 Envoy 日志里最重要的部分,通过这个五元组我们可以准确的观测流量「从哪里来」和「到哪里去」:

downstream_remote_address 下游远程地址,用来接收用户的请求
downstream_local_address 下游本地地址,用来接收用户的请求
upstream_remote_address 上游远程地址
upstream_local_address 上游本地地址
upstream_host 上游主机
upstream_cluster 上游集群

除了五元组信息之外,还有 request_id 属性,该属性主要用于链路追踪,可以将一条请求在不同的服务中的调用串联起来。

还有一个非常重要的属性 response_flags,可以通过该属性来查看当前请求的状态,用于调试请求的时候非常有用:
service mesh:istio全_第67张图片

不过需要注意的是,当 Pod 被销毁后,旧的日志将不复存在,也无法通过 kubectl logs 就行查看了。所以如果要查看历史的的日志数据,我们可以使用 EFK 方案将日志进行收集,前面我们已经仔细讲解过了,这里就不再赘述了。

分布式追踪

相比传统的单体应用,微服务的一个主要变化是将应用中的不同模块拆分为了独立的服务,在微服务架构下,原来进程内的方法调用成为了跨进程的远程方法调用。相对于单一进程内的方法调用而言,跨进程调用的调试和故障分析是非常困难的,难以使用传统的代码调试程序或者日志打印来对分布式的调用过程进行查看和分析。一个来自客户端的请求在其业务处理过程中很有可能需要经过多个微服务,我们如果想要对该请求的端到端调用过程进行完整的分析,则必须将该请求经过的所有进程的相关信息都收集起来并关联在一起,这就是分布式追踪,也是应用可观测性中非常重要的手段。(应用以pod方式其实就是container方式存在,即进程)
在 Istio 中通过分布式追踪我们可以让用户对跨多个分布式服务网格的一个请求进行追踪分析,进而可以通过可视化的方式更加深入地了解请求的延迟、序列化和并行度等。
Istio 利用 Envoy 的分布式追踪功能提供了开箱即用的追踪集成,默认 Istio 提供了集成各种追踪后端服务的选项,并且可以通过配置代理来自动发送追踪 span 到追踪后端服务。
service mesh:istio全_第68张图片
Span:跨度,分布式追踪的基本组成单元,表示一个分布式系统中的单独的工作单元,每一个 Span 可以包含其它 Span 的引用。多个 Span 在一起构成了 Trace。
Trace:追踪,检查,微服务中记录的完整的请求执行过程,一个完整的 Trace 由一个或多个 Span 组成。

随着分布式追踪技术的发展,社区推出了 OpenTracing 这个规范,提供了标准的 API 规范、框架以及一些公共的库。目前比较知名的追踪工具基本上都通过 OpenTracing 进行实现,比如 Jaeger、Zipkin 等。

我们这里主要来给大家介绍比较流行的 Jaeger。 Jaeger 是由 Uber 开源的分布式追踪系统,采用 Go 语言编写,主要借鉴了 Google Dapper 论文和 Zipkin 的设计,兼容 OpenTracing 以及 Zipkin 追踪格式,目前已成为 CNCF 基金会的开源项目。
service mesh:istio全_第69张图片

[root@master istio-1.13.2]# kubectl get pod -n istio-system -l app=jaeger
NAME                      READY   STATUS    RESTARTS   AGE
jaeger-648f4f4ddb-9m9b4   1/1     Running   1          5d21h
[root@master istio-1.13.2]# kubectl get svc -n istio-system -l app=jaeger
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE
jaeger-collector   ClusterIP   10.99.245.148   <none>        14268/TCP,14250/TCP,9411/TCP   5d21h
tracing            ClusterIP   10.98.182.245   <none>        80/TCP,16685/TCP               5d21h

我们可以通过 tracing 这个 Service 来访问 Jaeger 的 Dashboard 页面,同样为了方便测试将其修改为 NodePort 类型
在这里插入图片描述
service mesh:istio全_第70张图片
接下来我们重新访问 BookInfo 应用产生流量请求生成追踪数据。请求完成后回到 Jaeger 页面,在左侧 Servcie 区域选择一个我们要追踪的服务,比如 details.default,点击 “Find Traces” 按钮查看追踪结果。

[root@master istio-1.13.2]# kubectl get svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
details       ClusterIP   10.109.98.199    <none>        9080/TCP            6d
httpbin       ClusterIP   10.109.20.28     <none>        8000/TCP            3d21h
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP             6d5h
productpage   ClusterIP   10.109.236.5     <none>        9080/TCP            6d
ratings       ClusterIP   10.100.1.99      <none>        9080/TCP            6d
reviews       ClusterIP   10.104.135.222   <none>        9080/TCP            6d
sleep         ClusterIP   10.106.150.84    <none>        80/TCP              4d4h
tcp-echo      ClusterIP   10.106.154.67    <none>        9000/TCP,9001/TCP   3d23h
[root@master istio-1.13.2]# curl 10.109.236.5:9080

注:有时候curl没有返回值并不是错误
service mesh:istio全_第71张图片

点击列表项目还可以查看追踪详细信息,记录了一次请求涉及到的 Services、深度、Span
总数、请求总时长等信息,也可以对下方的单项服务展开,观察每一个服务的请求耗时和详情。

service mesh:istio全_第72张图片
此外,Jaeger 还可以展示服务依赖,点击顶部的 System Architecture 菜单查看,该页面可以查看服务的完整依赖调用关系
这些都是 Jaeger 的基本使用,不过需要注意的是 Istio 默认提供的 Jaeger 采用内存的存储方式,Pod 被销毁后数据也就丢失了。在生产环境中需要单独配置持久化存储数据库,具体可查看 Jaeger 官方文档。(建议使用ceph和nfs)

安全

关于istio的安全配置是一个大课题,一下知识常用,详情可以参考istio安全
安全网关
Istio 的身份和证书管理是通过 SDS(安全发现服务)来实现的,如下图所示:
service mesh:istio全_第73张图片

其执行的流程如下所示:

首先 Istio 提供一个 gRPC 服务来接受证书签名请求(CSRs) Envoy 通过 SDS API 发送证书和密钥请求 在收到
SDS 请求后,istio-agent 创建私钥和 CSR,然后将 CSR 及其凭据发送到 Istiod 中的 CA 服务进行签名 CA
验证 CSR 中携带的凭据并签署 CSR 以生成证书 istio-agent 通过 Envoy SDS API 将私钥与从 Istio CA
收到的证书发送给 Envoy 上述 CSR 过程会周期性地重复,以处理证书和密钥轮转

istio的身份认证和证书管理由SDS实现,istio-agent是sidecar容器中的一个进程(即非pid=1的某个进程),istio-agent收到SDS请求后,生成私钥(私钥很容易生成公钥)和证书签名请求文件(csr),将证书请求文件发送给istiod的ca中心,ca验证csr生成证书签名文件,将证书文件发给istio-agent,istio-agent 通过 Envoy SDS API 将私钥与从 Istio CA 收到的证书发送给 Envoy

(envoy,sidecar,proxy这三个放在一起说)
接下来我们可以通过 SDS 来为 Istio Ingress Gateway 配置 TLS 安全认证,配置 TLS Ingress Gateway,让它从 Ingress Gateway 代理通过 SDS 获取凭据。Ingress Gateway 代理和 Ingress Gateway 在同一个 Pod 中运行,监听 Ingress Gateway 所在命名空间中新建的 Secret。在 Ingress Gateway 中启用 SDS 具有如下好处:

Ingress Gateway 无需重启,就可以动态的新增、删除或者更新密钥/证书对以及根证书。
无需挂载 Secret 卷,创建了 kubernetes Secret 之后,这个 Secret 就会被 Gateway 代理捕获,并以密钥/证书对和根证书的形式发送给 Ingress Gateway。
Gateway 代理能够监视多个密钥/证书对,只需要为每个主机名创建 Secret 并更新 Gateway 定义就可以了。

单一主机(顺带总结openssl和CA流程)

首先使用 OpenSSL 生成证书和密钥,使用如下命令创建根证书和私钥来为你的服务签名证书:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt

然后使用上面的根证书和私钥为 httpbin.example.com 域名进行签名,首先生成证书签名请求:
openssl req -out httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin.example.com.key -subj "/CN=httpbin.example.com/O=some organization"

然后使用上面的证书签名请求来请求签发证书:
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin.example.com.csr -out httpbin.example.com.crt

service mesh:istio全_第74张图片

.key私钥文件,.csr证书请求文件,.crt证书文件
主机名.域名.(根域)

openssl genrsa生成私钥,不会生成公钥,因为公钥直接提取自私钥
-out:默认标准输出,指定私钥保存文件

openssl req 生成证书请求文件,验证证书请求文件(自签名证书),创建根CA(过程需要私钥,用-key指定,不然默认会自动生成私钥)。
-new:生成证书请求文件。
-x509:不是创建证书请求文件,而是直接生成一个自签名证书。(没有-x509默认生成证书请求文件)。
-days n:指定自签名证书的有效时间,要与-x509一起使用。
-sha256:指定用这个算法作为创建证书请求和对申请者信息进行数字签名时使用的单向加密算法。
-nodes:默认情况下openssl req自动创建私钥时都要求加密并提示输入密码,这个选项禁止对私钥文件加密
rsa:2048:指定rsa秘钥的长度,默认2048bits。
-subj:C是country,ST是state,L是localcity,O是organization,OU是Organization Unit,CN是common name。
-keyout:指定生成的私钥文件。
-out:指定生成的证书请求文件或自签名证书文件。
-newkey:用法-newkey args,如-newkey rsa:2048,生成私钥同时指定私钥长度。

openssl x509就像一个CA的工具箱,一般用来签署证书请求文件
-in:指定证书输入文件,即要被操作的文件
-req:与-in搭配使用,表示输入的文件是证书请求文件
-out:指定输出文件,一般就是证书请求文件签署后的证书文件(多数是自签名证书)
-CAkey:指定CA的私钥
-CA:指定CA的证书
-set_serial n:指定证书的序列号,如果和-CA一块使用则默认的序列号失效,序列号可以是数值或16进制(0x)

openssl genrsa生成私钥
openssl req一般用来生成证书请求文件或根CA(生成证书请求文件或根CA,其实主要就是先生成证书文件,得有证书申请信息,-subj)
openssl x509一般是用来签署证书请求文件

ssl(安全套接字层)是web安全通信的基石,现在是tls(传输层安全)
非对称加密:数学上两个质数很容易算出乘积,但一个很大的数,很难分解出两个质数。这两个质数对应私钥和公钥,与对称加密相反,这两把钥匙是不同的,要开锁就得两把钥匙一块。
私钥容易退出公钥,公钥难推出私钥。
数字签名:将报文使用一定的HASH算法算出一个固定位数的摘要信息,然后用私钥将摘要加密,连同原来的报文一起,发送给接收者,接受者通过公钥将摘要解出来,也通过HASH算法算出报文摘要,如果两个摘要一致,说明数据未被篡改,数据是完整的。因为接收者是使用公钥解出的数据,如果数据完整,证明发送数据的人持有私钥,就能证明发送者的身份,因此数字签名具有证明发送者身份和防篡改的功能。
数字证书:由CA颁发给网站的身份证书,里面包含了该网站的公钥,有效时间,网站的地址,CA的数字签名等。所谓的CA数字签名,实际上就是使用了CA的私钥将网站的公钥等信息进行了签名,当客户端请求服务器的时候,网站会把证书发给客户端,客户端首先可以通过CA的数字签名校验CA的身份,也能证明证书的真实完整性。浏览器一般有CA的地址,因此可以获得CA的公钥

现在来思考这样的一个问题,当我们访问百度的时候,我们怎么知道自己访问的是真百度还是假百度?是不是有可能有人劫持了我们访问百度的请求,然后路由给了一台他伪造的服务器?
为了确定我们访问的服务器没有被伪造,在SSL的通信流程中做了这样一个规定:一旦我们向服务器发送了请求后,服务器必须返回它的数字证书,当我们拿到数字证书之后,我们可以根据里面的ca数字签名,校验证书的合法性(不是被伪造的)。
此时,我们只能够证明证书确实是属于百度的,但不代表发送给我们证书的服务器就是百度,怎么办呢?
在证书里面会带有百度服务器的公钥,在之后的通信当中,客户端会使用该公钥加密数据给百度服务器,百度服务器必须使用私钥才能够解出里面的数据,假设百度服务器是假冒的,他一定没有正确的私钥,那么之后的通信都无法进行,所以假冒服务器是无法操作的。
由此,SSL就解决了服务器认证的问题。
加密数据以防止数据中途被窃取,非明文

由于 Ingress Gateway 是通过监听同一命名空间下面的 Secret 对象来进行安全服务发现(SDS)的,所以我们需要将上面生成的证书创建在 istio-system 命名空间之下:

kubectl create secret tls httpbin-tls --cert=httpbin.example.com.crt --key=httpbin.example.com.key -n istio-system
tls:创建tls类型的secret
需要服务(主机)的证书文件和私钥

创建httpbin应用
[root@master istio-1.13.2]# kubectl apply -f samples/httpbin/httpbin.yaml
serviceaccount/httpbin unchanged
service/httpbin configured
deployment.apps/httpbin unchanged
[root@master istio-1.13.2]# kubectl get pods,svc -l app=httpbin
NAME                              READY   STATUS    RESTARTS   AGE
pod/httpbin-74fb669cc6-vwlqk      2/2     Running   0          4d1h
pod/httpbin-v1-75d9447d79-w7k5m   2/2     Running   0          34h
pod/httpbin-v2-fb86d8d46-xgxpl    2/2     Running   0          34h

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/httpbin   ClusterIP   10.109.20.28   <none>        8000/TCP   4d1h


然后接下来创建一个 Gateway,其 servers 字段的端口为 443,设置 credentialName 的值为上面创建的 Secret 对象 httpbin-tls,并将 TLS 模式设置为 SIMPLE

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # 使用默认的 ingress gatewaygateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "httpbin-tls" # 必须使用上面创建的同名的Secret对象
    hosts:
    - "httpbin.example.com"
EOF

credential证书
tls的mode:
PASSTHROUGH客户端提供的SIN字符串将用作Virtualservice TLS路由中的匹配条件,以根据服务注册表确定目标服务。
SIMPLE使用标准TLS予以的安全连接。
MUTUAL通过提供服务器证书进行身份验证,使用双边TLS来保护下游的连接。
AUTO_PASSTHROUGH代理将转发到SIN值指定的上游。
ISTIO_MUTUAL通过提供身份验证的服务器证书,使用相互的TLD使用来自下游的安全连接。

这样就成功创建了一个 TLS 的安全网关,接下来为 httpbin 应用配置路由规则,创建一个 VirtualService 对象,如下所示:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF
[root@master istio-1.13.2]# kubectl get svc -n istio-system istio-ingressgateway
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   LoadBalancer   10.100.81.91   <pending>     15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   6d4h

这样我们就可以通过 HTTPS 协议安全的访问 httpbin 应用了,由于我们这里的 Istio Ingressgateway 是通过 NodePort 暴露的服务,所以我们在测试的时候应该使用 443 端口对应的 nodePort 端口

从上面可以看出我们的网关安全访问端口是31232,网关地址是任意的一个节点 IP,当然还要记得将域名 httpbin.example.com 也要解析到网关的节点上
service mesh:istio全_第75张图片
我这里的网关的节点是node2,对应ip192.168.23.137,所以就在指定curl命令的节点上修改/etc/hosts文件解析httpbin.example.com到node2的ip上

curl -v -H Host:httpbin.example.com --cacert example.com.crt https://httpbin.example.com:30433/status/418
-v冗长的,可以看到 TLS 握手建立连接的完整过程,-H请求头
[root@master istio-1.13.2]# curl -v -H Host:httpbin.example.com --cacert example.com.crt https://httpbin.example.com:31232/status/418
* About to connect() to httpbin.example.com port 31232 (#0)
*   Trying 192.168.23.137...
* Connected to httpbin.example.com (192.168.23.137) port 31232 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: example.com.crt
  CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
*       subject: O=some organization,CN=httpbin.example.com
*       start date: 4月 09 14:33:46 2022 GMT
*       expire date: 4月 09 14:33:46 2023 GMT
*       common name: httpbin.example.com
*       issuer: CN=example.com,O=example Inc.
> GET /status/418 HTTP/1.1
> User-Agent: curl/7.29.0
> Accept: */*
> Host:httpbin.example.com
>
< HTTP/1.1 418 Unknown
< server: istio-envoy
< date: Sat, 09 Apr 2022 15:30:51 GMT
< x-more-info: http://tools.ietf.org/html/rfc2324
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 135
< x-envoy-upstream-service-time: 374
<

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`
* Connection #0 to host httpbin.example.com left intact

多个主机

上面是将单个主机配置到一个 Ingress Gateway 中,同样我们还可以把多个主机名配置到同一个 Ingress Gateway 上,例如 httpbin.example.com 和 hello.example.com 两个主机名配置到同一个网关上,Ingress Gateway 会为每个 credentialName 获取一个唯一的凭据。

同样先部署 helloworld 示例应用,对应的资源清单文件如下所示:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: helloworld-v1
  labels:
    app: helloworld-v1
spec:
  ports:
  - name: http
    port: 5000
  selector:
    app: helloworld-v1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld-v1
      version: v1
  template:
    metadata:
      labels:
        app: helloworld-v1
        version: v1
    spec:
      containers:
      - name: helloworld
        image: istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent #Always
        ports:
        - containerPort: 5000
EOF
[root@master ~]# kubectl get pod,svc -l app=helloworld-v1
NAME                                 READY   STATUS    RESTARTS   AGE
pod/helloworld-v1-74459c7499-2zsx9   2/2     Running   0          79m

NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/helloworld-v1   ClusterIP   10.96.81.219   <none>        5000/TCP   79m

然后同样使用上面我们创建的根证书来对域名 hello.example.com 进行签名,首先生成证书签名请求:
openssl req -out hello.example.com.csr -newkey rsa:2048 -nodes -keyout hello.example.com.key -subj "/CN=hello.example.com/O=some organization"

然后使用上面的证书签名请求来请求签发证书:
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in hello.example.com.csr -out hello.example.com.crt

注意要在有CA证书文件存在的目录下执行
同样使用上面生成的证书和密钥在 istio-system 命名空间下面为 Ingress Gateway 新建一个 Secret 对象:
➜  ~ kubectl create secret tls hello-tls --cert=hello.example.com.crt --key=hello.example.com.key -n istio-system

然后重新定义上面的 Gateway 网关,其中包含了两个 server,都开放了 443 端口。两个 credentialName 字段分别赋值为 httpbin-tls 和 hello-tls,设置 TLS 的 mode 为 SIMPLE 。

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # 使用 istio 默认的 ingress gateway
  servers:
  - port:
      number: 443
      name: https-httpbin
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "httpbin-tls"
    hosts:
    - "httpbin.example.com"
  - port:
      number: 443
      name: https-hello
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "hello-tls"
    hosts:
    - "hello.example.com"
EOF


为gateway创建路由规则virtualservice

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: hello
spec:
  hosts:
  - "hello.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: helloworld-v1
        port:
          number: 5000
EOF

将域名 hello.example.com 解析到网关的地址
curl -v -HHost:hello.example.com --cacert example.com.crt https://hello.example.com:30433/hello

curl -v -HHost:httpbin.example.com --cacert example.com.crt https://httpbin.example.com:30433/status/418

mTLS认证

在 Istio 中主要有两种类型的认证:
1.对等认证(PeerAuthentication):用于服务到服务的认证,以验证进行连接的客户端,Istio 提供双向 TLS 作为传输认证的解决方案,无需更改服务代码就可以启用它。
2.请求认证(RequestAuthentication):用于最终用户认证,以验证附加到请求的凭据,Istio 使用 JSON Web Token(JWT)验证启用请求级认证。

Istio 中的认证策略范围主要包括如下三个方面:
网格
命名空间
特定服务

接下来我们来为服务网格内部的服务开启自动 mTLS(双向TLS认证),实现客户端、服务端都验证双方身份。前面我们已经在 default 命名空间下面部署了 httpbin 应用。
现在我们来测试下命名空间范围内的认证,部署 sleep 应用到 mtls 命名空间下面:

kubectl get svc,pod -l app=httpbin
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/httpbin   ClusterIP   10.109.20.28   <none>        8000/TCP   4d21h

NAME                              READY   STATUS    RESTARTS   AGE
pod/httpbin-74fb669cc6-vwlqk      2/2     Running   2          4d21h
pod/httpbin-v1-75d9447d79-w7k5m   2/2     Running   2          2d6h
pod/httpbin-v2-fb86d8d46-xgxpl    2/2     Running   2          2d6h
[root@master ~]# kubectl create ns mtls
namespace/mtls created
[root@master ~]# cd istio-1.13.2/
[root@master istio-1.13.2]# kubectl apply -f samples/sleep/sleep.yaml -n mtls
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created
[root@master istio-1.13.2]# kubectl get pod -n mtls
NAME                     READY   STATUS    RESTARTS   AGE
sleep-557747455f-jfh2z   1/1     Running   0          11s

当应用部署完成后,以 sleep 应用为客户端来访问 default 命名空间下面的 httpbin 应用,命令如下所示:

[root@master istio-1.13.2]# kubectl exec sleep-557747455f-jfh2z -it  -n mtls -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.6"
}

默认情况下是可以访问的。

cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication   #同等的验证
metadata:
  name: mtls-demo
  namespace: default  # 去掉namespace就是全局的,加上就是命名空间级别的
spec:
  selector:  # 如果配置了selector,则只会对下面匹配的服务生效(针对特定的服务),如果您没有为 selector 字段提供值,则 Istio 会将策略与策略存储范围内的所有工作负载进行匹配
    matchLabels:
      app: httpbin
  mtls:
    mode: PERMISSIVE  # 兼容模式:可以同时使用明文和加密的方式访问
    # mode: STRICT   # 严格模式:双方必须使用 mTLS 去访问
    # DISABLE:禁用双向 TLS,从安全角度来看,除非您提供自己的安全解决方案,否则请勿使用此模式
EOF

这个时候我们重新访问 httpbin 应用可以看到仍然可以访问,因为我们配置的是兼容的宽松模式:

kubectl exec sleep-557747455f-jfh2z -it  -n mtls -- curl http://httpbin.default:8000/ip

将上面的 mode 改成严格模式 STRICT,重新更新后,再次访问 httpbin 应用:

[root@master istio-1.13.2]# kubectl exec sleep-557747455f-jfh2z -it  -n mtls -- curl http://httpbin.default:8000/ip
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56

可以看到请求失败了,证明我们配置的严格模式生效了。那么正确访问 httpbin 服务的方式是怎样的呢?其实也非常简单,只需要将我们的客户端 sleep 应用加入到网格中来即可:

kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n mtls

kubectl exec -it sleep-bd5bdb485-rm4cl -c sleep -n mtls -- curl http://httpbin.default:8000/ip

我们只是在网格中创建了一个对等认证策略的资源对象,就完成了 mTLS 认证,这是因为 Istio 已经实现了自动的 mTLS,会帮我们自动完成证书和密钥的管理,直接注入即可
(使用tls认证,未必一定要使用https)

基于 JWT 的认证和授权

JWT(JSON Web Token)是一种多方传递可信 JSON 数据的方案,一个 JWT token 由.分隔的三部分组成:{Header}.{Payload}.{Signature},其中 Header 是 Base64 编码的 JSON 数据,包含令牌类型、签名算法以及秘钥 ID 等信息;Payload 是需要传递的 claims 数据,也是 Base64 编码的 JSON 数据,其中有些字段是 JWT 标准已有的字段如:exp、iat、iss、sub 等,也可以根据需求添加自定义字段;Signature 是对前两部分的签名,防止数据被篡改,以此确保 token 信息是可信的。
service mesh:istio全_第76张图片
Istio 中验签所需公钥由 RequestAuthentication 请求认证资源的 jwks 配置提供。JWT 授权则是对终端用户的访问控制,比如某个内部服务需要管理员才能够访问,这时候就需要验证终端用户的角色是否为管理员,可以在 JWT claims 中带有管理员角色信息,然后在授权策略中对该角色进行授权。

要使用 JWT 授权的当然就需要有效的 JWT 终端身份认证,所以在使用 JWT 授权前首先要为服务添加终端身份认证即 RequestAuthentication。

Request 认证策略指定验证 JWT 所需的值,这些值包括:

token 在请求中的位置
请求的 issuer 公共
JSON Web Key Set(JWKS)

Istio 会根据 request 认证策略中的规则检查提供的令牌(如果已提供),并拒绝令牌无效的请求。当请求不带有令牌时,默认情况下将接受它们。要拒绝没有令牌的请求,请提供授权规则。

现在我们来验证基于 JWT 的认证功能,同样使用 sleep 和 httpbin 应用为例,将 sleep 部署到名为 jwtest 的命名空间:

[root@master istio-1.13.2]# kubectl create ns jwtest
Error from server (AlreadyExists): namespaces "jwtest" already exists
[root@master istio-1.13.2]# kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n jwtest
serviceaccount/sleep unchanged
service/sleep unchanged
deployment.apps/sleep configured
[root@master istio-1.13.2]# kubectl get pod -n jwtest
NAME                     READY   STATUS    RESTARTS   AGE
sleep-7cb777fc8b-f2jsj   2/2     Running   0          26s

默认情况下我们是可以正常访问 httpbin 应用的:

[root@master istio-1.13.2]# kubectl exec sleep-7cb777fc8b-f2jsj -it -c sleep -n jwtest -- curl http://httpbin.default:8000/ip
{
  "origin": "127.0.0.6"
}

然后我们创建一个如下所示的请求认证对象:

cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication  # 请求认证
metadata:
  name: jwt-demo
  namespace: default
spec:
  selector:  # 针对特定的服务
    matchLabels:
      app: httpbin
  jwtRules:
  - issuer: "[email protected]"
    jwksUri: "http://my-oss-testing.oss-cn-beijing.aliyuncs.com/course/k8strain/jwks.json"
    # jwks
    # fromHeaders
    # fromParams
EOF

其中最重要的就是 jwtRules 属性下面的配置,我们这里通过 jwksUri 来指定所需的认证信息,这个请求认证策略使得 httpbin 应用接收 Issuer 为 [email protected] 的 JWT 令牌。比如我们通过一个无效的 Token 来访问应用:

kubectl exec -it sleep-bd5bdb485-xg92l -c sleep -n jwtest -- curl http://httpbin.default:8000/headers -s -o /dev/null -H "Authorization: Bearer invalidToken" -w "%{http_code}\n"
401

因为我们指定了 Token,所以就需要使用我们配置的 JWT Token 来进行认证。如果我们不带上 Token,则可以正常访问,这是因为我们并没有配置任何授权策略:

kubectl exec -it sleep-bd5bdb485-xg92l -c sleep -n jwtest -- curl http://httpbin.default:8000/headers -s -o /dev/null -w "%{http_code}\n"
200

接下来我们针对 httpbin 应用配置一个授权策略,要求所有发往 httpbin 应用的请求都要包含一个将 requestPrincipal 设置为 [email protected]/[email protected] 的有效 JWT。

cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy  # 授权策略
metadata:
  name: auth-policy-demo
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW  # ALLOW、DENY
  rules:
  - from:
    - source:
        requestPrincipals: ["[email protected]/[email protected]"]
EOF

Istio 会使用 / 连接 JWT 的 iss 和 sub 来组成 requestPrincipal 字段,比如我这里的 iss 和 sub 都为 [email protected],这样 Istio 生成的 requestPrincipal 属性值为 [email protected]/[email protected],我们可以通过如下命令获取测试的 Token 以及 JSON 信息:

JWT_JSON=$(curl http://my-oss-testing.oss-cn-beijing.aliyuncs.com/course/k8strain/demo.jwt -s) && echo $JWT_JSON | cut -d '.' -f2 - | base64 --decode -
{"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"[email protected]","sub":"[email protected]"}
TOKEN=$(curl http://my-oss-testing.oss-cn-beijing.aliyuncs.com/course/k8strain/demo.jwt -s)

策略创建完成后我们使用上面的有效的 TOKEN 来访问 httpbin 应用:

kubectl exec -it sleep-bd5bdb485-xg92l -c sleep -n jwtest -- curl "http://httpbin.default:8000/headers" -s -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n"
200

可以看到可以正常访问,我们重新使用没有 JWT Token 的 Header 来请求 httpbin 应用:

kubectl exec -it sleep-bd5bdb485-xg92l -c sleep -n jwtest -- curl "http://httpbin.default:8000/headers" -s -o /dev/null -w "%{http_code}\n"
403

可以看到是 403 错误,这是因为当前的请求不符合上面我们配置的授权策略。关于安全相关的更多信息或者资源对象属性字段可以查看官方文档

CertManager 自动 HTTPS

上边的证书是自签名证书,使用的是自定义的ca
cert-manager 是一种自动执行证书管理的工具,它可以与 Istio Gateway 集成以管理 TLS 证书,当然也可以很方便地和前面我们配置的 ingress-nginx 或者 traefik 配合使用。对于和 Istio 的集成使用,无需特殊配置即可。

在进行本节实验之前记得将前面章节的实验内容进行清空。

安装:
官方提供了一个单一的资源清单文件,包含了所有的资源对象,所以直接安装即可:

# Kubernetes 1.15+
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager.yaml

# Kubernetes <1.15
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager-legacy.yaml

validate验证

上面的命令会创建一个名为 cert-manager 的命名空间,安装大量的 CRD 以及 AdmissionWebhook 对象:

[root@master istio-1.13.2]# kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager.yaml

Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
namespace/cert-manager created
serviceaccount/cert-manager-cainjector created
serviceaccount/cert-manager created
serviceaccount/cert-manager-webhook created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrole.rbac.authorization.k8s.io/cert-manager-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-edit created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
Warning: rbac.authorization.k8s.io/v1beta1 Role is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 Role
role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
role.rbac.authorization.k8s.io/cert-manager:leaderelection created
role.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
Warning: rbac.authorization.k8s.io/v1beta1 RoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 RoleBinding
rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
service/cert-manager created
service/cert-manager-webhook created
deployment.apps/cert-manager-cainjector created
deployment.apps/cert-manager created
deployment.apps/cert-manager-webhook created
Warning: admissionregistration.k8s.io/v1beta1 MutatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
Warning: admissionregistration.k8s.io/v1beta1 ValidatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
[root@master istio-1.13.2]#
[root@master istio-1.13.2]# kubectl get pod -n cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-fc6c787db-gpnw9   1/1     Running   0          70s
cert-manager-d994d94d7-98tht              1/1     Running   0          70s
cert-manager-webhook-845d9df8bf-ws5mh     0/1     Running   0          69s
[root@master istio-1.13.2]# kubectl get pod -n cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-fc6c787db-gpnw9   1/1     Running   0          73s
cert-manager-d994d94d7-98tht              1/1     Running   0          73s
cert-manager-webhook-845d9df8bf-ws5mh     0/1     Running   0          72s
[root@master istio-1.13.2]# kubectl get pod -n cert-manager
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-fc6c787db-gpnw9   1/1     Running   0          84s
cert-manager-d994d94d7-98tht              1/1     Running   0          84s
cert-manager-webhook-845d9df8bf-ws5mh     1/1     Running   0          83s

cert-manager、cert-manager-cainjector 以及 cert-manager-webhook 这几个 Pod 处于 Running 状态

我们可以通过下面的测试来验证下是否可以签发基本的证书类型,创建一个 Issuer 资源对象来测试 webhook 工作是否正常(在开始签发证书之前,必须在群集中至少配置一个 Issuer 或 ClusterIssuer 资源):

cat <<EOF > test-resources.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager-test
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: test-selfsigned
  namespace: cert-manager-test
spec:
  selfSigned: {}  # 配置自签名的证书机构类型
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: selfsigned-cert
  namespace: cert-manager-test
spec:
  dnsNames:
  - example.com
  secretName: selfsigned-cert-tls
  issuerRef:
    name: test-selfsigned
EOF

这里我们创建了一个名为 cert-manager-test 的命名空间,创建了一个 Issuer 的证书颁发机构,然后使用这个 Issuer 来创建一个证书 Certificate 对象,直接创建上面的资源清单即可:

[root@master istio-1.13.2]# kubectl apply -f test-resources.yaml
namespace/cert-manager-test created
issuer.cert-manager.io/test-selfsigned created
certificate.cert-manager.io/selfsigned-cert created
[root@master istio-1.13.2]# kubectl get certificate -n cert-manager-test
NAME              READY   SECRET                AGE
selfsigned-cert   True    selfsigned-cert-tls   95s
[root@master istio-1.13.2]# kubectl describe certificate selfsigned-cert -n cert-manager-test
Name:         selfsigned-cert
Namespace:    cert-manager-test
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1beta1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2022-04-10T13:08:37Z
  Generation:          1
  Managed Fields:
    API Version:  cert-manager.io/v1alpha2
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:dnsNames:
        f:issuerRef:
          .:
          f:name:
        f:secretName:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2022-04-10T13:08:37Z
    API Version:  cert-manager.io/v1alpha2
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:conditions:
        f:notAfter:
        f:notBefore:
        f:renewalTime:
        f:revision:
    Manager:         controller
    Operation:       Update
    Time:            2022-04-10T13:08:38Z
  Resource Version:  657727
  UID:               b0590d8a-3f06-4de8-8fcb-0dc01c456f1a
Spec:
  Dns Names:
    example.com
  Issuer Ref:
    Name:       test-selfsigned
  Secret Name:  selfsigned-cert-tls
Status:
  Conditions:
    Last Transition Time:  2022-04-10T13:08:38Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2022-07-09T13:08:37Z
  Not Before:              2022-04-10T13:08:37Z
  Renewal Time:            2022-06-09T13:08:37Z
  Revision:                1
Events:
  Type    Reason     Age    From          Message
  ----    ------     ----   ----          -------
  Normal  Issuing    2m19s  cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  2m19s  cert-manager  Stored new private key in temporary Secret resource "selfsigned-cert-stbjb"
  Normal  Requested  2m19s  cert-manager  Created new CertificateRequest resource "selfsigned-cert-2x5qk"
  Normal  Issuing    2m18s  cert-manager  The certificate has been successfully issued

cert-manager 的功能非常强大,不只是可以支持 ACME 类型的证书签发,还支持其他众多的类型,比如 SelfSigned(自签名)、CA、Vault、Venafi、External、ACME,只是我们一般主要是使用 ACME 来帮我们生成自动化的证书。

环境配置:
由于通过 ACME 做自动化证书的时候,需要暴露 80 和 443 端口,当然如果我们使用 DNS 校验方式也可以,但是有时候我们根本就没有域名的情况下想要实现自动化证书,我们可以使用 xip.io 这类的服务来实现。前面我们部署 istio-ingressgateway 的时候是通过 NodePort 类暴露的服务,所以我们需要在前面加一个 LB 来转发下请求。这里为了简单,我直接使用 haproxy 来监听节点的 80 和 443 端口,将请求转发到后端的 NodePort 端口。

yum install haproxy
HAProxy是一个开源的可提供高可用性、负载均衡的软件,支持传输层(L4)和应用层(L7)的负载均衡能力,功能丰富,从性能和稳定性上来讲基本能媲美商用的负载均衡软件,因此目前使用非常广泛。
[root@master istio-1.13.2]# kubectl get svc istio-ingressgateway -n istio-system
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   LoadBalancer   10.100.81.91   <pending>     15021:31555/TCP,80:31060/TCP,443:31232/TCP,31400:30799/TCP,15443:32342/TCP   7d3h

然后配置 haproxy,配置文件 /etc/haproxy/haproxy.cfg,内容如下所示:

listen stats
  bind    *:9000
  mode    http
  stats   enable
  stats   hide-version
  stats   uri       /stats
  stats   refresh   30s
  stats   realm     Haproxy\ Statistics
  stats   auth      Admin:Password

frontend istio-https
    bind *:443
    mode tcp
    option tcplog
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }
    default_backend istio-https-svc

backend istio-https-svc
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    server istio-http-svc-1 127.0.0.1:31232 check

frontend istio-http
    bind *:80
    mode tcp
    option tcplog
    default_backend istio-http-svc

backend istio-http-svc
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    server istio-http-svc-1 127.0.0.1:31060 check

在配置文件加入
(vim黏贴时i,:set paste可以避免大多数的黏贴错误)

systemctl enable haproxy --now
netstat -tunlp | grep 9000

然后我们可以通过上面 9000 端口监控我们的 haproxy 的运行状态:
service mesh:istio全_第77张图片
注意访问路径
service mesh:istio全_第78张图片

service mesh:istio全_第79张图片
与 Istio 集成
这里我们来通过 cert-manager 为前面的 httpbin 应用配置自动的 HTTPS,首先单独创建一个用户 cert-manager 的自定义的 Gateway,如下所示:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: acme-gateway
  labels:
    app: ingressgateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - "*.xip.io"
    port:
      number: 80
      name: http
      protocol: HTTP
    # tls:
    #   httpsRedirect: true
  - hosts:
    - "*.xip.io"
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "xip-cert"
---
EOF

需要注意的是在配置 Gateway 的时候 tls 的 credentialName 代表的是 cert-manager 自动生成的证书名称。

接下来创建 VirtulService 对象,这里我们使用 https 方式的 ACME 证书校验方式,除了 http 方式之外还有 tls 与 dns 方式的校验,dns 方式的证书校验支持通配符的域名。所以我们需要为 /.well-known/ 这个 PATH 路径做一个正确的配置,方便进行 http 校验:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  gateways:
  - acme-gateway
  hosts:
  - httpbin.123.59.188.12.xip.io
  http:
  - route:
    - destination:
        host: httpbin
        port:
          number: 8000
EOF

由于acme的使用需要公望以及dns服务才能玩,所以这里暂时放放cert-manager,cert-manager的内容不多,也不算是重点,cert-manager后续再补上。

最后

istio更偏向与是用来提供微服务的运行环境的,运维可能会熟悉些,开发就不一定了,整个微服务开发流程,通常是开发以微服务框架开发应用后部署到istio上,这时就有了兼容了开发的框架和istio的使用的方案,叫dapr,后续学dapr

你可能感兴趣的:(总复习,k8s,Istio,kubernetes,云原生,istio)