下一代 Service Mesh -- istio 架構分析

yandy在掘金發表於2018-05-15

前面的分享中,我們講到,出於效能和穩定的考慮,我們沒有采用以 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 的架構:

下一代 Service Mesh -- 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 實際上包含兩個 "容器":discoveryistio-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-initistio-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 感興趣的同學有所幫助。

相關文章