Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • XML
      • Ruby
      • Perl
      • Lua
      • Rust
      • Network
      • IoT
      • GIS
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • XML
      • Ruby
      • Perl
      • Lua
      • Rust
      • Network
      • IoT
      • GIS
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Istio学习笔记

10
Sep
2018

Istio学习笔记

By Alex
/ in PaaS
/ tags K8S, ServiceMesh
0 Comments
简介
服务网格是什么

术语服务网格(Service Mesh)用于描述微服务之间的网络,以及通过此网络进行的服务之间的交互。随着服务数量和复杂度的增加,服务网格将变的难以理解和管理。

对服务网格的需求包括:服务发现、负载均衡、故障恢复、指标和监控,以及A/B测试、金丝雀发布、限速、访问控制、端对端身份验证等。

Istio是什么

使用云平台给DevOps团队带来了额外的约束,为了Portability开发人员通常需要使用微服务架构,运维人员则需要管理非常多数量的服务。Istio能够连接、保护、控制、观察这些微服务。

Istio是运行于分布式应用程序之上的透明(无代码入侵)服务网格,它同时也是一个平台,提供集成到其它日志、监控、策略系统的接口。

Istio的实现原理是,为每个微服务部署一个Sidecar,代理微服务之间的所有网络通信。在此基础上你可以通过Istio的控制平面实现:

  1. 针对HTTP、gRPC、WebSocket、TCP流量的负载均衡
  2. 细粒度的流量控制行为,包括路由、重试、故障转移、故障注入(fault injection)
  3. 可拔插的策略层+配置API,实现访问控制、限速、配额
  4. 自动收集指标、日志,跟踪集群内所有流量,包括Ingress/Egress
  5. 基于强身份认证和授权来保护服务之间的通信
Istio核心特性
流量管理

使用Istio你可以很容易的通过配置,对流量和API调用进行控制。服务级别的可配置属性包括断路器(circuit breakers)、超时、重试。

Istio支持基于流量百分比切分的A/B测试、金丝雀滚动发布、分阶段滚动发布。

安全性

可以提供安全信道,管理身份验证和授权,加密通信流量。

联用K8S的网络策略可以获得更多益处,例如保护Pod-to-Pod之间的通信。

可观察性

Istio强大的跟踪、监控、日志能力,让服务网格内部结构更容易观察 —— 一个服务的性能对上下游的影响可以直观的展现在仪表盘上。

Istio的Mixer组件——通用的策略和监控(Telemetry)中心(Hub)——负责策略控制、指标收集。Mixer提供后端基础设施(例如K8S)的隔离层,Istio的其它部分不需要关注后端细节。

后端支持

Istio当前支持:

  1. Kubernetes上的Service
  2. 通过Consul注册的服务
  3. 在独立虚拟机上运行的服务
架构

从整体上看,Istio的服务网格由数据平面、控制平面两部分组成:

  1. 数据平面由一系列作为Sidecar部署的智能代理(Envoy)构成。这些代理联合Mixer,中继、控制所有微服务之间的网络通信。需要注意,还有一些Envoy是独立部署(而非Sidecar)的,用来实现K8S Ingress控制器、Istio的Ingress/Egress网关
  2. 控制平面负责管理、配置智能代理,实现流量路由;配置Citadel实现TLS证书管理;配置Mixers来应用策略、收集指标

架构图:

istio-arch

Envoy

Istio使用一个扩展过的Envoy版本。Envoy是基于C++开发的高性能代理,Istio使用它的以下特性:

  1. 动态服务发现
  2. 负载均衡
  3. TLS termination —— 可将后端的HTTP服务包装为HTTPS
  4. HTTP/2和gRPC代理
  5. 断路器
  6. 健康检查
  7. 分阶段(基于流量百分比)发布
  8. 故障注入
  9. 丰富的监控指标

一般情况下Envoy在和目标服务的相同Pod中,以Sidecar形式部署。少量的Istio组件的主进程就是Envoy,包括Ingress控制器、Ingress/Egress网关。

Mixer

一个平台无关的组件:

  1. 为服务网格应用访问控制策略
  2. 从Envoy和其它服务中收集指标
  3. Envoy收集的请求级别的属性,被发送到Mixer进行分析

Mixer提供了一个灵活的插件模型,让Istio能够灵活的和多种宿主机环境、基础设施后端进行对接。

Pilot

该组件是Istio的控制器,它会监控各种规则、策略(通常存储在K8S中),一旦配置文件发生变化,就会提取、处理,并同步给Envoy:

  1. 为Envoy提供服务发现
  2. 为智能路由(AB测试、金丝雀部署……)提供流量管理能力
  3. 提供弹性(Resiliency)——超时、重试、断路器等
  4. 分发身份验证策略给Envoy

Pilot将高级别的路由规则转换为Envoy理解的配置信息,并在运行时将这些配置传播到Sidecars。

Pilot将平台相关的服务发现机制抽象为标准的(Envoy data plane API,xDS)格式,这让Istio可以在K8S、Consul、Nomad等多种环境下运行。

Citadel

负责证书、密钥的管理。提供服务-服务之间、或者针对终端用户的身份验证功能,可以加密服务网格中的流量。

Istio的性能

部署服务网格带来了额外的性能损耗,下面是Istio组件的推荐资源配置:

  1. 对于启用了访问日志(默认启用)的Sidecar,每1000请求/s可以配备1个vCPU,如果没有启用访问日志则配备0.5个vCPU
  2. 节点上的Fluentd是资源消耗大户,它会捕获并上报日志,如果不关心可以排除Sidecar日志不上报
  3. 在大部分情况下,Mixer Check有超过80%的缓存命中率,在此前提下,可以为Mixer的Pod每1000请求/s配备0.5个vCPU
  4. 由于在通信两端都引入了Sidecar代理、并且需要上报Mixer,大概会引入10ms的延迟
Istio的HA

任何控制平面组件都支持多实例部署,在K8S中可以考虑配置HPA。

考虑将Mixer的check、report功能分开部署。当前官方Chart已经分开,分别对应istio-policy、istio-telemetry两个Deployment。

术语列表
术语 说明
Service

一个应用程序“行为单元”,它对应服务注册表(例如K8S的集群服务DNS)中的唯一的名称

服务具有0-N个网络端点(Endpoint),这些端点可以是Pod、容器、虚拟机

Service versions

也叫subsets,这是持续部署(CD)中的概念

在持续部署时,网格中可能运行着某个微服务的多种变体,这些变体的二进制代码不同,但是API版本不一定不同。这些变体代表了对某个服务的迭代性的改变,部署在不同的环境中

Source 访问当前服务的下游(downstream)客户端
Host 客户端连接到服务器时所使用的DNS名称
Destination 表示一个可寻址的服务,请求/连接在经过路由处理后,将发送到Destination
新特性
1.6版本
  1. 进一步完善istiod(单体化)
  2. 可观察性增强:更多配置项、对追踪采样率的控制、更新的Grafana仪表盘
  3. 更好的虚拟机支持(未来将更进一步强化):WorkloadEntry资源让非K8S工作负载成为Istio的一等公民,物理机或者虚拟机和Pod现在处于同一级别。你可以定义同时由Pod和物理机Back的服务。这让逐步迁移到K8S提供便利
1.5版本
单体化

1.5版本的最大变化是,控制平面的大部分组件(pilot、citadel、sidecar-injector)合并到名为istiod的单体应用中,现在istiod负责:

  1. 配置处理和推送
  2. 证书分发
  3. Sidecar注入

以下组件被移除:

  1. SDS的node-agent(合并到Pilot Agent)
废弃Mixer

Mixer被废弃,但是仍然可以使用。

新版本HTTP遥测基于Envoy过滤器Stats filter实现,可以节省50%的CPU用量。

流量管理
  1. 提升了 ServiceEntry 的性能。
  2. 修复了 readiness 探针不一致问题
  3. 通过定向局部更新的方式改善了配置更新的性能
  4. 添加了为 host 设置所在负载均衡器设置的选项
  5. 修复了 Pod 崩溃会触发过度配置推送的问题
  6. 添加了使用 Istio CNI 时对 iptables 的探测
  7. 添加了 consecutive_5xx 和 gateway_errors 作为离群值探测选项。
  8. 提升了 EnvoyFilter 匹配性能
  9. 添加了对 HTTP_PROXY 协议的支持
  10. 改进了 iptables 设置,默认使用 iptables-restore
  11. 默认开启自动协议探测
安全
  1. 添加 Beta 认证 API。新 API 分为 PeerAuthentication 和 RequestAuthenticaiton,面向工作负载
  2. 添加认证策略,支持 deny 操作和语义排除
  3. Beta 版本默认开启自动 mTLS
  4. 稳定版添加 SDS
  5. Node agent 和 Pilot agent 合并,移除了 Pod 安全策略的需要,提升了安全性
  6. 合并 Citadel 证书发放功能到 Pilot
  7. 支持 Kubernetes first-party-jwt 作为集群中 CSR 认证的备用 token
  8. 通过 Istio Agent 向 Prometheus 提供密钥和证书
  9. 支持 Citadel 提供证书给控制平面
遥测
  1. 为 v2 版本的遥测添加 TCP 协议支持
  2. 在指标和日志中支持添加 gRPC 响应状态码
  3. 改进 v2 遥测流程的稳定性
  4. 为 v2 遥测的可配置性提供 alpha 级别的支持
  5. 支持在 Envoy 节点的元数据中添加 AWS 平台的元数据
  6. 更新了 Mixer 的 Stackdriver 适配器,以支持可配置的刷新间隔来跟踪数据
  7. 支持对 Jaeger 插件的 headless 收集服务
  8. 修复了 kubernetesenv 适配器以提供对名字中有.的 Pod 的支持
  9. 改进了 Fluentd 适配器,在导出的时间戳中提供毫秒级输出
1.4版本

仍然以用户体验改进为主,简化配置难度。

无Mixer遥测

可以简化安装、Mesh的操控,并很大程度上改善性能。

istio-proxy内部生成HTTP指标的特性,从试验升级为Alpha。

istio-proxy内部生成TCP指标(试验)

自动mTLS

不需要配置DestinationRule即可自动启用。

1.3版本

该版本主要专注于用户体验的改善。

智能协议检测(试验)

为了使用Istio的路由特性,服务端口名必须遵循特定的命名约定,来声明其使用的协议。

从1.3开始,出站流量的协议可以自动识别为TCP或HTTP,这意味着服务端口命名协定不再必须

无Mixer遥测(试验)

现在大部分的安全策略(例如RBAC)已经直接在Envoy里面实现了。

istio-proxy现在可以直接向Prometheus发送数据,而不需要istio-telemetry来丰富、完善信息。

对生成的Envoy配置的定制能力

对于Envoy中的高级特性,Istio可能尚未提供对应API。你现在可以基于EnvoyFilter API来定制:

  1. LDS返回的HTTP/TCP监听器,及其过滤器链
  2. RDS返回的Envoy HTTP路由配置
  3. CDS返回的上游集群
1.2版本
一般特性
  1. 添加注解 traffic.sidecar.istio.io/includeInboundPorts,不再强制要求服务在deployment的YAML中声明contrainerPort
  2. 添加IPv6的试验性支持
流量管理
  1. 在多集群环境下,优化了基于位置的路由
  2. 优化了ALLOW_ANY模式(即允许任何未知的出站流量,配置位于configmap istio中)下的出站流量策略。针对未知HTTP/HTTPS主机+已知端口的流量,会原样的转发
  3. 支持设置针对上游服务的idle超时
  4. 改进了NONE模式(不用iptables,不进行流量捕获)的Sidecar支持
  5. 可以配置Sidecar的Envoy的DNS刷新率,防止DNS服务器过载
安全方面
  1. 扩展自签名Citadel根证书有效期为10年
  2. 可以为Deployment添加PodSpec注解 sidecar.istio.io/rewriteAppHTTPProbers: "true",来重写健康探针
遥感方面
  1. 对Envoy统计信息生成的完全控制,基于注解控制
  2. Prometheus生成的不包含在指标中
1.1版本
性能方面

控制平面性能:Pilot的CPU、内存消耗受网格的配置变化情况、工作负载变化情况、连接到Pilot的代理数量的影响。增加Pilot的实例数来可以减少配置分发处理的时间,提高性能。网格中包含1000服务、2000工作负载、1000QPS的情况下,单个Pilot实例消耗1个vCPU和1.5GB内存。

数据平面性能:

  1. CPU,代理在1000QPS下大概消耗0.6个vCPU
  2. 内存,代理的内存消耗取决于它需要处理的配置数量,大量的Listener、Cluster、Route会增加内存使用。此外,代理在1000QPS下需要消耗50MB内存
  3. 延迟,请求需要同时经过客户端/服务器的sidecar,在1000QPS下P99大概是10ms级别。启用策略检查(Check)会额外增加延迟。L4过滤器的逻辑比较简单,因此TCP流量不会引入显著延迟
安装部署方面
  1. CRD从Istio的Chart中独立出来,放入istio-init这个Chart。这避免了升级Istio而丢失CR数据
  2. 添加几个安装配置Profile,这些配置提供了常用的安装模式,简化了Istio的安装过程
  3. 增强了多集群集成
流量管理方面
  1. 新的资源类型Sidecar。用于对工作负载的边车代理进行细粒度的控制,特别是可以限制边车能够向哪些服务发送流量。此资源可以减少需要计算和传输给边车代理的配置信息,提升启动速度,减少资源消耗,提升控制平面可扩容性。在大型集群中,建议为每个命名空间提供一个Sidecar资源
  2. 支持限制服务(ServiceEntry、VirtualService)的可见范围。exportTo可用于限制哪些命名空间能够访问本服务。除了在Istio CR上指定exportTo以外,你还可以在K8S的Service上使用networking.istio.io/exportTo注解
  3. Gateway引用VirtualService时,可能存在歧义,因为不同命名空间可能定义具有相同hostname的VirtualService。在1.1版本中,你可以用namespace/hostname-match的形式来来设置hosts字段,以避免歧义。类似的在Sidecar中你也可以为egress配置为这种形式
  4. 支持设置ServiceEntry的Locality,以及关联的SAN(用于mTLS)。现在使用HTTPS端口的ServiceEntry不再需要配套的VirtualService来工作
  5. 位置感知路由(Locality-Aware Routing),优先路由到位于当前地理位置(Locality)的服务端点
  6. 简化了多集群模式的安装、支持额外的部署模式。现在可以用Ingress网关连接多个集群,而不需要Pod级别的VPN。为了HA可以在各集群中部署控制平面,这种部署方式下位置感知路由自动开启。可以将命名空间扩展到多个集群,以创建全局命名空间
  7. Istio的Ingress组件被废弃
  8. 性能和可扩容性得到提升
  9. 默认关闭访问日志
安全方面
  1. 健康检查探针,当启用mTLS的情况下,支持Kubernetes的Readiness/Liveness探针。当启用mTLS后,进入Envoy的HTTP探针请求会被拒绝,1.1能够自动进行HTTP探针的重写,将探针请求转发给pilot-agent,并由后者直接转发给工作负载,绕开Envoy的TLS认证
  2. 集群RBAC配置,将RbacConfig资源替换为ClusterRbacConfig资源
  3. 基于SDS的身份配置(Identity Provisioning ),不需要重启Envoy即可实现动态证书轮换,实现on-node的密钥生成增强了安全性
  4. 在HTTP/gRPC的基础上,支持TCP服务的授权
  5. 支持基于终端用户组的授权
  6. Ingress网关控制器支持外部证书管理,新的控制器用于支持动态的加载、轮换外部证书
  7. 定制PKI集成,支持Vault PKI,基于Vault CA签发证书
