前面的分享中,我们讲到,出于性能和稳定的考虑,我们没有采用以 istio 为代表的第二代 service mesh技术,而是直接使用了 Envoy 搭配自己的 xDS 服务。
然而我们还是有必要去了解下 Istio,毕竟这代表了 Service Mesh 的未来。不出意外,在不远的将来,扇贝也会迁移到第二代 Service Mesh 的框架上去。
本文就针对 Istio 的架构做个简单的分析,会涉及部分源码的分析。
1. Istio 的架构
我们在介绍Envoy的时候提到,Envoy的动态配置给我们提供了一种可能:我们可以按照Envoy的规范,通过实现提供特定API的服务,来控制Envoy的路由,流量规则, ratelimit,日志等等。
在 Istio 的理念里,Envoy 这种真正执行流量转发和控制的 Proxy,叫做 数据面板。而那些通过提供 API 控制 Envoy 行为的服务叫做 控制面板。
现在借用官网的一幅图来解释下 Istio 的架构:
数据面板以 sidecar 的形式和微服务部署在一起,每个微服务实例通过各自的 sidecar 来实现发送和接受请求;微服务和微服务之间不直接通信,而是通过 sidecar 的代理(转发)来实现通信。sidecar 直接形成调用网络,就像一个“网格”一样。
控制面板由 Pilot, Mixer, Istio-Auth 组成。我们以 kubernetes 上部署以 Envoy 为数据面板的 Istio 为例来介绍。
Pilot 是控制面板的核心,是必不可少的。将 kubernetes 的资源信息翻译成 Envoy 需要的相关 xDS API(CDS, SDS/EDS, RDS),实现服务发现,包括用户定义的 Istio 的相关配置,翻译成 Envoy 所能理解的路由规则(RDS)。
Mixer 实现数据的收集,以及一些额外的流量控制。首先,数据面板会向 Mixer 汇报请求数据,这些请求数据都是按照 Istio 的规范结构化的。Mixer可以对这些汇报上来的数据做各种处理,例如打印日志,加工成 Prometheus 需要的 metrics 方便抓取从而进行性能监控,做 rate limit 等等。Mixer 是插件式的,这些数据处理都是通过配置一个个插件来实现的。
2. Pilot
以 Kubernetes 环境为例:Pilot 的每个 Pod 实际上包含两个 "容器":discovery 和 istio-proxy
2.1 discovery
discovery 对应的 image 是 docker.io/istio/pilot:0.4.0,是 Pilot 真正的功能提供者,其监听的地址是:tcp://127.0.0.1:8080,主要工作是将 Kubernetes的资源通过 xDS 服务的形式翻译成 Envoy 所能理解的配置。
其中:
xDS 的核心代码: pilot/proxy/envoy/discovery.go
// Struct,核心数据结构
type DiscoveryService struct {
proxy.Environment
server *http.Server
sdsCache *discoveryCache
cdsCache *discoveryCache
rdsCache *discoveryCache
ldsCache *discoveryCache
}
// Register adds routes a web service container
func (ds *DiscoveryService) Register(container *restful.Container) {
ws := &restful.WebService{}
ws.Produces(restful.MIME_JSON)
// 例如: List all known services (informational, not invoked by Envoy)
ws.Route(ws.
GET("/v1/registration").
To(ds.ListAllEndpoints).
Doc("Services in SDS"))
// 其他 xDS ...
}
翻译 Kubernetes 资源的核心代码: pilot/platform/kube/controller.go
// 例如: list services
func (c *Controller) Services() ([]*model.Service, error) {
list := c.services.informer.GetStore().List()
out := make([]*model.Service, 0, len(list))
for _, item := range list {
if svc := convertService(*item.(*v1.Service), c.domainSuffix); svc != nil {
out = append(out, svc)
}
}
return out, nil
}
2.2 istio-proxy
istio-proxy 对应的 image 是 docker.io/istio/proxy:0.4.0,是 Pilot 服务的 sidecar,负责反向代理发往 discovery 的请求,监听 tcp://0.0.0.0:15003。其 Envoy 的核心配置如下:
{
"listeners": [
{
"address": "tcp://0.0.0.0:15003",
"name": "tcp_0.0.0.0_15003",
"filters": [
{
"type": "read",
"name": "tcp_proxy",
"config": {
"stat_prefix": "tcp",
"route_config": {
"routes": [
{
"cluster": "in.8080"
}
]
}
}
}
],
"bind_to_port": true
}
],
"admin": {
"access_log_path": "/dev/stdout",
"address": "tcp://127.0.0.1:15000"
},
"cluster_manager": {
"clusters": [
{
"name": "in.8080",
"connect_timeout_ms": 1000,
"type": "static",
"lb_type": "round_robin",
"hosts": [
{
"url": "tcp://127.0.0.1:8080"
}
]
}
]
}
}
3. Sidecar
以 Kubernetes 环境为例:作为数据面板的 Sidecar,其实会在微服务的每个 Pod中,插入两个容器: proxy-init 和 istio-proxy
3.1 istio-proxy
istio-proxy 对应的 image 是 docker.io/istio/proxy:0.4.0,是 Sidecar 的实际功能承担者,监听 tcp://0.0.0.0:15003,接受所有发往该 Pod 的tcp流量,分发所有从 Pod 中发出的 tcp 流量。实际上,这个 proxy 由两部分组成:一个管理进程 agent 和 真正的代理进程 Envoy。
agent 负责生成 Envoy 的配置,并适当监控 Envoy 的运行状况,必要的时候会进行 Envoy 进程的管理(例如配置变更后 reload Envoy)。另外也负责与 Mixer 组件交互(包括汇报数据等)。
agent 关于生成 Envoy 配置的核心代码位于:pilot/proxy/envoy/config.go
func buildConfig(config meshconfig.ProxyConfig, pilotSAN []string) *Config {
listeners := Listeners{}
clusterRDS := buildCluster(config.DiscoveryAddress, RDSName, config.ConnectTimeout)
clusterLDS := buildCluster(config.DiscoveryAddress, LDSName, config.ConnectTimeout)
clusters := Clusters{clusterRDS, clusterLDS}
out := &Config{
Listeners: listeners,
LDS: &LDSCluster{
Cluster: LDSName,
RefreshDelayMs: protoDurationToMS(config.DiscoveryRefreshDelay),
},
Admin: Admin{
AccessLogPath: DefaultAccessLog,
Address: fmt.Sprintf("tcp://%s:%d", LocalhostAddress, config.ProxyAdminPort),
},
ClusterManager: ClusterManager{
Clusters: clusters,
SDS: &DiscoveryCluster{
Cluster: buildCluster(config.DiscoveryAddress, SDSName, config.ConnectTimeout),
RefreshDelayMs: protoDurationToMS(config.DiscoveryRefreshDelay),
},
CDS: &DiscoveryCluster{
Cluster: buildCluster(config.DiscoveryAddress, CDSName, config.ConnectTimeout),
RefreshDelayMs: protoDurationToMS(config.DiscoveryRefreshDelay),
},
},
StatsdUDPIPAddress: config.StatsdUdpAddress,
}
// 其他相关逻辑 ...
}
值得注意的是,
istio-proxy可以以多种“角色”运行,会根据不同的角色,生成不同的配置。例如2.2 节中,作为Pilot的 proxy,其配置就和Sidecar是不一样的。
3.2 proxy-init
proxy-init 对应的 image 是 docker.io/istio/proxy_init:0.4.0。在 3.1 节 中,我们讲到:istio-proxy会接受所有发往该 Pod 的tcp流量,分发所有从 Pod 中发出的 tcp 流量,而我们实际写代码的时候却完全不用考虑这些,那Istio是怎么做到的呢?答案就是通过proxy-init! 具体的做法是:通过注入 iptables 的改写流入流出 Pod 的流量的规则,使得流入流出 Pod 的流量重定向到 istio-proxy的不同监听端口。
例如关于流入流量的重定向规则:
iptables -t nat -N ISTIO_REDIRECT -m comment --comment "istio/redirect-common-chain"
iptables -t nat -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-port ${ENVOY_PORT} -m comment --comment "istio/redirect-to-envoy-port"
iptables -t nat -A PREROUTING -j ISTIO_REDIRECT -m comment --comment "istio/install-istio-prerouting"
4. 小结
Istio 作为下一代的 Service Mesh 框架,虽然现在还不能用于生产,但是其思想和架构是很值得我们去学习的。希望本文对于对 Istio 感兴趣的同学有所帮助。