简单来讲,Sidecar 注入会将额外容器的配置添加到 Pod 模板中。这里特指将Envoy容器注应用所在Pod中。web
Istio 服务网格目前所需的容器有:docker
istio-init
用于设置 iptables 规则,以便将入站/出站流量经过 Sidecar 代理。shell
初始化容器与应用程序容器在如下方面有所不一样:json
所以,您能够看到,对于不须要成为实际应用容器一部分的设置或初始化做业来讲,这种容器是多么的完美。在这种状况下,istio-init
就是这样作并设置了 iptables
规则。api
istio-proxy
这个容器是真正的 Sidecar 代理(基于 Envoy)。服务器
下面的内容描述了向 pod 中注入 Istio Sidecar 的两种方法:app
istioctl
手动注入手动注入直接修改配置,如 deployment,并将代理配置注入其中。ide
当 pod 所属namespace
启用自动注入后,自动注入器会使用准入控制器在建立 Pod 时自动注入代理配置。函数
经过应用 istio-sidecar-injector
ConfigMap 中定义的模版进行注入。jsonp
当你在一个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的注入。
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。
手动注入 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: {}
那么注入的内容模板从哪里获取,这里有两个选项。
若是在操做时发现Sidecar没有注入成功能够根据注入的方式查看上面的注入流程来查找问题。
https://kubernetes.io/zh/docs/reference/access-authn-authz/admission-controllers/
https://istio.io/zh/docs/reference/commands/istioctl/#istioctl-kube-inject