Kubernetes netwerk

Netwerk connecties binnen een Kubernetes omgeving kan een ingewikkelde zaak zijn. Pod networks, service networks, cluster IPs, container ports, host ports, node ports… soms zien we door de bomen het bos niet meer. Wellicht kan dit artikel wat licht doen schijnen op het ‘Kuberetes netwerk‘.

POD netwerk

Om bij de basis te beginnen, pods bevatten één of meerdere containers die samen op dezelfde node zijn geïnstalleerd en delen (dus) een gezamelijke netwerk. Dit houdt in dat alle containers in een pod elkaar kunnen bereiken via localhost. Als er bijvoorbeeld een WordPress container is die ‘luistert’ op TCP poort 8080 dan is deze voor alle overige containers bereikbaar via http://localhost:8080.

De default interface van Master is ‘eth0‘ met IP adres 192.168.1.10 en is verbonden met de interface genaamd ‘cni0‘ met IP adres 10.244.0.1
De default interface van Worker1 is ‘eth0‘ met IP adres 192.168.1.11 en is verbonden met de interface genaamd ‘cni0‘ met IP adres 10.244.1.1.
Worker2 heeft IP adres 192.168.1.12 op ‘eth0‘ en 10.244.2.1 op ‘cni0

Onderling kunnen de nodes dus via eth0 met elkaar communiceren en de pods communiceren onderling via een overlay-network. Deze gebruikt de ‘cni0‘ virtuele interface op Worker1 en dat overlay-network is dus 10.244.1.0/32. Op Worker2 is dat overlay-network 10.244.2.0/32. Alle pods krijgen een IP adres van het overlay-network en kunnen zo dus met elkaar communiceren. (CNI staat voor Container Network Interface) en wordt aangemaakt tijdens de initiële fase van de master(s) met de kubeadm parameter –pod-network-cidr
Een kubectl voorbeeld om pod IP’s te tonen:

$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP             NODE
hypriot-578c989b88-9plzz   1/1     Running   0          133m   10.244.1.102   worker1                           
nginx-654ddfd749-qqxq9     1/1     Running   0          138m   10.244.1.99    worker1              
nginx2-57bbd47f4d-xbkfv    1/1     Running   0          3d5h   10.244.2.81    worker2              

Zoals gezegd, kunnen alle pods communiceren met elkaar, zelfs als ze op een andere node zijn deployed. Flannel is (in dit geval) de default route voor dit netwerk en deze heeft eth0 weer als default gateway. Vanaf de Worker1 node:

$ ip route get 10.244.2.81
10.244.2.81 via 10.244.2.0 dev flannel.1 src 10.244.1.0
     cache

Hierdoor is de nginx2 pod op worker2 bereikbaar voor de pods op worker1 en vice versa.

DNS

Met het aanmaken van de cluster wordt door kubeadm een dns-service geïnstalleerd genaamd ‘kube-dns‘ die een selector gebruikt ‘k8s-app=kube-dns‘. De deployment ‘coredns‘ in de kube-system namespace heeft deze labels en de pods fungeren als DNS service voor het cluster.

De masters en workers hebben deze DNS services echter niet standaard in /etc/resolv.conf maar die kunnen we er bijzetten. Ook de standaard domain-name van de cluster kunnen we als zoek-pad in de search plaatsen. We zoeken eerst de dns-service in de namespace kube-system met:

$ kubectl get svc --namespace=kube-system -l k8s-app=kube-dns
kube-dns        ClusterIP   10.96.0.10              53/UDP,53/TCP,9153/TCP   44h   k8s-app=kube-dns

Vervolgens gebruiken we nslookup om het domain te vinden. Gebruik hiervoor het IP adres van de dns-service:

$ nslookup
> server 10.96.0.10
  Default server: 10.96.0.10
  Address: 10.96.0.10#53
> 10.96.0.10
  Server:        10.96.0.10
  Address:    10.96.0.10#53
 
  10.0.96.10.in-addr.arpa    name = kube-dns.kube-system.svc.cluster.local.

Zoals je kunt zien wordt dit vertaald naar [SERVICE_NAME].[NAMESPACE_NAME].svc.cluster.local waarbij ‘svc.cluster.local de domain naam is. Deze gaan we als zoekpad in /etc/resolv.conf plaatsen en tevens zetten we het IP adres van de dns-service als erbij:

search svc.cluster.local
nameserver 10.96.0.10
nameserver 8.8.8.8

Indien dit nu voor elke node zo ingesteld wordt, kan vanaf elke node een service aangeroepen worden via [SERVICE_NAME].[NAMESPACE_NAME], bijvoorbeeld:

$ curl http://app-v1.production 

Congratulations! Version 1.0 of your application is running on Kubernetes.

Om de DNS te testen kan een pod gebruikt worden waarin NSLOOKUPs gedaan kunnen worden. Deze pod.yml:

apiVersion: v1 
kind: Pod 
metadata:
  name: dnsutils 
  namespace: default 
