为了兼容 Service mesh 的非容器化场景,TSF Mesh 基于 Istio 构建了 Service mesh 微服务平台,对原生 Istio 实现进行了适当的改造,支持应用同时运行于容器环境和虚拟机环境(同时也打通了 Spring Cloud 框架,实现 Mesh 服务和 Spring Cloud 服务互联互通,服务统一治理)。

 

TSF Mesh 对容器和虚拟机统一化的改造主要体现在以下几个方面:

 

  • 应用部署和Sidecar注入
  • 流量劫持
  • 服务注册与发现

     

针对这几点,下面会先剖析对比 Istio service mesh 基于 K8s 的实现方案,再讲述 TSF Mesh 是如何实现的,做了哪些改造。

 

 

应用部署和 Sidecar 注入

 

首先,回顾下 Istio service mesh 的应用部署和 Sidecar 注入方式:

 

应用部署:Istio service mesh 依赖 K8s 进行应用的生命周期管理,包括应用的部署和管理(扩缩容、自动恢复、发布)

 

Sidecar 注入:分为手动注入和自动注入, 如下图所示:

 

  • 手工注入通过手工执行 istioctl kube-inject 来重新构造应用的 CRD yaml

  • 自动注入通过 K8s 的 mutable webhook 回调 istio-sidecar-injector 服务来重新构造应用的 CRD yaml

 

 

无论是手工注入还是自动注入,Sidecar 注入的本质是将运行 Sidecar 所需要的镜像地址、启动参数、所连接的 Istio 集群(Pilot、Mixes、Citadel)及配置信息填充到注入模版,并添加到应用的 CRD yaml 中,最终通过 K8s 持久化资源并拉起应用和 Sidecar 的 POD。

 

那 TSF Mesh 如何做应用部署和 Sidecar 注入的呢?


由于 TSF Mesh 需要同时支持容器和虚拟机环境,则首先需要解决虚拟机部署的问题,要实现等同 K8s 的部署能力,需要解决以下几个问题:

 

  1. 资源和配置管理,如 Istio 集群信息、配置信息等
  2. 对应于容器的镜像,虚拟机就是程序包,那就涉及到包管理
  3. 虚拟机应用生命周期的管理
  4. 虚拟机 Sidecar 注入

     

为了解决容器和虚拟机统一部署问题,不能再用 K8s 的存储方式,而是需要更高层的管理模式,我们引入了 tsf-resource 资源管控模块来负责容器和虚拟机相关资源的统一管理,像 Istio 集群相关的信息在控制平台部署时会持久化在 TSF 的 DB 中。

 

对于容器平台,当用户从 TSF 控制台部署一个容器应用时,tsf-resource 从 DB 中获取像容器的镜像地址、Istio 集群信息、配置、启动参数等,进行 K8s CRD 的组装,组装完将 CRD 创建请求发送给容器平台完成应用 POD 的拉起,其实这里在组装 CRD 时已经实现了 Sidecar 的自动注入,注入时的动态参数由控制台传递,静态参数如 Sidecar 镜像地址、启动参数等从 DB 中获取。

 

对于虚拟机平台,TSF 引入了以下几个模块来解决程序包管理和应用部署的问题:

 

  1. tsf-repo,程序包仓库管理,存储应用程序包及相关依赖
  2. tsf-master,虚拟机节点管理 master,发送部署/下线/启动/停止等任务给 tsf-agent
  3. tsf-agent,虚拟机节点管理 agent,部署在应用机器上,负责初始化机器环境、执行应用部署/下线/启动/停止等任务

 

对于虚拟机应用的变更,如例如应用部署、启动、停止、下线,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:xenialRUN 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. 虚拟机下如何执行 iptables 应用劫持策略
  2. 虚拟机下如何劫持流量,不能劫持虚拟机整个网络空间的流量

 

问题1的解决比较简单,我们对 pilot-agent 做些一些扩展,在 pilot-agent 中执行 iptables 脚本,pilot-agent 一个主要工作是生成 envoy 的 bootstrap 配置并启动 envoy、管理 envoy 的生命周期,类似容器环境下做 envoy 启动前的 init 准备,在启动 envoy 前执行 iptables 脚本,也比较合理。

 

问题2的解决就比较麻烦了,但又非常重要,不像 K8s 的 POD,POD 间网路是隔离的,一个 POD 一般只会运行一个应用,劫持整个 POD 网路空间里的流量完全没有问题,而虚拟机中可能还有其它进程的存在,这些进程可能也有 Outbound 的流量,因此我们不能劫持虚拟机所有的流量,一种比较合理的劫持方案应该是:

 

  • 对于 Inbound 流量,只劫持到部署应用的端口,这个原生 Istio 已经做到,无需改造
  • 对于 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 改造点

 

  1. pilot-discovery 扩展一个 ServiceInfos 的 grpc 服务,提供注册服务变更同步接口
  2. pilot-discovery 早期的 consul controller 实现是,定时通过 Consul 的 Rest API 获取服务数据并和上一次的查询结果进行对比,如果数据发生了变化则通知 Pilot discovery 进行更新,这里我们进行了优化,采用 Consul 的 watch 机制来代替轮询(下面服务注册与发现中也有提到),并在 ServiceInfos 服务初始化时向 consul controller 注册了服务变更的 event 通知
  3. ServiceInfos 服务在 mesh-dns 请求第一次到来时同步全量的服务注册表,之后则根据服务的变更情况增量同步

 

mesh-dns实现

 

  1. DNS 服务基于 github.com/miekg/dns 实现(一个非常轻量级的 DNS 库)
  2. 和 pilot-discovery 保持注册服务列表的同步,mesh-dns 启动时进行全量同步,运行时进行增量同步
  3. 处理 DNS 请求时,先检查 Domain 是否在注册服务列表里,如果在则返回一个特定的 IP(可配置),否则请求本地配置的域名服务进行解析