1.深入Istio:Sidecar自動注入如何實現的?


80614165_p0_master1200

轉載請聲明出處哦~,本篇文章發布於luozhiyun的博客:https://www.luozhiyun.com

本文使用的Istio源碼是 release 1.5。

這篇文章打算講一下sidecar,我在剛學習Istio的時候會有一些疑惑,sidecar是如何做到無感知的注入的,很多學習資料都沒有詳細去講這部分的內容,下面打算解析一下。

Sidecar 介紹

在Sidecar部署方式中會為每個應用的容器部署一個伴生容器。對於Istio,Sidecar接管進出應用程序容器的所有網絡流量。

使用 Sidecar 模式部署服務網格時,無需在節點上運行代理,但是集群中將運行多個相同的 Sidecar 副本。在 Kubernetes 的 Pod 中,在原有的應用容器旁邊運行一個 Sidecar 容器,可以理解為兩個容器共享存儲、網絡等資源,可以廣義的將這個注入了 Sidecar 容器的 Pod 理解為一台主機,兩個容器共享主機資源。

Sidecar 注入過程

注入 Sidecar的時候會在生成pod的時候附加上兩個容器:istio-init、istio-proxy。istio-init這個容器從名字上看也可以知道它屬於k8s中的Init Containers,主要用於設置iptables規則,讓出入流量都轉由 Sidecar 進行處理。istio-proxy是基於Envoy實現的一個網絡代理容器,是真正的Sidecar,應用的流量會被重定向進入或流出Sidecar。

我們在使用Sidecar自動注入的時候只需要給對應的應用部署的命名空間打個istio-injection=enabled標簽,這個命名空間中新建的任何 Pod 都會被 Istio 注入 Sidecar。

應用部署后我們可以通過kubectl describe查看pod內的容器:

 [root@localhost ~]# kubectl describe pod details-v1-6c9f8bcbcb-shltm
 
 Name:         details-v1-6c9f8bcbcb-shltm
Namespace:    default
...
Labels:       app=details
              pod-template-hash=6c9f8bcbcb
              security.istio.io/tlsMode=istio
              service.istio.io/canonical-name=details
              service.istio.io/canonical-revision=v1
              version=v1
Annotations:  sidecar.istio.io/status:
                {"version":"3bc68d1f27d8b6b9bf1cb3e9904f5d5f8c2ecab1c93d933fbb3d0db76fae2633","initContainers":["istio-init"],"containers":["istio-proxy"]...
Status:       Running
IP:           172.20.0.14
IPs:
  IP:           172.20.0.14
Controlled By:  ReplicaSet/details-v1-6c9f8bcbcb
Init Containers:
  istio-init:
    Container ID:  docker://6d14ccc83bd119236bf8fda13f6799609c87891be9b2c5af7cbf7d8c913ce17e
    Image:         docker.io/istio/proxyv2:1.5.10
    Image ID:      docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
    ...
    Ready:          True
    Restart Count:  0
    ...
Containers:
  details:
    Container ID:   docker://ed216429216ea1b8a1ba20960590edb7322557467c38cceff3c3e847bcff0a14
    Image:          docker.io/istio/examples-bookinfo-details-v1:1.15.1
    Image ID:       docker-pullable://istio/examples-bookinfo-details-v1@sha256:344b1c18703ab1e51aa6d698f459c95ea734f8317d779189f4638de7a00e61ae
    ...
  istio-proxy:
    Container ID:  docker://a3862cc8f53198c8f86a911089e73e00f4cc4aa02eea05aaeb0bd267a8e98482
    Image:         docker.io/istio/proxyv2:1.5.10
    Image ID:      docker-pullable://istio/proxyv2@sha256:abbe8ad6d50474814f1aa9316dafc2401fbba89175638446f01afc36b5a37919
    ...
    Ready:          True

details-v1-6c9f8bcbcb-shltm這個應用是我們在上篇文章中創建的一個details服務,里面有istio-init、istio-proxy、details這三個container。

Sidecar 注入原理

Sidecar 注入主要是依托k8s的准入控制器Admission Controller來實現的。

准入控制器會攔截 Kubernetes API Server 收到的請求,攔截發生在認證和鑒權完成之后,對象進行持久化之前。可以定義兩種類型的 Admission webhook:Validating 和 Mutating。Validating 類型的 Webhook 可以根據自定義的准入策略決定是否拒絕請求;Mutating 類型的 Webhook 可以根據自定義配置來對請求進行編輯。

我們可以看看配置詳情:

[root@localhost ~]# kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  annotations:
    ...
  creationTimestamp: "2020-10-18T08:22:01Z"
  generation: 2
  labels:
    app: sidecar-injector
    operator.istio.io/component: Pilot
    operator.istio.io/managed: Reconcile
    operator.istio.io/version: 1.5.10
    release: istio
  ... 
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    caBundle: ...
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Exact
  name: sidecar-injector.istio.io
  namespaceSelector:
    matchLabels:
      istio-injection: enabled
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  ...

這里有一個namespaceSelector,match的標簽是istio-injection: enabled的命名空間,請求規則rules是CREATE,表示匹配所有pod的創建請求。當apiserver收到一個符合規則的請求時,apiserver會給 Webhook 服務發送一個准入審核的請求,在上面的配置中webhook指定的是一個叫istiod的service。

[root@localhost ~]# kubectl get svc --namespace=istio-system | grep istiod
istiod                      ClusterIP      10.68.222.38    <none>        15012/TCP,443/TCP
                                                  32h

通常Sidecar注入由以下步驟完成:

  1. 解析Webhook REST請求,將AdmissionReview原始數據反序列化;
  2. 解析pod,將AdmissionReview中的AdmissionRequest反序列化;
  3. 利用Pod及網格配置渲染Sidecar配置模板;
  4. 利用Pod及渲染后的模板創建Json Patch;
  5. 構造AdmissionResponse;
  6. 構造AdmissionReview,將其發給apiserver;

源碼流程差不多是這個樣子:

image-20201107174326312

下面我們來看看源碼。

源碼位置:pkg/kube/inject/webhook.go

func NewWebhook(p WebhookParameters) (*Webhook, error) {
    wh := &Webhook{
        ...
    }
	...
	if p.Mux != nil {
		p.Mux.HandleFunc("/inject", wh.serveInject)
		mux = p.Mux
	} else {
		wh.server = &http.Server{
			Addr: fmt.Sprintf(":%v", p.Port), 
			TLSConfig: &tls.Config{GetCertificate: wh.getCert},
		}
		mux = http.NewServeMux()
		mux.HandleFunc("/inject", wh.serveInject)
		wh.server.Handler = mux
	}
	...
}

在初始化Webhook實例的時候會注冊/inject對應的處理器,也就是當apiserver回調/inject請求的時候會調用到serveInject方法中。

然后我們進入到serveInject方法中:

文件位置:pkg/kube/inject/webhook.go

func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
	totalInjections.Increment()
	var body []byte
	if r.Body != nil {
		//讀取請求體
		if data, err := ioutil.ReadAll(r.Body); err == nil {
			body = data
		}
	}
	... 
	var reviewResponse *v1beta1.AdmissionResponse
	ar := v1beta1.AdmissionReview{}
	//解碼請求體
	if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
		handleError(fmt.Sprintf("Could not decode body: %v", err))
		reviewResponse = toAdmissionResponse(err)
	} else {
		//解碼成功調用inject方法,並傳入AdmissionReview
		reviewResponse = wh.inject(&ar)
	}
	//構建AdmissionReview作為參數返回給調用方
	response := v1beta1.AdmissionReview{}
	if reviewResponse != nil {
		response.Response = reviewResponse
		if ar.Request != nil {
			response.Response.UID = ar.Request.UID
		}
	}

	resp, err := json.Marshal(response)
	if err != nil {
		log.Errorf("Could not encode response: %v", err)
		http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
	}
	if _, err := w.Write(resp); err != nil {
		log.Errorf("Could not write response: %v", err)
		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
	}
}

