Istio Sidecar註入原理

来源:https://www.cnblogs.com/haoyunlaile/archive/2020/05/25/12960441.html
-Advertisement-
Play Games

簡單來說,Sidecar 註入會將額外容器的配置添加到 Pod 模板中。這裡特指將Envoy容器註應用所在Pod中。 Istio 服務網格目前所需的容器有: istio-init 用於設置 iptables 規則,以便將入站/出站流量通過 Sidecar 代理。 ...


概念

簡單來說,Sidecar 註入會將額外容器的配置添加到 Pod 模板中。這裡特指將Envoy容器註應用所在Pod中。

Istio 服務網格目前所需的容器有:

istio-init 用於設置 iptables 規則,以便將入站/出站流量通過 Sidecar 代理。

初始化容器與應用程式容器在以下方面有所不同:

  • 它在啟動應用容器之前運行,並一直運行直至完成。
  • 如果有多個初始化容器,則每個容器都應在啟動下一個容器之前成功完成。

因此,您可以看到,對於不需要成為實際應用容器一部分的設置或初始化作業來說,這種容器是多麼的完美。在這種情況下,istio-init 就是這樣做並設置了 iptables 規則。

istio-proxy 這個容器是真正的 Sidecar 代理(基於 Envoy)。

下麵的內容描述了向 pod 中註入 Istio Sidecar 的兩種方法:

  1. 使用 istioctl手動註入
  2. 啟用 pod 所屬命名空間的 Istio Sidecar 註入器自動註入。

手動註入直接修改配置,如 deployment,並將代理配置註入其中。

當 pod 所屬namespace啟用自動註入後,自動註入器會使用準入控制器在創建 Pod 時自動註入代理配置。

通過應用 istio-sidecar-injector ConfigMap 中定義的模版進行註入。

自動註入

當你在一個namespace中設置了 istio-injection=enabled 標簽,且 injection webhook 被啟用後,任何新的 pod 都有將在創建時自動添加 Sidecar. 請註意,區別於手動註入,自動註入發生在 pod 層面。你將看不到 deployment 本身有任何更改 。

kubectl label namespace default istio-inhection=enabled
kubectl get namespace -L istio-injection
NAME           STATUS    AGE       ISTIO-INJECTION
default        Active    1h        enabled
istio-system   Active    1h
kube-public    Active    1h
kube-system    Active    1h

註入發生在 pod 創建時。殺死正在運行的 pod 並驗證新創建的 pod 是否註入 sidecar。原來的 pod 具有 READY 為 1/1 的容器,註入 sidecar 後的 pod 則具有 READY 為 2/2 的容器 。

自動註入原理

自動註入是利用了k8s Admission webhook 實現的。 Admission webhook 是一種用於接收準入請求並對其進行處理的 HTTP 回調機制, 它可以更改發送到 API 伺服器的對象以執行自定義的設置預設值操作。 具體細節可以查閱 Admission webhook 文檔。

istio 對應的istio-sidecar-injector webhook配置,預設會回調istio-sidecar-injector service的/inject 地址。

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-sidecar-injector
webhooks:
  - name: sidecar-injector.istio.io
    clientConfig:
      service:
        name: istio-sidecar-injector
        namespace: istio-system
        path: "/inject"
      caBundle: ${CA_BUNDLE}
    rules:
      - operations: [ "CREATE" ]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
    namespaceSelector:
      matchLabels:
        istio-injection: enabled

回調API入口代碼在 pkg/kube/inject/webhook.go

// 創建一個用於自動註入sidecar的新實例
func NewWebhook(p WebhookParameters) (*Webhook, error) {
	
    // ...省略一萬字...
		
	wh := &Webhook{
		Config:                 sidecarConfig,
		sidecarTemplateVersion: sidecarTemplateVersionHash(sidecarConfig.Template),
		meshConfig:             p.Env.Mesh(),
		configFile:             p.ConfigFile,
		valuesFile:             p.ValuesFile,
		valuesConfig:           valuesConfig,
		watcher:                watcher,
		healthCheckInterval:    p.HealthCheckInterval,
		healthCheckFile:        p.HealthCheckFile,
		env:                    p.Env,
		revision:               p.Revision,
	}
    
    //api server 回調函數,監聽/inject回調
	p.Mux.HandleFunc("/inject", wh.serveInject)
	p.Mux.HandleFunc("/inject/", wh.serveInject)
    
    // ...省略一萬字...

	return wh, nil
}

