Istio概念与服务治理功能
服务网格
Istio是服务网格(Service Mesh)理念的代表之作。
服务网格概念如下:
服务网格(Service Mesh)是处理服务间通信的基础设施层。它负责构成现代云原生应用程序的复杂服务拓扑来可靠地交付请求。在实践中,Service Mesh 通常以轻量级网络代理阵列的形式实现,这些代理与应用程序代码部署在一起,对应用程序来说无需感知代理的存在。
一句话总结,服务网格通过网络代理方式,承担起服务之间的网络调用、限流、熔断和监控等功能,且服务无感知。
服务网格是服务治理下放基础架构层的终级方案。一开始所有服务治理由服务自身代码逻辑实现,由开发负责应用运维的设计开发是不合理的,但好在单体应用间通讯并不复杂;随着微服务架构的发展,服务之间的依赖和通讯十分复杂且重要,以SpringCloud架构为代表的微服务框架将服务治理从代码中抽离为独立服务和SDK库,虽然没有侵入业务逻辑和代码,但依然侵入了应用进程;而服务网格的服务治理功能对业务是完全无侵入的,完全下沉到基础设施层,完全不需要改动项目代码。
之所能做到完全无侵入,服务网格会在每个 pod 中注入一个sidecar代理,该代理对应用程序来说是透明,所有应用程序间的流量都会通过它,所有对应用程序流量的控制都可以在服务网格中实现。追本溯源,服务网格实际上是一种 SDN,等同于 OSI 模型中的会话层。

服务网格中分为控制平面和数据平面,当前流行的两款开源的服务网格 Istio 和 Linkerd 实际上都是这种构造,只不过 Istio 的划分更清晰,而且部署更零散,很多组件都被拆分,控制平面中包括 Mixer(Istio 1.5 之前版本)、Pilot、Citadel,数据平面默认是用 Envoy;而 Linkerd 中只分为 Linkerd 做数据平面,namerd 作为控制平面。
控制平面特点:
- 不直接解析数据包
- 与控制平面中的代理通信,下发策略和配置
- 负责网络行为的可视化
- 通常提供 API 或者命令行工具可用于配置版本化管理,便于持续集成和部署
数据平面的特点:
- 通常是按照无状态目标设计的,但实际上为了提高流量转发性能,需要缓存一些数据,因此无状态也是有争议的
- 直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等
- 对应用来说透明,即可以做到无感知部署
Istio
Istio是当前使用最广的服务网格产品,Istio 为希腊语,意思是”起航“,延用了 Kubernetes(在希腊语中是飞行员或舵手的意思)建立的希腊航海主题。
Istio架构
Istio 服务网格从逻辑上分为数据平面和控制平面。
- 数据平面由一组智能代理(Envoy)组成,被部署为sidecar。
- 控制平面管理并配置代理来进行流量路由。

