Istio 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 本身有任何更改 。

1kubectl label namespace default istio-inhection=enabled 2kubectl get namespace -L istio-injection 3NAME STATUS AGE ISTIO-INJECTION 4default Active 1h enabled 5istio-system Active 1h 6kube-public Active 1h 7kube-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 地址。

1apiVersion: admissionregistration.k8s.io/v1beta1 2kind: MutatingWebhookConfiguration 3metadata: 4 name: istio-sidecar-injector 5webhooks: 6 - name: sidecar-injector.istio.io 7 clientConfig: 8 service: 9 name: istio-sidecar-injector 10 namespace: istio-system 11 path: "/inject" 12 caBundle: ${CA_BUNDLE} 13 rules: 14 - operations: [ "CREATE" ] 15 apiGroups: [""] 16 apiVersions: ["v1"] 17 resources: ["pods"] 18 namespaceSelector: 19 matchLabels: 20 istio-injection: enabled

回调API入口代码在 pkg/kube/inject/webhook.go

1// 创建一个用于自动注入sidecar的新实例 2func NewWebhook(p WebhookParameters) (*Webhook, error) { 3 4 // ...省略一万字... 5 6 wh := &Webhook{ 7 Config: sidecarConfig, 8 sidecarTemplateVersion: sidecarTemplateVersionHash(sidecarConfig.Template), 9 meshConfig: p.Env.Mesh(), 10 configFile: p.ConfigFile, 11 valuesFile: p.ValuesFile, 12 valuesConfig: valuesConfig, 13 watcher: watcher, 14 healthCheckInterval: p.HealthCheckInterval, 15 healthCheckFile: p.HealthCheckFile, 16 env: p.Env, 17 revision: p.Revision, 18 } 19 20 //api server 回调函数,监听/inject回调 21 p.Mux.HandleFunc("/inject", wh.serveInject) 22 p.Mux.HandleFunc("/inject/", wh.serveInject) 23 24 // ...省略一万字... 25 26 return wh, nil 27}

serveInject逻辑