serveInject邏輯

func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
	 
	// ...省略一萬字...
    
	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邏輯
		reviewResponse = wh.inject(&ar, path)
	}

    // 響應inject sidecar後的內容給k8s api server
	response := v1beta1.AdmissionReview{}
	if reviewResponse != nil {
		response.Response = reviewResponse
		if ar.Request != nil {
			response.Response.UID = ar.Request.UID
		}
	}

	// ...省略一萬字...
}

// 註入邏輯實現
func (wh *Webhook) inject(ar *v1beta1.AdmissionReview, path string) *v1beta1.AdmissionResponse {
    
    // ...省略一萬字...
    
    // injectRequired判斷是否有設置自動註入
	if !injectRequired(ignoredNamespaces, wh.Config, &pod.Spec, &pod.ObjectMeta) {
		log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
		totalSkippedInjections.Increment()
		return &v1beta1.AdmissionResponse{
			Allowed: true,
		}
	}

	// ...省略一萬字...
    
	// 返回需要註入Pod的對象
	spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig, path) // nolint: lll
	if err != nil {
		handleError(fmt.Sprintf("Injection data: err=%v spec=%vn", err, iStatus))
		return toAdmissionResponse(err)
	}

    // 執行容器註入邏輯
	patchBytes, err := createPatch(&pod, injectionStatus(&pod), wh.revision, annotations, spec, deployMeta.Name, wh.meshConfig)
	if err != nil {
		handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%vn", err, spec))
		return toAdmissionResponse(err)
	}

	reviewResponse := v1beta1.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *v1beta1.PatchType {
			pt := v1beta1.PatchTypeJSONPatch
			return &pt
		}(),
	}

	return &reviewResponse
}

injectRequired函數

func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta) bool { 
    // HostNetwork模式直接跳過註入
	if podSpec.HostNetwork {
		return false
	}

    // k8s系統命名空間(kube-system/kube-public)跳過註入
	for _, namespace := range ignored {
		if metadata.Namespace == namespace {
			return false
		}
	}

	annos := metadata.GetAnnotations()
	if annos == nil {
		annos = map[string]string{}
	}

    
	var useDefault bool
	var inject bool
    // 優先判斷是否申明瞭`sidecar.istio.io/inject` 註解,會覆蓋命名配置
	switch strings.ToLower(annos[annotation.SidecarInject.Name]) {
	case "y", "yes", "true", "on":
		inject = true
	case "":
        // 使用命名空間配置
		useDefault = true
	}

	// 指定Pod不需要註入Sidecar的標簽選擇器
	if useDefault {
		for _, neverSelector := range config.NeverInjectSelector {
			selector, err := metav1.LabelSelectorAsSelector(&neverSelector)
			if err != nil {
			} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels))
                // 設置不需要註入
				inject = false
				useDefault = false
				break
			}
		}
	}

	// 總是將 sidecar 註入匹配標簽選擇器的 pod 中,而忽略全局策略
	if useDefault {
		for _, alwaysSelector := range config.AlwaysInjectSelector {
			selector, err := metav1.LabelSelectorAsSelector(&alwaysSelector)
			if err != nil {
				log.Warnf("Invalid selector for AlwaysInjectSelector: %v (%v)", alwaysSelector, err)
			} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)){ 				  // 設置需要註入
				inject = true
				useDefault = false
				break
			}
		}
	}

	// 如果都沒有配置則使用預設註入策略
	var required bool
	switch config.Policy {
	default: // InjectionPolicyOff
		log.Errorf("Illegal value for autoInject:%s, must be one of [%s,%s]. Auto injection disabled!",
			config.Policy, InjectionPolicyDisabled, InjectionPolicyEnabled)
		required = false
	case InjectionPolicyDisabled:
		if useDefault {
			required = false
		} else {
			required = inject
		}
	case InjectionPolicyEnabled:
		if useDefault {
			required = true
		} else {
			required = inject
		}
	}

	return required
}

