刚才介绍服务发现、负载均衡、流量治理等过程时提到了 Istio 的服务、服务版本和服务实例等几个对象。这几个对象构成了 Istio 的服务模型,在介绍后面的内容前先对服务模型做下简要介绍。Istio支持将由服务、服务版本和服务实例构造的抽象模型映射到不同的平台上,这里重点关注基于Kubernetes的场景。可以认为,Istio的几个资源对象就是基于Kubernetes的相应资源对象构建的,加上部分约束来满足Istio服务模型的要求。
Istio 官方对这几个约束的描述如下。如果从较早版本就开始关注 Istio 的话,会注意到这些约束其实已经慢慢减少了,即功能增强则约束减少,但保留了某些原理上的约束。
◎ 端口命名:对 Istio 的服务端口必须进行命名,而且名称只允许是<protocol>[-<suffix>]这种格式,其中<protocol>可以是tcp、http、http2、https、grpc、tls、mongo、mysql、redis等,Istio根据在端口上定义的协议来提供对应的路由能力。例如“name:http2-forecast”和“name:http”是合法的端口名,但是“name:http2forecast”是非法的端口名。如果端口未命名或者没有基于这种格式进行命名,则端口的流量会被当作TCP流量来处理。
◎ 服务关联:Pod 需要关联到服务,如果一个 Pod 属于多个 Kubernetes 服务,则要求服务不能在同一个端口上使用不同的协议。在 Istio 0.8 之前的版本中要求一个Pod 只能属于一个 Kubernetes 服务,这种约束更简单,也更能满足绝大多数使用要求。
◎ Deployment使用app和version标签:建议Kubernetes Deployment显式地包含app和 version 标签。每个 Deployment 都需要有一个有业务意义的 app 标签和一个表示版本的version标签。在分布式追踪时可以通过app标签来补齐上下文信息,还可以通过app和version标签为遥测数据补齐上下文信息。
从逻辑上看,服务是Istio主要管理的资源对象,是一个抽象概念,主要包含HostName和Ports等属性,并指定了Service的域名和端口列表。每个端口都包含端口名称、端口号和端口的协议。
不同的协议有不同的内容,相应地,在 Istio 中对不同的协议也有不同的治理规则集合,可以参照3.2.2节中的详细内容。这也是Istio关于端口命名约束的机制层面的原因,具体来讲就是要求将端口的协议通过“-”连接符加在端口名称上。
从物理层面看,Istio服务的存在形式就是Kubernetes的Service,在启用了Istio的集群中创建 Kubernetes的 Service时只要满足以上约束,就可以转换为 Istio的 Service并配置规则进行流量治理。
Service是Kubernetes的一个核心资源,用户通过一个域名或者虚拟的IP就能访问到后端Pod,避免向用户暴露Pod地址的问题,特别是在Kubernetes中,Pod作为一个资源创建、调度和管理的最小部署单元的封装,本来就是动态变化的,在节点删除、资源变化等多种情况下都可能被重新调度,Pod的后端地址也会随之变化。
一个最简单的Service示例如下:
如上所示创建了一个名称为 forecast 的 Service,通过一个 ClusterIP 的地址就可以访问这个Service,指向有“app:forecast”标签的Pods。Kubernetes自动创建一个和Service同名的Endpoints对象,Service的selector会持续关注属于Service的Pod,结果会被更新到相应的Endpoints对象。
Istio的Service比较简单,可以看到差别就是要满足Istio服务的约束,并在端口名称上指定协议。例如,在以下示例中指定了forecast服务的3002端口是HTTP,对这个服务的访问就可以应用HTTP的诸多治理规则:
Istio虽然依赖于了Kubernetes的Service定义,但是除了一些约束,在定位上还有些差别。在 Kubernetes中,一般先通过 Deploymnent创建工作负载,再通过创建 Service关联这些工作负载,从而暴露工作负载的接口。因而看上去主体是工作负载,Service只是一种访问方式,某些后台执行的负载若不需要被访问,就不用定义Service。在Istio中,Service是治理的对象,是Istio中的核心管理实体,所以在Istio中,Service是一个提供了对外访问能力的执行体,可以将其理解为一个定义了服务的工作负载,没有访问方式的工作负载不是Istio的管理对象,Kubernetes的Service定义就是Istio服务的元数据。
在Istio的应用场景中,灰度发布是一个重要的场景,即要求一个Service有多个不同版本的实现。而 Kubernetes在语法上不支持在一个 Deployment上定义多个版本,在 Istio中多个版本的定义是将一个Service关联到多个Deployment,每个Deployment都对应服务的一个版本,如图2-2所示。
图2-2 Istio服务版本
在下面的实例中,forecast-v1 和 forecast-v2 这两个 Deployment 分别对应服务的两个版本:
观察和比较这两个Deployment的描述文件,可以看到:
◎ 这两个Deployment都有相同的“app:forecast”标签,正是这个标签和Service的标签选择器一致,才保证了Service能关联到两个Deployment对应的Pod。
◎ 这两个 Deployment 都有不同的镜像版本,因此各自创建的 Pod 也不同;这两个Deployment的version标签也不同,分别为v1和v2,表示这是服务的不同版本,这个不同的版本标签用来定义不同的Destination,进而执行不同的路由规则。
下面根据对Service和两个Deployment的如上定义分别创建3个Pod和两个Pod,假设5个Pod都运行在两个不同的Node上。在对Service进行访问时,根据配置的流量规则,可以将不同的流量转发到不同版本的Pod上,如图2-3所示。
图2-3 多版本的Service
服务实例是真正处理服务请求的后端,就是监听在相同端口上的具有同样行为的对等后端。服务访问时由代理根据负载均衡策略将流量转发到其中一个后端处理。Istio 的ServiceInstance 主要包括 Endpoint、Service、Labels、AvailabilityZone 和 ServiceAccount等属性,Endpoint 是其中最主要的属性,表示这个实例对应的网络后端(ip:port),Service表示这个服务实例归属的服务。
Istio的服务发现基于Kubernetes构建,本章讲到的Istio的Service对应Kubernetes的Service,Istio的服务实例对应Kubernetes的Endpoint,如图2-4所示。
图2-4 Istio的服务实例
Kubernetes提供了一个 Endpoints对象,这个 Endpoints对象的名称和 Service的名称相同,它是一个<Pod IP>:<targetPort>列表,负责维护Service后端Pod的变化。如前面例子中介绍的,forecast服务对应如下Endpoints对象,包含两个后端Pod,后端地址分别是172.16.0.16和 172.16.0.19,当实例数量发生变化时,对应的Subsets列表中的后端数量会动态更新;同样,当某个Pod迁移时,Endpoints对象中的后端IP地址也会更新: