Déploiement de metalLB dans un cluster Kubernetes

Table des matières
Commençons par un point d’avancement dans le voyage cloud native :
✔️ Cluster opérationnel
✔️ Stockage résilient
✔️ Création et mise à disposition d’images personnalisées
👉 Réseau & certificats
Objectifs
🎯 Publier un micro service et le rendre accessible depuis un navigateur.
Déploiement de l’applicatif
Reprenons le prototype d’un précédent article
En deux coups de cuillère à pot l’outil kubectl nous génère la source à injecter dans notre cluster.
$ kubectl create deployment protopod --image docker.io/redteamsfr/proto:v1 --dry-run=client -o yaml --namespace protopod --replicas 3
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: protopod
name: protopod
namespace: protopod
spec:
replicas: 3
selector:
matchLabels:
app: protopod
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: protopod
spec:
containers:
- image: docker.io/redteamsfr/proto:v1
name: proto
resources: {}
status: {}
Déploiement :
$ kubectl apply -f deployment.yml
deployment.apps/protopod created
Et voilà : notre application tourne …
kubectl -n protopod get pods
NAME READY STATUS RESTARTS AGE
protopod-6877b59c97-8r77h 1/1 Running 0 1mn
protopod-6877b59c97-bg2n8 1/1 Running 0 1mn
protopod-6877b59c97-h25vg 1/1 Running 0 1mn
… mais est isolé du monde.
Déploiement de Metallb
Pour bénéficier d’un service de LoadBalancing et exposer nos services, nous allons utiliser MetalLB.
MetalLB : le brief
👉 But: gérer des IPs externes sur des déploiements bare-metal de Kubernetes.
👉 Projet « open source » de Google.
Attention : bien qu’il soit réputé stable, il est officiellement en statut beta.
👉 Il est en mesure de gérer n’importe que tel type de trafic (pas uniquement du HTTP)
👉 Deux modes de fonctionnement (le choix est structurant pour l’architecture)
ARP - réponse aux requêtes ARP : simple efficace et surtout suffisant pour le lab dans un premier temps
BGP - utilisation de peering BGP et table de routage pour distribuer le traffic. L’avantage est la finesse (et le cloisonnement possible par VRF), inconvénients : la complexité d’implémentation et la nécessité d’avoir un router supportant le BGP.
Maintenant rentrons dans le dur !
Installation
Deux commandes suffisent :
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml
Regardons sous le capot :
Dans le nouveau namespace metallb-system :
$ kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/controller-7476b58756-vmjbn 0/1 ContainerCreating 0 12s
pod/speaker-6nfts 0/1 CreateContainerConfigError 0 12s
pod/speaker-9k6d4 0/1 ContainerCreating 0 12s
pod/speaker-b2knq 0/1 CreateContainerConfigError 0 12s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/speaker 3 3 0 3 0 kubernetes.io/os=linux 12s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/controller 0/1 1 0 12s
NAME DESIRED CURRENT READY AGE
replicaset.apps/controller-7476b58756 1 1 0 12s
Ce qu’il faut noter :
👉 Un animateur : le controller
👉 Un speaker est déployé sur chacun des noeuds
👉 Un secret est généré pour chiffrer les échanges entre chacun des noeuds (via les “speaker”)
$ kubectl get secrets memberlist -n metallb-system -o yaml
apiVersion: v1
data:
secretkey: Q0IvWlhoN3JwZStiWGFKQXdBMWkrRR==
kind: Secret
metadata:
creationTimestamp: "2022-05-26T09:25:03Z"
name: memberlist
namespace: metallb-system
ownerReferences:
- apiVersion: apps/v1
kind: Deployment
name: controller
uid: c7c5ed82-xxxx-xxxx-xxxx-xxce7cabb56d
resourceVersion: "10415"
uid: 84a19aa1-xxxx-xxxx-xxxx-xx3516d0d783
type: Opaque
Configuration de MetalLB
Définition de deux plages d’adresses dans lequel Metallb ira piocher les adresses.
metallb-configMap.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: config
namespace: metallb-system
labels:
app: metallb
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 172.16.3.1-172.16.3.254
- name: poc
protocol: layer2
addresses:
- 172.16.13.1-172.16.13.254
$ kubectl apply -f metallb-configMap.yml
configmap/config created
Reprenons un peu de hauteur :
Après cette digression retournons à notre problématique : rendre accessible notre applicatif.
Exposition de notre applicatif
Création du service :
protopod-service.yml
apiVersion: v1
kind: Service
metadata:
name: protopod
namespace: protopod
annotations:
metallb.universe.tf/address-pool: poc
spec:
type: LoadBalancer
selector:
app: protopod
ports:
- protocol: TCP
name: http
port: 80
targetPort: 5000
loadBalancerIP: 172.16.13.254
$ kubectl apply -f protopod-service.yml
Dans l’exemple ci-dessus une adresse statique est affectée, pour utiliser l’affectation dynamique il suffit de supprimer la ligne : ** loadBalancerIP: 172.16.13.254**.
Faisons un petit audit de notre configuration.
$ kubectl get svc -o wide -n protopod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
protopod LoadBalancer 10.107.47.109 172.16.13.254 80:30080/TCP 36d app=protopod
Si on résume nos briques :