從上面我們可以看出,是否註入Sidecar的優先順序為

Pod Annotations → NeverInjectSelector → AlwaysInjectSelector → Default Policy

createPath函數

func createPatch(pod *corev1.Pod, prevStatus *SidecarInjectionStatus, revision string, annotations map[string]string,
	sic *SidecarInjectionSpec, workloadName string, mesh *meshconfig.MeshConfig) ([]byte, error) {

	var patch []rfc6902PatchOperation

	// ...省略一萬字...

    // 註入初始化啟動容器
	patch = append(patch, addContainer(pod.Spec.InitContainers, sic.InitContainers, "/spec/initContainers")...)
    // 註入Sidecar容器
	patch = append(patch, addContainer(pod.Spec.Containers, sic.Containers, "/spec/containers")...)
    // 註入掛載捲
	patch = append(patch, addVolume(pod.Spec.Volumes, sic.Volumes, "/spec/volumes")...)
	patch = append(patch, addImagePullSecrets(pod.Spec.ImagePullSecrets, sic.ImagePullSecrets, "/spec/imagePullSecrets")...)
    // 註入新註解
	patch = append(patch, updateAnnotation(pod.Annotations, annotations)...)

    // ...省略一萬字...
	return json.Marshal(patch)
}

總結:可以看到,整個註入過程實際就是原本的Pod配置反解析成Pod對象,把需要註入的Yaml內容(如:Sidecar)反序列成對象然後append到對應Pod (如:Container)上,然後再把修改後的Pod重新解析成yaml 內容返回給k8s的api server,然後k8s 拿著修改後內容再將這兩個容器調度到同一臺機器進行部署,至此就完成了對應Sidecar的註入。

卸載 sidecar 自動註入器

kubectl delete mutatingwebhookconfiguration istio-sidecar-injector
kubectl -n istio-system delete service istio-sidecar-injector
kubectl -n istio-system delete deployment istio-sidecar-injector
kubectl -n istio-system delete serviceaccount istio-sidecar-injector-service-account
kubectl delete clusterrole istio-sidecar-injector-istio-system
kubectl delete clusterrolebinding istio-sidecar-injector-admin-role-binding-istio-system

上面的命令不會從 pod 中移除註入的 sidecar。需要進行滾動更新或者直接刪除對應的pod,並強制 deployment 重新創建新pod。

手動註入 sidecar

手動註入 deployment ,需要使用 使用 istioctl kube-inject

istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -

預設情況下將使用集群內的配置,或者使用該配置的本地副本來完成註入。

kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml

指定輸入文件,運行 kube-inject 並部署

istioctl kube-inject 
    --injectConfigFile inject-config.yaml 
    --meshConfigFile mesh-config.yaml 
    --valuesFile inject-values.yaml 
    --filename samples/sleep/sleep.yaml 
    | kubectl apply -f -

驗證 sidecar 已經被註入到 READY 列下 2/2 的 sleep pod 中

kubectl get pod  -l app=sleep
NAME                     READY   STATUS    RESTARTS   AGE
sleep-64c6f57bc8-f5n4x   2/2     Running   0          24s

手動註入原理

手動註入的代碼入口在 istioctl/cmd/kubeinject.go

手工註入跟自動註入還是有些差異的。手動註入是改變了Deployment。我們可以看下它具體做了哪些動作:

Deployment註入前配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 7
  selector:
    matchLabels:
      app: hello
      tier: backend
      track: stable
  template:
    metadata:
      labels:
        app: hello
        tier: backend
        track: stable
    spec:
      containers:
        - name: hello
          image: "fake.docker.io/google-samples/hello-go-gke:1.0"
          ports:
            - name: http
              containerPort: 80

