Istio的一个亮点就是为每个应用自动添加代理程序,并且采用无代码侵入的方式,这样的好处是,代理与应用进行解耦。
[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
# 如果使用的默认版本则修改下面的配置
# 如果是其余版本那么它的名称一般是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 ,那么就是默认不注入
本文不讲解过多的模板具体实如何与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的注入判断是通过上面代码进行判断的,我在初看时,一直以为都在代码进行处理,以至于找命名空间的处理逻辑找到怀疑认生!!!