spec: 
  containers: 
  - name: dnsutils 
    image: k8s.gcr.io/e2e-test-images/jessie-dnsutils:1.3
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent 
  restartPolicy: Always

Nadat deze applied wordt kan een nslookup gedaan worden met:

$ kubectl exec -it dnsutils -- nslookup <domeinnaam>

Eventuele logs kunnen dan opgevraagd worden. Voeg daarvoor ‘log’ toe aan de configmap via: met:

$ kubectl -n kube-system edit configmap coredns

Voeg dan ‘log’ toe aan de Corefile sectie (net boven ‘errors’). Binnen een aantal minuten worden coredns events gelogd en kunnen bekeken worden met:

$ kubectl logs -n kube-system -l k8s-app=kube-dns

(niet vergeten de log weer uit te zetten!)

Opmerking: Sommige Linux distributies (zoals Ubuntu) gebruiken ‘systemd-resolved.service‘ voor DNS requests en het bestand ‘/etc/resolv.conf‘ wordt op deze systemen overschreven (gelinked) door een stub-file. Deze resolved-service proxied de DNS requests naar de upstream DNS resolvers die geconfigureerd staan in ‘/etc/systemd/resolved.conf‘.

CoreDNS ConfigMap

De coredns configmap heeft standaard een verwijzing naar /etc/resolv.conf voor alle requests die buiten het Kubernetes netwerk vallen:

forward . /etc/resolv.conf

Dit kan gewijzigd worden naar een upstream DNS server, bijvoorbeeld:

forward . 8.8.8.8

Indien de DNS verzoeken voor stub-domains naar een andere DNS server dienen te worden gestuurd, kan een stub-domain-stanza toegevoegd worden aan de configMap. Bijvoorbeeld:

digitalinfo.local:53 {
    errors
    cache 30
    forward . 192.168.178.1
}

De complete configmap ziet er dan alsvolgt uit:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
          lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . 8.8.8.8
        cache 30
        loop
        reload
        loadbalance
    } # STUBDOMAINS
    digitalinfo.local:53 {
      errors
      cache 30
      forward . 192.168.178.1
    }

Als altternatief kunnen host-entries toegevoegd worden aan de coredns ConfigMap. Dat ziet er dan bijvoorbeeld zo uit:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
          lameduck 5s
        }
        hosts {
          192.168.1.1 server1.domain.com
          192.168.1.2 server2.domain.com
          fallthrough
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . "/etc/resolv.conf"
        cache 30
        loop
        reload
        loadbalance
    }

NetworkPolicy

Uiteraard kan een NetworkPolicy gebruikt worden om te voorkomen dat pods van verschillende namespaces elkaar kunnen bereiken. Helaas werkt flannel niet met networkpolicies maar daar kan eventueel Calico op gezet worden of gebruik Weaver als CNI. Een voorbeeld: (networkpolicy.yml)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-from-other-namespaces
spec:
  podSelector:
    matchLabels:
  ingress:  
  - from: 
    - podSelector: {} 

apply dit dan in de te isoleren namespace.

Service

In kubernetes kunnen services aangemaakt worden om pods/applicaties te kunnen bereiken van buiten de kubernetes cluster. Deze services zorgen dan, m.b.v. zogenaamde ‘selectors’, welke pods bereikt dienen te worden. Voor ‘service’ zijn veschillende ‘types’ mogelijk:

ClusterIP

ClusterIP wordt gebruikt om pods met elkaar te (laten) connecten. Dit netwerk is alleen beschikbaar binnen het cluster.

NodePort

NodePort genereert een poort op elke node zodat via deze poort de onderliggende applicaties bereikt kunnen worden. Het poort-nummer wordt random gegenereerd bij aanmaken van de service en heeft een nummer boven 32000. Dit en dan gelijk het nadeel, je weet van te voren niet welk poort-nummer gekoppeld wordt, tenzij je dit vaststeld in de configuratie van de service. Daarnaast zal het node-network niet via Internet bereikbaar zijn dus werkt dit alleen binnen het cluster-netwerk. Een proxy-service kan dit echter wel beschikbaar maken, bijvoorbeeld nginx of HAProxy.

LoadBalancer

Bijna hetzelfde als NodePort echter wordt nu gebruik gemaakt van een LoadBalancer om verkeer naar de applicaties te sturen. Een LoadBalancer maakt geen gebruik van een specifiek poort-nummer maar van een extern IP adres. Deze functie dient dan aanwezig te zijn in de kubernetes cluster, iets wat de cloud-providers standaard aanbieden. Het IP adres van deze load-balancers bevinden zich dan op een te configureren netwerk dat via Internet bereikbaar gemaakt kan worden.

Ingress

Ingress is een andere methode om http– en https-services te kunnen bereiken vanaf een externe locatie, buiten de kubernetes cluster. Ingress dient echter als controller toegevoegd te worden aan de cluster om Ingress objecten te herkennen. Een populaire Ingress Controller is ‘nginx-ingress’.


zie ook deze pagina over de installatie van Nginx Ingress.