Deployment註入後配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  name: hello
spec:
  replicas: 7
  selector:
    matchLabels:
      app: hello
      tier: backend
      track: stable
  strategy: {}
  template:
    metadata:
      annotations:
        sidecar.istio.io/status: '{"version":"2343d4598565fd00d328a3388421ee637d25d3f7068e7d5cadef374ee1a06b37","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":null,"imagePullSecrets":null}'
      creationTimestamp: null
      labels:
        app: hello
        istio.io/rev: ""
        security.istio.io/tlsMode: istio
        tier: backend
        track: stable
    spec:
      containers:
      - image: fake.docker.io/google-samples/hello-go-gke:1.0
        name: hello
        ports:
        - containerPort: 80
          name: http
        resources: {}
      - image: docker.io/istio/proxy_debug:unittest
        name: istio-proxy
        resources: {}
      initContainers:
      - image: docker.io/istio/proxy_init:unittest-test
        name: istio-init
        resources: {}
      securityContext:
        fsGroup: 1337
status: {}
---

可以新增了一個容器鏡像

 - image: docker.io/istio/proxy_debug:unittest
        name: istio-proxy
        resources: {}

那麼註入的內容模板從哪裡獲取,這裡有兩個選項。

  1. —injectConfigFile 指定對應的註入文件
  2. —injectConfigMapName 註入配置的 ConfigMap 名稱

如果在操作時發現Sidecar沒有註入成功可以根據註入的方式查看上面的註入流程來查找問題。

參考文獻

https://preliminary.istio.io/zh/docs/setup/additional-setup/sidecar-injection/#automatic-sidecar-injection

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

https://istio.io/zh/docs/reference/commands/istioctl/#istioctl-kube-inject


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 5.列表 列表是實際應用非常多的一種數據類型,需要好好掌握。列表的主要特性如下所示: 1.支持增加、修改和刪除元素操作 2.列表的長度是可變的 3.列表支持排序 5.1 索引與切片 列表中索引是從0開始,且支持正向和反向兩種索引方式。如下所示: a=[1,2,"abc",[4,5,6],{"a":7 ...
  • 最近在讀秦小波的《設計模式之禪》。本文又是一篇長達2000行的又水又長的筆記,記錄書中所講23個設計模式中的22個,基本上是將書中講的各個設計模式的定義、優點、缺點、適用場景、demo抄下來了。推薦去閱讀原書,這是一位學識豐富且有一個有趣的靈魂的作者所寫,原書中每個設計模式的講解都有一個十分有趣的例 ...
  • python序列之集合 1.概念 集合是一組無序不可重覆元素組成,用{}表示,在集合中元素是唯一的並且集合中只能包括數字,字元串和元組等不可變元素。 2.創建 創建集合可有set()或者{}創建。 3.集合中增加元素的方法為add()方法 4.集合刪除 pop()方法刪除集合中某一個元素。 remo ...
  • 最近看到一個 UP 主做的視頻,使用可視化動態圖,把目前播放量最多的 UP 主一一列出來,結果第一名是嗶哩嗶哩番劇,第一名的播放量是第二名近 10 倍。 B站的番劇數量,也是相對其他平臺比較多的,而且質量都還不錯。說實話,剛開始用嗶哩嗶哩的時候,就是為了看番劇。作為一個喜歡看番劇的 pk 哥,我決定 ...
  • # Definition for a binary tree node.# 用遞歸的思想來做題。# 首先比較自身節點是否相同,然後比較節點左兒子,最後比較右兒子class TreeNode: def __init__(self, x): self.val = x self.left = None s ...
  • # 這道題是遞歸的思想,想要爬上第n臺階,# 1,可以通過n-1層上去,2,通過第n-2層上去# 因此f(n) = f(n-1) + f(n-2)class Solution: def climbStairs(self, n: int) -> int: # 前兩層比較特殊,需要自行定義。 if n ...
  • 我們用spring boot 快速開發應用程式時,經常會引入很多這樣以spring-boot-starter開頭的的庫, 我就演示下自定義一個這樣的庫,功能是日誌輸出到什麼地方(控制台、文件還是資料庫) 前提是maven已經配置好了,參考 https://my.oschina.net/u/15486 ...
  • 接上文《C/C++編程筆記:C語言貪吃蛇源代碼控制台(一),會動的那種哦!》如果你在學習C語言開發貪吃蛇的話,零基礎建議從上一篇開始哦!接下來正式開始吧! 三、蛇的運動 上次我已經教大家畫出蛇了,現在我就教大家讓蛇動起來。為了讓大家更好理解,蛇的移動就用最簡單的辦法,這裡就不用鏈表,順序隊列什麼的了 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...