随着业务的增长,一些传统企业对诸如灰度发布、服务路由、服务熔断、服务限流等服务治理的需求越来越强烈,但他们又不想对业务代码做大量的改造,因而 Service Mesh 成了他们比较好的选择;不幸的是业内比较成熟能落地的 Service Mesh 方案如 Istio 都是基于各种容器平台构建的,而这些传统企业很多没有接入容器平台也不想做容器化改造,这就导致 Service mesh 很难应用于这些传统企业或者一些非容器化的场景。
张培培
腾讯云微服务团队高级工程师
TSF Mesh 研发及负责人
热衷于云原生和开源技术,在容器、Service Mesh、消息队列、区块链等领域拥有丰富经验,目前致力于Service Mesh 技术的落地和推广
为了兼容 Service mesh 的非容器化场景,TSF Mesh 基于 Istio 构建了 Service mesh 微服务平台,对原生 Istio 实现进行了适当的改造,支持应用同时运行于容器环境和虚拟机环境(同时也打通了 Spring Cloud 框架,实现 Mesh 服务和 Spring Cloud 服务互联互通,服务统一治理)。
TSF Mesh 对容器和虚拟机统一化的改造主要体现在以下几个方面:
针对这几点,下面会先剖析对比 Istio service mesh 基于 K8s 的实现方案,再讲述 TSF Mesh 是如何实现的,做了哪些改造。
首先,回顾下 Istio service mesh 的应用部署和 Sidecar 注入方式:
应用部署:Istio service mesh 依赖 K8s 进行应用的生命周期管理,包括应用的部署和管理(扩缩容、自动恢复、发布)
Sidecar 注入:分为手动注入和自动注入, 如下图所示:
无论是手工注入还是自动注入,Sidecar 注入的本质是将运行 Sidecar 所需要的镜像地址、启动参数、所连接的 Istio 集群(Pilot、Mixes、Citadel)及配置信息填充到注入模版,并添加到应用的 CRD yaml 中,最终通过 K8s 持久化资源并拉起应用和 Sidecar 的 POD。
那 TSF Mesh 如何做应用部署和 Sidecar 注入的呢?
由于 TSF Mesh 需要同时支持容器和虚拟机环境,则首先需要解决虚拟机部署的问题,要实现等同 K8s 的部署能力,需要解决以下几个问题:
为了解决容器和虚拟机统一部署问题,不能再用 K8s 的存储方式,而是需要更高层的管理模式,我们引入了 tsf-resource 资源管控模块来负责容器和虚拟机相关资源的统一管理,像 Istio 集群相关的信息在控制平台部署时会持久化在 TSF 的 DB 中。
对于容器平台,当用户从 TSF 控制台部署一个容器应用时,tsf-resource 从 DB 中获取像容器的镜像地址、Istio 集群信息、配置、启动参数等,进行 K8s CRD 的组装,组装完将 CRD 创建请求发送给容器平台完成应用 POD 的拉起,其实这里在组装 CRD 时已经实现了 Sidecar 的自动注入,注入时的动态参数由控制台传递,静态参数如 Sidecar 镜像地址、启动参数等从 DB 中获取。
对于虚拟机平台,TSF 引入了以下几个模块来解决程序包管理和应用部署的问题:
对于虚拟机应用的变更,如例如应用部署、启动、停止、下线,TSF 通过任务的方式来跟踪每个变更,在任务下发的具体流程中,所有任务都是异步执行的,tsf-resource 将任务转发给 tsf-master 后就返回给 TSF 控制台,并由 tsf-master 完成任务的下发和状态跟踪;用户在 TSF 控制台执行操作后,可以根据返回的任务 ID 查询执行结果。
Service mesh 需要透明的进行服务治理,也就需要透明的接管服务进出流量,将流量劫持到 Sidecar,由 Sidecar 进行流量管理,传统的方式是 iptables 流量劫持(也可采用 BPF、IPVS 等方式),同样下面先回顾下 Istio 的 Service mesh 方案具体是如何劫持流量的,然后再看下 TSF mesh 为了统一容器和虚拟机做了哪些改造。
查看经过 Sidecar 注入后的应用 YAML 文件,发现 istio-sidecar-injector 服务在注入 Sidecar 容器本身时,还注入了 istio-init 容器,istio-init 容器属于 init 容器(init 容器在应用程序容器启动之前运行,用来初始化一些应用镜像中不存在的实用工具或安装脚本),下面是官方例子中注入的 init 容器部分:
initContainers:
- args:
- -p
- "15001"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- 9080,
- -d
- ""
image: istio/istio-release-proxy_init:1.0.1
imagePullPolicy: IfNotPresent
name: istio-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
...
可以看出 init 容器 istio-init,被赋予了 NET_ADMIN 的 POD 网络空间权限,具体执行了哪些初始化还看不出来,那再来看下 istio/istio-release-proxy_init:1.0.1 镜像的 Dockerfile。
FROM ubuntu:xenial
RUN apt-get update && apt-get install -y \
iproute2 \
iptables \
&& rm -rf /var/lib/apt/lists/*
ADD istio-iptables.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/istio-iptables.sh"]
istio-init 容器的 ENTRYPOINT 是 /usr/local/bin/istio-iptables.sh 脚本,顾名思义用于 Istio iptables 流量劫持的脚本,组合上面 istio-init 容器的启动参数,完整命令为:
$ /usr/local/bin/istio-iptables.sh -p 15001 -u 1337 -m REDIRECT -i '*' -x "" -b 9080 -d ""
该命令的主要作用是,将应用容器中访问9080端口的流量(inbound 流量)和所有出站流量(outbound 流量)重定向到 Sidecar(即 envoy)的15001端口。
总结下来,Istio 是通过 init 容器完成了流量劫持到 Sidecar 的初始化工作。
TSF Mesh 如何实现流量劫持的呢?
TSF Mesh 同样采用 iptables 方式,不过要兼顾虚拟机平台,需要解决两个主要问题:
问题1的解决比较简单,我们对 pilot-agent 做些一些扩展,在 pilot-agent 中执行 iptables 脚本,pilot-agent 一个主要工作是生成 envoy 的 bootstrap 配置并启动 envoy、管理 envoy 的生命周期,类似容器环境下做 envoy 启动前的 init 准备,在启动 envoy 前执行 iptables 脚本,也比较合理。
问题2的解决就比较麻烦了,但又非常重要,不像 K8s 的 POD,POD 间网路是隔离的,一个 POD 一般只会运行一个应用,劫持整个 POD 网路空间里的流量完全没有问题,而虚拟机中可能还有其它进程的存在,这些进程可能也有 Outbound 的流量,因此我们不能劫持虚拟机所有的流量,一种比较合理的劫持方案应该是:
下面来具体讲下 TSF Mesh 如何针对服务来劫持 Outbound 流量的。
其实我们的方案和 K8s 的 kube-DNS+kube-proxy 的服务发现机制类似,TSF Mesh 在数据平面引入了一个 mesh-dns 模块,通过连接 pilot-discovery 同步获取注册中心的服务变更来更新本地的 DNS cache,对于来自注册中心的服务会被解析到一个特定的 IP,然后在 iptables 策略中把目的地址到这个特定 IP 的流量重定向 envoy,当然,还需要劫持 DNS 53 端口的流量,先把 DNS 请求引到 mesh-dns,可以看下 iptables nat 表中完整的规则内容:
Inbound 流量劫持跟原生 Istio 实现类似就不赘述了,下图显示的是 Outbound 流量 iptables 劫持的详细过程,其中红色部分为新增的 DNS 劫持规则。
注册服务的域名劫持,除了引入了 mesh-dns 自研模块,还涉及到 pilot-discovery 和 pilot-agent 的改造:
pilot-discovery 改造点
mesh-dns实现
pilot-agent 改造点
基于流量比例的路由 Istio 中负责流量管理的组件为 Pilot,其中服务发现也是通过 Pilot 完成,Pilot 的高层架构图如下所示:
Istio服务注册与发现的原理
TSF Mesh如何实现服务注册与发现的呢?
同样,TSF Mesh 要兼顾虚拟机平台,需要解决三个主要问题:
问题1: TSF Mesh 容器和虚拟机统一采用 Consul 作为注册中心,因为虚拟机部署不会依赖容器平台,因此服务注册发现也不能依赖容器平台;Pilot 原生实现是支持 Consul 的,但早期的实现比较鸡肋(1.4.0以前还是通过 Rest API 轮询的方式去获取 Consul 注册服务信息的变更),TSF Mesh 针对早期的版本也做了优化,采用 Consul watch 来代替轮询。
问题2: TSF Mesh 统一了容器和虚拟机的服务注册方式,服务注册都在 envoy 中完成 ,
1. 类似通过 K8s 部署服务一样,TSF Mesh 在部署时需要用户在应用程序所在目录中创建一个 spec.yaml 服务描述文件,spec.yaml 格式如下:
apiVersion: v1
kind: Application
spec:
services:
- name: user # 服务名
ports:
- targetPort: 8091 # 服务监听端口
protocol: http # 服务协议类型
healthCheck:
path: /health # 健康检查 URL
2. Pilot-agent 将 spec.yaml 文件中内容读出并填充到 envoy-rev0.yaml(envoy启动时的静态配置文件)文件的 node 信息中;
3. Pilot-agent 启动 envoy,envoy 本身是支持 HDS 的,TSF Mesh 改造了 envoy 代码默认把本地部署的服务 Endpoint 作为 HealthCheck 请求的 cluster,在启动时把请求发送给 Pilot-discovery;
4. Pilot-discovery 原生是不支持 HDS 控制的,TSF Mesh 扩展 Pilot-discovery 以支持 HDS 服务端来接收 envoy 的 HealthCheck 请求,由于 HDS 定义的请求数据结构里包含 node 信息,也就包含了上面的服务描述信息,Pilot-discovery 组装服务描述信息将服务注册到 consul;
5. Pilot-discovery 是注册中心服务信息的消费者,因此原生是不支持服务注册的,TSF Mesh 再次扩展了 Pilot-discovery,在 Consul Apater 中增加了 RegisterService 接口;
问题3:TSF Mesh 中服务的实例健康状态也是由 envoy 来维护的:
TSF Mesh 在深入理解了 Istio service mesh 方案的基础上对其进行了针对性的改造和优化,使得应用能同时运行于容器环境和非容器环境,通过抽象出更高层的管控平台,TSF Mesh 可以不依赖于具体的底层平台而对应用进行统一管理和控制;而 TSF Mesh 作为 TSF 的一种微服务框架实现,不仅仅解决了平台统一化问题,还提供了应用全生命周期管理、数据化运营、立体化监控和服务治理的整套解决方案,具体介绍和使用可参考 TSF Mesh 官网。
- ServiceMesher 社区:
https://www.servicemesher.com/
- TSF Mesh 微服务平台:
https://cloud.tencent.com/product/tsf-mesh
- Istio 服务注册插件机制代码解析:
https://www.servicemesher.com/blog/istio-pilot-service-registry-code-analysis/