策略和遥感方面
  1. 策略检查默认关闭,主要是出于性能方面的考虑
  2. 性能的增强:
    1. 极大的减少了Envoy默认生成的统计信息的收集
    2. 为Mixer工作负载添加了负载限制的功能
    3. 改善了Mixer和Envoy之间的交互协议
  3. 适配器现在能够影响入站请求的头和路由
  4. 进程外适配器,生产级可用,进程内适配器被废弃
  5. 添加了默认的用于跟踪TCP连接的指标
多集群支持

在1.0版本Istio只提供了一种基于扁平网络的多集群方案:Istio控制平面部署在其中单个Kubernetes集群中。这种方案要求各集群的 Pod 地址范围不能重叠,且所有的 Kubernetes 控制平面API Server 互通。看上去就是物理上把多个Kubernetes并到一个Istio控制面下,在Istio看来是同一个网格。这种方案的网络要求苛刻,实际应用并不多。

1.1版本对多集群上做了非常多的增强,除了保留1.0扁平网络作为一种单控制面的方案外,还提出了另外两种方案供用户根据实际环境和需求灵活选择,这两种方案都不要求是扁平网络:

  1. 多控制平面方案:在每个集群中安装完整的Istio控制平面,可以看成是一种松散的联邦,集群间服务在Gateway处联通即可。通过一个全局的DNS将跨集群的请求路由到另外一个集群里去。这种集群的访问是基于Istio的ServiceEntry和Gateway来实现的,配置较多且复杂,需用户自己维护
  2. 一种集群感知(Split Horizon EDS)的单控制平面方案:Istio控制平面只在一个Kubernetes集群中安装,Istio控制平面仍然需要连接所有Kubernetes集群的K8S API Server。每个集群都有集群标识和网络标识。在服务间访问时,如果目标是本集群的负载,则类似单集群的方式直接转发;如果是其他集群的实例,则将流量转发到集群的入口网关上,再经过网关转发给实际负载
exportTo字段

在1.1版本中添加了一个重要字段exportTo。用于控制VirtualService、DestinationRule和 ServiceEntry 跨Namespace的可见性。这样就可以控制一个Namespace下定义的资源是否可以被其他Namespace下的Envoy访问。

如果不设置exportTo则默认全局可见。目前exportTo仅仅支持两个取值:

".",表示仅仅当前命名空间可以使用当前资源

"*",表示任何命名空间都可以使用当前资源

如果服务对Pod不可见,则Istio不会为该Pod的Envoy生成Listener、Cluster等信息,因而可以减少内存消耗。

sidecar资源

Sidecar是具有命名空间的资源。每个Sidecar应用到命名空间的一个和多个工作负载,工作负载的选择通过workloadSelector进行,如果不指定workloadSelector(每个命名空间只能有一个这样的Sidecar)则Sidecar应用到命名空间的所有(没有被其它带有workloadSelector的Sidecar匹配的)工作负载。

如果命名空间包含多个没有workloadSelector的Sidecar,或者多个Sidecar的workloadSelector匹配同一工作负载,则网格的行为是未定义的。

配置示例一,允许prod-us1命名空间的Pod发起针对prod-us1, prod-apis, istio-system命名空间的公共服务的egress流量:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: default
  namespace: prod-us1
spec:
  # 需要通信的服务白名单,可以很大程度上减少Envoy内存消耗
  # 经过测试,创建此资源后,istioctl proxy-config listener PODNAME 输出立刻变得非常少
  # 但是并没有禁止对外访问,可能和ALLOW_ANY有关?
  egress:
  - hosts:
    # 支持namespace/hostname-match形式的host规则
    - "prod-us1/*"
    - "prod-apis/*"
    - "istio-system/*"
    # 当前命名空间
    - "./ratings.default.svc.cluster.local"

配置示例二:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: default
  namespace: prod-us1
spec:
  # 入站流量规则
  ingress:
  # 对于从9080端口入站的HTTP流量,将其转发给Sidecar关联的工作负载监听的UDS
  - port:
      number: 9080
      protocol: HTTP
      name: somename
    defaultEndpoint: unix:///var/run/someuds.sock
  # 出站流量规则
  egress:
  # 允许针对istio-system命名空间的出站流量
  - hosts:
    - "istio-system/*"
  # 允许针对prod-us1命名空间的9080端口的HTTP流量
  - port:
      number: 9080
      protocol: HTTP
      name: egresshttp
    hosts:
    - "prod-us1/*"

配置示例三,如果工作负载没有启用基于Iptables的流量劫持,则Sidecar资源是唯一的配置工作负载边车端口的途径:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 假设匹配的目标工作负载没有设置Iptables规则(没有istio-init容器)并且代理元数据ISTIO_META_INTERCEPTION_MODE设置为NONE
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: no-ip-tables
  namespace: prod-us1
spec:
  # 启用此规则的工作负载选择器
  workloadSelector:
    labels:
      app: productpage
  # 入站流量规则
  ingress:
  - port:
      # 让边车绑定 0.0.0.0:9080
      number: 9080
      protocol: HTTP
      name: somename
    # 并且将9080的流量转发给工作负载的8080端口
    defaultEndpoint: 127.0.0.1:8080
    # 如果为整个代理设置了元数据则不需要
    captureMode: NONE
  # 出站流量规则
  egress:
  # 让边车绑定 127.0.0.1:3306,并且将流量转发给外部的MySQL服务mysql.gmem.cc:3306
  - port:
      number: 3306
      protocol: MYSQL
      name: egressmysql
    captureMode: NONE
    bind: 127.0.0.1
    hosts:
    # 外部服务,命名空间部分写*
    - "*/mysql.gmem.cc"
 
 
# 外部服务用ServiceEntry来定义
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-svc-mysql
  namespace: ns1
spec:
  hosts:
  - mysql.gmem.cc
  ports:
  - number: 3306
    name: mysql
    protocol: MYSQL
  location: MESH_EXTERNAL
  resolution: DNS

字段captureMode指定流向监听器的流量如何被捕获(劫持),仅仅当监听器绑定到IP地址的情况下此字段才有意义:

取值 说明
DEFAULT 由环境定义的默认捕获模式
IPTABLES 基于Iptables重定向进行捕获
NONE

不进行流量捕获,当用在:

  1. egress监听器中时,应用程序被期望明确的和监听的端口/UDS进行直接通信
  2. ingress监听器中时,必须保证工作负载的其它进程不会占用目标监听端口
位置感知路由

Istio支持使用三元组:Region、Zone、Sub-zone来描述网格的地理位置,地理位置通常精确到某个数据中心。Istio能够使用此地理位置信息来对负载均衡池进行优先级控制。

在1.1版本中,基于地理位置的负载均衡仍然是试验特性,且默认关闭。设置环境变量PILOT_ENABLE_LOCALITY_LOAD_BALANCING到所有Pilot实例以开启此特性。

服务发现平台负责自动产生地理位置信息,在K8S中,Pod的地理位置基于它所在Node的知名标签来获取。在云环境下,Node的这些标签会自动设置,否则你需要手工设置Node标签。这些标签包括:

failure-domain.beta.kubernetes.io/region

failure-domain.beta.kubernetes.io/zone

注意,K8S没有sub-zone的概念。

基于地理位置的负载均衡,其默认模式是地理位置优先负载均衡(locality-prioritized load balancing )。Istio会提示Envoy,尽可能将流量转发给和当前Enovy更近的目标工作负载实例。

另一种模式是地理位置权重负载均衡(Locality-weighted load balancing),直接指定不同地理位置负责提供服务的权重。

CLI
istioctl

Istio的命令行配置工具,用于创建、查询、修改、删除Istio配置资源。

通用选项
选项 说明
--context 使用的kubeconfig context,默认""
-i Istio安装在的命名空间,默认istio-system
-c 使用的kubeconfig
--namespace 在哪个命名空间执行命令
--log_output_level  

日志级别,格式为scope:level,scope:level...默认default:info

scope取值:ads, default, model, rbac

level取值:debug, info, warn, error, none

authn

一组子命令,用于操控Istio的身份验证策略:

Shell
1
2
3
4
5
6
7
# 列出所有服务的身份验证策略
istioctl authn tls-check
# 仅仅显示单个服务的
istioctl authn tls-check grafana.istio-system.svc.k8s.gmem.cc
# 服务主机名和端口                 状态                    身份验证策略(服务器配置)                     客户端配置
# HOST:PORT                     STATUS  SERVER  CLIENT  AUTHN POLICY                              DESTINATION RULE
# grafana.istio-system...:3000  OK      HTTP    HTTP    grafana-ports-mtls-disabled/istio-system  -
create

创建策略、规则:

Shell
1
istioctl create -f example-routing.yaml
get

获取策略、规则:

Shell
1
2
3
4
5
6
7
# 获取所有虚拟服务
istioctl get virtualservices
# 获取所有目的地规则
istioctl get destinationrules
 
# 输出为YAML
istioctl get virtualservice bookinfo -o yaml 
replace

替换掉策略、规则:

Shell
1
istioctl replace -f example-routing.yaml 
delete 

删除策略、规则,命令格式:

Shell
1
istioctl delete <type> <name> [<name2> ... <nameN>] [flags]

示例:

Shell
1
2
3
4
istioctl delete virtualservice bookinfo
 
# 也可以通过yaml文件删除
istioctl delete -f example-routing.yaml
register

注册一个服务实例(例如一个VM)到服务网格中:

Shell
1
istioctl register <svcname> <ip> [name1:]port1 [name2:]port2 ... [flags]
deregister 

注销虚拟服务实例,命令格式:

Shell
1
istioctl deregister <svcname> <ip> [flags]
experimental 

一组试验性命令,未来可能修改或删除。

子命令 说明
convert-ingress

尽最大努力的把Ingress资源转换为VirtualService,示例:

Shell
1
2
kubectl -n devops get ingresses.extensions grafana --export -o yaml > /tmp/grafana.yaml
istioctl experimental convert-ingress -f /tmp/grafana.yaml

输出的VirtualService总是以svc.cluster.local为集群DNS后缀,可能需要修改 

metrics

打印一个或多个虚拟服务的统计指标,示例:

Shell
1
2
3
4
5
6
7
# 列出所有虚拟服务
istioctl get virtualservice
# 显示某个服务的指标
istioctl experimental metrics productpage
#  工作负载名称  请求/秒       错误/秒     延迟百分比分布
   WORKLOAD    TOTAL RPS    ERROR RPS  P50 LATENCY  P90 LATENCY  P99 LATENCY
productpage        0.000        0.000           0s           0s           0s

 

rbac

检查是否有权限访问服务,格式:

Shell
1
istioctl experimental rbac can METHOD SERVICE PATH [flags]

示例:

Shell
1
2
3
4
# 用户alex是否有权访问rating服务的/v1/health路径
istioctl experimental rbac can -u alex GET rating /v1/health
# 服务product-page是否有权以POST方法访问rating服务的dev版本的/data路径
istioctl experimental rbac can -s service=product-page POST rating /data -a version=dev
post-install

安装后相关操作:

post-install webhook disable 禁用Webhook
post-install webhook enable 启用Webhook
post-install webhook status 查看Webhook状态

remove-from-mesh

将工作负载从Mesh中移除:

remove-from-mesh deployment 移除此Deployment对应的Pod的Sidecar并重启
remove-from-mesh service 移除此服务对应的Pod的Sidecar并重启
remove-from-mesh external-service 移除ServiceEntry以及对应的K8S服务

upgrade 检查新版本并升级
wait

等待和Istio有关的资源Ready

kube-inject

手工的将Envoy Sidecar注入到K8S的工作负载的定义文件中。定义文件中包含多种不支持注入的资源是安全的:

Shell
1
2
3
4
# 输出到标准输出
kubectl apply -f <(istioctl kube-inject -f resource.yaml)
# 输出到文件
istioctl kube-inject -f deployment.yaml -o deployment-injected.yaml
proxy-config

一组命令,用于Dump出Envoy代理的各方面的配置:

子命令 说明
bootstrap

获取Envoy实例的Boostrap信息:

Shell
1
istioctl proxy-config bootstrap <pod-name> [flags]
cluster 获取Envoy实例的集群配置信息
listener 获取Envoy实例的listener配置信息
route 获取Envoy实例的路由配置信息

本节不做更细致的描述,具体参考运维-Dump出Envoy配置一节。

proxy-status

查询从Pilot最后一次发送到各Envoy的xDS同步的状态:

Shell
1
2
3
istioctl proxy-status
# PROXY                               CDS     LDS    EDS            RDS    PILOT                         VERSION
# details-v1-67c8f895b4-dnddn.default SYNCED  SYNCED SYNCED (100%)  SYNCED istio-pilot-77646875cd-jqmdh  1.0.2
analyze

分析配置文件并打印校验信息: istioctl analyze <file>... [flags]

示例:

Shell
1
2
3
4
5
6
7
8
# 分析当前服务网格
istioctl analyze
 
# 分析应用指定配置文件后的当前服务网格
istioctl analyze a.yaml b.yaml my-app-config/
 
# 不连接到服务网格,进行分析
istioctl analyze --use-kube=false a.yaml b.yaml my-app-config/
dashboard

访问Istio的Web UI。

Shell
1
2
3
4
5
6
7
8
9
10
11
12
# 为Istio控制仪表盘中打开指定Pod的ControlZ WebUI
istioctl dashboard controlz <pod-name[.namespace]> [flags]
 
# 为指定的Sidecar打开Envoy Admin仪表盘
istioctl dashboard envoy <pod-name[.namespace]> [flags]
 
# 打开集成的软件
istioctl dashboard grafana [flags]
istioctl dashboard jaeger [flags]
istioctl dashboard kiali [flags]
istioctl dashboard prometheus [flags]
istioctl dashboard zipkin [flags]
manifest

生成、应用(到K8S)Istio资源的清单文件,或者显示Diff:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 生成清单文件直接写入K8S
istioctl manifest apply
  --dry-run                         # 只是打印会做什么
  --filename,-f                  # 指向IstioOperator资源,用于在Base配置上进行Overlay,可以指定多次
  --force                         # 即使校验失败也继续
  --kubeconfig,-c                # Kubeconfig文件
  --set,-s                       # 覆盖IstioOperator中的值,下载Istio并到install/kubernetes/operator/profiles
                                 # 中看有哪些值,Spec字段下的值可以直接Set,例如Spec.hub可以 -s hub=docker.gmem.cc
  --wait,-w                      # 等待必要的资源就绪
 
# 应用默认配置
istioctl manifest apply
# 设置变量
istioctl manifest apply --set values.grafana.enabled=true
# 应用Profile,可用的Profile列表位于install/kubernetes/operator/profiles
istioctl manifest apply --set profile=demo --skip-confirmation
# 特殊字符转义
istioctl manifest apply --set "values.sidecarInjectorWebhook.injectedAnnotations.container\.apparmor\.security\.beta\.kubernetes\.io/istio-proxy=runtime/default"
 
# 显示两个目录或文件的区别
istioctl manifest diff
 
