Sidecar Injection Webhook

Automatic sidecar injection adds the sidecar proxy into user-created pods. It uses a MutatingWebhook to append the sidecar’s containers and volumes to each pod’s template spec during creation time. Injection can be scoped to particular sets of namespaces using the webhooks namespaceSelector mechanism. Injection can also be enabled and disabled per-pod with an annotation.

Whether or not a sidecar is injected is depends on three things:

  • webhooks namespaceSelector
  • default policy
  • per-pod override annotation

The following truth table shows the final injection status based on these three variables.

namespaceSelector matchdefault policyPod override annotation sidecar.istio.io/injectSidecar injected?
yesenabledtrueyes
yesenabledfalseno
yesenabledyes
yesdisabledtrueyes
yesdisabledfalseno
yesdisabledno
noenabledtrueno
noenabledfalseno
noenabledno
nodisabledtrueno
nodisabledfalseno
nodisabledno

The result of sidecar injection was not what I expected

This includes an injected sidecar when it wasn’t expected and a lack of injected sidecar when it was.

  1. Check the webhook’s namespaceSelector to determine whether the webhook is scoped to opt-in or opt-out for the target namespace.

    The namespaceSelector for opt-in will look like the following:

    $ kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml | grep "namespaceSelector:" -A5
      namespaceSelector:
        matchLabels:
          istio-injection: enabled
      rules:
      - apiGroups:
        - ""

    The injection webhook will be invoked for pods created in namespaces with the istio-injection=enabled label.

    $ kubectl get namespace -L istio-injection
    NAME           STATUS    AGE       ISTIO-INJECTION
    default        Active    18d       enabled
    istio-system   Active    3d
    kube-public    Active    18d
    kube-system    Active    18d

    The namespaceSelector for opt-out will look like the following:

    $ kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml | grep "namespaceSelector:" -A5
      namespaceSelector:
        matchExpressions:
        - key: istio-injection
          operator: NotIn
          values:
          - disabled
      rules:
      - apiGroups:
        - ""

    The injection webhook will be invoked for pods created in namespaces without the istio-injection=disabled label.

    $ kubectl get namespace -L istio-injection
    NAME           STATUS    AGE       ISTIO-INJECTION
    default        Active    18d
    istio-system   Active    3d        disabled
    kube-public    Active    18d       disabled
    kube-system    Active    18d       disabled

    Verify the application pod’s namespace is labeled properly and (re) label accordingly, e.g.

    $ kubectl label namespace istio-system istio-injection=disabled --overwrite

    (repeat for all namespaces in which the injection webhook should be invoked for new pods)

    $ kubectl label namespace default istio-injection=enabled --overwrite
  2. Check default policy

    Check the default injection policy in the istio-sidecar-injector configmap.

    $ kubectl -n istio-system get configmap istio-sidecar-injector -o jsonpath='{.data.config}' | head
    policy: enabled
    template: |-
      initContainers:
      - name: istio-init
        image: "docker.io/jasonayoung/proxy_init:d49fa0a7f7d17f25552ad749d23f8ac73596e0cc"
        args:
        - "-p"
        - [[ .MeshConfig.ProxyListenPort ]]
        - "-u"
        - 1337

    Allowed policy values are disabled and enabled. The default policy only applies if the webhook’s namespaceSelector matches the target namespace. Unrecognized policy values default to disabled.

  3. Check the per-pod override annotation

    The default policy can be overridden with the sidecar.istio.io/inject annotation in the pod template spec’s metadata. The deployment’s metadata is ignored. Annotation value of true forces the sidecar to be injected while a value of false forces the sidecar to not be injected.

    The following annotation overrides whatever the default policy was to force the sidecar to be injected:

    $ kubectl get deployment sleep -o yaml | grep "sidecar.istio.io/inject:" -C3
    template:
      metadata:
        annotations:
          sidecar.istio.io/inject: "true"
        labels:
          app: sleep

Pods cannot be created at all

Run kubectl describe -n namespace deployment name on the failing pod’s deployment. Failure to invoke the injection webhook will typically will be captured in the event log.

Warning  FailedCreate  3m (x17 over 8m)  replicaset-controller  Error creating: Internal error occurred: \
    failed calling admission webhook "sidecar-injector.istio.io": Post https://istio-sidecar-injector.istio-system.svc:443/inject: \
    x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying \
    to verify candidate authority certificate "Kubernetes.cluster.local")

x509: certificate signed by unknown authority errors are typically caused by an empty caBundle in the webhook configuration.

Verify the caBundle in the mutatingwebhookconfiguration matches the root certificate mounted in the istio-sidecar-injector pod.

$ kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | md5sum
4b95d2ba22ce8971c7c92084da31faf0  -
$ kubectl -n istio-system get secret istio.istio-sidecar-injector-service-account -o jsonpath='{.data.root-cert\.pem}' | md5sum
4b95d2ba22ce8971c7c92084da31faf0  -

The CA certificate should match. If they do not, restart the sidecar-injector pods.

$ kubectl -n istio-system patch deployment istio-sidecar-injector \
    -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}"
deployment.extensions "istio-sidecar-injector" patched

no such hosts or no endpoints available errors in deployment status

Injection is fail-close. If the istio-sidecar-injector pod is not ready, pods cannot be created. In such cases you’ll see an error about no such host (Kubernetes 1.9) or no endpoints available (>=1.10).

Kubernetes 1.9:

Internal error occurred: failed calling admission webhook "istio-sidecar-injector.istio.io": \
    Post https://istio-sidecar-injector.istio-system.svc:443/admitPilot: dial tcp: lookup \
    istio-sidecar-injector.istio-system.svc on 169.254.169.254:53: no such host

Kubernetes 1.10:

Internal error occurred: failed calling admission webhook "istio-sidecar-injector.istio.io": \
    Post https://istio-sidecar-injector.istio-system.svc:443/admitPilot?timeout=30s: \
    no endpoints available for service "istio-sidecar-injector"
$  kubectl -n istio-system get pod -listio=sidecar-injector
NAME                            READY     STATUS    RESTARTS   AGE
istio-sidecar-injector-5dbbbdb746-d676g   1/1       Running   0          2d
$ kubectl -n istio-system get endpoints istio-sidecar-injector
NAME           ENDPOINTS                          AGE
istio-sidecar-injector   10.48.6.108:9093,10.48.6.108:443   3d

If the pods or endpoints aren’t ready, check the pod logs and status for any indication about why the webhook pod is failing to start and serve traffic.

$ for pod in $(kubectl -n istio-system get pod -listio=sidecar-injector -o jsonpath='{.items[*].metadata.name}'); do \
    kubectl -n istio-system logs ${pod} \
done

$ for pod in $(kubectl -n istio-system get pod -listio=sidecar-injector -o name); do \
    kubectl -n istio-system describe ${pod} \
done