Envoy:
Istio 使用 Envoy 代理的扩展版本。Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。
由 Envoy 代理启用的一些 Istio 的功能和任务包括:
- 流量控制功能:通过丰富的 HTTP、gRPC、WebSocket 和 TCP 流量路由规则来执行细粒度的流量控制。
- 网络弹性特性:重试设置、故障转移、熔断器和故障注入。
- 安全性和身份认证特性:执行安全性策略,并强制实行通过配置 API 定义的访问控制和速率限制。
- 基于 WebAssembly 的可插拔扩展模型,允许通过自定义策略执行和生成网格流量的遥测。
Istiod:
istiod 将先前由 Pilot,Galley,Citadel 执行的功能统一为一个二进制文件。现在看Isito官网的架构图,控制平面只有一个istiod。
Istiod 将控制流量行为的高级路由规则转换为 Envoy 特定的配置, 并在运行时将其传播给 Sidecar。它提取了特定平台的服务发现机制, 并将其综合为一种所有符合Envoy API 的 Sidecar 都可以使用的标准格式。(原Pilot作用)。
Istiod 安全通过内置的身份和凭证管理, 实现了强大的服务对服务和终端用户认证。您可以使用 Istio 来升级服务网格中未加密的流量。(原Citadel作用)。
Istio原理
Istio限制
Istio并不是完美的,虽然有业务无侵入和服务治理下放基础设施这种顶级理念加持,业务实际使用Istio还是要考虑各种问题:
-
对非 Kubernetes 环境的支持有限
Istio与Kubernetes几乎是强绑定的,虽然Istio一直在积极摆脱这种限制(如合并为Istiod就有为了VM环境使用的考虑),但如果没有K8S,Istio服务发现、Sidecar透明注入和大规模部署等都可能是问题。
-
在集群规模较大时的性能问题
Istio毕竟是一套完整的组件,虽然Istio一方面通过组件合并和代码优化减少资源使用,另一方面通过大量测试结果改变固有偏见,但资源占用以及带来的性能问题依旧是不可忽略的问题。
按照Istio的性能测试结果,Envoy 代理每秒处理 1000 个请求时,使用 0.6 vCPU 和 50 MB 内存,延迟增加了 8 毫秒。在集群规模较小时这样的资源和延迟消耗问题不大,但一旦集群规模变大,Envoy代理数量是要随服务pod数量同时增长的,这样的资源消耗就会成为问题。
另外,Istio 默认的工作模式下,每个 sidecar 都会收到全集群所有服务的信息。在稍大一些的集群规模,Envoy 的内存开销、Istio 的 CPU 开销、XDS 的下发时效性等问题,一定会变得尤为突出。(可以通过 sidecar CRD 来显示定义服务调用关系,使 Envoy 只得到需要的服务信息,从而大幅降低 Envoy 的资源开销,但前提是在业务线中能梳理出这些调用关系。)
-
无法做到完全对应用透明
服务通信和治理相关的功能迁移到 sidecar 进程中后, 应用中的 SDK 通常需要作出一些对应的改变。
比如 SDK 需要关闭一些功能,例如重试。一个典型的场景是,SDK 重试 m 次,sidecar 重试 n 次,这会导致 m * n 的重试风暴,从而引发风险。
-
组件故障时是否有退路?
Sidecar Envoy直接承担了所有业务流量,一旦Envoy故障,服务访问就会成为问题。这种情况下,需要访问fallback到直连模式,这通常决定了核心业务能否接入服务网格。目前,大部分fallback到直连模式方案需要手动切换,部分商业平台有自动化方案,如百度增加Naming Agent,正常情况下Consumer请求Agent返回的Provider的地址为Consumer Envoy的地址,有Envoy代理访问,如果Envoy故障,Agent返回Provider实际pod ip,实现直连访问。
Istio部分功能原理
Istio的完整原理是很复杂的,在这里,只探索最为关键的流量劫持和流量路由过程。
流量劫持
Sidecar流量劫持是通过iptables实现的。
当Sidecar注入pod中时,会先启动istio-init容器,用于给 Sidecar 容器即 Envoy 代理做初始化,设置 iptables 端口转发。

