前面的分享中,我們講到,出於效能和穩定的考慮,我們沒有采用以 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
感興趣的同學有所幫助。