4204d4

K8S – LoadBalancer externe + Traefik

Load Balancer Icon

1 – Mise en situation

Kubernetes ne m’offre pas de service de balance de charge (load balancer externe au réseau K8s) pour plusieurs applications qui partagent le même point d’entrée (une seule adresse IP/même port).

La raison étant que ces services sont habituellement intégrés aux systèmes des fournisseurs Infonuagique: Google Cloud, Azure, AWS, etc.

En labo, nous avons utilisé le tunnel ‘kubectl port-forward service/…’ ou bien celui de Docker Desktop/K8s comme solution externe.

Pour le déploiement natif d’un amas K8S, il faut se rabattre sur des solutions tierces.

Par exemple: metallb, etc.


2 – Installation de MetalLB

2.1 – Installation

# Sur le cluster K8S suivant:
kubectl get nodes

# Résultat:
# NAME    STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP
# k8s01   Ready    control-plane   155d   v1.25.3   192.168.2.51   <none>
# k8s02   Ready    worker          155d   v1.25.3   192.168.2.52   <none>
# k8s03   Ready    worker          155d   v1.25.3   192.168.2.53   <none>
# k8s04   Ready    worker          155d   v1.25.3   192.168.2.54   <none>
# k8s05   Ready    worker          155d   v1.25.3   192.168.2.55   <none>

# ---------------------------------------------------------
# Installer metallb (LoadBalancer externe)
# NOTE: Ajuster le numéro de version. Référence: https://metallb.io/installation/
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml

# Résultat:
# namespace/metallb-system created
# customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
# customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
...
metallb-webhook-configuration created

# ---------------------------------------------------------
# Afficher les ressources de l'espace de nom du LB
kubectl get all -n metallb-system -o wide

# Résultat:
# NAME                              READY   STATUS    RESTARTS   AGE     IP             NODE 
# pod/controller-844979dcdc-64nms   1/1     Running   0          5m46s   10.244.77.5    k8s04
# pod/speaker-7r8bw                 1/1     Running   0          5m46s   192.168.2.55   k8s05
# pod/speaker-8n5x5                 1/1     Running   0          5m46s   192.168.2.52   k8s02
# pod/speaker-md92s                 1/1     Running   0          5m45s   192.168.2.53   k8s03
# pod/speaker-rb4wh                 1/1     Running   0          5m45s   192.168.2.54   k8s04
# pod/speaker-sth9t                 1/1     Running   0          5m46s   192.168.2.51   k8s01
# 
# NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE     SELECTOR
# service/webhook-service   ClusterIP   10.103.93.242   <none>        443/TCP   6m10s   component=controller
# 
# NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE     CONTAINERS   IMAGES                            SELECTOR
# daemonset.apps/speaker   5         5         5       5            5           kubernetes.io/os=linux   6m10s   speaker      quay.io/metallb/speaker:v0.13.9   app=metallb,component=speaker
# 
# NAME                         READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                               SELECTOR
# deployment.apps/controller   1/1     1            1           6m10s   controller   quay.io/metallb/controller:v0.13.9   app=metallb,component=controller
# 
# NAME                                    DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES                               SELECTOR
# replicaset.apps/controller-844979dcdc   1         1         1       5m46s   controller   quay.io/metallb/controller:v0.13.9   app=metallb,component=controller,pod-template-hash=844979dcdc

2.2 – Renseigner la plage IP

Il faut maintenant renseigner la plage d’adresse IP que MetalLB pourra utiliser comme points d’entrées. Ce sont des adresses valides et disponibles dans votre réseau local.

nano metallb-config.yaml

2.2.1 – Coller le manifeste suivant et sauvegarder

NOTE: Pour la plage d’adresses, il faut utiliser les IP disponibles dans le laboratoire.

Plage IP - D139


🛑 NOTE IMPORTANTE: Les adresses IP disponibles au D139 pour réaliser ce laboratoire sont situées entre 192.168.139.100 et 192.168.139.254. L’enseignant va attribuer deux (2) adresses IP par station de travail.


apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: mon-premier-groupe-adresses-d139
  namespace: metallb-system
spec:
  addresses:
    # Attention, utiliser la plage fournie en laboratoire!!  
  - 192.168.139.90-192.168.139.99
  autoAssign: true        # défaut: true, mais explicite c'est mieux
  avoidBuggyIPs: true     # évite .0 et .255  

---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: first-pool-l2adv
  namespace: metallb-system
  labels:
    env: lab
spec:
  ipAddressPools:
    - mon-premier-groupe-adresses-d139  

💡 Le rôle de L2Advertisement

Indique à MetalLB quels pools d’IPs annoncer et sur quelles interfaces/nœuds. Sans quoi, MetalLB ne sait pas qu’il doit annoncer les adresses d’IPAddressPool, même si celles-ci sont bien configurées.

💡Comportement sans L2Advertisement