# 只是生成到标准输出,不Apply到K8S
istioctl manifest generate [flags]
 
# 从Helm Values或IstioControlPlane转换为IstioOperator格式的配置
istioctl manifest migrate
profile

列出、Dump、Diff可用的Profile,这些Profile可以在manifest命令中引用:

Shell
1
2
3
4
5
6
7
8
9
10
# 列出Profile
istioctl profile list [flags]
 
 
# 打印出来
istioctl profile dump [<profile>] [flags]
 
 
# 显示两个Profile的差异
istioctl profile diff <file1.yaml> <file2.yaml> [flags]
fortio

这是Istio的负载测试工具。

格式
Shell
1
2
       # 子命令 # 选项   # 目标URL
fortio command [flags] target
子命令
子命令 说明
load 执行负载测试
server 启动GRPC ping/http服务
grpcping 启动GRPC客户端
report 启动用于查看报告的UI服务器
redirect 启动仅执行重定向的服务器
curl 简单的调试一个URL,显示响应的详细信息
选项
选项 说明
-H value 添加额外的请求头
-abort-on int 如果遇到目标HTTP状态码,立即终止运行。例如503或-1,表示套接字错误
-allow-initial-errors 允许预热期间的错误,且不终止运行
-c int 连接/Goroutine/线程的并发数,默认4
-compression 启用HTTP压缩
-http1.0 使用HTTP 1.0而非1.1
-k 不校验服务器端证书
-keepalive 传输层连接重用,默认true
-qps float 目标QPS,如果为0则不做任何休眠,尽可能提高QPS,默认8
-n int 运行指定的次数
-t duration 运行指定的时间,默认5s,指定为0则一直运行
-p string 统计的百分比值(即耗时N%百分比的请求的最大耗时值),默认 50,75,90,99,99.9
-timeout duration HTTP连接/读取超时,默认15s
-user string HTTP基本认证的凭证信息,格式user:pswd
-loglevel lv 日志级别,可以取值Warning、Error等
mixc

Mixer的命令行客户端。

check

用于执行前置条件检查、配额(quota allocations)。Mixer期望以一组Attributes为输入,它基于自己的配置来判断调用哪个适配器、如何传递参数,进而进行策略检查、配额。

选项 短选项 说明
--attributes  -a 自动感知的属性列表,形式name1=value1,name2=value2...
--bool_attributes -b 布尔值属性的列表
--bytes_attributes   字节值属性的列表
--concurrency -c 并行的调用Mixer的线程数
--double_attributes -d 浮点数属性的列表
--duration_attributes   时间长度属性的列表
--int64_attributes -i 整数属性的列表
--string_attributes -s 字符串属性的列表
--stringmap_attributes   string:string映射类型的属性列表,形式name1=k1:v1;k2:v2,name2=k3:v3...
--timestamp_attributes -t

时间戳属性的列表

时间戳必须以ISO8601格式指定,例如:

Shell
1
2
3
4
5
6
7
8
# UTC时间
2019-03-27T11:42:57Z
 
# 北京时间
2019-03-27T11:42:57+08:00
 
# 带毫秒
2019-03-27T11:42:57.000+08:00
--mixer -m 目标Mixer实例
--quotas -q 需要分配的配额的列表,形式name1=amount1,name2=amount2...
--repeat -r 连续发送指定次数的请求,默认1
--trace_jaeger_url   追踪相关
--trace_log_spans  
--trace_zipkin_url  
report

用于产生遥感(telemetry)数据。Mixer期望以一组Attributes为输入,它基于自己的配置来判断调用哪个适配器、如何传递参数,进而输出遥感数据。

选项和check子命令一样。

安装到K8S
下载最新版
Shell
1
2
3
cd /home/alex/Applications
curl -L https://git.io/getLatestIstio | sh -
mv istio-1.0.3 istio

然后把istio/bin目录加入PATH环境变量。

支持Sidecar自动注入

Istio可以自动为Pod注入Sidecar,它基于Webhook实现此功能。

你可以执行下面的命令,确认MutatingAdmissionWebhook、ValidatingAdmissionWebhook这两种Admission controllers是否默认启用:

Shell
1
2
3
kubectl -n kube-system exec kube-apiserver-xenon -- kube-apiserver -h | grep enable-admission-plugins
# 如果在你的K8S版本中,in addition to default enabled ones...后面的括号中有MutatingAdmissionWebhook
# 和ValidatingAdmissionWebhook则说明默认已经启用

如果没有启用,则需要修改API Server的命令行参数。

此外,你还需要检查admissionregistration API是否启用:

Shell
1
2
kubectl api-versions | grep admissionregistration
admissionregistration.k8s.io/v1beta1

和手工注入不同,自动注入是发生在Pod级别的。在Deployment上看不到任何变化,你需要直接检查Pod才能看到注入的Envoy。 

对Pod/Service的要求

为了加入到服务网格,K8S中的相关Pod、Service必须满足如下条件:

  1. 命名端口:服务(指K8S的服务,不是Pod)的端口必须被命名,为了使用Istio的路由特性,端口名必须是 <protocol>[-<suffix>]的形式。protocol可以是http, http2, grpc, mongo或者redis,suffix是可选的,如果指定suffix则必须在前面加上短横线。如果protocol不支持,或者端口没有命名,则针对该端口的流量作为普通TCP流量看待(除非通过Protocol: UDP明确指定为UDP)。示例:
    YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    apiVersion: v1
    kind: Service
    metadata:
      name: details
    spec:
      ports:
      - port: 9080
        # 命名服务端口
        name: http
      selector:
        app: details 
  2. 服务关联:如果一个Pod属于多个Kubernetes Service,则这些Service不得将同一端口用作不同协议
  3. app和version标签:建议为Pod添加app和version两个标签,分别表示应用程序的名称和版本。在分布式追踪(distributed tracing)中app标签用于提供上下文信息。在Metrics收集时app、version标签也提供上下文信息
通过Helm安装

Istio的Chart定义在install/kubernetes/helm/instio目录下 。默认情况下,Istio会以Sub chart的方式安装很多组件:

requirements.yaml
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
dependencies:
  # 这是一个Webhook,自动为所有Pod注入Sidecar。默认启用
  - name: sidecarInjectorWebhook
    version: 1.0.3
    condition: sidecarInjectorWebhook.enabled
  # 安全模块,即citadel。默认禁用
  - name: security
    version: 1.0.3
    condition: security.enabled
  # Ingress控制器,用于处理K8S的Ingress资源。默认禁用
  - name: ingress
    version: 1.0.3
    condition: ingress.enabled
  # 入口、出口网关。默认启用
  - name: gateways
    version: 1.0.3
    condition: gateways.enabled
  # 默认启用,核心组件
  - name: mixer
    version: 1.0.3
    condition: mixer.enabled
  # 默认启用,核心组件
  - name: pilot
    version: 1.0.3
    condition: pilot.enabled
  # 默认禁用
  - name: grafana
    version: 1.0.3
    condition: grafana.enabled
  # 默认启用
  - name: prometheus
    version: 1.0.3
    condition: prometheus.enabled
  # 用于绘制调用链图。默认禁用,被kiali代替
  - name: servicegraph
    version: 1.0.3
    condition: servicegraph.enabled
  # APM。默认禁用
  - name: tracing
    version: 1.0.3
    condition: tracing.enabled
  # 用于服务器端的配置合法性验证。默认启用
  - name: galley
    version: 1.0.3
    condition: galley.enabled
  # 服务网格可视化。默认禁用
  - name: kiali
    version: 1.0.3
    condition: kiali.enabled
  # 负责基于ACME执行数字证书签名。默认禁用
  - name: certmanager
    version: 1.0.3
    condition: certmanager.enabled 

如需禁用,在values.yaml中修改对应配置项即可。

镜像列表

1.0.3版本:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker pull docker.io/istio/proxyv2:1.0.3
docker pull docker.io/istio/proxy_init:1.0.3
docker pull docker.io/istio/sidecar_injector:1.0.3
docker pull docker.io/istio/galley:1.0.3
docker pull docker.io/istio/mixer:1.0.3
docker pull docker.io/istio/pilot:1.0.3
docker pull docker.io/istio/citadel:1.0.3
docker pull docker.io/istio/servicegraph:1.0.3
docker pull docker.io/jaegertracing/all-in-one:1.5
docker pull docker.io/kiali/kiali:v0.9
docker pull docker.io/prom/prometheus:v2.3.1
docker pull quay.io/jetstack/cert-manager-controller:v0.3.1
docker pull quay.io/coreos/hyperkube:v1.7.6_coreos.0
docker pull grafana/grafana:5.2.3

1.1.4版本:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker pull docker.io/istio/kubectl:1.1.4
docker pull docker.io/istio/node-agent-k8s:1.1.4
docker pull docker.io/istio/citadel:1.1.4
docker pull docker.io/istio/proxy_init:1.1.4
docker pull docker.io/istio/proxyv2:1.1.4
docker pull docker.io/istio/galley:1.1.4
docker pull docker.io/istio/pilot:1.1.4
docker pull docker.io/istio/mixer:1.1.4
docker pull docker.io/istio/sidecar_injector:1.1.4
docker pull docker.io/jaegertracing/all-in-one:1.9
docker pull docker.io/kiali/kiali:v0.16
docker pull docker.io/prom/prometheus:v2.3.1
docker pull busybox:1.30.1
docker pull grafana/grafana:6.0.2
docker pull quay.io/jetstack/cert-manager-controller:v0.6.2
执行安装

到Github下载Istio的压缩包,解压后在其根目录执行。

我的环境下,镜像拉取需要Secret:

Shell
1
2
3
4
5
6
7
8
9
export PSWD=...
 
# 创建命名空间
kubectl create ns istio-system
 
# 创建访问Docker镜像仓库的保密字典
kubectl -n istio-system create secret docker-registry gmemregsecret \
    --docker-server=docker.gmem.cc --docker-username=alex  \
    --docker-password=$PSWD --docker-email=k8s@gmem.cc

对于Istio 1.1,需要首先安装独立的Chart istio-init,进行CRD的注册:

Shell
1
helm install install/kubernetes/helm/istio-init  --name istio-init --namespace istio-system -f install/kubernetes/helm/istio-init/overrides/gmem.yaml

安装Istio组件:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 创建入口网关控制器所需的数字证书,这里是*.k8s.gmem.cc的通配符证书
# 注意,ingressgateway没有自动reload数字证书的能力,必须重启Pod
pushd /home/alex/Documents/puTTY/mesh.gmem.cc
kubectl create -n istio-system secret generic istio-ingressgateway-certs --from-file=.
popd
 
# 创建kiali的Ingress所需的数字证书保密字典
pushd /etc/letsencrypt/live/kiali.k8s.gmem.cc
kubectl create -n istio-system secret generic gmemk8scert-kiali --from-file=tls.crt=fullchain.pem,tls.key=privkey.pem
popd
# 创建Jaeger的Ingress所需的数字证书保密字典
pushd /etc/letsencrypt/live/jaeger.k8s.gmem.cc
kubectl create -n istio-system secret generic gmemk8scert-jaeger --from-file=tls.crt=fullchain.pem,tls.key=privkey.pem
popd
 
# 为默认证书设置imagePullSecrets
kubectl -n istio-system patch serviceaccount default -p '{"imagePullSecrets": [{"name": "gmemregsecret"}]}'
 
# 不让Citadel使用自签名根证书
pushd ~/Documents/puTTY/
touch empty.pem
kubectl -n istio-system create secret generic cacerts --from-file=ca-cert.pem=ca.crt \
                                                      --from-file=ca-key.pem=ca.key \
                                                      --from-file=root-cert.pem=ca.crt \
                                                      --from-file=cert-chain.pem=empty.pem
popd
 
# 挂载Ceph卷所需的密钥
kubectl get secrets pvc-ceph-key -o yaml --export | kubectl -n istio-system apply -f -
 
 
# 安装Istio
# 1.0
helm install install/kubernetes/helm/istio --name istio --namespace istio-system --set kiali.dashboard.passphrase=$PSWD -f install/kubernetes/helm/istio/overrides/gmem.yaml
# 1.1
kubectl -n istio-system create secret generic kiali --from-literal=username=alex --from-literal=passphrase=$PSWD
 
# 为kiali的服务账号授予必要的权限
kubectl create clusterrolebinding istio-system-kiali-role-binding --clusterrole=cluster-admin --serviceaccount=istio-system:kiali-service-account
最小化安装
Shell
1
2
3
4
5
6
7
8
9
10
11
helm install install/kubernetes/helm/istio --name istio-minimal --namespace istio-system \
  --set security.enabled=false \
  --set ingress.enabled=false \
  --set gateways.istio-ingressgateway.enabled=false \
  --set gateways.istio-egressgateway.enabled=false \
  --set galley.enabled=false \
  --set sidecarInjectorWebhook.enabled=false \
  --set mixer.enabled=false \
  --set prometheus.enabled=false \
  --set global.proxy.envoyStatsd.enabled=false \
  --set pilot.sidecar=false

最小化安装时,仅仅安装pilot组件。 

执行删除
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
helm delete istio --purge
 
kubectl -n istio-system delete all --all
 
kubectl delete ns istio-system
 
kubectl delete crd adapters.config.istio.io            
kubectl delete crd apikeys.config.istio.io              
kubectl delete crd attributemanifests.config.istio.io  
kubectl delete crd authorizations.config.istio.io      
kubectl delete crd bypasses.config.istio.io            
kubectl delete crd certificates.certmanager.k8s.io      
kubectl delete crd checknothings.config.istio.io        
kubectl delete crd circonuses.config.istio.io          
kubectl delete crd cloudwatches.config.istio.io        
kubectl delete crd clusterissuers.certmanager.k8s.io    
kubectl delete crd deniers.config.istio.io              
kubectl delete crd destinationrules.networking.istio.io
kubectl delete crd dogstatsds.config.istio.io          
kubectl delete crd edges.config.istio.io                
kubectl delete crd envoyfilters.networking.istio.io    
kubectl delete crd fluentds.config.istio.io            
kubectl delete crd gateways.networking.istio.io        
kubectl delete crd handlers.config.istio.io            
kubectl delete crd httpapispecbindings.config.istio.io  
kubectl delete crd httpapispecs.config.istio.io        
kubectl delete crd instances.config.istio.io            
kubectl delete crd issuers.certmanager.k8s.io          
kubectl delete crd kubernetesenvs.config.istio.io      
kubectl delete crd kuberneteses.config.istio.io        
kubectl delete crd listcheckers.config.istio.io        
kubectl delete crd listentries.config.istio.io          
kubectl delete crd logentries.config.istio.io          
kubectl delete crd memquotas.config.istio.io            
kubectl delete crd meshpolicies.authentication.istio.io
kubectl delete crd metrics.config.istio.io              
kubectl delete crd noops.config.istio.io                
kubectl delete crd opas.config.istio.io                
kubectl delete crd policies.authentication.istio.io    
kubectl delete crd prometheuses.config.istio.io        
kubectl delete crd quotas.config.istio.io              
kubectl delete crd quotaspecbindings.config.istio.io    
kubectl delete crd quotaspecs.config.istio.io          
kubectl delete crd rbacconfigs.rbac.istio.io            
kubectl delete crd rbacs.config.istio.io                
kubectl delete crd redisquotas.config.istio.io          
kubectl delete crd reportnothings.config.istio.io      
kubectl delete crd rules.config.istio.io                
kubectl delete crd servicecontrolreports.config.istio.io
kubectl delete crd servicecontrols.config.istio.io      
kubectl delete crd serviceentries.networking.istio.io  
kubectl delete crd servicerolebindings.rbac.istio.io    
kubectl delete crd serviceroles.rbac.istio.io          
kubectl delete crd signalfxs.config.istio.io            
kubectl delete crd solarwindses.config.istio.io        
kubectl delete crd stackdrivers.config.istio.io        
kubectl delete crd statsds.config.istio.io              
kubectl delete crd stdios.config.istio.io              
kubectl delete crd templates.config.istio.io            
kubectl delete crd tracespans.config.istio.io          
kubectl delete crd virtualservices.networking.istio.io  
处理集群DNS后缀