這個方法很簡單,主要就是讀取請求體並解碼,然后調用inject方法,構建AdmissionReview作為參數返回給調用方。

主要邏輯從這里可以看出都在inject方法里面,下面看看這個方法:

func (wh *Webhook) inject(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
	req := ar.Request
	var pod corev1.Pod
	//json反序列化請求數據
	if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
		handleError(fmt.Sprintf("Could not unmarshal raw object: %v %s", err,
			string(req.Object.Raw)))
		return toAdmissionResponse(err)
	}

	...
	//封裝模板數據
	spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig.DefaultConfig, wh.meshConfig) // nolint: lll
	if err != nil {
		handleError(fmt.Sprintf("Injection data: err=%v spec=%v\n", err, iStatus))
		return toAdmissionResponse(err)
	} 
	...
	//將需要注入的有istio-init/istio-proxy container封裝成patch操作
	//具體可以看這里:https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#response
	patchBytes, err := createPatch(&pod, injectionStatus(&pod), annotations, spec, deployMeta.Name)
	if err != nil {
		handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%v\n", err, spec))
		return toAdmissionResponse(err)
	}

	log.Debugf("AdmissionResponse: patch=%v\n", string(patchBytes))
	//將需要patch的配置封裝成AdmissionResponse返回
	reviewResponse := v1beta1.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *v1beta1.PatchType {
			pt := v1beta1.PatchTypeJSONPatch
			return &pt
		}(),
	}
	totalSuccessfulInjections.Increment()
	return &reviewResponse
}

inject方法邏輯主要分為以下幾個步驟:

  1. json反序列化請求數據到pod中;
  2. 調用InjectionData根據模板封裝數據,主要是構造istio-init、istio-proxy等容器配置;
  3. 調用createPatch方法將模板數據轉化成json形式,到時候在創建容器的時候會patch到創建容器的配置中,具體可以看這里:https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#response
  4. 最后將數據封裝成AdmissionResponse返回;

總結

本篇文章重點講解Sidecar容器注入實現原理,通過使用k8s的准入控制器來做到在每個新建的pod里面都無感知的創建sidecar做流量托管。

Reference

https://github.com/istio/istio.io/blob/release-1.1/content/blog/2019/data-plane-setup/index.md

https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

https://jimmysong.io/blog/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM