流量匹配和路由
通过定义Istio的VirtualService虚拟服务和DestinationRule目标规则,我们可以很容易的控制服务之间的流量和API调用,实现负载均衡、断路器、超时和重试配置、灰度发布等功能。Istio的流量管理功能是基于Envoy代理实现的,网格内数据平面流量都会通过Envoy代理,因此这些流量管理规则对业务应用都是透明的。
Istio的VirtualService和DestinationRule资源对象的概念其实比较抽象,不好用文字描述,这里建议直接通过例子来体会两者能够实现的功能,而具体详细的可配置字段可以参考官方文档。
相关概念
VirtualService和DestinationRule是Istio中实现流量管理的核心配置组件。
VirtualService:虚拟服务定义了网格流量的匹配规则,以及定义流量路由到哪一个目标地址(通常是k8s的Service地址)。虚拟服务可以管理k8s中Service的流量,也可以绑定到istio-ingressgateway网关上,实现入口流量的控制。
DestinationRule:流量路由到目标地址后,由目标规则定义具体路由到某一个服务实例(通常是k8s中Deployment定义的Pod)上。
Gateway:网关资源对象定义了域名和端口,具体的流量路由还是由绑定的VirtualService控制,这和k8s的Ingress有些不同。

对于这些概念,我们可以按如图所示理解:k8s内置的Service通过DNS方式实现了服务名到具体Pod的流量路由关系,功能比较简单;而引入Istio后,我们虽然也需要定义k8s的Service,但SideCar(Envoy代理)做了很多其它工作,它通过对流量的拦截偷偷摸摸的实现了更多功能,在Service的基础上,结合使用VirtualService和DestinationRule,能够实现更复杂的流量匹配和路由机制。
VirtualService 虚拟服务
VirtualService是Istio中最重要的一个资源,它定义了一系列针对网格内流量的路由规则。我们知道k8s自带的Service组件功能比较简单,它支持通过标签机制绑定到一个或多个Pod上,以最基本的轮询方式进行负载均衡的访问。而VirtualService配合Service使用功能会更加强大,它支持更丰富的流量管理配置。
首先假设我们不考虑Istio只用k8s内置的资源对象,下面例子定义了Deployment和Service资源,我们部署了一个demo服务,运行于8080端口,具体代码中存在一个/api/demo/echo的HTTP服务路径:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demo
name: demo
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: demo
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: demo
version: v1
spec:
containers:
- image: gacfox/demo
name: demo
resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
labels:
app: demo
name: demo
namespace: dev
spec:
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
selector:
app: demo
status:
loadBalancer: {}
此时我们可以在集群内请求http://demo/api/demo/echo路径。然而,如果我们希望实现一个类似Nginx的Rewrite功能,将/sss重写为/,即该服务路径也要匹配请求地址http://demo/sss/api/demo/echo,那么k8s的Service是没有该功能的,你可能需要再部署一个Nginx服务,这就非常麻烦了。
如果使用Istio,则可以基于VirtualService很容易的实现该功能:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: demo
namespace: dev
spec:
hosts:
- "demo"
http:
- match:
- uri:
prefix: /sss
rewrite:
uri: /
route:
- destination:
host: demo
port:
number: 8080
spec.hosts[]:指定VirtualService要处理的域名,这里我们指定了k8s的Service名(支持全限定名,因此可以实现跨命名空间的访问);除此之外,VirtualService也支持绑定到网关,比如我们将匹配api.gacfox.com域名的VirtualService绑定到网关,那么我们在集群外部通过该域名请求网关地址和端口,流量会由对应VirtualService处理。spec.http[].match:指定流量的匹配规则,这里使用前缀匹配/sss,除此之外也支持精确匹配、正则匹配、HTTP的Header匹配等。spec.http[].rewrite:该配置实现了URL重写。spec.http[].route:指定了流量的目标配置。这里指定k8s的Service名和端口信息。
DestinationRule 目标规则
VirtualService指定了流量的匹配规则和目标,而DestinationRule指定流量具体如何发送到Pod上,两者通常需要配合使用。
我们这里同样先不考虑Istio只用k8s的资源对象,下面例子中我们部署了两个Deployment和一个Service,这里我们通过定义标签匹配机制,对demo服务的请求会均匀的负载均衡到两个Pod上。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demo
name: demo1
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: demo
version: v1
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: demo
version: v1
spec:
containers:
- image: gacfox/demo
name: demo1
resources: {}
status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demo
name: demo2
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: demo
version: v2
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: demo
version: v2
spec:
containers:
- image: gacfox/demo
name: demo2
resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
labels:
app: demo
name: demo
namespace: dev
spec:
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
selector:
app: demo
status:
loadBalancer: {}
这里我们继续定义VirtualService和DestinationRule,实现基于HTTP请求头的流量管理逻辑,我们希望请求头中带有beta=true的流量,路由到version=v2的Pod上,其它流量则继续路由到version=v1的Pod上,这样实现了一个灰度发布的效果。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: demo
namespace: dev
spec:
hosts:
- "demo"
http:
- match:
- uri:
prefix: /
headers:
beta:
exact: "true"
route:
- destination:
host: demo
port:
number: 8080
subset: v2
- match:
- uri:
prefix: /
route:
- destination:
host: demo
port:
number: 8080
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: demo
namespace: dev
spec:
host: demo
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
VirtualService配置中,我们定义了两个匹配规则,其中第一个规则会匹配HTTP请求头beta=true,两个匹配规则会按从上到下的顺序匹配。VirtualService的目标则指定了一个subset子服务集属性,对应DestinationRule的配置。
DestinationRule中,我们创建了2个subset子服务集,其中通过k8s的标签匹配机制,v1子服务集路由到指定了version=v1标签的Pod上,v2同理。
Gateway 网关
我们知道k8s中有Ingress Controller和Ingress组件,前者是入口网关控制器,后者相当于其具体的配置文件,用于具体定义网关对象。Istio中相当于网关控制器的组件在istio-system命名空间中已经默认安装,我们可以找到名为istio-ingressgateway的服务,而Istio网关资源对象和k8s的Ingress稍有不同,Istio中的网关只定义域名、端口即可,具体的路由规则等须配合VirtualService使用。
基于上一个例子定义的灰度发布效果,我们修改VirtualService绑定为网关访问方式:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: demo
namespace: dev
spec:
hosts:
- "demo.gacfox.com"
gateways:
- demo-gateway
http:
- match:
- uri:
prefix: /
headers:
beta:
exact: "true"
route:
- destination:
host: demo
port:
number: 8080
subset: v2
- match:
- uri:
prefix: /
route:
- destination:
host: demo
port:
number: 8080
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: demo
namespace: dev
spec:
host: demo
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: demo-gateway
namespace: dev
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "demo.gacfox.com"
此时,我们可以使用Postman等工具,访问http://demo.gacfox.com/api/demo/echo地址,观察HTTP请求头中带上beta=true和不带时的效果。
注:这里如果未使用公网IP和域名解析,可以在操作系统中配置一个host,让你的域名指向LB为istio-ingressgateway分配的地址。