如果你的Kubernetes集群不使用缺省的集群DNS后缀(即cluster.local),则很多组件的参数需要修改,可以参考chart-istio项目,搜索{{ .Values.global.domain }}找到需要修改的位置。

注意:1.1版本的Chart这块已经处理好,修改values.yaml中的global.proxy.clusterDomain即可。

配置

Istio暴露了几个Configmap,修改这些Configmap以影响Istio的行为。

istio
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
apiVersion: v1
kind: ConfigMap
metadata:
  name: istio
  namespace: istio-system
  labels:
    app: istio
    chart: istio-1.0.5
    release: istio
    heritage: Tiller
data:
  mesh: |-
    # 设置为true则禁用策略检查
    # 不影响遥测指标的报送
    disablePolicyChecks: false
 
    # 是否启用请求跟踪
    enableTracing: true
 
    # 设置为空字符串可以禁用访问日志
    accessLogFile: "/dev/stdout"
 
    # Deprecated: mixer is using EDS
    mixerCheckServer: istio-policy.istio-system.svc.k8s.gmem.cc:9091
    mixerReportServer: istio-telemetry.istio-system.svc.k8s.gmem.cc:9091
 
    # 如果设置为true,则Mixer的策略服务不可用导致策略检查失败时,允许请求而非拒绝
    policyCheckFailOpen: false
 
    defaultConfig:
      #
      # Envoy和应用之间、Envoy之间的TCP链接超时
      connectTimeout: 10s
      #
      ### ADVANCED SETTINGS #############
      # istio-proxy容器中,Envoy的配置存放在何处
      configPath: "/etc/istio/proxy"
      # Envoy二进制文件的路径
      binaryPath: "/usr/local/bin/envoy"
      # The pseudo service name used for Envoy.
      serviceCluster: istio-proxy
      # 如果发生偶然性的Envoy reload,旧的Envoy进程保留多长时间
      drainDuration: 45s
      parentShutdownDuration: 1m0s
      #
      # 将入口流量重定向到Envoy的模式,对出口流量无影响,后者总是使用iptables REDIRECT
      # 取值:
      #   REDIRECT,使用iptables REDIRECT来NAT,并重定向到Envoy。源地址信息会丢失
      #   TPROXY,使用iptables TPROXY来重定向到Envoy,同时保留原始的source/dest IP地址和端口
      #           TPROXY可以在不对封包进行任何变动(例如NAT)的情况下重定向封包给一个本地套接字
      #           可以用于高级过滤和操控。此模式会配置sidecar,启用CAP_NET_ADMIN能力,这是TPROXY所需的
      #
      interceptionMode: REDIRECT
      #
      # Envoy在本地环回网卡上监听的管理端口,你可以进入istio-proxy容器并执行
      # curl http://localhost:15000/获取Envoy的诊断信息
      # 访问https://lyft.github.io/envoy/docs/operations/admin.html了解更多信息
      proxyAdminPort: 15000
      #
      # Envoy工作线程数量,如果设置为0,自动根据CPU核心数量确定
      concurrency: 0
      #
      # Zipkin/Jaeger的访问地址,Envoy向其报送APM数据
      zipkinAddress: zipkin.istio-system:9411
      #
      # Sidecar和Istio控制平面的mTLS认证策略
      controlPlaneAuthPolicy: NONE
      #
      # Pilot服务的地址
      discoveryAddress: istio-pilot.istio-system:15007
跨集群网格

可以跨越多个Kubernetes集群来部署Istio服务网格。

前提条件
  1. 两个或更多的运行1.9+版本的Kubernetes集群
  2. 有权在其中一个集群上部署Istio控制平面
  3. 使用RFC1918网络、VPN或更高级的网络技术将集群连接在一起,并且满足:
    1. 所有集群的Pod网络CIDR、Service网络CIDR不重叠
    2. 所有集群的Pod网络可以相互路由
    3. 所有集群的API Server可相互联通
  4. Helm 2.7.2或更高版本
Istio CNI插件

Istio 将用户 pod 流量转发到 proxy 的默认方式是使用 privileged 权限的 istio-init 容器,Istio CNI插件可以消除这一priviledged要求。

Istio CNI Plugin是Kubernetes CNI的一个实现,Kubernetes CNI插件是一个链,在创建和销毁pod的时候会调用链上所有插件来安装和卸载容器的网络。

此插件会运行一个Daemonset: istio-cni-node,在每个节点上安装CNI插件。此插件负责写入iptables规则。

启用此插件,istio-init容器不再需要。

安装

首先安装CNI插件:

Shell
1
helm install install/kubernetes/helm/istio-cni --name istio-cni --namespace kube-system

然后,安装Istio,注意设置参数:

Shell
1
helm install install/kubernetes/helm/istio --name istio --namespace istio-system --set istio_cni.enabled=true 
安装1.5版本

此版本的Helm Chart不正常,安装出来的不是isitd单体模式。

推荐使用 istioctl manifest命令来安装,生成的清单文件包括namespace istio-system,因此删除操作:

Shell
1
istioctl manifest generate <your original installation options> | kubectl delete -f -

会导致预先创建手工创建在istio-system中的资源丢失。建议先 istioctl manifest generate,然后修改它。 

覆盖默认值

使用istioctl预置的Profile不能满足需要的话,可通过-f参数自己提供一个IstioOperator资源,在其中覆盖参数值:

Shell
1
istioctl manifest generate -f gmem.yaml

gmem.yaml
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  namespace: istio-system
spec:
  hub: docker.gmem.cc/istio
  imagePullSecrets:
    - gmemregsecret
  addonComponents:
    kiali:
      enabled: true
    grafana:
      enabled: true
    tracing:
      enabled: true
  values:
    global:
      logging:
        level: "default:info"
      proxy:
        clusterDomain: "k8s.gmem.cc"
        logLevel: info
        privileged: true
        accessLogFile: /dev/stdout
      trustDomain: "k8s.gmem.cc"
      sds:
        enabled: true
    security:
      selfSigned: false
    prometheus:
      hub: docker.gmem.cc/prom
    grafana:
      image:
        repository: docker.gmem.cc/grafana
    tracing:
      jaeger:
        hub: docker.gmem.cc/jaegertracing
    kiali:
      hub: docker.gmem.cc/kiali

1.5.0版本中-f的行为异常,不能生成资源清单。暂时可用-s代替:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
istioctl manifest apply \
  -s profile=demo -s hub=docker.gmem.cc/istio -s values.global.logging.level="default:info" \
  -s values.global.proxy.clusterDomain=k8s.gmem.cc \
  -s values.global.proxy.logLevel=info -s values.global.proxy.privileged=true \
  -s values.global.proxy.accessLogFile=/dev/stdout \
  -s values.global.trustDomain=k8s.gmem.cc -s values.global.sds.enabled=true \
  -s values.security.selfSigned=false  -s values.prometheus.hub=docker.gmem.cc/prom \
  -s values.grafana.image.repository=docker.gmem.cc/grafana \
  -s values.tracing.jaeger.hub=docker.gmem.cc/jaegertracing \
  -s values.kiali.hub=docker.gmem.cc/kiali -s values.kiali.contextPath=/ \
  -s values.tracing.contextPath=/ \
  -s values.global.imagePullSecrets[0]=gmemregsecret -s values.global.imagePullPolicy=Always
卸载
Shell
1
istioctl manifest generate --set profile=demo | kubectl delete -f -

警告:命名空间也会被删除。 

构建和调试
构建
环境变量
Shell
1
2
3
4
5
export HUB=docker.gmem.cc
export TAG=1.5
# 使用本地构建的,位于$GOPATH/src/github.com/istio/proxy/bazel-bin/src/envoy/envoy的Istio Proxy
export USE_LOCAL_PROXY=1
export GOOS=linux
构建Istio Proxy
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export BAZEL_BUILD_ARGS="-c dbg"
 
cd $GOPATH/src/istio.io/
git clone https://github.com/istio/proxy.git
 
# 安装Bazelisk
sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v0.0.8/bazelisk-linux-amd64
sudo chmod +x /usr/local/bin/bazel
 
# 安装相关依赖
sudo apt-get install libtool  cmake automake autoconf make ninja-build curl unzip  build-essential software-properties-common
sudo pip install virtualenv
 
# 安装GCC 7+ 或 Clang/LLVM 7+,因为使用了C++14。我们使用LLVM 7.1.0
cd/home/alex/CPP/llvm
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-7.1.0/clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz
xz -d clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz
tar xvf clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04.tar
mv clang+llvm-7.1.0-x86_64-linux-gnu-ubuntu-14.04 7.1.0
 
# Bazel文件格式化工具
go get -u github.com/bazelbuild/buildtools/buildifier
# 用于移动应用打包
go get -u github.com/bazelbuild/buildtools/buildozer
 
# 执行构建
cd proxy
bazel-proxy/external/envoy/bazel/setup_clang.sh /home/alex/CPP/llvm/7.1.0
http_proxy=http://127.0.0.1 https_proxy=http://127.0.0.1:8087 make
构建Istio
Shell
1
2
3
4
5
6
7
8
9
10
11
cd $GOPATH/src/istio.io/
git clone https://github.com/istio/istio.git
 
cd istio/
# 构建出Docker镜像
make docker
# 推送镜像
make push
 
# 生成K8S资源清单
make generate_yaml
示例应用
bookinfo

Istio提供了一个示例应用程序Bookinfo,我可以部署该应用 ,并为其配置服务网格,以学习Istio。该应用程序能够显示书籍的基本信息,包括ISBN、页数,还能显示书籍的评论信息。

Bookinfo由4个独立的微服务组成:

  1. productpage,调用details、reviews渲染页面
  2. details,提供书籍基本信息
  3. reviews,提供书评信息,调用ratings。该服务有v1、v2、v3三个版本
  4. ratings,提供书籍的评级信息

下图显示了没有部署Istio之前,Bookinfo的端对端架构:

下图显示受到Istio服务网格管理的Bookinfo的架构:

为命名空间启用自动注入

如果启用了自动化的Sidecar注入,你需要在安装到的命名空间上打标签:

Shell
1
kubectl label namespace default istio-injection=enabled

打上此标签后,default命名空间中创建的新Pod,自动会有一个名为stio-proxy的容器,它运行istio/proxyv2镜像。 

手工注入

如果没有启用自动化的Sidecar注入,你需要执行:

Shell
1
kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml)
安装bookinfo资源

然后,执行下面的命令安装所有组件:

Shell
1
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

等所有容器都运行起来了,检查一下当前命名空间,应该多出6个Pod、4个Service。

安装Istio网关

当所有Pod都进入Running状态后,你需要创建一个(入口)网关,这样浏览器才能访问到集群中的Bookinfo服务:

Shell
1
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml

上述命令会创建一个Gateway,一个VirtualService:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# kubectl get gateway bookinfo-gateway -o yaml --export
 
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
  namespace: default
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - '*'
    port:
      name: http
      number: 80
      protocol: HTTP
 
 
# kubectl get virtualservice bookinfo -o yaml --export
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  gateways:
  - bookinfo-gateway  # 和前面的网关绑定
  hosts:
  - '*'
  http:
  - match:
    # 以下3个URL模式发送给productpage服务
    - uri:
        exact: /productpage
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage
        port:
          number: 9080
获取网关地址

我的本地环境,通过配置路由,允许集群外部访问ClusterIP:

Shell
1
2
3
kubectl get svc istio-ingressgateway -n istio-system
# NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                                                                AGE
# istio-ingressgateway   NodePort   10.109.97.20   <none>        80:20080/TCP,443:20443/TCP,31400:31400/TCP,15011:20493/TCP,8060:1164/TCP,853:5745/TCP,15030:1299/TCP,15031:11899/TCP   14d

因此直接使用上述地址10.109.97.20即可。如果你使用NodePort方式,则需要使用宿主机IP + 映射后的端口。

如果使用外部负载均衡器(LoadBalancer类的Service),则参考如下命令:

Shell
1
2
3
4
5
6
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].port}')
 
# 某些环境下,LB通过主机名而非IP暴露
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
确认应用可访问
Shell
1
2
3
4
5
export INGRESS_HOST=`kubectl -n istio-system get service istio-ingressgateway --no-headers | awk '{print $3}'`
export INGRESS_PORT=80
export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT
 
curl -o /dev/null -s -w "%{http_code}\n" http://${GATEWAY_URL}/productpage

如果一切正常,应该打印200到控制台。 如果通过浏览器查看,界面右侧的Reviews面板会随机出现无星、黑星、红星,这种随机是K8S的Service负载均衡机制导致的。

创建默认DestRule

下面的DestinationRule声明了productpage、reviews、ratings、details等微服务的不同版本。版本通过Pod的version标签来区分。

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
---
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
---
httpbin

一个简单的HTTP测试服务,用于试验Istio的各种特性:

Shell
1
kubectl apply -f samples/httpbin/httpbin.yaml
fortio

Fortio是Istio的负载测试工具,提供CLI和Web UI。

Fortio能够按照指定的QPS来执行HTTP请求,录制执行耗时、百分比分布(percentiles,例如p99表示99%的请求耗时小于的数值)直方图。

Shell
1
kubectl apply -f  samples/httpbin/sample-client/fortio-deploy.yaml
sleep

这是一个基于Ubuntu的容器,它启动后只是简单的休眠。你可以通过kubectl连接到此容器并执行curl命令:

Shell
1
kubectl apply -f samples/sleep/sleep.yaml 
试验流量管理
请求路由

下面的例子创建了几个“虚拟服务“,这些虚拟服务将请求流量全部路由给各微服务的v1版本:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: details
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---

虚拟服务的会被转换为Envoy配置信息,在若干秒内传递到所有Pod的Sidecar,达到最终一致性。

虚拟服务可以遮蔽掉K8S的同名Service,启用了Envoy Sidecar的Pod(包括入口网关)访问productpage、reviews、ratings、details这几个微服务(各自对应了一个K8S Service、一个虚拟服务、以及一个DNS名称)时,会自动经由上面定义的几个虚拟服务处理,其效果就是全部访问v1版本的微服务。虚拟服务的路由逻辑在请求方的Sidecar中实现。

现在再打开浏览器访问/productpage,你会发现,不管刷新多少次,Reviews面板总是显示为无星。

从本章开始处的架构图可以知道,productpage会访问reviews服务,我们看一下前者Sidecar日志,可以发现类似下面的内容:

Shell
1
2
3
"GET /reviews/0 HTTP/1.1" 200 - 0 295 85 84 "-" "python-requests/2.18.4" "4e51c6d9-b7d7-483f-912c-986b4ca1ccff"
# 目标服务      实际访问的IP:PORT组合    出站流量      访问reviews的v1版本                     # 服务端             客户端
"reviews:9080" "172.27.252.190:9080" outbound|9080|v1|reviews.default.svc.k8s.gmem.cc - 10.105.173.219:9080 172.27.121.172:56202

在看看后者v1版本的Sidecar日志,可以发现相呼应的内容:

Shell
1
2
3
"GET /reviews/0 HTTP/1.1" 200 - 0 295 14 8 "-" "python-requests/2.18.4" "148f4c19-8106-4833-8a62-7b464b33e807"
                                     # 入站流量
"reviews:9080" "127.0.0.1:9080"      inbound|9080||reviews.default.svc.k8s.gmem.cc    - 172.27.252.190:9080 172.27.121.172:49704

你可以修改虚拟服务reviews,将v1替换为v2、v3,看看有何效果:

Shell
1
kubectl edit virtualservice reviews
基于请求头路由

虚拟服务支持根据请求信息路由,下面的例子依据请求头end-user的值决定使用什么版本的reviews服务:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  # 如果登陆用户为alex,则使用v2版本
  - match:
    - headers:
        end-user:
          exact: alex
    route:
    - destination:
        host: reviews
        subset: v2
  # 否则使用v1版本
  - route:
    - destination:
        host: reviews
        subset: v1

现在,刷新/productpage可以发现是无星,以alex登陆(密码随意)后则显示为黑星。 

故障注入

故障注入(Fault Injection)属于破坏性测试,用于考验应用的韧性(resiliency)。

下面的例子,专为alex用户在reviews:v2和ratings之间引入7s的延迟:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  # 对于alex用户,全部请求引入7s延迟,使用ratings的v1版本
  - match:
    - headers:
        end-user:
          exact: alex
    fault:
      delay:
        percent: 100
        fixedDelay: 7s
    route:
    - destination:
        host: ratings
        subset: v1
  # 对于其它用户,也使用ratings的v1版本,但是不引入延迟
  - route:
    - destination:
        host: ratings
        subset: v1

以alex访问/productpage,页面会在挂起6秒后提示reviews不可用。这是由于productpage到reviews的超时被硬编码为6秒,而reviews到ratings的超时被硬编码为10秒,我们的故障注入导致productpage访问reviews出现超时错误。

除了timeout之外,你还可以注入abort形式的故障,即立刻访问一个HTTP错误码。

灰度发布

Istio支持逐步的将流量从一个版本迁移到另一个,通常是从老版本迁移到新版本。

定义虚拟服务时,你可以设置不同destination的权重,并逐步调整,直到完全切换到另一个版本。下面的例子会让你由50%的概率看到无星或红星:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 50
    - destination:
        host: reviews
        subset: v3
      weight: 50
设置请求超时

你可以为虚拟服务中的路由规则设置超时,默认的HTTP调用超时为15秒。

下面的例子把reviews服务的timeout设置为1ms,这意味着访问该服务的客户端都会基本都会收到超时错误:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# kubectl edit virtualservice reviews
 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
  namespace: default
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
    # 立即超时
    timeout: 1ms

此时再访问/productpage,会提示product reviews are currently unavailable。

使用HTTPS网关

istio-ingressgateway负责处理Gateway资源,就像K8S中Ingress Controller负责处理Ingress资源那样。

istio-ingressgateway默认已经同时支持HTTP/HTTPS,并在80/443端口上监听。

下面的例子创建一个HTTPS网关:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      # 单向认证
      mode: SIMPLE
      # 下面指定istio-ingressgateway的Pod的一个文件系统路径,提示HTTPS所需的证书、私钥的所在位置
      # 默认情况下,网关控制器会将保密字典istio-ingressgateway-certs挂载到/etc/istio/ingressgateway-certs目录下
      # 但是此保密字典默认并不创建,挂载也是可选的(optional: true),本文在《安装到K8S》一章已经创建了保密字典
 
      # 如果你要多套数字证书需要使用,则必须在部署Charts时指定:
      #   --set gateways.istio-ingressgateway.secretVolumes[N].name=ingressgateway-*-certs \
      #   --set gateways.istio-ingressgateway.secretVolumes[N].secretName=istio-ingressgateway-*-certs \
      #   --set gateways.istio-ingressgateway.secretVolumes[N].mountPath=/etc/istio/ingressgateway-*-certs \
      #   ...
      # 来挂载所有数字证书,添加新证书后,deployment istio-ingressgateway必须被编辑以使用新证书,对应Pod会重启
      # 使用通配符证书以免除此麻烦
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      privateKey: /etc/istio/ingressgateway-certs/tls.key
      # 如果mode为MUTUAL则需要提供CA证书位置
      caCertificates: /etc/istio/ingressgateway-certs/ca.crt
    hosts:
    - "httpbin.k8s.gmem.cc"
EOF

你总是需要一个VirtualService来配合Gateway:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.k8s.gmem.cc"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

注意SSL Termination在网关,后端服务不需要启用HTTPS。

完成上述步骤后,访问https://httpbin.k8s.gmem.cc/status/418可以看到如下输出:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
                                                                  # 这里是入口网关的IP
curl -HHost:httpbin.k8s.gmem.cc --resolve httpbin.k8s.gmem.cc:443:10.102.172.91 https://httpbin.k8s.gmem.cc/status/418
 
#   -=[ teapot ]=-
 
#      _...._
#    .'  _ _ `.
#   | ."` ^ `". _,
#   \_;`"---"`|//
#     |       ;/
#     \_     _/
#       `"""`
使用双向认证网关

和上一节的单向认证类似,但双向(mutual TLS )认证需要额外为入口网关控制器提供一个CA证书,用于校验客户端的数字证书。

使用官方Istio的Chart安装时,它默认从保密字典istio-ingressgateway-ca-certs加载CA证书,且挂载到/etc/istio/ingressgateway-ca-certs。

参考下面的代码修改Gateway的定义:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      # 双向认证
      mode: MUTUAL
      # 客户端证书和私钥
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      privateKey: /etc/istio/ingressgateway-certs/tls.key
      # 服务器用来校验客户端证书的CA证书
      caCertificates: /etc/istio/ingressgateway-ca-certs/ca-chain.cert.pem
    hosts:
    - "httpbin.k8s.gmem.cc"
EOF

然后,通过CA为客户端签发证书,并在访问服务器时指定自己的密钥和此证书:

Shell
1
curl ... --cacert /home/alex/Documents/puTTY/ca.crt --cert alex.cert --key alex.key ... 
控制出口流量

要从服务网格内访问外部URL,需要配置绕过代理的IP范围,或者配置出口网关,否则无法访问仅仅能得到404:

Shell
1
2
3
4
SLEEP_POD=`kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}`
 
kubectl exec -it $SLEEP_POD -c sleep -- curl -o /dev/null -s -w "%{http_code}\n" http://gmem.cc 
# 404

下面是通过出口网关访问gmem.cc的例子:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: gmem-ext
spec:
  hosts:
  - gmem.cc
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  # 如果解析名称:
  #   NONE,假设入站连接已经被解析。也就是说必须路由给请求发起者已经解析出的那个IP
  #   STATIC,使用Endpoint中定义的静态IP地址
  #   DNS,通过DNS服务器解析。可能转发给不同的IP(与请求者的解析结果不同)
  resolution: DNS
  # 服务位置:
  #   MESH_EXTERNAL,提示目标服务位于网格外部
  #   MESH_INTERNAL,提示目标服务位于网格内部。用于那些手工加入的服务,例如手工加入到K8S网格的VM
  #                  对于网格外部的服务,必须明确指定,否则Kiali会显示Unkown节点
  location: MESH_EXTERNAL
EOF

现在再尝试上面的kubectl exec命令,会发现能正常获得响应。 

当访问外部HTTPS服务(TLS协议)时,可能需要同时创建ServiceEntry + VirtualService。此外如同访问网格内部服务一样,你可以通过VirtualService对外部服务进行流量管理。

下面的例子,允许网格内部访问mynewproddb.idc2提供的MySQL服务:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: mynewproddb
spec:
  hosts:
  - mynewproddb.idc2
  ports:
  - name: tcp
    number: 3306
    protocol: TCP
  resolution: NONE
  location: MESH_EXTERNAL
断路器

断路器(和异常检测)是创建韧性微服务应用所常用的一种模式,可以让应用程序免受上游(Upstream,即调用链中更加远离根的节点)服务失败、延迟峰值(latency spikes)或其它网络异常的侵害。

下面的示例配置了一个断路器,它在客户端访问httpbin服务时生效:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  trafficPolicy:
    # 在客户端Sidecar中维护连接池,包含断路器配置(负载限制)
    connectionPool:
      # 对应Envoy的断路器配置
      tcp:
        # 最大TCP连接数
        maxConnections: 1
      http:
        # 最大等待转发的、从应用容器发来的HTTP请求数
        http1MaxPendingRequests: 1
        # 每个TCP连接最多可以处理的HTTP请求数
        maxRequestsPerConnection: 1
    # 异常检测,用于剔除不正常的上游服务的实例
    outlierDetection:
      # 将实例从负载均衡池中剔除,需要连续的错误(HTTP 5XX或者TCP断开/超时)次数
      consecutiveErrors: 1
      # 分析是否需要剔除的频率,多久分析一次
      interval: 1s
      # 实例被剔除后,至少多久不得返回负载均衡池
      baseEjectionTime: 3m
      # 负载均衡池中最多有多大比例被剔除
      maxEjectionPercent: 100
EOF

登陆到fortio来访问受断路器控制的httpbin服务: 

Shell
1
2
3
FORTIO_POD=`kubectl get pod -l app=fortio -o  jsonpath={.items..metadata.name}`
                                           # 负载测试   # 并发2,不限QPS,执行20次    
kubectl exec -it $FORTIO_POD  -c fortio -- fortio load -c 2 -qps 0 -n 20 -loglevel Error http://httpbin:8000/get

上面的fortio命令并发度为2,而断路器仅仅允许1个并发请求,因此会有几率出现错误:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
Fortio 1.0.1 running at 0 queries per second, 4->4 procs, for 20 calls: http://httpbin:8000/get
Aggregated Function Time : count 20 avg 0.022291169 +/- 0.03153 min 0.000243608 max 0.086822606 sum 0.44582338
# target 50% 0.0055
# target 75% 0.0225
# target 90% 0.0834113
# target 99% 0.0864815
# target 99.9% 0.0867885
Sockets used: 5 (for perfect keepalive, would be 2)
# 错误率15%
Code 200 : 17 (85.0 %)
Code 503 : 3 (15.0 %)
All done 20 calls (plus 0 warmup) 22.291 ms avg, 86.0 qps

如果将并发度设置为20,连续发送1000次,则错误率飙高到90%:

Shell
1
2
Code 200 : 94 (9.4 %)
Code 503 : 906 (90.6 %)

查询客户端Proxy的统计信息,可以看到更多细节:

Shell
1
2
3
4
5
6
7
kubectl exec -it $FORTIO_POD  -c istio-proxy  -- sh -c 'curl localhost:15000/stats' | grep httpbin | grep pending
 
# cluster.outbound|8000||httpbin.default.svc.k8s.gmem.cc.upstream_rq_pending_active: 0
# cluster.outbound|8000||httpbin.default.svc.k8s.gmem.cc.upstream_rq_pending_failure_eject: 0
# pstream_rq_pending_overflow提示被断路掉的请求次数
# cluster.outbound|8000||httpbin.default.svc.k8s.gmem.cc.upstream_rq_pending_overflow: 10326
# cluster.outbound|8000||httpbin.default.svc.k8s.gmem.cc.upstream_rq_pending_total: 1016
流量复制

下面的虚拟服务,将所有请求发送给v1,同时镜像一份给v2:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat <<EOF | kubectl apply -f -
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
EOF

发送给镜像服务的请求,其Host/Authority请求头被自动加上-shadow后缀。 

试验遥测功能

利用Mixer和Sidercar,可以获得服务网格的各种统计指标(Metrics)、日志,或者跨越不同的服务进行分布式的调用链追踪。

收集指标和日志

通过配置,Istio可以自动收集网格中某个服务的统计指标,或者为每次请求产生日志。

下面的例子,为每个请求生成一个Prometheus指标值:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 定义一个指标(Metrics)类型的instances的规格,将Istio请求的属性映射为包含维度、度量的指标
# 针对每次请求,都会根据这里的配置,生成一个metrics类型的实例
apiVersion: "config.istio.io/v1alpha2"
kind: metric
metadata:
  # 名称
  name: doublerequestcount
  namespace: istio-system
spec:
  # 度量信息,为固定值2
  value: "2"
  # 维度信息
  dimensions:
    # 维度名   维度值
    # context,source,destination等都是预定义的Istio请求属性
    reporter: conditional((context.reporter.kind | "inbound") == "outbound", "client", "server")
    #                            竖线用于提供默认值
    source: source.workload.name | "unknown"
    destination: destination.workload.name | "unknown"
    #        除了请求属性表达式外,还可以使用字面值
    message: '"twice the fun!"'
  monitored_resource_type: '"UNSPECIFIED"'
 
---
# 配置一个prometheus适配器,也叫处理器(Handler)
# 适配器能够处理instances并和输出到外部系统
apiVersion: "config.istio.io/v1alpha2"
kind: prometheus
metadata:
  name: doublehandler
  namespace: istio-system
spec:
  metrics:
    # 生成的Prometheus指标的名称,注意,prometheus适配器总是自动添加istio_前缀
  - name: double_request_count
    # instance规格的全限定名称,提供当前适配器的输入
    instance_name: doublerequestcount.metric.istio-system
    # 指标的类型
    kind: COUNTER
    # 产生哪些标签
    label_names:
      # 这些维度作为标签
    - reporter
    - source
    - destination
    - message
 
---
# 将doublerequestcount.metric的所有instance派发给适配器prometheus
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: doubleprom
  # 定义在默认配置命名空间,且没有match配置,因此会派发所有instance
  namespace: istio-system
spec:
  actions:
  - handler: doublehandler.prometheus
    instances:
    - doublerequestcount.metric

下面的例子,为每个请求产生一条日志:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 定义另外一种instance —— 日志条目 —— 的规格
apiVersion: "config.istio.io/v1alpha2"
kind: logentry
metadata:
  name: newlog
  namespace: istio-system
spec:
  # 日志严重度级别
  severity: '"warning"'
  # 日志时间戳
  timestamp: request.time
  # 日志变量
  variables:
    source: source.labels["app"] | source.workload.name | "unknown"
    user: source.user | "unknown"
    destination: destination.labels["app"] | destination.workload.name | "unknown"
    responseCode: response.code | 0
    responseSize: response.size | 0
    latency: response.duration | "0ms"
  monitored_resource_type: '"UNSPECIFIED"'
---
 
# 这种处理器处理instance并输出到标准输出
apiVersion: "config.istio.io/v1alpha2"
kind: stdio
metadata:
  name: newhandler
  namespace: istio-system
spec:
severity_levels:
   # 将instance.severity == warning映射为Params.Level.WARNING这个日志级别
   warning: 1
# 生成JSON格式的日志
outputAsJson: true
 
---
# 将logentry的所有实例发送给stdio处理
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: newlogstdio
  namespace: istio-system
