Skip to content

Instantly share code, notes, and snippets.

@dougbtv
Last active May 11, 2023 16:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dougbtv/babe9b59e4d54e7a0fc67f134b74b908 to your computer and use it in GitHub Desktop.
Save dougbtv/babe9b59e4d54e7a0fc67f134b74b908 to your computer and use it in GitHub Desktop.
Istio + Multus CNI: Annotation clobbering, replication and fix

Istio + Multus CNI: Annotation clobbering, replication and fix

This details a reference deployment of Istio w/ Multus CNI to demonstrate a problem where annotations are being clobbered by the Istio webhook. It also provides a patch and workflow for a possible fix.

This article first demonstrates how to reproduce the article, then proposes a patch, and demonstrates a way to build and deploy Istio with the modified code.

NOTE: Ignore the 1.5.1 through the install, I replicate it with latest (Nov 2021), and provide further steps following the rest of the installation.

Suggested system

  • Kubernetes (I used 1.18.0 and later 1.22.3)
  • Some pod-to-pod CNI plugin installed (I used flannel)
  • Multus CNI installed using the quickstart guide.

Initialize an empty CR for istio-cni

cat <<EOF | kubectl apply -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: istio-cni
spec:
  config: ''
EOF

This enables Multus to search for the on-disk configuration that contains a JSON CNI config with the name field set to istio-cni.

Istio installation

Download Istio per these installation instructions.

curl -L https://istio.io/downloadIstio | sh -
cd istio-1.5.1

When going to make the installation, we use the parameters for OpenShift 4.2 from the istio-cni installation instructions.

istioctl manifest apply \
  --set profile=demo \
  --set components.cni.enabled=true \
  --set components.cni.namespace=kube-system \
  --set values.cni.cniBinDir=/opt/cni/bin \
  --set values.cni.cniConfDir=/etc/cni/multus/net.d \
  --set values.cni.chained=false \
  --set values.cni.cniConfFileName="istio-cni.conf" \
  --set values.sidecarInjectorWebhook.injectedAnnotations."k8s\.v1\.cni\.cncf\.io/networks"=istio-cni 

Short description of what I believe the parameters are accomplishing:

Parameter Assumed purpose
--set components.cni.enabled=true Enable istio CNI plugin
--set components.cni.namespace=kube-system Namespace where istio cni daemonset runs
--set values.cni.cniBinDir=/opt/cni/bin Directory where istio-cni binary will be dropped
--set values.cni.cniConfDir=/etc/cni/multus/net.d Where istio-cni.conf will be written (important)
--set values.cni.chained=false Disables istio-cni as a chained CNI plugin (important)
--set values.cni.cniConfFileName="istio-cni.conf" Name of configuration file to write (required but actual filename can be arbitrary)
--set values.sidecarInjectorWebhook.injectedAnnotations [...] Annotation that's added to pods that will use Istio.

This results in files being created for Istio configuration, especially landing in /etc/cni/multus/net.d/

[centos@kube-singlehost-master istio-1.5.1]$ ls /etc/cni/multus/net.d/
istio-cni.conf  ZZZ-istio-cni-kubeconfig
[centos@kube-singlehost-master istio-1.5.1]$ cat /etc/cni/multus/net.d/istio-cni.conf 
{
  "cniVersion": "0.3.1",
  "name": "istio-cni",
  "type": "istio-cni",
  "log_level": "info",
  "kubernetes": {
      "kubeconfig": "/etc/cni/multus/net.d/ZZZ-istio-cni-kubeconfig",
      "cni_bin_dir": "/opt/cni/bin",
      "exclude_namespaces": [ "istio-system" ]
  }
}

Deploying the sample application...

kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

Once the pods come up, use the early validation as suggested in the install docs...

kubectl exec -it $(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}') -c ratings -- curl productpage:9080/productpage | grep -o "<title>.*</title>"

(It should say "simple bookstore app")

Installation update: November 2021.

Latest attempt used Istio 1.11.4

I found that I followed the same steps, but! I needed to enable istio on a given namespace...

Run istioctl analyze to see if there's a problem. And then I ran:

kubectl label namespace default istio-injection=enabled

Then, I could reproduce the issue.

Reproducing the issue: Using a pod with istio + Multus, the annotation is clobbered by Istio.

Create a custom resource for Multus (this uses the example from the Multus CNI quickstart guide)

cat <<EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: macvlan-conf
spec:
  config: '{
      "cniVersion": "0.3.0",
      "type": "macvlan",
      "master": "eth0",
      "mode": "bridge",
      "ipam": {
        "type": "host-local",
        "subnet": "192.168.1.0/24",
        "rangeStart": "192.168.1.200",
        "rangeEnd": "192.168.1.216",
        "routes": [
          { "dst": "0.0.0.0/0" }
        ],
        "gateway": "192.168.1.1"
      }
    }'
EOF

Then, when deploying a pod, you reference both istio-cni and macvlan-conf in the k8s.v1.cni.cncf.io/networks annotation.

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: samplepod
  annotations:
    k8s.v1.cni.cncf.io/networks: macvlan-conf,istio-cni
