22 minute read

Kubernetes Networking with Calico CNI

Let us build another minikube cluster, this time with the Calico CNI. Minikube supports many CNIs.

From the minikube start -h output, we can see the supported CNI plug-ins list.

 --cni='': CNI plug-in to use. Valid options: auto, bridge, calico, cilium, flannel, kindnet, or path to a CNI
manifest (default: auto)

Start the 3-node minikube cluster with calico CNI plugin.

pradeep@learnk8s$ minikube start --nodes=3 --cni=calico
😄  minikube v1.25.2 on Darwin 12.2.1
✨  Automatically selected the hyperkit driver
👍  Starting control plane node minikube in cluster minikube
🔥  Creating hyperkit VM (CPUs=2, Memory=2200MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
    ▪ kubelet.housekeeping-interval=5m
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔗  Configuring Calico (Container Networking Interface) ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: default-storageclass, storage-provisioner

👍  Starting worker node minikube-m02 in cluster minikube
🔥  Creating hyperkit VM (CPUs=2, Memory=2200MB, Disk=20000MB) ...
🌐  Found network options:
    ▪ NO_PROXY=172.16.30.6
🐳  Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
    ▪ env NO_PROXY=172.16.30.6
🔎  Verifying Kubernetes components...

👍  Starting worker node minikube-m03 in cluster minikube
🔥  Creating hyperkit VM (CPUs=2, Memory=2200MB, Disk=20000MB) ...
🌐  Found network options:
    ▪ NO_PROXY=172.16.30.6,172.16.30.7
🐳  Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
    ▪ env NO_PROXY=172.16.30.6
    ▪ env NO_PROXY=172.16.30.6,172.16.30.7
🔎  Verifying Kubernetes components...
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
pradeep@learnk8s$

Verify the cluster node IPs: 172.16.30.6, 172.16.30.7, and 172.16.30.8.

pradeep@learnk8s$ kubectl get nodes -o wide
NAME           STATUS   ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE              KERNEL-VERSION   CONTAINER-RUNTIME
minikube       Ready    control-plane,master   22m   v1.23.3   172.16.30.6   <none>        Buildroot 2021.02.4   4.19.202         docker://20.10.12
minikube-m02   Ready    <none>                 20m   v1.23.3   172.16.30.7   <none>        Buildroot 2021.02.4   4.19.202         docker://20.10.12
minikube-m03   Ready    <none>                 17m   v1.23.3   172.16.30.8   <none>        Buildroot 2021.02.4   4.19.202         docker://20.10.12
pradeep@learnk8s$

Verify the list of Pods in all namespaces.

pradeep@learnk8s$ kubectl get pods -A -o wide
NAMESPACE     NAME                                       READY   STATUS    RESTARTS        AGE     IP            NODE           NOMINATED NODE   READINESS GATES
kube-system   calico-kube-controllers-8594699699-dztlm   1/1     Running   0               6m50s   10.88.0.3     minikube       <none>           <none>
kube-system   calico-node-gqvw6                          1/1     Running   1 (2m54s ago)   4m38s   172.16.30.7   minikube-m02   <none>           <none>
kube-system   calico-node-qdbcf                          1/1     Running   0               6m50s   172.16.30.6   minikube       <none>           <none>
kube-system   calico-node-sw74l                          1/1     Running   0               114s    172.16.30.8   minikube-m03   <none>           <none>
kube-system   coredns-64897985d-58btq                    1/1     Running   0               6m50s   10.88.0.2     minikube       <none>           <none>
kube-system   etcd-minikube                              1/1     Running   0               7m1s    172.16.30.6   minikube       <none>           <none>
kube-system   kube-apiserver-minikube                    1/1     Running   0               7m1s    172.16.30.6   minikube       <none>           <none>
kube-system   kube-controller-manager-minikube           1/1     Running   0               7m1s    172.16.30.6   minikube       <none>           <none>
kube-system   kube-proxy-7k4lb                           1/1     Running   0               114s    172.16.30.8   minikube-m03   <none>           <none>
kube-system   kube-proxy-gm2dh                           1/1     Running   0               4m38s   172.16.30.7   minikube-m02   <none>           <none>
kube-system   kube-proxy-hvkqd                           1/1     Running   0               6m50s   172.16.30.6   minikube       <none>           <none>
kube-system   kube-scheduler-minikube                    1/1     Running   0               7m1s    172.16.30.6   minikube       <none>           <none>
kube-system   storage-provisioner                        1/1     Running   1 (2m26s ago)   6m47s   172.16.30.6   minikube       <none>           <none>
pradeep@learnk8s$

There are two Pods using the 10.88.0.X subnet (calico-kube-controllers and coredns Pods ).

Login to the controlplane node and check the CNI directory.

pradeep@learnk8s$ minikube ssh
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ cat /etc/cni/net.d/
.keep                      10-calico.conflist         87-podman-bridge.conflist  calico-kubeconfig
$ cat /etc/cni/net.d/10-calico.conflist
{
  "name": "k8s-pod-network",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "calico",
      "log_level": "info",
      "log_file_path": "/var/log/calico/cni/cni.log",
      "datastore_type": "kubernetes",
      "nodename": "minikube",
      "mtu": 0,
      "ipam": {
          "type": "calico-ipam"
      },
      "policy": {
          "type": "k8s"
      },
      "kubernetes": {
          "kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
      }
    },
    {
      "type": "portmap",
      "snat": true,
      "capabilities": {"portMappings": true}
    },
    {
      "type": "bandwidth",
      "capabilities": {"bandwidth": true}
    }
  ]
}$

Apart from the Calico , there is a config file for Podman as well. Let us check that as well.

$ cat /etc/cni/net.d/87-podman-bridge.conflist
{
  "cniVersion": "0.4.0",
  "name": "podman",
  "plugins": [
    {
      "type": "bridge",
      "bridge": "cni-podman0",
      "isGateway": true,
      "ipMasq": true,
      "hairpinMode": true,
      "ipam": {
        "type": "host-local",
        "routes": [{ "dst": "0.0.0.0/0" }],
        "ranges": [
          [
            {
              "subnet": "10.88.0.0/16",
              "gateway": "10.88.0.1"
            }
          ]
        ]
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    },
    {
      "type": "firewall"
    },
    {
      "type": "tuning"
    }
  ]
}
$

From this, it is clear that while starting the minikube cluster, those two Pods got the IP from the Podman CNI, which seems to compete with the Calico CNI.

This issue is reported here: https://github.com/kubernetes/kubernetes/issues/107687

Let us create a new Pod in the default namespace and check which IP will be obtained.

pradeep@learnk8s$ kubectl run nginx --image=nginx
pod/nginx created

Verify the IP of the new Pod and the node on which it got hosted.

pradeep@learnk8s$ kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          56s   10.244.205.193   minikube-m02   <none>           <none>

The new nginx pod got the IP address of 10.244.205.193 and is running on the minikube-m02 node.

Let us log in to this minikube-m02 node and verify the routing table and list of interfaces.

pradeep@learnk8s$ minikube ssh -n minikube-m02
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 22:a7:1a:d9:be:42 brd ff:ff:ff:ff:ff:ff
    inet 172.16.30.7/24 brd 172.16.30.255 scope global dynamic eth0
       valid_lft 84337sec preferred_lft 84337sec
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:d3:aa:60:ec brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.205.192/32 scope global tunl0
       valid_lft forever preferred_lft forever
8: calic440f455693@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ ip route
default via 172.16.30.1 dev eth0 proto dhcp src 172.16.30.7 metric 1024
10.244.120.64/26 via 172.16.30.6 dev tunl0 proto bird onlink
10.244.151.0/26 via 172.16.30.8 dev tunl0 proto bird onlink
blackhole 10.244.205.192/26 proto bird
10.244.205.193 dev calic440f455693 scope link
172.16.30.0/24 dev eth0 proto kernel scope link src 172.16.30.7
172.16.30.1 dev eth0 proto dhcp scope link src 172.16.30.7 metric 1024
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
$

There are two special interfaces, 5: tunl0@NONE: and 8: calic440f455693@if5:. We can see that it is an ipip tunnel and tunnel interface (tunl0) has the IP address of 10.244.205.192/32.

From the routing table, there are two /26 routes pointing to the tunl0 interface. The 10.244.120.64/26 via 172.16.30.6 and 10.244.151.0/26 via 172.16.30.8 entries in the routing table, shows the Calico assigned subnets on the other two nodes, and the routes for those remote subnets via the IPIP tunnel interface.

From within the minikube-m02 node, issue the docker ps command the retrieve the container ID of the nginx container.

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS     NAMES
0d1f5d390956   nginx                  "/docker-entrypoint.…"   28 minutes ago   Up 28 minutes             k8s_nginx_nginx_default_5c5b022b-70d0-4e59-bbba-35a9bb43aa5c_0
6b67d9586b86   k8s.gcr.io/pause:3.6   "/pause"                 28 minutes ago   Up 28 minutes             k8s_POD_nginx_default_5c5b022b-70d0-4e59-bbba-35a9bb43aa5c_0
6f65ec9ee321   5ef66b403f4f           "start_runit"            39 minutes ago   Up 39 minutes             k8s_calico-node_calico-node-gqvw6_kube-system_6a835837-d36e-4366-aefe-cedc09d2f148_1
3d0be715b116   9b7cc9982109           "/usr/local/bin/kube…"   41 minutes ago   Up 41 minutes             k8s_kube-proxy_kube-proxy-gm2dh_kube-system_770d601e-7b93-42fa-8019-5976ae95684e_0
9c077d1f8374   k8s.gcr.io/pause:3.6   "/pause"                 41 minutes ago   Up 41 minutes             k8s_POD_calico-node-gqvw6_kube-system_6a835837-d36e-4366-aefe-cedc09d2f148_0
4b2360ad79a9   k8s.gcr.io/pause:3.6   "/pause"                 41 minutes ago   Up 41 minutes             k8s_POD_kube-proxy-gm2dh_kube-system_770d601e-7b93-42fa-8019-5976ae95684e_0
$

Get the Pid of the nginx container with the docker inspect command.

$ docker inspect 0d1f5d390956 | grep Pid
            "Pid": 11380,
            "PidMode": "",
            "PidsLimit": null,
$

With the nsenter command, verify the list of interfaces inside the nginx container.

$ sudo nsenter -t 11380 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
5: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether e6:ea:af:be:ec:53 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.205.193/32 brd 10.244.205.193 scope global eth0
       valid_lft forever preferred_lft forever
$

We can see that the Pod IP is configured on the 5: eth0@if8: interface. From the @if8, we can see the link to the 8: calic440f455693@if5: interface on the host (like the veth interfaces in the case of Kindnet CNI).

Look at the routing table of the nginx pod.

$ sudo nsenter -t 11380 -n ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
$

Exit the minikube-m02 node and use the kubectl describe nodes command and filter for the PodCIDRs.

pradeep@learnk8s$ kubectl describe nodes | grep -e Name -e PodCIDR
Name:               minikube
PodCIDR:                      10.244.0.0/24
PodCIDRs:                     10.244.0.0/24
  Namespace                   Name                                        CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
Name:               minikube-m02
PodCIDR:                      10.244.1.0/24
PodCIDRs:                     10.244.1.0/24
  Namespace                   Name                 CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
Name:               minikube-m03
PodCIDR:                      10.244.2.0/24
PodCIDRs:                     10.244.2.0/24
  Namespace                   Name                 CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
pradeep@learnk8s$

Currently, there is only one pod (nginx) in the cluster. Similar to what we have verified on the minikube-m02 node, do the same on the other two nodes as well.

From the minikube node:

pradeep@learnk8s$ minikube ssh
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 9e:55:51:7f:e1:98 brd ff:ff:ff:ff:ff:ff
    inet 172.16.30.6/24 brd 172.16.30.255 scope global dynamic eth0
       valid_lft 82845sec preferred_lft 82845sec
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:92:37:51:98 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
5: cni-podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 36:85:e8:76:d2:1c brd ff:ff:ff:ff:ff:ff
    inet 10.88.0.1/16 brd 10.88.255.255 scope global cni-podman0
       valid_lft forever preferred_lft forever
6: veth5a561323@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP group default
    link/ether f6:67:49:87:51:36 brd ff:ff:ff:ff:ff:ff link-netnsid 0
7: vethb6ed82ba@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP group default
    link/ether 22:b5:fe:8a:37:59 brd ff:ff:ff:ff:ff:ff link-netnsid 1
8: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.120.64/32 scope global tunl0
       valid_lft forever preferred_lft forever
$ ip route
default via 172.16.30.1 dev eth0 proto dhcp src 172.16.30.6 metric 1024
10.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1
blackhole 10.244.120.64/26 proto bird
10.244.151.0/26 via 172.16.30.8 dev tunl0 proto bird onlink
10.244.205.192/26 via 172.16.30.7 dev tunl0 proto bird onlink
172.16.30.0/24 dev eth0 proto kernel scope link src 172.16.30.6
172.16.30.1 dev eth0 proto dhcp scope link src 172.16.30.6 metric 1024
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
$ exit
logout
pradeep@learnk8s$

On the controlplane node, there are extra interfaces coming from the Podman CNI.

From the minikube-m03 node:

pradeep@learnk8s$ minikube ssh -n minikube-m03
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether de:b8:1d:5e:d9:c0 brd ff:ff:ff:ff:ff:ff
    inet 172.16.30.8/24 brd 172.16.30.255 scope global dynamic eth0
       valid_lft 83040sec preferred_lft 83040sec
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:66:0b:71:73 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.151.0/32 scope global tunl0
       valid_lft forever preferred_lft forever
$ ip route
default via 172.16.30.1 dev eth0 proto dhcp src 172.16.30.8 metric 1024
10.244.120.64/26 via 172.16.30.6 dev tunl0 proto bird onlink
blackhole 10.244.151.0/26 proto bird
10.244.205.192/26 via 172.16.30.7 dev tunl0 proto bird onlink
172.16.30.0/24 dev eth0 proto kernel scope link src 172.16.30.8
172.16.30.1 dev eth0 proto dhcp scope link src 172.16.30.8 metric 1024
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
$

On other nodes also, we do see similar tunl0 interface and routes for Calico subnets (/26s) via the IPIP tunnel. For the local subnet, there is a blackhole route: for example on the minikube-m03 node blackhole 10.244.151.0/26 proto bird, and on minikube-m02 node blackhole 10.244.205.192/26 proto bird, on the minikube node blackhole 10.244.120.64/26 proto bird.

Let us manually schedule a Pod on the minikube-m03 node.

pradeep@learnk8s$ cat my-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-manual
spec:
  nodeName: minikube-m03
  containers:
  - image: nginx
    name: nginx
pradeep@learnk8s$ kubectl create -f my-pod.yaml
pod/nginx-manual created
pradeep@learnk8s$ kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
nginx          1/1     Running   0          53m   10.244.205.193   minikube-m02   <none>           <none>
nginx-manual   1/1     Running   0          39s   10.244.151.1     minikube-m03   <none>           <none>
pradeep@learnk8s$

As expected, the nginx-manual pod got an IP from the 10.244.151.0/26 subnet.

Log back to the minikube-m03 node and check the routing table and list of interfaces.

pradeep@learnk8s$ minikube ssh -n minikube-m03
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether de:b8:1d:5e:d9:c0 brd ff:ff:ff:ff:ff:ff
    inet 172.16.30.8/24 brd 172.16.30.255 scope global dynamic eth0
       valid_lft 82414sec preferred_lft 82414sec
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:66:0b:71:73 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
5: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.151.0/32 scope global tunl0
       valid_lft forever preferred_lft forever
8: califba6dd09590@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ ip route
default via 172.16.30.1 dev eth0 proto dhcp src 172.16.30.8 metric 1024
10.244.120.64/26 via 172.16.30.6 dev tunl0 proto bird onlink
blackhole 10.244.151.0/26 proto bird
10.244.151.1 dev califba6dd09590 scope link
10.244.205.192/26 via 172.16.30.7 dev tunl0 proto bird onlink
172.16.30.0/24 dev eth0 proto kernel scope link src 172.16.30.8
172.16.30.1 dev eth0 proto dhcp scope link src 172.16.30.8 metric 1024
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
$

Compared to the previous output (when there were no pods on this node), there is a new interface 8: califba6dd09590@if5: and new route entry for the Pod IP 10.244.151.1 dev califba6dd09590 scope link.

Get the container ID and Pid of this nginx-manual container.

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED             STATUS             PORTS     NAMES
244b50fad9d4   nginx                  "/docker-entrypoint.…"   5 minutes ago       Up 5 minutes                 k8s_nginx_nginx-manual_default_ffbeaf88-b368-48af-b181-fc30cb49406a_0
e7117e519923   k8s.gcr.io/pause:3.6   "/pause"                 5 minutes ago       Up 5 minutes                 k8s_POD_nginx-manual_default_ffbeaf88-b368-48af-b181-fc30cb49406a_0
c4c993bc1f2e   calico/node            "start_runit"            About an hour ago   Up About an hour             k8s_calico-node_calico-node-sw74l_kube-system_0e30bbad-8370-4907-ab20-ce81450ad13c_0
a3482f106e3a   9b7cc9982109           "/usr/local/bin/kube…"   About an hour ago   Up About an hour             k8s_kube-proxy_kube-proxy-7k4lb_kube-system_f284e305-e71b-40b0-a715-796d0733bc03_0
25a914d158da   k8s.gcr.io/pause:3.6   "/pause"                 About an hour ago   Up About an hour             k8s_POD_calico-node-sw74l_kube-system_0e30bbad-8370-4907-ab20-ce81450ad13c_0
abce35d2a0ae   k8s.gcr.io/pause:3.6   "/pause"                 About an hour ago   Up About an hour             k8s_POD_kube-proxy-7k4lb_kube-system_f284e305-e71b-40b0-a715-796d0733bc03_0
$
$ docker inspect 244b50fad9d4 | grep Pid
            "Pid": 42125,
            "PidMode": "",
            "PidsLimit": null,
$

Use the nsenter command and verify the Pod interfaces and route table.

$ nsenter -t 42125 -n ip a
nsenter: cannot open /proc/42125/ns/net: Permission denied
$ sudo nsenter -t 42125 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
5: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 9a:93:7b:77:8f:c7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.151.1/32 brd 10.244.151.1 scope global eth0
       valid_lft forever preferred_lft forever
$

The eth0 interface (5: eth0@if8) of the Pod has the IP address 10.244.151.1/32 and is mapped to the new cali interface 8: califba6dd09590@if5: on the host.

$ sudo nsenter -t 42125 -n ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
$

Verify communication between the two Pods. 10.244.205.193 is the IP address of the nginx pod running on the other node minikube-m02.

$ sudo nsenter -t 42125 -n ping 10.244.205.193
PING 10.244.205.193 (10.244.205.193): 56 data bytes
64 bytes from 10.244.205.193: seq=0 ttl=62 time=17.321 ms
64 bytes from 10.244.205.193: seq=1 ttl=62 time=0.963 ms
64 bytes from 10.244.205.193: seq=2 ttl=62 time=0.891 ms
64 bytes from 10.244.205.193: seq=3 ttl=62 time=1.529 ms
^C
--- 10.244.205.193 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.891/5.176/17.321 ms
$

Verify communication to the outside cluster hosts, like any internet host ( for example 8.8.8.8)

$ sudo nsenter -t 42125 -n ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=115 time=20.260 ms
64 bytes from 8.8.8.8: seq=1 ttl=115 time=15.457 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 15.457/17.858/20.260 ms
$

Let us install calicoctl as a Pod itself.

pradeep@learnk8s$ kubectl apply -f https://projectcalico.docs.tigera.io/manifests/calicoctl.yaml
serviceaccount/calicoctl created
pod/calicoctl created
clusterrole.rbac.authorization.k8s.io/calicoctl created
clusterrolebinding.rbac.authorization.k8s.io/calicoctl created
pradeep@learnk8s$

Verify that the calicoctl pod is running.

pradeep@learnk8s$ kubectl get pods -n kube-system -o wide
NAME                                       READY   STATUS    RESTARTS      AGE   IP            NODE           NOMINATED NODE   READINESS GATES
calico-kube-controllers-8594699699-dztlm   1/1     Running   0             82m   10.88.0.3     minikube       <none>           <none>
calico-node-gqvw6                          1/1     Running   1 (78m ago)   80m   172.16.30.7   minikube-m02   <none>           <none>
calico-node-qdbcf                          1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
calico-node-sw74l                          1/1     Running   0             77m   172.16.30.8   minikube-m03   <none>           <none>
calicoctl                                  1/1     Running   0             18s   172.16.30.7   minikube-m02   <none>           <none>
coredns-64897985d-58btq                    1/1     Running   0             82m   10.88.0.2     minikube       <none>           <none>
etcd-minikube                              1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
kube-apiserver-minikube                    1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
kube-controller-manager-minikube           1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
kube-proxy-7k4lb                           1/1     Running   0             77m   172.16.30.8   minikube-m03   <none>           <none>
kube-proxy-gm2dh                           1/1     Running   0             80m   172.16.30.7   minikube-m02   <none>           <none>
kube-proxy-hvkqd                           1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
kube-scheduler-minikube                    1/1     Running   0             82m   172.16.30.6   minikube       <none>           <none>
storage-provisioner                        1/1     Running   1 (77m ago)   82m   172.16.30.6   minikube       <none>           <none>
pradeep@learnk8s$

Verify the calicoctl

pradeep@learnk8s$ kubectl exec -ti -n kube-system calicoctl -- /calicoctl get nodes -o wide
Failed to get resources: Version mismatch.
Client Version:   v3.22.1
Cluster Version:  v3.20.0
Use --allow-version-mismatch to override.

command terminated with exit code 1
pradeep@learnk8s$

Repeat it with --allow-version-mismatch.

pradeep@learnk8s$ kubectl exec -ti -n kube-system calicoctl -- /calicoctl get nodes -o wide --allow-version-mismatch
NAME           ASN       IPV4             IPV6
minikube       (64512)   172.16.30.6/24
minikube-m02   (64512)   172.16.30.7/24
minikube-m03   (64512)   172.16.30.8/24

pradeep@learnk8s$

We can confirm that the calicoctl is working and we can see some BGP Autonomous systems shown as well. Currently all three nodes are part of the same ASN (64512).

Create an alias for the calicoctl

pradeep@learnk8s$ alias calicoctl="kubectl exec -i -n kube-system calicoctl -- /calicoctl --allow-version-mismatch"

By default, calicoctl will attempt to read from the Kubernetes API using the default kubeconfig located at $(HOME)/.kube/config.

If the default kubeconfig does not exist, or you would like to specify alternative API access information, you can do so using the following configuration options.

pradeep@learnk8s$ DATASTORE_TYPE=kubernetes KUBECONFIG=~/.kube/config calicoctl get nodes
NAME
minikube
minikube-m02
minikube-m03

pradeep@learnk8s$

Verify all API-Resources installed by Calico CNI plugin.

pradeep@learnk8s$ kubectl api-resources | grep calico
bgpconfigurations                              crd.projectcalico.org/v1               false        BGPConfiguration
bgppeers                                       crd.projectcalico.org/v1               false        BGPPeer
blockaffinities                                crd.projectcalico.org/v1               false        BlockAffinity
clusterinformations                            crd.projectcalico.org/v1               false        ClusterInformation
felixconfigurations                            crd.projectcalico.org/v1               false        FelixConfiguration
globalnetworkpolicies                          crd.projectcalico.org/v1               false        GlobalNetworkPolicy
globalnetworksets                              crd.projectcalico.org/v1               false        GlobalNetworkSet
hostendpoints                                  crd.projectcalico.org/v1               false        HostEndpoint
ipamblocks                                     crd.projectcalico.org/v1               false        IPAMBlock
ipamconfigs                                    crd.projectcalico.org/v1               false        IPAMConfig
ipamhandles                                    crd.projectcalico.org/v1               false        IPAMHandle
ippools                                        crd.projectcalico.org/v1               false        IPPool
kubecontrollersconfigurations                  crd.projectcalico.org/v1               false        KubeControllersConfiguration
networkpolicies                                crd.projectcalico.org/v1               true         NetworkPolicy
networksets                                    crd.projectcalico.org/v1               true         NetworkSet
pradeep@learnk8s$

Verify some of these resources with kubectl get and kubectl describe commands.

pradeep@learnk8s$ kubectl get bgppeers -A
No resources found
pradeep@learnk8s$ kubectl get ippools
NAME                  AGE
default-ipv4-ippool   97m
pradeep@learnk8s$ kubectl describe ippools
Name:         default-ipv4-ippool
Namespace:
Labels:       <none>
Annotations:  projectcalico.org/metadata: {"uid":"cf94cf43-8887-4528-a934-ca498f0422e1","creationTimestamp":"2022-03-19T18:20:36Z"}
API Version:  crd.projectcalico.org/v1
Kind:         IPPool
Metadata:
  Creation Timestamp:  2022-03-19T18:20:36Z
  Generation:          1
  Managed Fields:
    API Version:  crd.projectcalico.org/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:projectcalico.org/metadata:
      f:spec:
        .:
        f:blockSize:
        f:cidr:
        f:ipipMode:
        f:natOutgoing:
        f:nodeSelector:
        f:vxlanMode:
    Manager:         Go-http-client
    Operation:       Update
    Time:            2022-03-19T18:20:36Z
  Resource Version:  659
  UID:               8d9e076a-dae6-4bbd-8f44-920da83472ba
Spec:
  Block Size:     26
  Cidr:           10.244.0.0/16
  Ipip Mode:      Always
  Nat Outgoing:   true
  Node Selector:  all()
  Vxlan Mode:     Never
Events:           <none>
pradeep@learnk8s$
pradeep@learnk8s$ kubectl get ipamblocks
NAME                AGE
10-244-120-64-26    98m
10-244-151-0-26     93m
10-244-205-192-26   95m
pradeep@learnk8s$

We have seen these IPAM blocks already, when we verified the routing tables on each node.

pradeep@learnk8s$ kubectl get networkpolicies
No resources found in default namespace.

Let us create a network policy now.

pradeep@learnk8s$ cat network-policy.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress
pradeep@learnk8s$
pradeep@learnk8s$ kubectl create -f network-policy.yaml
networkpolicy.networking.k8s.io/default-deny-ingress created

Verify that the network policy is created.

pradeep@learnk8s$ kubectl get netpol
NAME                   POD-SELECTOR   AGE
default-deny-ingress   <none>         2s

Describe it for more details.

pradeep@learnk8s$ kubectl describe netpol
Name:         default-deny-ingress
Namespace:    default
Created on:   2022-03-20 01:45:29 +0530 IST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Not affecting egress traffic
  Policy Types: Ingress
pradeep@learnk8s$ cat network-policy.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress
pradeep@learnk8s$

If you recall, we have two pods in our cluster at the moment.

pradeep@learnk8s$ kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE    IP               NODE           NOMINATED NODE   READINESS GATES
nginx          1/1     Running   0          105m   10.244.205.193   minikube-m02   <none>           <none>
nginx-manual   1/1     Running   0          52m    10.244.151.1     minikube-m03   <none>           <none>

Now that the network policy is applied, let us check the connectivity between these two pods again.

pradeep@learnk8s$ minikube ssh -n minikube-m02
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ docker ps | grep nginx
0d1f5d390956   nginx                  "/docker-entrypoint.…"   2 hours ago      Up 2 hours                k8s_nginx_nginx_default_5c5b022b-70d0-4e59-bbba-35a9bb43aa5c_0
6b67d9586b86   k8s.gcr.io/pause:3.6   "/pause"                 2 hours ago      Up 2 hours                k8s_POD_nginx_default_5c5b022b-70d0-4e59-bbba-35a9bb43aa5c_0
$ docker inspect 0d1f5d390956 | grep Pid
            "Pid": 11380,
            "PidMode": "",
            "PidsLimit": null,
$ sudo nsenter -t 11380 -n ping 10.244.151.1
PING 10.244.151.1 (10.244.151.1): 56 data bytes
^C
--- 10.244.151.1 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
$

From nginx pod, we are not able to ping to nginx-manual pod.

Similarly, verify the other way, ping from nginx-manual to nginx Pod (which was working earlier, that we tested already!).

pradeep@learnk8s$ minikube ssh -n minikube-m03
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ docker ps | grep nginx
244b50fad9d4   nginx                  "/docker-entrypoint.…"   55 minutes ago   Up 55 minutes             k8s_nginx_nginx-manual_default_ffbeaf88-b368-48af-b181-fc30cb49406a_0
e7117e519923   k8s.gcr.io/pause:3.6   "/pause"                 55 minutes ago   Up 55 minutes             k8s_POD_nginx-manual_default_ffbeaf88-b368-48af-b181-fc30cb49406a_0
$ docker inspect 244b50fad9d4 | grep Pid
            "Pid": 42125,
            "PidMode": "",
            "PidsLimit": null,
$ sudo nsenter -t 42125 -n ping 10.244.205.193
PING 10.244.205.193 (10.244.205.193): 56 data bytes
^C
--- 10.244.205.193 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
$ exit 0
logout
pradeep@learnk8s$

With network policy applied, the communication is blocked.

Let us create another network policy, this time to allow all ingress.

pradeep@learnk8s$ cat allow-ingress.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

pradeep@learnk8s$ kubectl create -f allow-ingress.yaml
networkpolicy.networking.k8s.io/allow-all-ingress created
pradeep@learnk8s$ kubectl get netpol
NAME                   POD-SELECTOR   AGE
allow-all-ingress      <none>         5s
default-deny-ingress   <none>         11m

Describe the new policy.

pradeep@learnk8s$ kubectl describe netpol allow-all-ingress
Name:         allow-all-ingress
Namespace:    default
Created on:   2022-03-20 01:56:32 +0530 IST
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    To Port: <any> (traffic allowed to all ports)
    From: <any> (traffic not restricted by source)
  Not affecting egress traffic
  Policy Types: Ingress
pradeep@learnk8s$

Now that we have allowed the ingress traffic to all pods/all ports.

Verify again. Now that we know the Pid of the containers, those steps are not needed.

pradeep@learnk8s$ minikube ssh -n minikube-m02
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ sudo nsenter -t 11380 -n ping 10.244.151.1
PING 10.244.151.1 (10.244.151.1): 56 data bytes
64 bytes from 10.244.151.1: seq=0 ttl=62 time=3.834 ms
64 bytes from 10.244.151.1: seq=1 ttl=62 time=1.409 ms
64 bytes from 10.244.151.1: seq=2 ttl=62 time=0.723 ms
^C
--- 10.244.151.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.723/1.988/3.834 ms
$ exit 0
logout

Yay! Ping is working again.

Similarly, verify in the other direction.

pradeep@learnk8s$ minikube ssh -n minikube-m03
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ sudo nsenter -t 42125 -n ping 10.244.205.193
PING 10.244.205.193 (10.244.205.193): 56 data bytes
64 bytes from 10.244.205.193: seq=0 ttl=62 time=1.132 ms
64 bytes from 10.244.205.193: seq=1 ttl=62 time=1.672 ms
64 bytes from 10.244.205.193: seq=2 ttl=62 time=1.695 ms
^C
--- 10.244.205.193 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.132/1.499/1.695 ms
$ exit 0
logout

This completes our initial discussion on the Calico CNI. We just scratched the surface of it, there are many more features, which will be discussed in other posts.

Back to Top ↑