spec:
  # 匹配所有请求
  match: "true"
  actions:
   - handler: newhandler.stdio
     instances:
     - newlog.logentry
---

 将上述两端配置文件保存为yaml文件,并通过kubectl apply命令安装到K8S,然后进行下一步试验。 

查询指标

打开Istio内置Prometheus服务的Web界面, 输入istio_double_request_count进行查询,可以看到每个微服务的请求次数都被client、server方分别报送,并预先聚合了。

实际上Istio官方提供的Chart中,以及提供了HTTP/TCP请求的次数、持续时间、请求长度、应答长度,以及访问日志的instance/handler/rule配置,可以直接安装使用。

指标可视化

打开Istio内置Grafana服务的Web界面,可以看到Istio目录下预定义好了7个仪表盘页面。该服务使用grafana/grafana:5.0.4版本的镜像。

试验策略功能
启用请求限速

参考配置请求限速一节。

禁止访问

你可以使用Mixer中可见的任何请求属性来控制对服务的访问。下面的例子禁用了对ratings:v3的访问:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 这种适配器用于提示Envoy拒绝访问
apiVersion: "config.istio.io/v1alpha2"
kind: denier
metadata:
  name: denyreviewsv3handler
spec:
  status:
    # 状态码和消息
    code: 7
    message: Not allowed
---
# instance
apiVersion: "config.istio.io/v1alpha2"
kind: checknothing
metadata:
  name: denyreviewsv3request
spec:
---
# rule
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: denyreviewsv3
spec:
  # 只要是reviews访问ratings的v3版本,立即拒绝
  match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
  actions:
  - handler: denyreviewsv3handler.denier
    instances: [ denyreviewsv3request.checknothing ]
黑白名单

Istio也支持基于请求属性的黑白名单,下面的例子和上面的denier是等价的:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 列表检查器,值匹配列表项则进入黑或白名单
apiVersion: config.istio.io/v1alpha2
kind: listchecker
metadata:
  name: whitelist
spec:
  overrides: ["v1", "v2"]  # 静态值列表
  # 这是白名单而非黑名单
  blacklist: false        
 
---
# 列表条目instance,从请求抽取单个值
apiVersion: config.istio.io/v1alpha2
kind: listentry
metadata:
  name: appversion
spec:
  # 取版本号
  value: destination.labels["version"]
 
---
# 关联两者,效果就是检查版本号,如果不是v1/v2则拒绝访问
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: checkversion
spec:
  match: destination.labels["app"] == "ratings"
  actions:
  - handler: whitelist.listchecker
    instances:
    - appversion.listentry&nbsp;
试验分布式追踪

以--set tracing.enabled=true选项安装Istio Chart,即可使用。由于默认设置的采样率较低,你需要反复访问多次/productpage才能捕获到Trace。

Search

打开Istio内置Jaeger服务的Web界面,点击顶部按钮切换到Search页签,在Find Traces面板中Service选取productpage,点击Find Traces,看到如下的Trace列表:

jaeger-search

点击其中一个Trace,可以查看其调用链信息:

jaeger-search-2

可以看到,这个由单次/productpage请求产生的调用链由6个Span构成,每个Span对应一个彩色条带。每个Span消耗的时间都标注在界面上了。

Dependencies

打开Istio内置Jaeger服务的Web界面,点击顶部按钮切换到Dependencies页签,可以看到Jaeger依据Traces计算出的服务依赖关系图(DAG,有向无环图):

jaeger-dependencies

 

Trace如何收集

尽管Istio的Sidecar能够自动发送Span给Jaeger,要将这些Span合并为完整的Trace还需要额外的信息,应用程序必须提供必要的HTTP请求头。

每个微服务都需要收集调用它的HTTP请求的x-request-id、x-b3-traceid、x-b3-spanid、x-b3-parentspanid、x-b3-sampled、x-b3-flags、x-ot-span-context头,并在它调用任何上游(Upstream,上图的ratings是reviews的Upstream)微服务时,带上这些头。

Bookinfo中,productpage服务基于Python编写,收集请求头的代码如下:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def getForwardHeaders(request):
    headers = {}
 
    if 'user' in session:
        headers['end-user'] = session['user']
 
    incoming_headers = [ 'x-request-id',
                         'x-b3-traceid',
                         'x-b3-spanid',
                         'x-b3-parentspanid',
                         'x-b3-sampled',
                         'x-b3-flags',
                         'x-ot-span-context'
    ]
 
    for ihdr in incoming_headers:
        val = request.headers.get(ihdr)
        if val is not None:
            headers[ihdr] = val
 
    return headers

reviews服务则基于Java编写, 收集请求头的代码如下:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@GET
@Path("/reviews/{productId}")
public Response bookReviewsById(@PathParam("productId") int productId,
                            @HeaderParam("end-user") String user,
                            @HeaderParam("x-request-id") String xreq,
                            @HeaderParam("x-b3-traceid") String xtraceid,
                            @HeaderParam("x-b3-spanid") String xspanid,
                            @HeaderParam("x-b3-parentspanid") String xparentspanid,
                            @HeaderParam("x-b3-sampled") String xsampled,
                            @HeaderParam("x-b3-flags") String xflags,
                            @HeaderParam("x-ot-span-context") String xotspan) {
  int starsReviewer1 = -1;
  int starsReviewer2 = -1;
 
  if (ratings_enabled) {
    JsonObject ratingsResponse = getRatings(Integer.toString(productId), u
      ser, xreq, xtraceid, xspanid, xparentspanid, xsampled, xflags, xotspan);
  }
}
关于采样率

Istio默认会追踪任何请求, 对于开发、测试环境这是可以的,线上环境通常需要降低采样率,否则压力太大。调整采样率有两种方式:

  1. 使用官方Chart安装时,调整pilot.traceSampling,当前使用的Chart版本已经将其设置为1.0,即百分之一的采样率
  2. 修改Deployment istio-pilot的环境变量PILOT_TRACE_SAMPLING

采样率的精度是0.01

删除示例应用
Shell
1
2
3
4
samples/bookinfo/platform/kube/cleanup.sh
kubectl delete -f samples/httpbin/httpbin.yaml
kubectl delete -f  samples/httpbin/sample-client/fortio-deploy.yaml
kubectl delete -f samples/sleep/sleep.yaml
流量管理基础

Istio的流量管理模型,从根本上将流量(traffic flow)和基础设施扩容(infrastructure scaling)进行了解耦。通过Pilot,你可以通过更细致的规则来说明流量如何流动,而不是简单的指定哪个VM/Pod来接收流量。例如,你可以指定某个服务的5%的流量发往金丝雀版本(不管金丝雀的实例数量),或者指定根据请求内容来选择特定版本的服务:

将流量(traffic flow)和基础设施扩容解耦后,Istio可以在应用程序代码外部提供大量的流量管理特性,包括AB测试的请求路由、逐步发布、金丝雀发布,基于超时、重试、断路器的故障恢复,以及为了测试故障恢复策略而进行的故障注入(fault injection)。

Pilot和Envoy

流量管理的核心组件是Pilot,它管理、配置所有部署在服务网格中的Envoy代理实例,对Envoy的完整生命周期负责。Pilot除了可以配置路由规则、配置故障恢复特性之外,还维护网格中所有服务的规范(canonical)模型。Envoy就是基于此模型,通过自己的发现服务,找到网格中其它Envoy。

每个Envoy实例都基于从Pilot得到的信息、以及针对负载均衡池中的实例进行周期性的健康检查,来维护负载均衡信息。根据此负载均衡信息,Envoy能够在遵守路由规则的同时,智能的在目的实例之间分发请求。

规范模型是独立于底层平台的,和底层平台的对接由Pilot中的适配器负责,适配器调用底层平台的API(例如K8S适配器实现了必要的控制器来Watch API Server,获取Pod、Ingress等资源的变动),并产生适当的规范模型。

Pilot启用了服务发现,路由表,以及对负载均衡池的动态更新。

使用Pilot的规则配置(Rule Configuration),可以制定高级别的流量管理规则。这些规则会被转换为低级别的配置信息,并分发给Envoy实例。

请求路由
服务版本

如上文所述,网格中服务的规范模型由Pilot维护。Istio还引入了服务版本的概念,用于对服务实例进行细粒度区分。服务版本既可以指服务API的版本(v1,v2),也可以指服务运行环境(stg,pdt)或任何其它属性。

在上图中,服务的客户端对服务的不同版本毫无感知。客户端仍然使用服务的IP或主机名来访问,只是Envoy在客户端-服务器之间拦截并转发了请求和响应。

Envoy根据你通过Pilot指定的路由规则,决定实际使用的服务版本。这让应用程序代码和被依赖服务之间实现解耦,可分别独立演化。在路由规则中,你可以指定让Envoy依据源/目标的消息头、Tag来判定服务版本。

入口/出口

Istio假设服务网格的所有出入流量都经由Envoy代理转发。通过这种前置代理可以实现面向用户的服务,包括AB测试、金丝雀部署。在访问外部服务时,Envoy可以实现必要的故障恢复特性,包括超时、重试,并获取外部服务的性能指标。

发现和负载均衡

Istio能够在网格中服务实例之间进行负载均衡。 

Istio假设基础设置提供一个服务注册表(service registry,例如K8S的Service),此表跟踪某个服务的VM/Pod集。此服务注册表应当负责新实例的加入、不健康实例的移除。

Istio从服务注册表中读取服务的信息,并生成平台无关的服务发现接口。Envoy执行服务发现(基于xDS和Pilot交互),并动态的更新其本地负载均衡池。

网格中的服务,利用DNS名称来相互访问。所有针对某个服务的流量,都会在出站前经由Envoy进行重新路由。负载均衡池是决定路由到哪个节点的关键因素。Envoy支持多种负载均衡算法,但是Istio目前仅仅允许:轮询、随机、带权重的最少请求。

Envoy还会周期性的检查负载均衡池中实例的健康情况,这种检查从外部(准确的说是服务消费者)发出,而不是像K8S Pod健康检查那样,在Pod本地执行。

判断实例是否健康时,Envoy遵循一种断路器模式(circuit breaker pattern),此模式下调用实例API的故障率决定了实例是否健康。当健康检查连续失败指定的次数后,实例被移除负载均衡池;当被移除的实例连续成功指定次数后,它又回到负载均衡池。

如果实例以503代码响应健康检查请求,则它会立即被调用者移出负载均衡池。

错误处理

Envoy提供了一系列开箱即用的错误恢复机制:

  1. 超时
  2. 有限次数的重试,支持可变的重试延迟(jitter)
  3. 限制针对上游服务的并发连接数、并发请求数
  4. 主动健康检查——针对负载均衡池中所有成员进行健康检查,并移除不健康实例,移回健康实例
  5. 可精细控制的断路器(被动健康检查) ,同样针对负载均衡池中的每个实例

所有这些特性,都可以在运行时,利用Istio的流量管理规则动态配置。

联用主动、被动健康检查,可以更大程度上降低访问到不健康实例的几率。联用底层基础设施的健康检查机制,不健康实例可以很快的被剔除。 

利用流量管理规则,你可以为每个服务/版本来配置默认的故障恢复特性。

故障注入

用于辅助测试端对端的故障恢复能力。

Istio支持多种具体的协议,并向其注入错误信息。而不是粗暴的杀死Pod,或者在TCP层延迟/污染网络包。你可以在应用层注入更加有意义的错误,例如HTTP的状态码。

Istio支持对特定请求进行错误注入,或者指定多少百分比的请求被错误注入。

可以注入的错误有两类:

  1. 延迟:模拟网络延迟和上游服务过载
  2. 中止:模拟上游服务错误,通常使用HTTP错误码、TCP连接错误的方式
流量镜像/复制

流量镜像(Traffic mirroring)也叫shadowing,允许开发团队在最小风险的前提下改变产品的功能。其做法是将线上环境的流量复制到一个“镜像服务“中,镜像服务和主请求处理链是隔离的。

VirtualService支持为route配置mirror,实现流量镜像。

网关

网关是运行在服务网格边缘的负载均衡器,它处理入站或出站的TCP/HTTP连接。

Istio提供入口网关,出口网关,对应:

  1. 的CRD为Gateway、Gateway+ServiceEntry
  2. 的Controller为istio-ingressgateway、istio-egressgateway

当提起术语“网关”时,基于上下文可能指Gateway这种资源,也可能指出口/入口网关的Service/Pod。

IngressGateway

在典型的Kubernetes环境下,Ingress控制器读取Ingress资源,提供外部访问K8S集群的入口。

而使用Istio时,Ingress资源的地位可以由Gateway + VirtualServices代替。在集群内部组件相互交互时,不需要配置Gateway资源。需要注意,Istio也可以作为Ingress控制器,处理K8S原生的Ingress资源。

使用Istio Gateway时的典型的客户端访问时序如下:

  1. 客户端发起针对负载均衡器的请求
  2. 负载均衡器将请求转发给集群的istio-ingressgateway服务,该服务可以部署为NodePort,它的后端是一个Deployment
  3. IngressGateway根据Gateway资源、VirtualService资源的配置信息,将请求路由给K8S的Service
    1. Gateway提供端口、协议、数字证书信息
    2. VirtualService提供到K8S Service的路由信息
  4. K8S Service将请求路由给Pod

该时序如下图所示,注意IngressGateway的Sidecar负责转发客户端请求:

istio-networking

 

每当你创建/修改Gateway、VirtualService资源,Pilot会监测到并将其转换为Envoy配置,然后发送给相关的Sidecar,包括运行在IngressGateway Pod中的Envoy。

EgressGateway

默认情况下,启用了Istio的微服务无法访问集群外部的URL, 这是因为Envoy修改了Pod所在网络命名空间的Iptables,透明的将所有出口流量都重定向到Sidecar,而不经任何配置Sidecar只会处理网格内部的Destination。

要将外部服务暴露给启用了Sidecar的网格内部Pod/VM(它们是外部服务的客户端),必须:

  1. 定义ServiceEntry,这种自定义资源允许针对指定的外部IP/DNS名称的流量通过Sidecar。ServiceEntry支持通配符匹配主机,可以降低配置工作量
  2. 或者,配置一个IP范围,让针对此IP范围的出站请求经过/绕过Envoy代理。你可以设置Chart变量,
    Shell
    1
    2
    # 仅仅10.5.0.0/16走Envoy代理,其它直连。你可以将此Pod网络、K8S服务网络的CIDR配置到该字段中。这样,所有针对网格外部的访问都开放了
    --global.proxy.includeIPRanges="10.5.0.0/16"

    则当从网格内部访问10.5网段时直接绕过代理。
    注意:此特性已经弃用,可以使用配置字典istio-sidecar-injector中的的includeOutboundIpRanges,或者Pod注解traffic.sidecar.istio.io/includeOutboundIPRanges代替

强调一下,访问集群内部域名、IP地址不受限制,即使不配置VirtualService也可以访问,而且Kiali直接可以识别出这种流量。

ServiceEntry还可以配合Gateway+VirtualService,将出口流量导向特殊的服务 —— istio-egressgateway,这种配置方式的适用场景是:

  1. 离开网格的所有流量必须流经一组专用节点,这一组节点有特殊的监控和审查
  2. 集群中的一般性节点不能联通网格外部
流量管理配置

Istio使用一个简单的配置模型来描述API调用/TCP流量应该如何在服务网格中流动。使用此配置模型,你可以:

  1. 配置服务级别的属性,例如断路器、超时、重试
  2. 配置通用的持续交付(CD)任务,例如金丝雀发布(Canary rollout,即灰度发布)、A/B测试、阶段性部署(Staged rollout)——基于百分比的流量分配 

Istio的配置模型映射为K8S的资源,包括VirtualService、DestinationRule、ServiceEntry、Gateway四种。在K8S中你可以用kubectl来配置这些资源,它们都是CR。

VirtualService

定义服务的请求如何在服务网格中路由。虚拟服务能够将请求路由给服务的不同版本,甚至是完全不同的服务。

按版本路由

下面的示例,将针对review服务的请求,3/4发给v1版本,1/4发给v2版本:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  # 虚拟服务的名字
  name: reviews
spec:
  hosts:
  # 访问到此虚拟服务时,使用主机名,可以是DNS名称、带通配符的DNS名称、IP地址
  # DNS名称可以是FQDN,对于K8S这样的平台,可以使用短DNS名称(无点号),例如使用reviews代替reviews.default.svc.cluster.local
  # Istio会根据此虚拟服务(而非K8S的Service)所在的命名空间,生成FQDN
  # 注意:
  # 1、每个host只能被单个虚拟服务声明。单个虚拟服务可以用于描述多个HTTP/TCP端口的流量属性
  # 2、对于HTTP/TCP流量,hosts字段均生效
  # 3、对于网格内部的服务(存在于服务注册表),必须总是使用服务DNS名,不得使用IP地址
  - reviews
  http:
  # 路由规则的数组
  - route:
    - destination:
        # host必须是Istio服务注册表中,没有歧义的名称。此注册表中的服务来源包括:
        # 1、底层平台(K8S)服务注册表的全部条目(即K8S Service)
        # 2、通过ServiceEntry声明的服务条目
        #
        # 对于K8S用户需要注意,使用DNS短名时,Istio将根据DestinationRule的命名空间来生成FQDN
        # 而不是当前VirtualService的命名空间
        host: reviews
        # 发送给reviews服务实例的v1子集
        # 这里仅仅指出子集的名称,子集的规格通过DestinationRule配置
        subset: v1
      # 路由到此版本的权重,取值0-100
      weight: 75
    - destination:
        host: reviews
        # 如果目标服务仅仅暴露单个端口,则不需要声明下面这个字段
        port: 80
        subset: v2
      weight: 25
超时和重试

默认情况下HTTP请求的超时为15秒,你可以覆盖此默认值。重试也类似:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
...
spec:
  ...
  http:
  - route:
    # 超时
    timeout: 10s
    # 重试
    retries:
      # 最多重试次数
      attempts: 3
      # 每次重试的超时
      perTryTimeout: 2s

也可以在请求级别覆盖超时、重试配置,只需要提供Envoy规定的特殊请求头x-envoy-upstream-rq-timeout-ms、x-envoy-max-retries即可。 

故障注入

你可以为http进行故障注入,要么是delay,要么是abort。

下面的例子,为ratings服务的v1版本10%的请求引入5秒的延迟:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - fault:
      delay:
        # 多少比例的请求被注入故障
        percent: 10
        # 引入固定5秒的延迟
        fixedDelay: 5s
    route:
    - destination:
        host: ratings
        subset: v1

下面的例子,则为10%的请求引发400错误:

YAML
1
2
3
4
5
6
7
8
9
10
...
spec:
  ...
  http:
  - fault:
      abort:
        # 多少比例的请求被注入故障
        percent: 10
        # 返回的HTTP状态码
        httpStatus: 400

delay和abort是可以联合使用的:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
metadata:
  name: ratings
spec:
  http:
  # 从reviews服务v2版本发起的,针对ratings服务v1版本的请求
  - match:
    - sourceLabels:
        app: reviews
        version: v2
    fault:
      # 引入5秒延迟
      delay:
        fixedDelay: 5s
      # 为10%请求引发400错误
      abort:
        percent: 10
        httpStatus: 400
有条件路由

规则可以仅仅针对满足特定条件的请求。

可以限定请求者的标签,在K8S环境下即Pod的Label:

YAML
1
2
3
4
5
6
7
8
9
...
spec:
  http:
  # 仅针对reviews服务v2版本发起的请求
  - match:
    - sourceLabels:
        app: reviews
        version: v2
    route: ...

也可以限定HTTP请求头: 

YAML
1
2
3
4
5
6
7
8
...
spec:
  http:
  - match:
    - headers:
        # end-user头必须为alex
        end-user:
          exact: alex

还可以限定请求URL路径、HTTP方法、HTTP Scheme、HTTP Authority值: 

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
spec:
  http:
  - match:
    - uri:
        # 请求URL,除去scheme://host部分,必须以/api/v1开头
        # 除了prefix,还可以配置
        #   exact,表示精确匹配
        #   regex,表示正则式匹配
        prefix: /api/v1
 
    - scheme:
        exact: https
    - authority:
        regex: ".*.gmem.cc"
    - method:
        regex: "GET|POST"

多个条件可以进行逻辑与/或。在单个match元素中声明多个条件,则为AND:

YAML
1
2
3
4
5
6
7
8
9
10
spec:
  http:
  - match:
    # 同时限定Pod标签、请求头
    - sourceLabels:
        app: reviews
        version: v2
      headers:
        end-user:
          exact: alex

在多个match元素中分别声明,则为OR:

YAML
1
2
3
4
5
6
7
8
9
10
11
spec:
  http:
  - match:
    # Pod标签匹配
    - sourceLabels:
        app: reviews
        version: v2
    # 或者请求头匹配
    - headers:
        end-user:
          exact: alex
多规则优先级

如果对于相同的目的地(host + subset),配置了两个或更多的规则(http子元素),那么第一个(配置文件最前面)匹配的规则优先级最高。 

CORS配置

Istio支持为虚拟服务配置跨源资源共享策略:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spec:
  http:
  - route:
    corsPolicy:
      # 允许发起CORS请求的源,设置为*则允许任何源
      allowOrigin:
      - gmem.cc
      # CORS请求可以使用的HTTP方法
      allowMethods:
      - POST
      - GET
      # Access-Control-Allow-Credentials头
      allowCredentials: false
      # 允许的请求头
      allowHeaders:
      - X-Foo-Bar
      # preflight request可以被缓存的时间
      maxAge: "1d" 
请求重定向

虚拟服务可以直接返回301重定向给调用者:

YAML
1
2
3
4
5
6
7
spec:
  http:
  redirect:
    # 重定向时覆盖URL查询路径
    uri: /v1/bookRatings
    # 重定向时覆盖URL的Authority(Host)部分
    authority: newratings.default.svc.cluster.local
请求重写

虚拟服务可以在转发请求给Dest之前,对HTTP请求的特定部分进行修改:

YAML
1
2
3
4
5
6
7
8
9
spec:
  http:
  - match:
    - uri:
        prefix: /ratings
    # 将URL前缀从/ratings修改为/v1/bookRatings
    rewrite:
      uri: /v1/bookRatings
      authority: ...
流量复制

参考上文的例子。

TCP服务

这里是一个TCP路由的例子:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: tcp-route
  namespace: default
spec:
  hosts:
  - mesh.gmem.cc
  tcp:
  - match:
    - port: 3306
      source_labels:
        app: mysql-client
    route:
    - destination:
        host: mysql
        subset: v2
        port:
          number: 3306
      weight: 100
  - match:
    - port: 2181
    route:
    - destination:
        host: zk
        subset: v1
        port:
          number: 2181
      weight: 100
DestinationRule

在VirtualService定义了路由规则后,DestinationRule进一步应用一系列的策略到请求上。这类规则可能声明断路器、负载均衡器的属性,执行TLS配置,最基本的是定义可寻址的服务子集。DestinationRule针对某个特定的真实服务(host,注册表中的条目)。

服务子集

下面的示例,声明了服务子集:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  # 服务的名称
  name: reviews
spec:
  host: reviews
  # 配置服务的子集
  # 在K8S中,服务的具有标签version=v1的Pod,并加入到v1子集
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
简单负载均衡

可以针对所有子集配置:

YAML
1
2
3
4
5
6
7
8
9
10
11
...
spec:
  host: reviews
  trafficPolicy:
    # 负载均衡策略
    loadBalancer:
      # 算法:ROUND_ROBIN(默认)
      #      LEAST_CONN, O(1)复杂度算法,随机选取两个健康实例,并取其中活动请求少的
      #      RANDOM
      #      PASSTHROUGH,直接转发给调用者请求的IP地址,不进行任何负载均衡
      simple: RANDOM 

也可以针对特定子集:

YAML
1
2
3
4
5
6
7
8
spec:
  subsets:
  - name: v2
    labels:
      version: v2
    trafficPolicy:
      loadBalancer:
        simple: ROUND_ROBIN
一致性哈希负载均衡

基于一致性哈希的负载均衡可以实现基于HTTP请求头、Cookie或其它属性的软的会话绑定(Session affinity):

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
   name: bookinfo-ratings
spec:
   host: ratings.prod.svc.cluster.local
   trafficPolicy:
     loadBalancer:
       consistentHash:
         # 三选一
         # 基于请求头进行哈希计算
         httpHeaderName: end-user
         # 基于源IP地址进行哈希计算
         useSourceIp: true
         # 基于Cookie进行哈希计算,如果Cookie不存在,Envoy会自动产生
         httpCookie:
           # Cookie的名称、路径、生命周期
           name: user
           path: ..
           ttl: 0s
并发限制

可以限制某个服务/子集的并发度:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spec:
  subsets:
  - name: v1
    labels:
      version: v1
    trafficPolicy:
      # 限制并发
      # 每个Sidecar(也就是每个Pod)为每个上游服务的实例都配备一个连接池,断路器依赖此连接池才能工作
      # connectionPool可以有tcp、http两个字段
      connectionPool:
        tcp:
          # 最大连接数
          maxConnections: 100&nbsp;
          # 连接超时
          connectTimeout:
        http:
          # 最大等待处理的请求数量
          http1MaxPendingRequests: 1024
          # HTTP2最大请求数
          http2MaxRequests: 1024
          # 每个TCP连接可以被多少请求共享使用(重用),设置为1则禁用keep-alive
          maxRequestsPerConnection: 32
          # 最大重试次数
          maxRetries: 3 
断路器

可以为某个服务/子集配置断路器:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spec:
  subsets:
  - name: v1
    labels:
      version: v1
    trafficPolicy:
      # 异常检测,断路
      outlierDetection:
        # 将实例从负载均衡池中剔除,需要连续的错误(HTTP 5XX或者TCP断开/超时)次数
        consecutiveErrors: 1
        # 分析是否需要剔除的频率,多久分析一次
        interval: 1s
        # 实例被剔除后,至少多久不得返回负载均衡池
        baseEjectionTime: 3m
        # 负载均衡池中最多有多大比例被剔除
        maxEjectionPercent: 100
ServiceEntry

用于访问位于服务网格外部的服务,将其作为服务条目添加到Istio内部管理的服务注册表(service registry)中。

简单示例

下面的例子,允许Envoy代理针对gmem.cc的HTTP请求:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: gmem-ext-svc
spec:
  hosts:
  # 可以使用通配符(仅仅针对网格外部服务),表示一个白名单,允许从服务网格内部访问它们
  # 注意,1.0.5貌似不支持通配符?
  - *.gmem.cc
  location: MESH_EXTERNAL
  resolution: DNS
  ports:
  - number: 80
    name: http
    protocol: HTTP
  - number: 443
    name: https
    protocol: HTTPS

ServiceEntry可以和VirtualService一起工作,作为VirtualService的host。这是因为从概念上来说,来自底层基础设施注册表的服务、ServiceEntry定义的服务,都是Istio的概念性的Service。

指定静态端点

下面的例子以主机名来静态的指定外部服务端点:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-svc-dns
spec:
  hosts:
  - foo.bar.com
  location: MESH_EXTERNAL
  ports:
  - number: 80
    name: https
    protocol: HTTP
  resolution: DNS
  # 端点列表
  endpoints:
  - address: us.foo.bar.com
    ports:
      https: 8080
  - address: uk.foo.bar.com
    ports:
      https: 9080
  - address: in.foo.bar.com
    ports:
      https: 7080

下面的例子以IP来指定外部服务端点:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: external-svc-mongocluster
spec:
  hosts:
  - mymongodb.somedomain
  # 下面的字段仅仅支持resolution=NONE|STATIC
  addresses:
  - mymongodb.ip/32
  ports:
  - number: 27018
    name: mongodb
    protocol: MONGO
  location: MESH_INTERNAL
  # 使用endpoints中定义的静态IP地址
  resolution: STATIC
  # 解析到:
  endpoints:
  - address: 2.2.2.2
  - address: 3.3.3.3

关于hosts、addresses,以及服务的“唯一性标识”,Istio官方API文档有如下说明:

  1. hosts字段:
    1. 对于非HTTP服务(包括不透明HTTPS)来说,此字段中指定的DNS名称被忽略
    2. 对于非HTTP服务,如果此字段指定的是DNS名称,则ports,或/和addresses中的IP地址用于唯一性的标识目的地
  2. addresses字段: 表示关联到此服务的虚拟IP地址,可以是CIDR前缀:
    1. 对于HTTP服务来说,此字段被忽略,目的地根据HTTP头Host/Authority确定
    2. 对于非HTTP服务,hosts头被忽略。如果指定了addresses,则入站请求的IP地址和addresses指定的CIDR进行匹配,如果匹配成功,则认为请求的是当前服务。如果不指定addresses,则入站流量仅仅根据ports来识别,这种情况下,服务的访问端口不能被网格内部任何其它服务共享

这段描述让人费解,也不符合实际试验结果。下面的两个ServiceEntry,都是非HTTP服务,都使用443端口,都没有指定addresses字段,但是没有任何冲突出现:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: cdn.gmem.site
spec:
  hosts:
  - cdn.gmem.site
  ports:
  - name: tcp
    number: 443
    protocol: TCP
  resolution: DNS
  location: MESH_EXTERNAL
 
---
 
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: blog.gmem.cc
spec:
  hosts:
  - blog.gmem.cc
  ports:
  - name: tcp
    number: 443
    protocol: TCP
  resolution: DNS
  location: MESH_EXTERNAL

生成的Envoy配置如下(可以看到集群、端点信息都是正常有效的):

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// istioctl proxy-config cluster ubuntu --fqdn=cdn.gmem.site -o json
[
    {
        "name": "outbound|443||cdn.gmem.site",
        "type": "STRICT_DNS",
        "hosts": [
            {
                "socketAddress": {
                    "address": "cdn.gmem.site",
                    "portValue": 443
                }
            }
        ]
    }
]
 
// istioctl proxy-config cluster ubuntu --fqdn=blog.gmem.cc -o json
[                                                                                                            
    {                                                                                                        
        "name": "outbound|443||blog.gmem.cc",                                                                
        "type": "STRICT_DNS",                                                                                
        "hosts": [                                                                                          
            {                                                                                                
                "socketAddress": {                                                                              
                    "address": "blog.gmem.cc",                                                              
                    "portValue": 443                                                                        
                }                                                                                            
            }                                                                                                
        ]                                                                                      
    }                                                                                                        
]  
// istioctl proxy-config endpoint ubuntu --cluster='outbound|443||cdn.gmem.site' -o json      
[
    {
        "name": "outbound|443||cdn.gmem.site",
        "addedViaApi": true,
        "hostStatuses": [
            {
                "address": {
                    "socketAddress": {
                        "address": "27.221.30.56",
                        "portValue": 443
                    }
                }
            }
        ]
    }
]
 