Sans cette ressource, aucune IP ne sera annoncée en ARP/NDP → les LoadBalancers auront une IP assignée, mais elle sera injoignable depuis le réseau.


2.2.2 – Appliquer le manifeste

kubectl apply -f Metallb-config.yaml

2.5 Tester le loadbalancer de type métal

kubectl expose deployment nginx-deployment \
  --name=nginx-service \
  --type=LoadBalancer \
  --port=80 \
  --target-port=80

kubectl expose deployment nginx-deployment \
  --name=nginx-service \
  --type=LoadBalancer \
  --port=80 \
  --target-port=80

kubectl get services
NAME            TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1      <none>           443/TCP        28m
nginx-service   LoadBalancer   10.109.182.2   192.168.139.91   80:31429/TCP   3s 

## --­> Remarquer EXTERNAL-IP = 192.168.139.91

3 – Installation d’un Ingress (reverse proxy) Traefik sur K8S

Documentation officielle

NOTE: 👉 La solution la plus simple est d’installer via helm. Voir ici pour l’installation de helm


Installation de helm sous Linux-Ubuntu:

$ sudo snap install helm --classic

3.1 – Déploiement d’un service ingress de type Traefik via Helm

helm repo add traefik https://traefik.github.io/charts
helm repo update

💡 NOTE: le fichier values.yaml' contient des informations qui vont être utilisées par helm` pour créer le manifeste de l’application.

ingressRoute:
  dashboard:
    enabled: true
    matchRule: Host(`dashboard.labo420`)
    entryPoints:
      - web

providers:
  kubernetesGateway:
    enabled: true

gateway:
  listeners:
    web:
      namespacePolicy:
        from: All
kubectl create namespace traefik
helm install traefik traefik/traefik -f values.yaml --wait --namespace traefik

# Suite à des modifications au fichier values.yaml:
helm upgrade traefik traefik/traefik -n traefik -f values.yaml

# Pour désinstaller une application installée avec Helm
helm uninstall traefik --namespace traefik
kubectl delete namespace traefik 

NOTE: Le ‘namespace’ est facultatif, sinon sera installé dans ‘default’

Il y a maintenant un service de type LoadBalancer qui roule et qui a obtenu une adresse IP (192.168.2.60) de MetalLB:

$ kgs -n traefik
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)                      AGE
traefik   LoadBalancer   10.106.10.59   192.168.2.60   80:32343/TCP,443:32229/TCP   31m

Il est maintenant possible d’avoir accès au réseau k8s via cette adresse IP:

Erreur 404 Traefik

Note: Nous obtenons une erreur 404 car il n’y a pas encore de routes de définies. Par contre, cette réponse nous indique qu’il y a un service HTTP à l’entrée, sauf pour le ‘Dashboard’ via le nom de DNS dashboard.labo420.

NOTE: Dans le cas d’un déploiement sans load balancer, utiliser un port-forward:

kubectl port-forward -n traefik svc/traefik 80

Dashboard Traefik


3.1.5 - Déployer un whoami standard (voir docum de Traefik)

# whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
        - name: whoami
          image: traefik/whoami
          ports:
            - containerPort: 80
---

# whoami-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: whoami
spec:
  ports:
    - port: 80
  selector:
    app: whoami

---
# whoami-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`whoami.labo420`)
      kind: Rule
      services:
        - name: whoami
          port: 80    

Erreur 404 Traefik


3.2 – Déployer un service Web (superMinou)

Manifeste SuperMinou

# Modifié pour la demo ingress
apiVersion: apps/v1
kind: Deployment
metadata:
  name: superminou
  labels:
    app: un-superminou
spec:
  replicas: 5
  selector:
    matchLabels:
      mon-app: un-superminou
  template:
    metadata:
      labels:
        mon-app: un-superminou
    spec:
      containers:
      - name: nginx
        image: alainboudreault/superminou:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: superminou-service
spec:
  selector:
    mon-app: un-superminou
  # type: par defaut = ClusterIP, ce qui est requis pour le service ingress
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Manifeste SuperPitou

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    mon-app: un-superpitou
  name: super-pitou
spec:
  replicas: 3
  selector:
    matchLabels:
      mon-app: un-superpitou
  template:
    metadata:
      labels:
        mon-app: un-superpitou
    spec:
      volumes:
      - name: webdata
        emptyDir: {}
      initContainers:
      - name: web-content
        image: busybox
        volumeMounts:
        - name: webdata
          mountPath: "/webdata"
        command: ["/bin/sh", "-c", 'echo "<h1>Je suis un super <font color=blue>PITOU</font></h1><hr/><h2>Servi par: <?php echo gethostname(); ?></h2>" > /webdata/index.php']
      containers:
      - image: php:8.0.3-apache-buster
        name: php-apache
        volumeMounts:
        - name: webdata
          mountPath: "/var/www/html"

---
apiVersion: v1
kind: Service
metadata:
  name: superpitou-service
