Istio-sidecar注入原理

前言

Istio的一个亮点就是为每个应用自动添加代理程序,并且采用无代码侵入的方式,这样的好处是,代理与应用进行解耦。

实战

  1. 部署istio集群,为default命名空间标签添加 istio-injection:enabled,istio会为default下的所有新建POD进行代理注入。
[root@master01 ~]# kubectl get ns default -o yaml
apiVersion: v1
kind: Namespace
metadata:
  labels:
    istio-injection: enabled # 这里
    kubernetes.io/metadata.name: default
  name: default
spec:
  finalizers:
  - kubernetes

  1. 添加代理的方式有多种下面让我们一一列举出来
    1. 在命名空间中添加istio-injection: enabled,这种也是最长用的方法之一
    2. 在命名空间中添加istio.io/rev: 这里需要注意后面的版本号一定是已经安装的,否则MutatingWebhookConfiguration的时候会匹配失败
    3. pod 注释或者标签中添加 sidecar.istio.io/inject:true
    4. istio提供了标签选择器的方式进行强制注入,配置方式是修改在istio-system命名空间中的configmap
# 如果使用的默认版本则修改下面的配置
# 如果是其余版本那么它的名称一般是istio-sidecar-injector-
[root@master01 ~]# kubectl get cm -n istio-system  istio-sidecar-injector 
NAME                     DATA   AGE
istio-sidecar-injector   2      56d
#主要修改下面的alwaysInjectSelector 和 neverInjectSelector
[root@master01 ~]# kubectl get cm -n istio-system  istio-sidecar-injector -o yaml
apiVersion: v1
data:
  config: |-
    # defaultTemplates defines the default template to use for pods that do not explicitly specify a template
    defaultTemplates: [sidecar]
    policy: enabled
    alwaysInjectSelector:
      []
    neverInjectSelector:
      []
# 都是 标签选择器
# neverInjectSelector  匹配的应用强制不注入
# alwaysInjectSelector 匹配的应用强制注入
# 这里我们需要注意 policy 策略,它是默认注入配置
# 如果上面我们什么都没有设置,那么在匹配MutatingWebhookConfiguration后默认是注入
# 如果设置成disabled ,那么就是默认不注入

原理图

下面我将使用一张流程图进行原理展示
Istio-sidecar注入原理_第1张图片

源码分析

本文不讲解过多的模板具体实如何与pod 合并的,本文主要关注使用层面,也就是注入判断匹配逻辑

// 这里我们可以看到,注入服务器,是通过/inject   uri进行访问的
func NewWebhook(p WebhookParameters) (*Webhook, error) {
    ...
    p.Mux.HandleFunc("/inject", wh.serveInject)
    p.Mux.HandleFunc("/inject/", wh.serveInject)
    ....
    return wh, nil
}
// 命名空间的判断是在mutatingwebhookconfigurations中处理的,剩下的就是单个注入pod使用注解和标签的操作.
func (wh *Webhook) inject(ar *kube.AdmissionReview, path string) *kube.AdmissionResponse {
	req := ar.Request
	var pod corev1.Pod
	// 将请求的pod转换为结构体
	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)
	}
	// Managed fields is sometimes extremely large, leading to excessive CPU time on patch generation
	// It does not impact the injection output at all, so we can just remove it.
	pod.ManagedFields = nil

	// Deal with potential empty fields, e.g., when the pod is created by a deployment
	podName := potentialPodName(pod.ObjectMeta)
	if pod.ObjectMeta.Namespace == "" {
		pod.ObjectMeta.Namespace = req.Namespace
	}
	log.Infof("Sidecar injection request for %v/%v", req.Namespace, podName)
	log.Debugf("Object: %v", string(req.Object.Raw))
	log.Debugf("OldObject: %v", string(req.OldObject.Raw))

	wh.mu.RLock()
	// 这里判断是否需要注入
	if !injectRequired(IgnoredNamespaces.UnsortedList(), wh.Config, &pod.Spec, pod.ObjectMeta) {
		log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
		totalSkippedInjections.Increment()
		wh.mu.RUnlock()
		return &kube.AdmissionResponse{
			Allowed: true,
		}
	}

	proxyConfig := mesh.DefaultProxyConfig()
	if wh.env.PushContext != nil && wh.env.PushContext.ProxyConfigs != nil {
		if generatedProxyConfig := wh.env.PushContext.ProxyConfigs.EffectiveProxyConfig(
			&model.NodeMetadata{
				Namespace:   pod.Namespace,
				Labels:      pod.Labels,
				Annotations: pod.Annotations,
			}, wh.meshConfig); generatedProxyConfig != nil {
			proxyConfig = generatedProxyConfig
		}
	}
	deploy, typeMeta := kube.GetDeployMetaFromPod(&pod)
	params := InjectionParameters{
		pod:                 &pod,
		deployMeta:          deploy,
		typeMeta:            typeMeta,
		templates:           wh.Config.Templates,
		defaultTemplate:     wh.Config.DefaultTemplates,
		aliases:             wh.Config.Aliases,
		meshConfig:          wh.meshConfig,
		proxyConfig:         proxyConfig,
		valuesConfig:        wh.valuesConfig,
		revision:            wh.revision,
		injectedAnnotations: wh.Config.InjectedAnnotations,
		proxyEnvs:           parseInjectEnvs(path),
	}
	wh.mu.RUnlock()

	// 开始编译模板
	patchBytes, err := injectPod(params)
	if err != nil {
		handleError(fmt.Sprintf("Pod injection failed: %v", err))
		return toAdmissionResponse(err)
	}

	reviewResponse := kube.AdmissionResponse{
		Allowed: true,
		Patch:   patchBytes,
		PatchType: func() *string {
			pt := "JSONPatch"
			return &pt
		}(),
	}
	totalSuccessfulInjections.Increment()
	return &reviewResponse
}


// 这里我们只看注入判断
func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata metav1.ObjectMeta) bool { // nolint: lll
	// 只要是HostNetwork为真就无法进行代理注入
	if podSpec.HostNetwork {
		return false
	}

	// skip special kubernetes system namespaces
	// 忽略命名空间
    // 系统命名空间,比如kube-system
	for _, namespace := range ignored {
		if metadata.Namespace == namespace {
			return false
		}
	}

	// 获取pod 注解
	annos := metadata.GetAnnotations()

	var useDefault bool
	var inject bool
	// key是 这个值 sidecar.istio.io/inject 
	objectSelector := annos[annotation.SidecarInject.Name]
	if lbl, labelPresent := metadata.GetLabels()[annotation.SidecarInject.Name]; labelPresent {
		// The label is the new API; if both are present we prefer the label
		objectSelector = lbl
	}
	switch strings.ToLower(objectSelector) {
	// http://yaml.org/type/bool.html
	case "y", "yes", "true", "on":
		inject = true
	case "":
		useDefault = true
	}

	// 如果没有指定注解,则开始查询全局配置
    // 下面是根据标签选择器不能注入的逻辑
	if useDefault {
		for _, neverSelector := range config.NeverInjectSelector {
			selector, err := metav1.LabelSelectorAsSelector(&neverSelector)
			if err != nil {
				log.Warnf("Invalid selector for NeverInjectSelector: %v (%v)", neverSelector, err)
			} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)) {
				log.Debugf("Explicitly disabling injection for pod %s/%s due to pod labels matching NeverInjectSelector config map entry.",
					metadata.Namespace, potentialPodName(metadata))
				inject = false
				useDefault = false
				break
			}
		}
	}

	// 这里是根据标签选择器,配置强制注入的逻辑
	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)) {
				log.Debugf("Explicitly enabling injection for pod %s/%s due to pod labels matching AlwaysInjectSelector config map entry.",
					metadata.Namespace, potentialPodName(metadata))
				inject = true
				useDefault = false
				break
			}
		}
	}

	var required bool
	switch config.Policy {
	default: //  这里是默认的判断,也就是上面都没有配置,则使用下面的默认值
		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
		}
	}

	if log.DebugEnabled() {
		// Build a log message for the annotations.
		annotationStr := ""
		for name := range AnnotationValidation {
			value, ok := annos[name]
			if !ok {
				value = "(unset)"
			}
			annotationStr += fmt.Sprintf("%s:%s ", name, value)
		}
    	// 日志输入,连带着注解也输出,便于检查.
		log.Debugf("Sidecar injection policy for %v/%v: namespacePolicy:%v useDefault:%v inject:%v required:%v %s",
			metadata.Namespace,
			potentialPodName(metadata),
			config.Policy,
			useDefault,
			inject,
			required,
			annotationStr)
	}
	return required
}


注意

最后还是要强调一下,命名空间的注入判断是根据mutatingwebhookconfigurations的时候就进行判断的.
而pod的注入判断是通过上面代码进行判断的,我在初看时,一直以为都在代码进行处理,以至于找命名空间的处理逻辑找到怀疑认生!!!

你可能感兴趣的:(istio,istio,kubernetes,docker)