前面的分享中,我们讲到,出于性能和稳定的考虑,我们没有采用以 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
感兴趣的同学有所帮助。