As of Jan 29 2017, almost all Kubernetes networking documentation assume that the Kubernetes cluster runs in some kind of public or private cloud technology. For example, Openstack, AWS or GCE

What about Baremetal Kubernetes? Currently Mirantis seems to be the only company addressing the IP mobility problem in Baremetal Kubernetes.

What is the Problem?

One can deploy Kubernetes using a tool like Kargo onto baremetal CoreOS or Ubuntu hosts. Probably even on Redhat Atomic. But the problem is after you go through that work, how to do you configure IP Mobility for a pod within the cluster?

Kubernetes has the concept of configuring ExternalIPs on the Service Resources that you configure like so:

kind: Service
apiVersion: v1
metadata:
  name: "frontend"
spec:
  selector:
    app: "frontend"
  ports:
    - protocol: "TCP"
      port: 443
      targetPort: 443
  type: NodePort
  externalIPs:
    - 200.1.1.1

In the above example the frontend loadbalancer service is created for the frontend pod(app). The service is assigned 200.1.1.1 as the external facing IP. On baremetal Kubernetes, you the admin, are responsible for configuring the 200.1.1.1/32 IP address onto the appropriate Kubernete minions and redistributing this route into your network routing tables. Painful task!

Kubernetes clusters on top of Openstack, AWS or GCE have a solution for IP Mobility. It calls it cloud providers. Example:

kind: Service
apiVersion: v1
metadata:
  name: "frontend"
spec:
  selector:
    app: "frontend"
  ports:
    - protocol: "TCP"
      port: 443
      targetPort: 443
  type: LoadBalancer

The above configuration triggers the cloud provider code to give the service an externally facing IP. So almost by magic. providing the service with IP mobility. Watch Kelsey Hightower demonstrate a service created using a cloud provide loadbalancer. Notice in the video the External IP. It appears magically without any user intervention.

I’m not sure if you destroy and restore the service, if the cloud provider code gives the service the same IP each time..hmmm?

The IP Mobility Solution for Baremetal Kubernetes

Mirantis have developed a cloud provider of sorts. It is called the external IP controller. In a nutshell, its a set of apps that listens to service resource requests, i.e loadbalancer service requests, and adds the ExternalIP address to the interface of choice on the kubernetes minions. Using the example shown earlier, when the External IP controller is installed, it takes the External IP and adds it to an interface on the Kubernetes minion. Which Kubernetes minion does it add it to? The minion running a claimsController pod. The interface is determined by the external IP controller config.

So if you have multiple claimControllers you can place the external IP on multiple kubernetes minions and use ECMP routing instead of configuring a dedicated loadbalancer.

Where the claimsController is running

Notice it says the claimController is running on k8s6 and k8s9 nodes.

$ kubectl get po -l app=externalipcontroller -o wide
NAME                              READY     STATUS              RESTARTS   AGE       IP         NODE
claimcontroller-711596365-1rv9j   1/1       Running             4          1d        10.1.3.6   k8s6
claimcontroller-711596365-b608s   1/1       Running             4          3d        10.1.3.9   k8s9

The number of claimController pods is managed by the claimController deployment

$ kubectl get deploy -l app=externalipcontroller
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
claimcontroller   2         2         2            2           6d

Where is the External IP placed?

Let’s review all the Kubernetes nodes in the cluster and examine their L3 config. The tool used to view L3 config is called netshow, a Cumulus Network app.

Notice that where the claimsController pod runs, the 200.1.1.1 address is assigned to that interface.


$ ansible -m command -a 'netshow l3' all

k8s9 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  ---------------------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.28/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.9/32, 200.1.1.1/32, ::1/128

k8s1 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name     Speed      MTU  Mode          Summary
--  -------  -------  -----  ------------  -------------------------------------
UP  eth0     -1M       1500  Interface/L3  IP: 192.168.200.20/24(DHCP)
UP  lo       N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.1/32, ::1/128
UP  vagrant  -1M       1500  Interface/L3  IP: 192.168.121.198/24

k8s6 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  ---------------------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.25/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.6/32, 200.1.1.1/32, ::1/128

k8s4 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  -------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.23/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.4/32, ::1/128

k8s3 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  -------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.22/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.3/32, ::1/128

k8s10 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  --------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.29/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.10/32, ::1/128

k8s11 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  --------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.30/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.11/32, ::1/128

k8s5 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  -------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.24/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.5/32, ::1/128

k8s2 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  -------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.21/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.2/32, ::1/128

k8s8 | SUCCESS | rc=0 >>

--------------------------------------------------------------------
    Name    Speed      MTU  Mode          Summary
--  ------  -------  -----  ------------  -------------------------------------
UP  eth0    -1M       1500  Interface/L3  IP: 192.168.200.27/24(DHCP)
UP  lo      N/A      65536  Loopback      IP: 127.0.0.1/8, 10.1.3.8/32, ::1/128

The External IP Controller can use any subnet mask you tell it and using IpPool definition files, those yaml files that kubectl create accepts, you can vary the subnet mask based on the External IP selected.

The external IP controller can also auto-allocate the IP, just like the cloud provider code does. But after some time using it, it seems better to preset the IP using the ExternalIP keyword in the Services definition file.

Enabling IP External controller

There are instructions on the github page on how to configure it manually. It was easier through to just include the kargo-compatible external IP controller role in the Kargo ansible playbook, then add this diff to the playbook. Then run ansible-playbook cluster.yml -t externalip to install the feature on the Kubernetes cluster.

iff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml
index d87d687..ec2a07f 100644
--- a/inventory/group_vars/all.yml
+++ b/inventory/group_vars/all.yml
@@ -1,3 +1,22 @@
+# External IP controller settings
+
+# Use the latest external ip controller code
+extip_image_tag: "latest"
+
+# Only create /32 entries
+extip_mask: 32
+
+# attach the created IPs to the lo interface
+extip_iface: lo
+
+# For ECMP place the generated IPs on 2 kubernetes minions
+extip_ctrl_replicas: 2
+
+# Apply IP on all claimcontrollers..use network ECMP
+extip_distribution: all
+
diff --git a/cluster.yml b/cluster.yml
index becad3b..d9225ea 100644
--- a/cluster.yml
+++ b/cluster.yml
@@ -26,7 +26,7 @@
 - hosts: k8s-cluster:etcd:calico-rr
   any_errors_fatal: true
   roles:
-    - { role: kubernetes/preinstall, tags: preinstall }
+    - { role: kubernetes/preinstall, tags: ['preinstall','externalip'] }
     - { role: docker, tags: docker }
     - { role: rkt, tags: rkt, when: "'rkt' in [ etcd_deployment_type, kubelet_deployment_type ]" }

@@ -58,6 +58,7 @@
   roles:
     - { role: dnsmasq, when: "dns_mode == 'dnsmasq_kubedns'", tags: dnsmasq }
     - { role: kubernetes/preinstall, when: "dns_mode != 'none' and resolvconf_mode == 'host_resolvconf'", tags: resolvconf }
+    - { role: externalip , tags: 'externalip' }