- 远程服务访问本地服务:Remote Pod -> Local Pod:Remote Pod ->
RREROUTING->ISTIO_INBOUND->ISTIO_IN_REDIRECT-> Envoy 15006(Inbound)->OUTPUT->ISTIO_OUTPUTRULE 1 ->POSTROUTING-> Local Pod - 本地服务访问远程服务:Local Pod -> Remote Pod:Local Pod->
OUTPUT->ISTIO_OUTPUTRULE 9 -> ISTIO_REDIRECT -> Envoy 15001 (Outbound)->OUTPUT->ISTIO_OUTPUTRULE 4 ->POSTROUTING-> Remote Pod - Prometheus 抓取本地服务的 metrics:Prometheus -> Local Pod:Prometheus 抓取数据平面 metrics 的流量不会也无须经过 Envoy 代理。Prometheus->
RREROUTING->ISTIO_INBOUND(对目的地为 15020、15090 端口流量将转到INPUT)->INPUT-> Local Pod - 本地 Pod 服务间的流量:Local Pod -> Local Pod:一个 Pod 可能同时存在两个或多个服务,如果 Local Pod 访问的服务也在该当前 Pod 上,流量会依次经过 Envoy 15001 和 Envoy 15006 端口最后到达本地 Pod 的服务端口上。Local Pod->
OUTPUT->ISTIO_OUTPUTRULE 9 ->ISTIO_REDIRECT-> Envoy 15001(Outbound)->OUTPUT->ISTIO_OUTPUTRULE 2 ->ISTIO_IN_REDIRECT-> Envoy 15006(Inbound)->OUTPUT->ISTIO_OUTPUTRULE 1 ->POSTROUTING-> Local Pod - Envoy 内部的进程间 TCP 流量:Envoy 内部进程的 UID 和 GID 为 1337,它们之间的流量将使用 lo 网卡,使用 localhost 域名来通信。Envoy 进程(Localhost) ->
OUTPUT->ISTIO_OUTPUTRULE 8 ->POSTROUTING-> Envoy 进程(Localhost) - Sidecar 到 Istiod 的流量:Sidecar 需要访问 Istiod 以同步配置,
pilot-agent进程会向 Istiod 发送请求,以同步配置。pilot-agent进程 ->OUTPUT->Istio_OUTPUTRULE 9 -> Envoy 15001 (Outbound Handler) -> OUTPUT ->ISTIO_OUTPUTRULE 4 ->POSTROUTING-> Istiod
iptables做流量劫持一个非常严重的问题是性能损耗,需要借助于 conntrack 模块实现连接跟踪,在连接数较多的情况下,会造成较大的消耗。目前出现了通过eBPF流量劫持方案,利用 eBPF 的 sockops 和 redir 能力,可以直接将数据包从 inbound socket 传输到 outbound socket。
流量路由
iptables只是做了流量劫持,真正决定路由转发到哪里的,是Envoy的 Inbound 和 Outbound 。
Inbound Handler
Inbound Handler 的作用是将 iptables 拦截到的 downstream 的流量转发给 Pod 内的应用程序容器。
Pod 中的 Iptables 将入站流量劫持到 15006 端口上,Envoy 的 Inbound Handler 在 15006 端口上监听,对来源为任何 IP 的对目标端口的请求将路由到 inbound|目标端口|| Cluster 上。该Cluster将流量发送到原始目标地址(Pod IP),并将原地址改为127.0.0.6,而且对于 Pod 内流量是通过 lo 网卡发送的,根据该规则,流量将被透传到 Pod 内的应用容器。
[!NOTE]
127.0.0.6 这个 IP 是 Istio 中默认的
InboundPassthroughClusterIpv4,在 Istio 的代码中指定。即流量在进入 Envoy 代理后被绑定的 IP 地址,作用是让 Outbound 流量重新发送到 Pod 中的应用容器,即 Passthought(透传),绕过 Outbound Handler。
Outbound Handler
Outbound Handler 的作用是将 iptables 拦截到的本地应用程序向外发出的流量,经由 Envoy 代理路由到上游。
Envoy 监听在 15001 端口上监听所有 Outbound 流量,Outbound Handler 处理,然后经过 virtualOutbound Listener、0.0.0.0_端口 Listener,然后通过 Route 找到上游的 cluster,进而通过 EDS 找到 Endpoint 执行路由动作。
sidecar 会根据 HTTP header 中的 domains 来匹配 VirtualHost,VirtualHost记录k8s service,并通过service找到Endpoint。
[!NOTE]
Cluster: 集群(cluster)是 Envoy 连接到的一组逻辑上相似的上游主机。Envoy 通过服务发现发现集群中的成员。Envoy 可以通过主动运行状况检查来确定集群成员的健康状况。Envoy 如何将请求路由到集群成员由负载均衡策略确定。
Istio资源
通过Helm可以一键部署Istio。Istio使用的核心是各种路由配置,先说下Istio和K8S服务访问的不同之处:
- 在kuberntes中, 服务与服务之间是通过clusterIP(kube-proxy)进行通信的, 而在istio中,对于网格间的两个服务,得益于xDS机制,通过CDS可直接得到EDS, 因此服务与服务之间不再经过clusterIP,而是直接到达对方的一个端点上(PodIP + Port)
- 在istio中,对于网格间的服务需要访问网格外的服务,仍然通过 ClusterIP 进行通信,但有一点需要注意:这里并没有
kube-proxy的参与!Envoy 自己实现了一套流量转发机制,当你访问 ClusterIP 时,Envoy 就把流量转发到具体的 Pod 上去,不需要借助 kube-proxy 的iptables或ipvs规则。 - 为什么不直接使用 Kubernetes Ingress API ? 原因是 Ingress API 无法表达 Istio 的路由需求。
部署Isito后,会将组件pod部署在istio-system命名空间,查看service,istio-ingressgateway服务默认为LoadBalancer模式,如果没有LB会不可用,可修改为NodePort模式。
root@k8s-master:~/istio-1.20.6# kubectl get pod -n istio-system
NAME READY STATUS RESTARTS AGE
istio-egressgateway-765d784c5-rp7jn 1/1 Running 0 6d2h
istio-ingressgateway-869d777659-6kxl8 1/1 Running 0 6d2h
istiod-5c4f4498d-m92w8 1/1 Running 0 6d2h
root@k8s-master:~/istio-1.20.6# kubectl get svc -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-egressgateway ClusterIP 10.96.0.249 <none> 80/TCP,443/TCP 6d2h
istio-ingressgateway NodePort 10.96.2.126 <none> 15021:30147/TCP,80:30582/TCP,443:31684/TCP,31400:30066/TCP,15443:30470/TCP 6d2h
istiod ClusterIP 10.96.1.148 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 6d2h
Gateway
Istio安装时,会安装istio-egressgateway和istio-ingressgateway,这两个网关都运行一个 Envoy 代理实例,它们在网格的边缘作为负载均衡器运行。入口网关接收入站连接,而出口网关接收从集群出去的连接。
使用入口网关,我们可以对进入集群的流量应用路由规则。我们可以有一个指向入口网关的单一外部 IP 地址,并根据主机头将流量路由到集群内的不同服务。

可以使用 Gateway 资源来配置网关。网关资源描述了负载均衡器的暴露端口、协议、SNI(服务器名称指示)配置等。网关资源在背后控制着 Envoy 代理在网络接口上的监听方式以及它出示的证书。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: my-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- dev.example.com
- test.example.com
通过gateway资源,实质只是配置了负载均衡策略。hosts 字段作为一个过滤器,只有以 dev.example.com 和 test.example.com 为目的地的流量会被允许通过。
为了控制和转发流量到集群内运行的实际 Kubernetes 服务,我们必须用特定的主机名(例如 dev.example.com 和 test.example.com)配置一个VirtualService,然后将网关连接到它。

VirtualService和DestinationRule
VirtualService 和 DestinationRule 是两个重要的配置对象,它们用于定义服务间的通信规则和服务实例的路由规则。
VirtualService:定义路由规则,描述满足条件的请求去哪里
- 路由:金丝雀发布、基于用户身、URI、Header 等匹配路由等;
- 错误注入:HTTP 错误代码注入、HTTP 延时注入;
- 流量切分:基于百分比的流量切分路由;
- 流量镜像:将一定百分比的流量镜像发送到其他集群;
- 超时:设置超时时间,超过设置的时间请求将失败;
- 重试:设置重试策略,如触发条件、重试次数、间隔时间等;
DestinationRule:定义虚拟服务路由目标地址的真实地址,即子集(subset),支持多种负载均衡策略
- 负载均衡:设置负载均衡策略,如简单负载均衡、区域感知负载均衡、区域权重负载均衡;
- 熔断(Circuit Breaking):通过异常点检测(Outlier Detection)和连接池设置将异常节点从负载均衡池中剔除;

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b
spec:
hosts:
- dev.example.com # 对应Gateway中的host,或者其他服务要访问的域名地址
- test.example.com
http:
- route:
- destination: # 目标服务,一般对应到k8s的某个service,与DestinationRule中的host和subset一致
host: service-b # 即k8s中的 service-b.default.svc.cluster.local
subset: v1 #subset 会在destinationRule中使用
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b
spec:
host: service-b #这里的名字需要跟virtualservice中定义的destination host一致
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1 #这里的名字需要跟virtualservice中定义的subset一致
labels:
version: v1
- name: v2
labels:
version: v2
ServiceEntry
使用服务条目资源(ServiceEntry)可以将条目添加到 Istio 内部维护的服务注册表中。添加服务条目后,Envoy 代理可以将流量发送到该服务,就好像该服务条目是网格中的服务一样。通过配置服务条目,可以管理在网格外部运行的服务的流量。
ServiceEntry的地位和功能与K8S的Service类似,不同的地方是ServiceEntry可以指向外部地址。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: baidu-vs
spec:
hosts:
- www.baidu.com
http:
- route:
- destination:
host: www.baidu.com
port:
number: 80
#subset: tls-origination
timeout: 1ms
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: baidu-se
spec:
hosts:
- www.baidu.com
location: MESH_EXTERNAL
ports:
- name: http
number: 80
protocol: HTTP
resolution: DNS
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: baidu-dr
spec:
host: www.baidu.com
trafficPolicy: # 流量策略,包括:负载平衡策略、连接池大小、异常检测
loadBalancer: # 默认LB策略
simple: ROUND_ROBIN # ROUND_ROBIN-循环,LEAST_CONN-最小连接,RANDOM-随机,PASSTHROUGH-只连
Istio bookinfo实战
Bookinfo是一个用于演示多种 Istio 特性的应用,该应用由四个单独的微服务构成:
productpage. 这个微服务会调用details和reviews两个微服务,用来生成页面。details. 这个微服务中包含了书籍的信息。reviews. 这个微服务中包含了书籍相关的评论。它还会调用ratings微服务。ratings. 这个微服务中包含了由书籍评价组成的评级信息。
reviews 微服务有 3 个版本:
- v1 版本不会调用
ratings服务。 - v2 版本会调用
ratings服务,并使用 1 到 5 个黑色星形图标来显示评分信息。 - v3 版本会调用
ratings服务,并使用 1 到 5 个红色星形图标来显示评分信息。
启动应用服务
-
进入 Istio 安装目录。
-
Istio 默认自动注入 Sidecar。 为
default命名空间打上标签istio-injection=enabled:$ kubectl label namespace default istio-injection=enabled -
使用
kubectl命令来部署应用:$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml上面这条命令会启动
bookinfo应用架构图中显示的全部四个服务。 也会启动三个版本的 reviews 服务:v1、v2 以及 v3。查看pod和service确认所有服务正常。
配置默认gateway、VirtualService和DestinationRule
Gateway配置,关键是选择ingressgateway和配置host,这里host为*表示匹配全部流量。
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 8080 # HTTP流量从8080端口进入网关,8080端口要对外开放
name: http
protocol: HTTP
hosts:
- "*"
VirtualService配置,匹配url发送到destination,选择刚创建的网关
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway # 指定流量来源,可以是一个或多个网关或网格内部的 sidecar,Gateway 填写按照 {namespace}/{Gateway 名称},保留字段 mesh 表示网格内部所有的 sidecar,如果没有配置gateway字段,默认为mesh
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
DestinationRule配置,关键是Review要分为3个版本
# 其他服务只有一个版本,只配置v1的subsets即可
apiVersion: networking.istio.io/v1
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/v1
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage
http:
- route:
- destination:
host: productpage
subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination: # review只到v1的subsets
host: reviews
subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: details
spec:
hosts:
- details
http:
- route:
- destination:
host: details
subset: v1
---
将来自特定用户的所有流量路由到特定服务版本。在这,来自名为 Jason 的用户的所有流量将被路由到服务 reviews:v2。
$ kubectl get virtualservice reviews -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
...
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
流量转移
将部分流量转移到其他版本,基于此可以实现灰度发布、A/B测试等
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
故障注入
故障注入(Fault Injection)是一种软件和硬件测试技术,它通过模拟或强制系统在运行时出现预期外的错误条件来评估系统的可靠性和健壮性。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 100
fixedDelay: 2s
route:
- destination:
host: ratings
subset: v1
超时重试
超时机制是当一个请求超过指定的时间(比如 1s)还没有被处理的话,这个请求就会直接被取消并抛出指定的异常或者错误,用于减小系统或者服务出现故障之后带来的影响。
timeout配置可以引入最大超时限制,自带2次重试;验证超时可先注入故障。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
timeout: 0.5s
看到 1 秒钟就会返回,但 reviews 是不可用的(页面没有reviews的数据)。
熔断
熔断器是 Istio 为创建具有弹性的微服务应用提供的有用的机制。在熔断器中,设置一个对服务中的单个主机调用的限制,例如并发连接的数量或对该主机调用失败的次数。一旦限制被触发,熔断器就会“跳闸”并停止连接到该主机。
使用熔断模式可以快速失败而不必让客户端尝试连接到过载或有故障的主机。
Istio熔断使用的是httpbin应用,首先部署httpbin应用及其Service。
##################################################################################################
# httpbin service
##################################################################################################
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
创建一个目标熔断规则(DestinationRule),在调用 httpbin 服务时应用熔断设置:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1 #最大连接数
http:
http1MaxPendingRequests: 1 #http请求pending状态的最大请求数
maxRequestsPerConnection: 1 #在一定时间内限制对后端服务发起的最大请求数
outlierDetection: #熔断设置
consecutiveErrors: 1 #从连接池开始拒绝连接,已经连接失败的次数,当通过HTTP访问时,返回代码是502、503或504则视为错误。
interval: 1s #拒绝访问扫描的时间间隔,即在interval(1s)内连续发生1个consecutiveErrors错误,则触发服务熔断,格式是1h/1m/1s/1ms,但必须大于等于1ms。即分析是否需要剔除的频率,多久分析一次,默认10秒。
baseEjectionTime: 3m #最短拒绝访问时长。这个时间主机将保持拒绝访问,且如果决绝访问达到一定的次数。格式:1h/1m/1s/1ms,但必须大于等于1ms。实例被剔除后,至少多久不得返回负载均衡池,默认是30秒。
maxEjectionPercent: 100 #服务在负载均衡池中被拒绝访问(被移除)的最大百分比,负载均衡池中最多有多大比例被剔除,默认是10%。
创建客户端程序以发送流量到 httpbin 服务。这是一个名为 Fortio 的负载测试客户的,其可以控制连接数、并发数及发送 HTTP 请求的延迟。通过 Fortio 能够有效的触发前面 在 DestinationRule 中设置的熔断策略。
apiVersion: v1
kind: Service
metadata:
name: fortio
labels:
app: fortio
service: fortio
spec:
ports:
- port: 8080
name: http
selector:
app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio-deploy
spec:
replicas: 1
selector:
matchLabels:
app: fortio
template:
metadata:
annotations:
# This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
# in addition to the stats normally served by Istio. The Circuit Breaking example task
# gives an example of inspecting Envoy stats via proxy config.
proxy.istio.io/config: |-
proxyStatsMatcher:
inclusionPrefixes:
- "cluster.outbound"
- "cluster_manager"
- "listener_manager"
- "server"
- "cluster.xds-grpc"
labels:
app: fortio
spec:
containers:
- name: fortio
image: fortio/fortio:latest_release
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http-fortio
- containerPort: 8079
name: grpc-ping
登入客户端 Pod 并使用 Fortio 工具调用 httpbin 服务。-curl 参数表明发送一次调用:
$ FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }')
$ kubectl exec -it $FORTIO_POD -c fortio -- /usr/bin/fortio load -curl http://httpbin:8000/get
HTTP/1.1 200 OK
server: envoy
date: Tue, 16 Jan 2018 23:47:00 GMT
content-type: application/json
access-control-allow-origin: *
access-control-allow-credentials: true
content-length: 445
x-envoy-upstream-service-time: 36
{
"args": {},
"headers": {
"Content-Length": "0",
"Host": "httpbin:8000",
"User-Agent": "istio/fortio-0.6.2",
"X-B3-Sampled": "1",
"X-B3-Spanid": "824fbd828d809bf4",
"X-B3-Traceid": "824fbd828d809bf4",
"X-Ot-Span-Context": "824fbd828d809bf4;824fbd828d809bf4;0000000000000000",
"X-Request-Id": "1ad2de20-806e-9622-949a-bd1d9735a3f4"
},
"origin": "127.0.0.1",
"url": "http://httpbin:8000/get"
}
可以看到调用后端服务的请求已经成功!接下来,可以测试熔断。
在 DestinationRule 配置中,定义了 maxConnections: 1 和 http1MaxPendingRequests: 1。 这些规则意味着,如果并发的连接和请求数超过一个,在 istio-proxy 进行进一步的请求和连接时,后续请求或 连接将被阻止。
发送并发数为 2 的连接(-c 2),请求 20 次(-n 20):
[root@node1 istio-1.6.5]# kubectl exec -it $FORTIO_POD -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
03:59:25 I logger.go:97> Log level is now 3 Warning (was 2 Info)
Fortio 1.3.1 running at 0 queries per second, 2->2 procs, for 20 calls: http://httpbin:8000/get
Starting at max qps with 2 thread(s) [gomax 2] for exactly 20 calls (10 per thread + 0)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
03:59:25 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
Ended after 79.166124ms : 20 calls. qps=252.63
Aggregated Function Time : count 20 avg 0.0064311497 +/- 0.007472 min 0.000340298 max 0.032824602 sum 0.128622994
# range, mid point, percentile, count
>= 0.000340298 <= 0.001 , 0.000670149 , 10.00, 2
> 0.001 <= 0.002 , 0.0015 , 20.00, 2
> 0.002 <= 0.003 , 0.0025 , 40.00, 4
> 0.003 <= 0.004 , 0.0035 , 60.00, 4
> 0.004 <= 0.005 , 0.0045 , 65.00, 1
> 0.006 <= 0.007 , 0.0065 , 80.00, 3
> 0.012 <= 0.014 , 0.013 , 85.00, 1
> 0.014 <= 0.016 , 0.015 , 90.00, 1
> 0.016 <= 0.018 , 0.017 , 95.00, 1
> 0.03 <= 0.0328246 , 0.0314123 , 100.00, 1
# target 50% 0.0035
# target 75% 0.00666667
# target 90% 0.016
# target 99% 0.0322597
# target 99.9% 0.0327681
Sockets used: 8 (for perfect keepalive, would be 2)
Code 200 : 14 (70.0 %)
Code 503 : 6 (30.0 %)
Response Header Sizes : count 20 avg 161.15 +/- 105.5 min 0 max 231 sum 3223
Response Body/Total Sizes : count 20 avg 668.15 +/- 279.6 min 241 max 852 sum 13363
All done 20 calls (plus 0 warmup) 6.431 ms avg, 252.6 qps
仅有70%的请求成功率,30%的访问被熔断。
可观测性
指标监控、日志和链路追踪是应用可观测性的三大重点。
指标监控通过Prometheus即可实现,在 Istio 网格内,每个组件都有一个对外暴露指标的接口,Prometheus 通过抓取这些接口的指标来收集数据。Istio也提供开箱即用的配置实现指标合并传输给Prometheus,如通过Istio安装文档部署的Prometheus就可直接获取Isito指标。
日志通过部署ELK直接获取容器内应用日志即可。
Istio 利用 Envoy 的分布式链路追踪功能提供开箱即用的链路追踪集成,通过集成OpenTelemetry协议支持Skywalking、Zipkin、Jeager等所有链路追踪工具。
以Skywalking为例,部署Skywalking后,配置IstioOperator来使用 SkyWalking Agent 作为默认的追踪器, 链路数据会被发送到 SkyWalking 后端。
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
defaultProviders:
tracing:
- "skywalking"
enableTracing: true
extensionProviders:
- name: "skywalking"
skywalking:
service: tracing.istio-system.svc.cluster.local
port: 11800
尽管 Istio 代理能够自动发送 span,但需要一些附加信息才能将这些 span 加到同一个调用链。 所以当代理发送 span 信息的时候,应用程序需要附加适当的 HTTP 请求头信息,这样才能够把多个 span 加到同一个调用链。
要做到这一点,每个应用程序必须从每个传入的请求中收集请求头,并将这些请求头转发到传入请求所触发的所有传出请求中。 具体选择转发哪些请求头取决于所配置的链路追踪后端,要转发的请求头在每个链路追踪系统特定的任务页面进行说明, 以下是一个汇总:
所有应用程序必须转发以下请求头:
x-request-id:一个 Envoy 特定的标头,用于一致地采样日志和链路。traceparent和tracestate:W3C 标准标头
skywalking要额外转发"sw8"。
以bookinfo中的productpage应用为例,使用Python编码,请求头转发函数如下:
def getForwardHeaders(request):
headers = {}
# x-b3-*** headers can be populated using the OpenTelemetry span
ctx = propagator.extract(carrier={k.lower(): v for k, v in request.headers})
propagator.inject(headers, ctx)
# We handle other (non x-b3-***) headers manually
if 'user' in session:
headers['end-user'] = session['user']
# Keep this in sync with the headers in details and reviews.
incoming_headers = [
# All applications should propagate x-request-id. This header is
# included in access log statements and is used for consistent trace
# sampling and log sampling decisions in Istio.
'x-request-id',
# Lightstep tracing header. Propagate this if you use lightstep tracing
# in Istio (see
# https://istio.io/latest/docs/tasks/observability/distributed-tracing/lightstep/)
# Note: this should probably be changed to use B3 or W3C TRACE_CONTEXT.
# Lightstep recommends using B3 or TRACE_CONTEXT and most application
# libraries from lightstep do not support x-ot-span-context.
'x-ot-span-context',
# Datadog tracing header. Propagate these headers if you use Datadog
# tracing.
'x-datadog-trace-id',
'x-datadog-parent-id',
'x-datadog-sampling-priority',
# W3C Trace Context. Compatible with OpenCensusAgent and Stackdriver Istio
# configurations.
'traceparent',
'tracestate',
# Cloud trace context. Compatible with OpenCensusAgent and Stackdriver Istio
# configurations.
'x-cloud-trace-context',
# Grpc binary trace context. Compatible with OpenCensusAgent nad
# Stackdriver Istio configurations.
'grpc-trace-bin',
# b3 trace headers. Compatible with Zipkin, OpenCensusAgent, and
# Stackdriver Istio configurations.
# This is handled by opentelemetry above
# 'x-b3-traceid',
# 'x-b3-spanid',
# 'x-b3-parentspanid',
# 'x-b3-sampled',
# 'x-b3-flags',
# SkyWalking trace headers.
'sw8',
# Application-specific headers to forward.
'user-agent',
# Context and session specific headers
'cookie',
'authorization',
'jwt',
]
# For Zipkin, always propagate b3 headers.
# For Lightstep, always propagate the x-ot-span-context header.
# For Datadog, propagate the corresponding datadog headers.
# For OpenCensusAgent and Stackdriver configurations, you can choose any
# set of compatible headers to propagate within your application. For
# example, you can propagate b3 headers or W3C trace context headers with
# the same result. This can also allow you to translate between context
# propagation mechanisms between different applications.
for ihdr in incoming_headers:
val = request.headers.get(ihdr)
if val is not None:
headers[ihdr] = val
return headers
应用部署后,即可在Skywalking处查看到链路。
[!NOTE]
Opentracing 提供了基于 Spring 的代码埋点,因此我们可以使用 Opentracing Spring 框架来提供 HTTP header 的传递,以避免这部分硬编码工作。在 Spring 中采用 Opentracing 来传递分布式跟踪上下文非常简单,只需要下述两个步骤:
在 Maven POM 文件中声明相关的依赖,首先是对 Opentracing Spring Cloud Starter 的依赖;另外由于本示例中后端接入的是 Jaeger ,也需要依赖 Jaeger 的相关 jar 包。 当然,你也可以选择使用其他支持 Opentracing 的 Tracer,例如 Zipkin、LightStep、SkyWalking 等。 由于 Opentracing 统一了 API,切换不同 Tracer 只需要非常少的修改,包括 Maven 依赖和生成 Tracer 的部分代码。
在 Spring Application 中声明一个 Tracer bean。
@Bean public Tracer jaegerTracer() { // 我们需要设置下面的环境变量: // JAEGER_ENDPOINT="http://${JAEGER_IP}:28019/api/traces" // JAEGER_PROPAGATION="b3" // JAEGER_TRACEID_128BIT="true" 使用 128bit 的 tracer id, 以兼容 Istio return Configuration.fromEnv("eshop-opentracing").getTracer(); }
安全
安全对于微服务这样的分布式系统来说至关重要。与单体应用在进程内进行通信不同,网络成为了服务间通信的纽带,这使得它对安全有了更迫切的需求。比如为了抵御外来攻击,我们需要对流量进行加密;为保证服务间通信的可靠性,需要使用 mTLS 的方式进行交互;为控制不同身份的访问,需要设置不同粒度的授权策略。作为一个服务网格,Istio 提供了一整套完整的安全解决方案。它可以以透明的方式,为我们的微服务应用添加安全策略。
Istio 中的安全架构是由多个组件协同完成的。Citadel 是负责安全的主要组件,用于密钥和证书的管理;Pilot 会将安全策略配置分发给 Envoy 代理;Envoy 执行安全策略来实现访问控制。下图展示了 Istio 的安全架构和运作流程。

Istio 提供的安全功能主要分为认证和授权两部分。
认证
Istio 提供两种类型的认证:
- 对等认证(Peer authentication):用于服务到服务的认证。这种方式是通过双向 TLS(mTLS)来实现的,即客户端和服务端都要验证彼此的合法性。Istio 中提供了内置的密钥和证书管理机制,可以自动进行密钥和证书的生成、分发和轮换,而无需修改业务代码。
- 请求认证(Request authentication):也叫最终用户认证,验证终端用户或客户端。Istio 使用目前业界流行的 JWT(JSON Web Token)作为实现方案。
授权
Istio 的授权策略可以为网格中的服务提供不同级别的访问控制,比如网格级别、命名空间级别和工作负载级别。授权策略支持 ALLOW 和 DENY 动作,每个 Envoy 代理都运行一个授权引擎,当请求到达代理时,授权引擎根据当前策略评估请求的上下文,并返回授权结果 ALLOW 或 DENY。授权功能没有显示的开关进行配置,默认就是启动状态,只需要将配置好的授权策略应用到对应的工作负载就可以进行访问控制了。
Istio 中的授权策略通过自定义资源AuthorizationPolicy来配置。除了定义策略指定的目标(网格、命名空间、工作负载)和动作(容许、拒绝)外,Istio 还提供了丰富的策略匹配规则,比如可以设置来源、目标、路径、请求头、方法等条件,甚至还支持自定义匹配条件,其灵活性可以极大的满足用户需求。

浙公网安备 33010602011771号