// istioctl proxy-config endpoint ubuntu --cluster='outbound|443||blog.gmem.cc' -o json        
[                                                                                                            
    {                                                                                                        
        "name": "outbound|443||blog.gmem.cc",                                                                
        "addedViaApi": true,                                                                                  
        "hostStatuses": [                                                                                      
            {                                                                                                
                "address": {                                                                                
                    "socketAddress": {
                        "address": "39.107.94.255",
                        "portValue": 443
                    }
                }
        ]
    }
]
UDS的例子
YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: unix-domain-socket-example
spec:
  hosts:
  - "example.unix.local"
  location: MESH_EXTERNAL
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: STATIC
  endpoints:
  - address: unix:///var/run/example/socket
Gateway

配置在网格的边缘,用于外部访问服务网格,为TCP/HTTP流量提供负载均衡。 

和Kubernetes的Ingress不同,Istio网关仅仅在L4-L6上配置,而不使用L7。 你可以把Gateway绑定到VirtualService,进而使用Istio标准的方式来管理HTTP请求和TCP流量。

HTTP网关

下面的例子,允许网格外部发送针对gmem.cc的HTTPS请求:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: mesh.gmem.cc
spec:
  # 标签列表,指定应用此网关配置的Pod/VM具有的标签集
  # 具有匹配标签的Pod(通常是ingressgateway/egressgateway)的Envoy接收配置
  # 目标Pod必须是以proxy模式运行的Pilot agent,也就是说你不能随意指定标签选择器
  selector:
    istio: ingressgateway
  # 在这里定义所有的服务器,其端口都将成为网关Pod的物理监听端口
  # 对于这个例子,你可以使用*.mesh.gmem.cc域名,访问istio-ingressgateway的任意Pod的443端口
  servers:
  # 多个服务器可以分开配置,也可以配置在同一个Gateway资源中
  - port:
      # 此Gateway创建后,ingressgateway的Envoy会立即添加一个0.0.0.0_443监听器
      # 并且引用名为https.443.https的路由
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    # 可以使用通配符,这样可以任何子域名访问网关
    - *.mesh.gmem.cc
    tls:
      mode: SIMPLE
      # 服务器数字证书,必须挂载到istio-ingressgateway
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      # 服务器私钥,必须挂载到istio-ingressgateway
      privateKey: /etc/istio/ingressgateway-certs/tls.key
      # 如果客户端发送HTTP请求,则将其重定向到HTTPS
      httpsRedirect: true
 
   # 下面是一个TCP网关,使用MongoDB协议
  - port:
      number: 2379
      name: mongo
      protocol: MONGO
    hosts: [ "*" ]

必须把上述网关绑定到VirtualService,它才能工作,精确的说,是网关产生的物理监听器才拥有了路由表:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ubuntu.mesh.gmem.cc
spec:
  hosts:
    # 域名必须和绑定的网关的域名兼容,例如是它的子集
    # 这里的hosts+绑定网关的hosts,生成Envoy路由表的virtualHosts.domains字段
    - ubuntu.mesh.gmem.cc
 
  # 将虚拟服务绑定到网关
  # 指定需要应用此虚拟服务(定义的路由规则)的Gateway(所选择的Pod)或Sidecar
  # 保留字mesh表示,网格中所有Sidecar使用此虚拟服务定义的路由规则
  # 如果要同时为Gateway和Sidecar应用此虚拟服务,传入mesh+相关Gateway的名字
  gateways:
  - mesh.gmem.cc
  http:
  - match:
    - uri:
        prefix: /blog
    route:
    - destination:
        # 生成Envoy路由表的virtualHosts[0].routes[0].route.cluster
        # 例如 outbound|8086||ubuntu.default.svc.k8s.gmem.cc
        host: ubuntu
        port:
          number: 8000
          # 端口可以自动选择,也可以明确指定
          # 这里明确指定了8000,生成Envoy Cluster
          # outbound|8000||ubuntu.default.svc.k8s.gmem.cc
          # 注意,不能随意指定,必须在default命名空间有个叫ubuntu的服务,在暴露其8000端口才可以
TCP网关

为集群内的TCP服务建立入口Gateway时,hosts字段被忽略:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: zookeeper
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    # 这边写什么都无所谓,只认客户端的目的IP地址
    - 'zk.mesh.gmem.cc'
    port:
      name: zookeeper
      number: 2181
      protocol: TCP

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: zk.mesh.gmem.cc
spec:
  gateways:
  - zookeeper
  hosts:
  - zk.mesh.gmem.cc
  tcp:
  - route:
    - destination:
        host: zk

需要以下细节:

  1. 上述虚拟服务和网关配对后,istio-ingressgateway的Pod立刻开始监听2181端口
  2. 但是,istio-ingressgateway服务默认只配置了80、443端口

这意味着上述网关配置不可用,你还需要修改istio-ingressgateway服务的配置。

ServiceEntry+Gateway

ServiceEntry可以配合Gateway+VirtualService,将出口流量导向一组特殊的Pod —— istio-egressgateway。本节给出一个样例。

首先定义一个ServiceEntry:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: gmem
spec:
  hosts:
  - k8s.gmem.cc
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS

有了这个配置以后,就可以从网格内部访问k8s.gmem.cc了,但是流量直接从Sidecar发往k8s.gmem.cc。

现在,我们要让流量通过istio-egressgateway的Pod转发。首先,定义一个Gateway:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: gmem
spec:
  # 指定哪些虚拟机/Pod应用这个网关配置,在K8S中,可匹配任意命名空间中的Pod
  selector:
    # 使用deployment istio-egressgateway定义的Pod,此deployment作为出口网关使用
    istio: egressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - k8s.gmem.cc

然后,定义一个虚拟服务:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
spec:
  hosts:
  - k8s.gmem.cc
  gateways:
  # 此虚拟服务生成的Envoy配置(监听器路由),将应用到网关gmem所应用到的Pod,以及所有sidecar
  - gmem
  - mesh
  http:
  - match:
    # 如果HTTP请求从mesh发出,也就是从sidecar发出
    - gateways:
      - mesh
      port: 80
    route:
    # 那么将流量转发到istio-egressgateway,也就是出口网关服务
    - destination:
        host: istio-egressgateway.istio-system.svc.k8s.gmem.cc
        port:
          number: 80
      weight: 100
  - match:
    # 如果HTTP请求从gmem网关所应用到的Pod,也就是istio-egressgateway对应的Pod发出
    - gateways:
      - gmem
      port: 80
    route:
    # 那么将流量直接发往外部
    - destination:
        host: k8s.gmem.cc
        port:
          number: 80
      weight: 100 
WorkloadEntry

1.6新增,让运维人员能够描述单个非K8S工作负载,例如物理机或虚拟机,的属性。

必须伴随ServiceEntry一起使用,ServiceEntry否则使用选择器来匹配工作负载(包括Pod以及虚拟机、物理机),产生MESH_INTERNAL类型的服务。

当工作负载连接到istiod后,WorkloadEntry的Status会发生变化,反应工作负载的状态和一些其它细节,类似于K8S更新Pod的状态。

下面是一个例子:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: networking.istio.io/v1beta1
kind: WorkloadEntry
metadata:
  name: details-svc
spec:
  # 此工作负载具有Sidecar,且Sidecar使用该SA启动
  serviceAccount: details-legacy
  # 工作负载的IP地址
  address: 2.2.2.2
  # 此工作负载的标签
  labels:
    app: details-legacy
    instance-id: vm1

对应的ServiceEntry:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: details-svc
spec:
  hosts:
  - details.bookinfo.com
  # 内部服务
  location: MESH_INTERNAL
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: STATIC
  # 此服务匹配的工作负载,不管它是Pod还是WorkloadEntry
  workloadSelector:
    labels:
      app: details-legacy

WorkloadEntry的address也可以指定为FQDN,对应的ServiceEntry的解析策略应该设置为 resolution: DNS。 

策略和遥测基础

Istio提供了一个灵活的模型,用以收集网格中服务的各种指标(telemetry)。指标收集工作主要由Mixer负责。

从逻辑上说,每次:

  1. 请求发起前,消费者的Envoy都会调用Mixer,执行前提条件检查(Check)
  2. 每次请求处理完成后,都会调用Mixer,发送监控指标(Report)

Sidecar在本地具有缓存,大部分前提条件检查都在本地完成。此外监控指标也具有本地缓冲,不会频繁的发送给Mixer。

配置模型

Istio的策略和监控特性基于一个公共的模型来配置,你需要配置三类资源:

  1. Handlers:决定使用哪些适配器,这些适配器如何运作
  2. Instances:定义如何把请求属性映射为适配器的输入。Instance代表一个或多个适配器需要处理的数据块
  3. Rules:决定何时调用适配器,传递什么instance给它

而这些配置是基于:

  1. Adapters:封装Mixer和特定的基础设施后端的交互逻辑
  2. Templates:定义将请求属性映射为适配器输入的Schema。一个适配器可以支持多个模板
适配器

Mixer是高度模块化的、可扩展的组件。通过适配器,它支持多种日志记录、配额、授权、监控后端,并将这些策略、监控后端和Istio的其它部分解耦。

适配器封装了Mixer和外部基础设施后端(例如Prometheus)交互的逻辑,每个适配器都需要特定的配置参数才能工作,例如logging适配器需要知道后端服务的IP和端口。

具体使用哪些适配器,也就是启用哪些后端,通过配置文件来指定。

Istio提供了很多内置适配器,每种适配器都对应一个CRD,这些CRD的实例就是适配器的实例,也即Handler。Adaptor CRD的实例,表示一种自定义的适配器。自定义适配器实例化时使用Handler类型的CR。

适配器类型

适配器的类型使用CRD来描述,执行下面的命令查看系统中可用的适配器类型:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kubectl get crd -l istio=mixer-adapter
# NAME                              CREATED AT
# adapters.config.istio.io          2019-03-21T11:09:28Z
# bypasses.config.istio.io          2019-03-21T11:09:28Z
# circonuses.config.istio.io        2019-03-21T11:09:28Z
# deniers.config.istio.io           2019-03-21T11:09:28Z
# fluentds.config.istio.io          2019-03-21T11:09:29Z
# kubernetesenvs.config.istio.io    2019-03-21T11:09:29Z
# listcheckers.config.istio.io      2019-03-21T11:09:29Z
# memquotas.config.istio.io         2019-03-21T11:09:29Z
# noops.config.istio.io             2019-03-21T11:09:29Z
# opas.config.istio.io              2019-03-21T11:09:29Z
# prometheuses.config.istio.io      2019-03-21T11:09:29Z
# rbacs.config.istio.io             2019-03-21T11:09:29Z
# redisquotas.config.istio.io       2019-03-21T11:09:29Z
# servicecontrols.config.istio.io   2019-03-21T11:09:30Z
# signalfxs.config.istio.io         2019-03-21T11:09:30Z
# solarwindses.config.istio.io      2019-03-21T11:09:30Z
# stackdrivers.config.istio.io      2019-03-21T11:09:30Z
# statsds.config.istio.io           2019-03-21T11:09:30Z
# stdios.config.istio.io            2019-03-21T11:09:30Z

执行下面的命令查看Prometheus适配器的实例:

Shell
1
2
3
kubectl -n istio-system get prometheuses.config.istio.io
# NAME      AGE
# handler   1h

本节后续内容讨论主要的内置适配器。

Prometheus

支持 metric 模板

此适配器暴露了HTTP访问端点,Prometheus可以来采集此端点,获取服务网格的各项指标

使用Istio官方提供的Chart时,默认会在当前命名空间安装一个Prometheus服务器,并自动配置以抓取以下端点:

  1. istio-mixer.istio-system:42422,所有由Mixer的Prometheus适配器生成的网格指标。指标名以istio_开头
  2. istio-mixer.istio-system:9093,用于监控Mixer自身的指标
  3. istio-mixer.istio-system:9102,Envoy生成的原始统计信息。指标名以envoy_开头

注意:目前Envoy的http://0.0.0.0:15090/stats/prometheus也暴露了Prometheus指标

该适配器包含的配置参数如下:

参数 说明
metrics

类型 Params.MetricInfo[],需要插入到Prometheus的指标的列表。如果指标在Istio中定义了,但是没有在这里进行适当配置,则不会推送到Prometheus:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
metrics:
  # 作为指标名前缀的命名空间。如果指标名为requests_total,该字段设置为istio,则最终完整的、
  # 提供给Prometheus的指标名是istio_requests_total。默认istio
- namespace: string
  # 注册到Prometheus的指标的名称,如果不指定从instanceName自动生成
  name: string
  # 必须,此MetricInfo处理的Istio Metric实例的全限定名称
  instanceName: string
  # 易读的描述
  description: string
  # 该指标的类别
  # Params.MetricInfo.Kind
  kind: UNSPECIFIED | GAUGE | COUNTER | DISTRIBUTION
  # 对于DISTRIBUTION类型的metric,该配置指定了聚合后数据所存放的桶
  # 对于非DISTRIBUTION类型的metric该字段无意义
  # Params.MetricInfo.BucketsDefinition
  buckets:
    # 三选一:
    # 线性桶,每个桶的大小(区间一致)
    linearBuckets:
      # 桶数量为: N = num_finite_buckets + 2
      numFiniteBuckets: int32
      # 桶的宽度
      width: double
      # 第一个桶的下限
      offset: double
    # 指数桶
    exponentialBuckets:
      # 桶数量为: N = num_finite_buckets + 2,两个额外的桶用作下溢、上溢桶
      # 对于桶i,它的左右边界为:
      # [scale * (growth_factor ^ (i - 1)), scale * (growth_factor ^ i) ]
      growthFactor: double
      scale: double
      numFiniteBuckets: int32
    # 明确定义的桶
    explicitBuckets:
      bounds: [ double,... ]
  # 指标的标签名的数组,这些标签必须和metric实例的dimension匹配
  labelNames:
  - dimension1
  - dimension2

示例:

YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
apiVersion: config.istio.io/v1alpha2
kind: prometheus
metadata:
  name: handler
spec:
  metrics:
  # 从那个metric类型的instance上读取生成该指标所需的信息
  - instance_name: requestcount.metric.istio-system
    # 生成Prometheus的Counter
    kind: COUNTER
    # 将instance上的以下维度作为标签
    label_names:
    - reporter
    - source_app
    - source_principal
    - source_workload
    - source_workload_namespace
    - source_version
    - destination_app
    - destination_principal
    - destination_workload
    - destination_workload_namespace
    - destination_version
    - destination_service
    - destination_service_name
    - destination_service_namespace
    - request_protocol
    - response_code
    - connection_security_policy
    name: requests_total
  - buckets:
      # 明确定义的桶,桶数量是下面数组的长度+2
      explicit_buckets:
        bounds:
        - 0.005
        - 0.01
        - 0.025
        - 0.05
        - 0.1
        - 0.25
        - 0.5
        - 1
        - 2.5
        - 5
        - 10
    instance_name: requestduration.metric.istio-system
    # 生成Prometheus的Histogram
    kind: DISTRIBUTION
    label_names:
    -