$ kubectl -n protopod describe pods protopod-6877b59c97-
Name: protopod-6877b59c97-8r77h
Namespace: protopod
Priority: 0
Node: kube-worker03/172.16.1.13
Start Time: Fri, 01 Jul 2022 17:53:49 +0200
Labels: app=protopod
pod-template-hash=6877b59c97
Annotations: [...]
cni.projectcalico.org/podIP: 10.244.153.123/32
cni.projectcalico.org/podIPs: 10.244.153.123/32
Status: Running
IP: 10.244.153.123
IPs:
IP: 10.244.153.123
Controlled By: ReplicaSet/protopod-6877b59c97
Containers:
proto:
Container ID: cri-o://e53b67c3c828ec97be09c5cb2ade345c10831750a0d84621351b50c40fe1f996
Image: docker.io/redteamsfr/proto:v1
Image ID: docker.io/redteamsfr/proto@sha256:39286af0d19bac8311fee8bdd6846bd6dbcc1420e96e7ab9d78a87c70befaa8d
Port: 5000/TCP
Host Port: 0/TCP
[...]
Name: protopod-6877b59c97-bg2n8
Namespace: protopod
Priority: 0
Node: kube-worker02/172.16.1.12
Start Time: Fri, 01 Jul 2022 17:53:49 +0200
Labels: app=protopod
pod-template-hash=6877b59c97
Annotations: [...]
cni.projectcalico.org/podIP: 10.244.247.222/32
cni.projectcalico.org/podIPs: 10.244.247.222/32
Status: Running
IP: 10.244.247.222
IPs:
IP: 10.244.247.222
Controlled By: ReplicaSet/protopod-6877b59c97
Containers:
proto:
Container ID: cri-o://9b61ac70c880dc89427a9d6984c0a500c9a0de111a0b8daa950de986d2e4003d
Image: docker.io/redteamsfr/proto:v1
Image ID: docker.io/redteamsfr/proto@sha256:39286af0d19bac8311fee8bdd6846bd6dbcc1420e96e7ab9d78a87c70befaa8d
Port: 5000/TCP
Host Port: 0/TCP
[...]
Name: protopod-6877b59c97-h25vg
Namespace: protopod
Priority: 0
Node: kube-worker01/172.16.1.11
Start Time: Fri, 01 Jul 2022 17:53:49 +0200
Labels: app=protopod
pod-template-hash=6877b59c97
Annotations: [...]
cni.projectcalico.org/podIP: 10.244.132.9/32
cni.projectcalico.org/podIPs: 10.244.132.9/32
Status: Running
IP: 10.244.132.9
IPs:
IP: 10.244.132.9
Controlled By: ReplicaSet/protopod-6877b59c97
Containers:
proto:
Container ID: cri-o://6e711f71fcb0da02aef04c8ece24234c8ae192b645adfd9c66cbc852c4a1c31c
Image: docker.io/redteamsfr/proto:v1
Image ID: docker.io/redteamsfr/proto@sha256:39286af0d19bac8311fee8bdd6846bd6dbcc1420e96e7ab9d78a87c70befaa8d
Port: 5000/TCP
Host Port: 0/TCP
[...]
$ kubectl -n protopod describe service protopod
Name: protopod
Namespace: protopod
Labels: <none>
Annotations: metallb.universe.tf/address-pool: poc
Selector: app=protopod
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.107.47.109
IPs: 10.107.47.109
IP: 172.16.13.254
LoadBalancer Ingress: 172.16.13.254
Port: http 80/TCP
TargetPort: 5000/TCP
NodePort: http 30080/TCP
Endpoints: 10.244.132.9:5000,10.244.153.123:5000,10.244.247.222:5000
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal nodeAssigned 38m (x5 over 38m) metallb-speaker announcing from node "kube-worker03"
Warning AllocationFailed 38m (x2 over 38m) metallb-controller Failed to allocate IP for "protopod/protopod": unknown pool "poc"
Normal IPAllocated 31m metallb-controller Assigned IP ["172.16.13.1"]
Normal LoadbalancerIP 6m12s service-controller -> 172.16.13.254
Normal nodeAssigned 6m9s (x4 over 31m) metallb-speaker announcing from node "kube-worker01"
Normal IPAllocated 6m9s metallb-controller Assigned IP ["172.16.13.254"]
Confirmation :
$ kubectl get all -n protopod
NAME READY STATUS RESTARTS AGE
pod/protopod-6877b59c97-8r77h 1/1 Running 0 10m
pod/protopod-6877b59c97-bg2n8 1/1 Running 0 10m
pod/protopod-6877b59c97-h25vg 1/1 Running 0 10m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/protopod LoadBalancer 10.107.47.109 172.16.13.1 80:30080/TCP 36d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/protopod 3/3 3 3 10m
NAME DESIRED CURRENT READY AGE
replicaset.apps/protopod-6877b59c97 3 3 3 10m

Avant de conclure une petite astuce de dépannage
Pas mal de temps perdu avec un status “< pending >”
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/nginx-8f458dc5b-4hqwb 1/1 Running 0 104s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13h
service/nginx LoadBalancer 10.104.160.207 <pending> 80:30991/TCP 44s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 105s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-8f458dc5b 1 1 1 104s
Comment souvent la réponse est dans les journaux…
Il y avait une typo dans mon fichier de configuration.
kubectl logs pod/controller-7476b58756-4xlrv -n metallb-system
[...]
{"caller":"level.go:63","configmap":"metallb-system/config","error":"could not parse config: yaml: line 3: mapping values are not allowed in this context","event":"configStale","level":"error","msg":"config (re)load failed, config marked stale","ts":"2022-05-25T08:33:01.379041647Z"}
Notre pod est accessible mais les échanges ne sont pas chiffrés, il faut maintenant se pencher sur les problématiques de certificats mais ce sera pour un prochain épisode …