1func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) { 2 3 // ...省略一万字... 4 5 var reviewResponse *v1beta1.AdmissionResponse 6 ar := v1beta1.AdmissionReview{} 7 if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { 8 handleError(fmt.Sprintf("Could not decode body: %v", err)) 9 reviewResponse = toAdmissionResponse(err) 10 } else { 11 //执行具体的inject逻辑 12 reviewResponse = wh.inject(&ar, path) 13 } 14 15 // 响应inject sidecar后的内容给k8s api server 16 response := v1beta1.AdmissionReview{} 17 if reviewResponse != nil { 18 response.Response = reviewResponse 19 if ar.Request != nil { 20 response.Response.UID = ar.Request.UID 21 } 22 } 23 24 // ...省略一万字... 25} 26 27// 注入逻辑实现 28func (wh *Webhook) inject(ar *v1beta1.AdmissionReview, path string) *v1beta1.AdmissionResponse { 29 30 // ...省略一万字... 31 32 // injectRequired判断是否有设置自动注入 33 if !injectRequired(ignoredNamespaces, wh.Config, &pod.Spec, &pod.ObjectMeta) { 34 log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName) 35 totalSkippedInjections.Increment() 36 return &v1beta1.AdmissionResponse{ 37 Allowed: true, 38 } 39 } 40 41 // ...省略一万字... 42 43 // 返回需要注入Pod的对象 44 spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig, path) // nolint: lll 45 if err != nil { 46 handleError(fmt.Sprintf("Injection data: err=%v spec=%vn", err, iStatus)) 47 return toAdmissionResponse(err) 48 } 49 50 // 执行容器注入逻辑 51 patchBytes, err := createPatch(&pod, injectionStatus(&pod), wh.revision, annotations, spec, deployMeta.Name, wh.meshConfig) 52 if err != nil { 53 handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%vn", err, spec)) 54 return toAdmissionResponse(err) 55 } 56 57 reviewResponse := v1beta1.AdmissionResponse{ 58 Allowed: true, 59 Patch: patchBytes, 60 PatchType: func() *v1beta1.PatchType { 61 pt := v1beta1.PatchTypeJSONPatch 62 return &pt 63 }(), 64 } 65 66 return &reviewResponse 67}

injectRequired函数

1func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta) bool { 2 // HostNetwork模式直接跳过注入 3 if podSpec.HostNetwork { 4 return false 5 } 6 7 // k8s系统命名空间(kube-system/kube-public)跳过注入 8 for _, namespace := range ignored { 9 if metadata.Namespace == namespace { 10 return false 11 } 12 } 13 14 annos := metadata.GetAnnotations() 15 if annos == nil { 16 annos = map[string]string{} 17 } 18 19 20 var useDefault bool 21 var inject bool 22 // 优先判断是否申明了`sidecar.istio.io/inject` 注解,会覆盖命名配置 23 switch strings.ToLower(annos[annotation.SidecarInject.Name]) { 24 case "y", "yes", "true", "on": 25 inject = true 26 case "": 27 // 使用命名空间配置 28 useDefault = true 29 } 30 31 // 指定Pod不需要注入Sidecar的标签选择器 32 if useDefault { 33 for _, neverSelector := range config.NeverInjectSelector { 34 selector, err := metav1.LabelSelectorAsSelector(&neverSelector) 35 if err != nil { 36 } else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)) 37 // 设置不需要注入 38 inject = false 39 useDefault = false 40 break 41 } 42 } 43 } 44 45 // 总是将 sidecar 注入匹配标签选择器的 pod 中,而忽略全局策略 46 if useDefault { 47 for _, alwaysSelector := range config.AlwaysInjectSelector { 48 selector, err := metav1.LabelSelectorAsSelector(&alwaysSelector) 49 if err != nil { 50 log.Warnf("Invalid selector for AlwaysInjectSelector: %v (%v)", alwaysSelector, err) 51 } else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)){ // 设置需要注入 52 inject = true 53 useDefault = false 54 break 55 } 56 } 57 } 58 59 // 如果都没有配置则使用默认注入策略 60 var required bool 61 switch config.Policy { 62 default: // InjectionPolicyOff 63 log.Errorf("Illegal value for autoInject:%s, must be one of [%s,%s]. Auto injection disabled!", 64 config.Policy, InjectionPolicyDisabled, InjectionPolicyEnabled) 65 required = false 66 case InjectionPolicyDisabled: 67 if useDefault { 68 required = false 69 } else { 70 required = inject 71 } 72 case InjectionPolicyEnabled: 73 if useDefault { 74 required = true 75 } else { 76 required = inject 77 } 78 } 79 80 return required 81}

从上面我们可以看出,是否注入Sidecar的优先级为

Pod Annotations → NeverInjectSelector → AlwaysInjectSelector → Default Policy

createPath函数

1func createPatch(pod *corev1.Pod, prevStatus *SidecarInjectionStatus, revision string, annotations map[string]string, 2 sic *SidecarInjectionSpec, workloadName string, mesh *meshconfig.MeshConfig) ([]byte, error) { 3 4 var patch []rfc6902PatchOperation 5 6 // ...省略一万字... 7 8 // 注入初始化启动容器 9 patch = append(patch, addContainer(pod.Spec.InitContainers, sic.InitContainers, "/spec/initContainers")...) 10 // 注入Sidecar容器 11 patch = append(patch, addContainer(pod.Spec.Containers, sic.Containers, "/spec/containers")...) 12 // 注入挂载卷 13 patch = append(patch, addVolume(pod.Spec.Volumes, sic.Volumes, "/spec/volumes")...) 14 patch = append(patch, addImagePullSecrets(pod.Spec.ImagePullSecrets, sic.ImagePullSecrets, "/spec/imagePullSecrets")...) 15 // 注入新注解 16 patch = append(patch, updateAnnotation(pod.Annotations, annotations)...) 17 18 // ...省略一万字... 19 return json.Marshal(patch) 20}

总结:可以看到,整个注入过程实际就是原本的Pod配置反解析成Pod对象,把需要注入的Yaml内容(如:Sidecar)反序列成对象然后append到对应Pod (如:Container)上,然后再把修改后的Pod重新解析成yaml 内容返回给k8s的api server,然后k8s 拿着修改后内容再将这两个容器调度到同一台机器进行部署,至此就完成了对应Sidecar的注入。

卸载 sidecar 自动注入器

1kubectl delete mutatingwebhookconfiguration istio-sidecar-injector 2kubectl -n istio-system delete service istio-sidecar-injector 3kubectl -n istio-system delete deployment istio-sidecar-injector 4kubectl -n istio-system delete serviceaccount istio-sidecar-injector-service-account 5kubectl delete clusterrole istio-sidecar-injector-istio-system 6kubectl 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 -

默认情况下将使用集群内的配置,或者使用该配置的本地副本来完成注入。

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

指定输入文件,运行 kube-inject 并部署

1istioctl kube-inject 2 --injectConfigFile inject-config.yaml 3 --meshConfigFile mesh-config.yaml 4 --valuesFile inject-values.yaml 5 --filename samples/sleep/sleep.yaml 6 | kubectl apply -f -

验证 sidecar 已经被注入到 READY 列下 2/2 的 sleep pod 中

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

手动注入原理

手动注入的代码入口在 istioctl/cmd/kubeinject.go

手工注入跟自动注入还是有些差异的。手动注入是改变了Deployment。我们可以看下它具体做了哪些动作:

Deployment注入前配置:

1apiVersion: apps/v1 2kind: Deployment 3metadata: 4 name: hello 5spec: 6 replicas: 7 7 selector: 8 matchLabels: 9 app: hello 10 tier: backend 11 track: stable 12 template: 13 metadata: 14 labels: 15 app: hello 16 tier: backend 17 track: stable 18 spec: 19 containers: 20 - name: hello 21 image: "fake.docker.io/google-samples/hello-go-gke:1.0" 22 ports: 23 - name: http 24 containerPort: 80

Deployment注入后配置:

1apiVersion: apps/v1 2kind: Deployment 3metadata: 4 creationTimestamp: null 5 name: hello 6spec: 7 replicas: 7 8 selector: 9 matchLabels: 10 app: hello 11 tier: backend 12 track: stable 13 strategy: {} 14 template: 15 metadata: 16 annotations: 17 sidecar.istio.io/status: '{"version":"2343d4598565fd00d328a3388421ee637d25d3f7068e7d5cadef374ee1a06b37","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":null,"imagePullSecrets":null}' 18 creationTimestamp: null 19 labels: 20 app: hello 21 istio.io/rev: "" 22 security.istio.io/tlsMode: istio 23 tier: backend 24 track: stable 25 spec: 26 containers: 27 - image: fake.docker.io/google-samples/hello-go-gke:1.0 28 name: hello 29 ports: 30 - containerPort: 80 31 name: http 32 resources: {} 33 - image: docker.io/istio/proxy_debug:unittest 34 name: istio-proxy 35 resources: {} 36 initContainers: 37 - image: docker.io/istio/proxy_init:unittest-test 38 name: istio-init 39 resources: {} 40 securityContext: 41 fsGroup: 1337 42status: {} 43---

可以新增了一个容器镜像

1 - image: docker.io/istio/proxy_debug:unittest 2 name: istio-proxy 3 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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Istio Sidecar注入原理 - HelloWorld