spec:
  containers:
  - name: samplepod
    command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
    image: alpine
EOF

However, the result is unsatisfactory, the annotation is clobbered by Istio:

[centos@kube-singlehost-master istio-1.5.1]$ kubectl describe pod samplepod | grep "/networks:"
              k8s.v1.cni.cncf.io/networks: istio-cni

Therefore macvlan-conf is never executed.

The offending lines in the istio webhook that creates the annotation can be found here. It may be as simple as checking to see if the value is set, and not overwriting it there.

Modifying the code.

I got a good amount of the workflow for making/running it from the Istio using-the-code-base docs.

It seems that as of right now, the lines that cause the problem are here in webhook.go in Istio.

I currently have a work-in-progress diff.

And I have my WIP changes in a branch in my dougbtv/istio fork.

NOTE: I then actually took this patch and cherry-picked it onto the release-1.11 branch so that I could have a stable to work against, and then built/ran that.

One issue is that the code is marked as Deprecated; should be set directly in the template instead -- and I haven't dug deep enough yet to understand what that is.

Go ahead and clone this code, apply the path, however you want to get the modified code running.

So, how are we supposed to build this thing?

Well, I found this tip where we should setup some variables for us to use...

export HUB="docker.io/dougbtv"
# TAG should equal the version of the istioctl you'll use later in the process, or the one you downloaded earlier.
export TAG=1.11.4

I built the images using this make target....

make docker

And then I pushed the images with:

make docker.push

Uninstall the typical version.

You're going to uninstall what we reproduced it with, because we'll install our custom version.

Same as install but adds a x uninstall command and a --purge flag, like so:

./bin/istioctl x uninstall   --set profile=demo   --set components.cni.enabled=true   --set components.cni.namespace=kube-system   --set values.cni.cniBinDir=/opt/cni/bin   --set values.cni.cniConfDir=/etc/cni/multus/net.d   --set values.cni.chained=false   --set values.cni.cniConfFileName="istio-cni.conf"   --set values.sidecarInjectorWebhook.injectedAnnotations."k8s\.v1\.cni\.cncf\.io/networks"=istio-cni --purge

Install the patched version....

Note that we're setting the hub value here to match how we tagged/pushed our images...

/usr/src/istio-1.11.4/bin/istioctl manifest apply \
  --set profile=demo \
  --set hub=docker.io/dougbtv \
  --set components.cni.enabled=true \
  --set components.cni.namespace=kube-system \
  --set values.cni.cniBinDir=/opt/cni/bin \
  --set values.cni.cniConfDir=/etc/cni/multus/net.d \
  --set values.cni.chained=false \
  --set values.cni.cniConfFileName="istio-cni.conf" \
  --set values.sidecarInjectorWebhook.injectedAnnotations."k8s\.v1\.cni\.cncf\.io/networks"=istio-cni 

Next, let's see if we can replicate it...

Just go ahead and create our Multus-annotated pod again...

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
  name: samplepod
  annotations:
    k8s.v1.cni.cncf.io/networks: macvlan-conf,istio-cni
spec:
  containers:
  - name: samplepod
    command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
    image: alpine
EOF

The result is what we're looking for:

[root@kube-fedoralab-master istio-1.11.4]# kubectl describe pod samplepod | grep "/networks:"
              k8s.v1.cni.cncf.io/networks: macvlan-conf,istio-cni

That'll do it.

@willowmck
Copy link

@dougbtv you mind if I give this a test? This could be a very useful upstream contribution.

@dougbtv
Copy link
Author

dougbtv commented Nov 18, 2021

@willowmck be my guest!! Would be happy about it.

@navjotsinghji
Copy link

Hi @dougbtv

There is an interesting finding. I was NOT able to reproduce the issue after multiple attempts with my deployment's pods as well as the sample pod that you have mentioned above.

I checked and figured out that I am NOT using the
--set values.sidecarInjectorWebhook.injectedAnnotations."k8s.v1.cni.cncf.io/networks"=istio-cni
in my operator. Below is the snippet of the istio-operator options that I am using

values:
# operatorNamespace: {{ .Release.Namespace }} Note: This value is not a recognized value by istio-operator. Results in "error".
# These namespace values are required to override the default values.
cni:
image:
cniConfDir: /etc/cni/multus/net.d
chained: false

With this configuration, I am getting
[root@saranya-navanee-tx-k8-master-1-oek6y1-kh8ujctjpokz3b3s ~]# kubectl describe pod samplepod | grep "/networks:"
k8s.v1.cni.cncf.io/networks: macvlan-conf,istio-cni

If I edit the istiooperator on my cluster and add the Values.sidecarInjectorWebhook.injectedAnnotations as defined in https://istio.io/latest/docs/setup/additional-setup/cni/#hosted-kubernetes-settings (the link that you had shared above), then I am able to reproduce the issue.

So to sum it up, the culprit seems to be the injectedAnnotations configuration since without it, I do not face the annotations clobbering issue. I am not sure on why the line is recommended to be configured since I still get all the annotations in my POD definition without configuring it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment