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.

https://metallb.universe.tf/concepts/maturity/

👉 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 :

alt
$ 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
alt

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 …

Related