spec:
  selector:
    mon-app: un-superpitou
  # type: par defaut = ClusterIP, ce qui est requis pour le service ingress
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

3.3 – Fichier: ingress-demo.yml

# whoami-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: super
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`superminou.labo420`)
      kind: Rule
      services:
        - name: superminou-service
          port: 80
    - match: Host(`superpitou.labo420`)
      kind: Rule
      services:
        - name: superpitou-service
          port: 80
kubectl apply -f ingress-demo.yml

# -----------------------------------------------------------
$ kubectl get ingressRoute
NAME     AGE
super    4m4s
whoami   65m


# -----------------------------------------------------------
alain@k8smaster:~/Documents$ kubectl describe ingressRoute super
Name:         super
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  traefik.io/v1alpha1
Kind:         IngressRoute
Metadata:
  Creation Timestamp:  2026-01-13T21:32:57Z
  Generation:          1
  Resource Version:    100098
  UID:                 ebecfc4b-98f9-4aa2-bc82-4dcd3226793b
Spec:
  Entry Points:
    web
  Routes:
    Kind:   Rule
    Match:  Host(`superminou.labo420`)
    Services:
      Name:  superminou-service
      Port:  80
    Kind:    Rule
    Match:   Host(`superpitou.labo420`)
    Services:
      Name:  superpitou-service
      Port:  80
Events:      <none>

3.4 – Fichier /etc/hosts

192.168.2.60  superminou.labo420 superpitou.labo420

3.5 – Tester l’ingress (reverse proxy)

SuperMinou Demo

SuperPitou Demo



💡 Exemple complet version Google-Cloud et DuckDNS

NOTE: Le loadbalancer (par exemple MetalLB) sera géré par le Cloud provider.

Fichier values.yaml

# values.yaml
# Sans autres directives, les rêgles suivantes seront en fonction:
# Les ports 80 et 443 en points d'entrée
# Le Dashboard sera disponible via 'matchrule'
# Les routes seront disponibles dans tous le namespace.
# Activera le 'Kubernetes Gateway API provider'
ingressRoute:
  dashboard:
    enabled: true
    matchRule: Host(`dashboard.4204d4.duckdns.org`)
    entryPoints:
      - web

providers:
  kubernetesGateway:
    enabled: true

gateway:
  listeners:
    web:
      namespacePolicy:
        from: All
kubectl create namespace traefik
./helm install traefik traefik/traefik -f values.yaml --wait --namespace traefik

Le fichier déploiement

# Tester un reverse proxy avec Traefik Ingress Controller, version pour GCloud
# Tester avec http://superpitou.4204d4.duckdns.org/
# Tester avec http://superminou.4204d4.duckdns.org/
# Pré-requis: un cluster Kubernetes avec un Ingress Controller de type Traefik installé et configuré pour gérer les hôtes vers 4204d4.duckdns.org.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: superminou
  labels:
    app: un-superminou
spec:
  replicas: 3
  selector:
    matchLabels:
      mon-app: un-superminou
  template:
    metadata:
      labels:
        mon-app: un-superminou
    spec:
      containers:
      - name: nginx
        resource: 
        image: alainboudreault/superminou:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: superminou-service
spec:
  selector:
    mon-app: un-superminou
  # type: par defaut = ClusterIP, ce qui est requis pour le service ingress
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    mon-app: un-superpitou
  name: super-pitou
spec:
  replicas: 1
  selector:
    matchLabels:
      mon-app: un-superpitou
  template:
    metadata:
      labels:
        mon-app: un-superpitou
    spec:
      volumes:
      - name: webdata
        emptyDir: {}
      initContainers:
      - name: web-content
        image: busybox
        volumeMounts:
        - name: webdata
          mountPath: "/webdata"
        command: ["/bin/sh", "-c", 'echo "<h1>Je suis un super <font color=blue>PITOU</font></h1><hr/><h2>Servi par: <?php echo gethostname(); ?></h2>" > /webdata/index.php']
      containers:
      - image: php:8.0.3-apache-buster
        name: php-apache
        volumeMounts:
        - name: webdata
          mountPath: "/var/www/html"

---
apiVersion: v1
kind: Service
metadata:
  name: superpitou-service
spec:
  selector:
    mon-app: un-superpitou
  # type: par defaut = ClusterIP, ce qui est requis pour le service ingress
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80      

---

# whoami-ingressroute.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: super
spec:
  entryPoints:
    - web
  routes:
    - match: Host(`superminou.4204d4.duckdns.org`)
      kind: Rule
      services:
        - name: superminou-service
          port: 80
    - match: Host(`superpitou.4204d4.duckdns.org`)
      kind: Rule
      services:
        - name: superpitou-service
          port: 80

Certificats TLS - À compléter


Crédits

Document rédigé par Alain Boudreault © 2021-2026
Version 2026.04.27.1
Site par ve2cuy