05/10/2023
NIS2: wat is de impact voor je bedrijf en wat kun je nu al doen?
30% van alle webverkeer is kwaadaardig (!). Nieuwe NIS2-wetgeving vergroot de cyberbeveiliging en onze digitale weerbaarheid. Wat betekent dat voor jouw bedrijf?
Blog
Hoe regel je automatisering van loadbalancers en storage classes met Kubernetes op een private cloud? De gebruikelijke plek om Kubernetes te installeren is op een Public Cloud. Alle aanbieders ondersteunen een manier om automatisch een cluster uit te rollen, met diepgaande integratie met de rest van de cloud. Denk hierbij aan Google GKE, Microsoft AKS en ook Fuga Cloud Enterprise Managed Kubernetes.
Maar wat als je Kubernetes wilt draaien vanaf een Private Cloud, bijvoorbeeld VMware? Bepaalde features die je misschien gewend bent zijn dan vaak niet aanwezig, zoals LoadBalancers met een automatisch publiek IP, en Storage Classes die automatisch een volume aanmaken.
Een volledig automatisch CI/CD proces is voor CYSO een vereiste. Omdat we bij CYSO alle onze diensten draaien op onze eigen VMware Private Cloud, moeten we deze twee blockers oplossen. Laten we kijken hoe we dit kunnen doen.
Op een Kubernetes platform met Cloud integratie zal je zien dat zodra er een Service van type LoadBalancer wordt aangemaakt, er direct een IP wordt gealloceerd:
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: traefik
app.kubernetes.io/instance: service
ports:
- protocol: TCP
port: 80
name: http
- protocol: TCP
port: 443
name: https
Het laden van deze definitie resulteert dan in het volgende:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
traefik-ingress LoadBalancer 10.104.146.73 93.94.224.164 80:32109/TCP,443:30450/TCP 47d
Op een ‘normaal’ Kubernetes platform zal er echter niet automatisch een IP worden gealloceerd. Kubernetes weet immers niet uit zichzelf welke IP’s er beschikbaar zijn. In een Public Cloud omgeving kan het platform zelf dat vertellen aan Kubernetes. Op een Private Cloud omgeving moet dat echter worden toegevoegd aan Kubernetes. Eén manier om dat te doen is met MetalLB.
MetalLB is een loadbalancer implementatie voor Kubernetes, die integreert met het lokale netwerk. Er zijn meerdere manieren waarop MetalLB zijn werk kan doen, maar de meest interessante is de BGP modus. Hiermee kan direct aan een router verteld worden welke IP’s er in gebruik zijn op Kubernetes, zodat verkeer dynamisch gerouteerd wordt naar de juiste nodes. Het enige wat nog handmatig gedaan moet worden is het instellen van de BGP peer configuratie op de router, en het selecteren van een IP range die MetalLB mag uitdelen op Kubernetes.
In dit voorbeeld gaan we ervoor zorgen dat MetalLB de range 1.2.3.4 – 1.2.3.8 mag uitdelen. We doen de aanname dat de router(s) waarnaar geadverteerd wordt al ingesteld zijn en BGP advertenties accepteren van alle nodes in het Kubernetes cluster. Het is ook aan te raden om enkel advertenties te accepteren voor IP’s uit de reeks die we geven aan MetalLB, dit wordt prefix matching genoemd.
De eerste stap is MetalLB zelf installeren met een manifest:
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml
Daarna moeten we MetalLB vertellen hoe hij moet peeren via BGP, en welke IP’s hij mag uitdelen:
kubectl create -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
apiVersion: v1
data:
config: |
peers:
- peer-address: 10.x.x.1
peer-asn: 65559
my-asn: 65559
password: "foo"
address-pools:
- name: public-pool
protocol: bgp
addresses:
- 1.2.3.4-1.2.3.8
EOF
Zodra MetalLB draait en de configuratie is geladen, zal aan de router kant te zien zijn dat elke node uit het Kubernetes cluster zich heeft aangemeld als peer. Op een FortiGate ziet dat er zo uit:
# get router info bgp summary
BGP router identifier 172.x.x.1, local AS number 65559
BGP table version is 1
1 BGP AS-PATH entries
0 BGP community entries
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
10.x.x.101 4 65559 4767 5436 0 0 0 00:52:00 2
10.x.x.102 4 65559 4812 5475 0 0 0 01:36:10 2
10.x.x.103 4 65559 3190 3644 0 0 0 02:32:26 2
Total number of neighbors 3
De laatste stap is het aanmaken van een Service met type LoadBalancer. Hierbij zijn er twee opties: MetalLB het IP laten uitkiezen, of deze handmatig aangeven. MetalLB zal ervoor zorgen dat een bepaald IP niet tweemaal wordt gebruikt. In dit voorbeeld rollen we een simpele webapplicatie met een LoadBalancer uit:
kubectl create -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
labels:
deployment: whoami
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-lb
annotations:
metallb.universe.tf/address-pool: public-pool
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: whoami
ports:
- name: http
port: 80
targetPort: 80
EOF
Na het opstarten van de Pods zal MetalLB beginnen met het adverteren van een route. Een route wordt enkel geadverteerd naar nodes die de Pod draaien. Zodra een Pod wordt verplaatst of verwijderd dan zullen de routes automatisch worden aangepast. In Kubernetes en op een FortiGate is dit zichtbaar:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
whoami-lb LoadBalancer 10.111.65.124 1.2.3.4 80:31174/TCP 24h
# get router info routing-table bgp
B 1.2.3.4/32 [200/0] via 10.x.x.101, vlxxxx, 00:57:11
[200/0] via 10.x.x.102, vlxxxx, 00:57:11
[200/0] via 10.x.x.103, vlxxxx, 00:57:11
Het resultaat is vergelijkbaar met een LoadBalancer op een Public Cloud.
Voor dynamische volumes op Kubernetes loop je op een Private Cloud tegen dezelfde problemen aan als met LoadBalancers: het platform is vaak niet in staat om flexibel volumes aan te maken en dit direct te gebruiken in Kubernetes. Zelfs als de storage laag wel dynamisch volumes kan aanmaken, dan moet deze storage alsnog worden geformateerd en gemount op de juiste locatie, afhankelijk van welke Pods het volume nodig hebben.
Kubernetes heeft ondersteuning voor een groot aantal Volume plugins, met elk hun eigen voor- en nadelen. Eén daarvan is de AccessMode, of: kan één of meerdere Pods de storage tegelijk benaderen. Bepaalde workloads hebben lees- en schrijfrechten nodig voor hetzelfde volume, denk bijvoorbeeld aan het CMS van een website die uploads moet verwerken. Elke Pod moet dan een geüpload bestand kunnen wegschrijven.
De Volume plugins die de ReadWriteMany modus ondersteunen en beschikbaar zijn op een Private Cloud zijn de volgende: CephFS, GlusterFS en NFS. Uit deze lijst heeft enkel GlusterFS ondersteuning voor Storage Classes. In dit voorbeeld zullen we GlusterFS op zo’n manier integreren dat Volumes automatisch kunnen worden aangemaakt vanuit Kubernetes, zonder dat daarvoor een handmatige actie nodig is op de storage laag.
Gluster is een schaalbare storage techniek die een bestandsysteem kan aanbieden via het eigen GlusterFS protocol of NFS. Voor het benaderen hiervan vanuit Kubernetes is echter een API nodig, omdat de storage laag vaak gescheiden staat van de Kubernetes nodes. Gluster heeft zelf geen ingebouwde API, maar via een apart project genaamd Heketi kan deze wel worden toegevoegd.
Het opzetten van Gluster samen met Heketi vereist wat werk en expertise, maar hoeft maar één keer te gebeuren. Voor dit artikel gaan we ervan uit dat er al een Gluster cluster met Heketi aanwezig is, en zullen we ons focussen op de configuratie van Kubernetes. Hiervoor is het belangrijk dat we tijdens de installatie van Kubernetes het admin wachtwoord en de FQDN van de Heketi API opschrijven, deze hebben we nodig tijdens het configureren van Kubernetes.
Het admin wachtwoord voegen we als eerste toe aan Kubernetes als Secret:
kubectl create -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: heketi-secret
namespace: kube-system
type: "kubernetes.io/glusterfs"
stringData:
key: ADMIN_PASSWORD
EOF
Daarna moeten we ervoor zorgen dat op alle Kubernetes nodes dezelfde versie van gluster-client
geïnstalleerd staat. Dit is afhankelijk van de Linux distributie, maar is meestal zo simpel als een:
$ apt install glusterfs-client
De laatste stap is het aanmaken van een Storage Class waarin we Kubernetes vertellen waar de Heketi API leeft, welke API credentials gebruikt moeten worden en welke soort volumes we willen aanmaken in Gluster. In dit voorbeeld maken we een Storage Class die volumes aanmaakt waarbij alles twee keer wordt opgeslagen voor redundantie, en we refereren naar het Secret die we eerder hebben aangemaakt:
kubectl create -f - <<EOF
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: gluster-heketi-external
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://gfs1:8080"
restuser: "admin"
secretName: "heketi-secret"
secretNamespace: "kube-system"
volumetype: "replicate:2"
EOF
Zodra de StorageClass is aangemaakt, kan Kubernetes automatisch volumes maken op Gluster. Er zijn meerdere manieren om dit te doen, maar de meest directe manier is om een PersistentVolumeClaim aan te maken:
kubectl create -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: website-uploads
annotations:
volume.beta.kubernetes.io/storage-class: gluster-heketi-external
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
EOF
In reactie hierop zal Kubernetes een PersistentVolume aanmaken die aan de claim voldoet, dus met de gekozen StorageClass en de opgegeven grootte:
$ kubectl get pvc website-uploads
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
website-uploads Pending gluster-heketi-external 12s
$ kubectl get pvc website-uploads
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
website-uploads Bound pvc-03c57d35-afc8-4b62-8495-7c9ae4557f17 2Gi RWX gluster-heketi-external 27s
$ kubectl get pv pvc-03c57d35-afc8-4b62-8495-7c9ae4557f17
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-03c57d35-afc8-4b62-8495-7c9ae4557f17 2Gi RWX Delete Bound namespace/website-uploads gluster-heketi-external 27s
Deze PersistentVolume is ook terug te vinden via Heketi, als we weten wat het interne ID is:
$ kubectl get pv pvc-03c57d35-afc8-4b62-8495-7c9ae4557f17 -ojson | jq -r '.spec.glusterfs.path' | sed 's/vol_//'
edcb81b740c8a4b048243bd2803cf73d
$ heketi-cli volume info edcb81b740c8a4b048243bd2803cf73d
Name: vol_edcb81b740c8a4b048243bd2803cf73d
Size: 2
Volume Id: edcb81b740c8a4b048243bd2803cf73d
Cluster Id: c1584b184593707ee6fc1bd613d59e1a
Mount: 10.xx.xx.132:vol_edcb81b740c8a4b048243bd2803cf73d
Mount Options: backup-volfile-servers=10.xx.xx.131
Block: false
Free Size: 0
Reserved Size: 0
Block Hosting Restriction: (none)
Block Volumes: []
Durability Type: replicate
Distribute Count: 1
Replica Count: 2
Snapshot Factor: 1.00
We hebben nu een volume met redundantie aangemaakt op Gluster door via Kubernetes daarom te vragen. Dit is vergelijkbaar met de werking op Public Clouds.
In het grote geheel van een Kubernetes cluster hebben we nu twee componenten toegevoegd, LoadBalancers en Storage Classes:
Deze twee componenten zorgen er samen voor dat we alle features hebben die we kunnen verwachten van Kubernetes op een Public Cloud dienst, maar dan op onze VMware Private Cloud.