Skip to content

Ingress et Traefik

Comprendre l'Ingress

Le problème : exposer des applications web

Jusqu'ici, nous avons vu deux façons d'exposer une application :

TypeFonctionnementLimite
NodePortExpose sur un port du nœud (30000-32767)URLs moches (http://ip:31234), pas de TLS natif
LoadBalancerProvisionne un LB externe1 LB (et 1 IP) par service = coûteux

Le problème : comment exposer 10 applications web avec des URLs propres (app1.example.com, app2.example.com) et du HTTPS, sans payer 10 load balancers ?

La solution : Ingress

L'Ingress est un objet Kubernetes qui gère l'accès HTTP/HTTPS aux services. Il agit comme un reverse proxy centralisé.

                         Internet


                    ┌─────────────────┐
                    │  Load Balancer  │  ← Un seul LB pour tout
                    │   (IP publique) │
                    └────────┬────────┘


                    ┌─────────────────┐
                    │    Ingress      │
                    │   Controller    │  ← Traefik, NGINX, etc.
                    │                 │
                    │  Règles:        │
                    │  app1.com → svc1│
                    │  app2.com → svc2│
                    │  app3.com → svc3│
                    └────────┬────────┘

           ┌─────────────────┼─────────────────┐
           ▼                 ▼                 ▼
      ┌─────────┐       ┌─────────┐       ┌─────────┐
      │ Service │       │ Service │       │ Service │
      │  app1   │       │  app2   │       │  app3   │
      └─────────┘       └─────────┘       └─────────┘

Ingress Controller

L'objet Ingress seul ne fait rien. Il faut un Ingress Controller qui :

  • Lit les objets Ingress
  • Configure le reverse proxy en conséquence
  • Gère le routage du trafic

Ingress Controllers populaires :

ControllerPoints forts
TraefikAuto-discovery, Let's Encrypt natif, dashboard
NGINX IngressMature, très configurable, standard de facto
HAProxyPerformant, entreprise
ContourEnvoy-based, moderne
CiliumeBPF, très performant

Pourquoi Traefik ?

Traefik est un reverse proxy moderne, cloud-native, avec des fonctionnalités qui le rendent idéal pour Kubernetes :

Avantages de Traefik

-> Auto-discovery

Traefik découvre automatiquement les services via les APIs Kubernetes. Pas besoin de recharger la configuration.

-> Let's Encrypt natif

Génération et renouvellement automatique des certificats TLS. Supporte les challenges HTTP-01 et DNS-01.

-> Dashboard intégré

Interface web pour visualiser les routes, services, et middlewares.

-> Middlewares

Fonctionnalités intégrées : rate limiting, authentification, headers, redirections, etc.

-> IngressRoute (CRD)

En plus des Ingress standards, Traefik propose ses propres CRDs plus expressifs.

Architecture Traefik

┌─────────────────────────────────────────────────────────────┐
│                         Traefik                              │
│                                                              │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐      │
│  │ Entrypoints │    │   Routers   │    │  Services   │      │
│  │             │    │             │    │             │      │
│  │  :80 (web)  │───▶│ Host/Path   │───▶│  Backend    │      │
│  │  :443(https)│    │  matching   │    │  servers    │      │
│  └─────────────┘    └──────┬──────┘    └─────────────┘      │
│                            │                                 │
│                     ┌──────▼──────┐                         │
│                     │ Middlewares │                         │
│                     │             │                         │
│                     │ - Auth      │                         │
│                     │ - RateLimit │                         │
│                     │ - Headers   │                         │
│                     └─────────────┘                         │
└─────────────────────────────────────────────────────────────┘

Installation de Traefik

Méthode 1 : Helm (recommandée)

sh
# Ajouter le repo Helm
helm repo add traefik https://traefik.github.io/charts
helm repo update

# Créer le namespace
kubectl create namespace traefik

# Installer Traefik
helm install traefik traefik/traefik \
  --namespace traefik \
  --set dashboard.enabled=true \
  --set dashboard.ingressRoute=true \
  --set logs.general.level=INFO

Méthode 2 : Manifestes YAML

yaml
# traefik-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik
  namespace: traefik
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: traefik
rules:
- apiGroups: [""]
  resources: ["services", "endpoints", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "networking.k8s.io"]
  resources: ["ingresses", "ingressclasses"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["extensions", "networking.k8s.io"]
  resources: ["ingresses/status"]
  verbs: ["update"]
- apiGroups: ["traefik.io", "traefik.containo.us"]
  resources: ["*"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: traefik
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik
subjects:
- kind: ServiceAccount
  name: traefik
  namespace: traefik
yaml
# traefik-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
  namespace: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik
      containers:
      - name: traefik
        image: traefik:v3.0
        args:
        - --api.dashboard=true
        - --api.insecure=true
        - --entrypoints.web.address=:80
        - --entrypoints.websecure.address=:443
        - --providers.kubernetesingress=true
        - --providers.kubernetescrd=true
        - --log.level=INFO
        ports:
        - name: web
          containerPort: 80
        - name: websecure
          containerPort: 443
        - name: dashboard
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: traefik
  namespace: traefik
spec:
  type: LoadBalancer
  selector:
    app: traefik
  ports:
  - name: web
    port: 80
    targetPort: 80
  - name: websecure
    port: 443
    targetPort: 443
  - name: dashboard
    port: 8080
    targetPort: 8080

Vérifier l'installation

sh
# Pods Traefik
kubectl get pods -n traefik
# NAME                       READY   STATUS    RESTARTS   AGE
# traefik-xxx                1/1     Running   0          1m

# Service (noter l'EXTERNAL-IP)
kubectl get svc -n traefik
# NAME      TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)
# traefik   LoadBalancer   10.96.xxx.xxx   203.0.113.10    80:31xxx/TCP,443:32xxx/TCP

# Accéder au dashboard
kubectl port-forward -n traefik svc/traefik 9000:8080
# Ouvrir http://localhost:9000/dashboard/

Ingress basique

Déployer une application de test

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

Créer un Ingress

yaml
# whoami-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
  rules:
  - host: whoami.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: whoami
            port:
              number: 80

Tester

sh
kubectl apply -f whoami.yaml
kubectl apply -f whoami-ingress.yaml

# Obtenir l'IP de Traefik
TRAEFIK_IP=$(kubectl get svc -n traefik traefik -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# Tester avec le header Host
curl -H "Host: whoami.example.com" http://$TRAEFIK_IP
# Hostname: whoami-xxx
# IP: 10.244.0.15
# ...

# Ou ajouter dans /etc/hosts
echo "$TRAEFIK_IP whoami.example.com" | sudo tee -a /etc/hosts
curl http://whoami.example.com

Certificats TLS avec Let's Encrypt

Le problème : HTTPS automatique

Pour avoir HTTPS, il faut des certificats TLS. Les options :

  1. Certificats manuels : achat, renouvellement manuel, galère
  2. Let's Encrypt : gratuit, automatique, standard

Les challenges Let's Encrypt

Let's Encrypt doit vérifier que vous contrôlez le domaine. Deux méthodes :

ChallengeFonctionnementCas d'usage
HTTP-01Let's Encrypt accède à http://domain/.well-known/acme-challenge/xxxServeur accessible depuis Internet sur port 80
DNS-01Créer un enregistrement TXT _acme-challenge.domainWildcards, serveurs internes, pas de port 80

Pourquoi DNS-01 ?

Le challenge DNS-01 est plus puissant :

  • Wildcards : un seul certificat pour *.example.com
  • Pas besoin du port 80 : fonctionne même si le serveur n'est pas accessible depuis Internet
  • Serveurs internes : parfait pour les homelabs

Inconvénient : nécessite un accès API au provider DNS (OVH, Cloudflare, etc.)


Configuration Traefik + Let's Encrypt + OVH

Prérequis OVH

  1. Un domaine géré par OVH
  2. Des credentials API OVH

Créer les credentials API OVH

  1. Allez sur https://eu.api.ovh.com/createToken/

  2. Remplissez :

    • Application name : traefik-acme
    • Application description : Traefik Let's Encrypt DNS challenge
    • Validity : Unlimited
    • Rights :
      • GET /domain/zone/*
      • POST /domain/zone/*
      • DELETE /domain/zone/*
  3. Notez les credentials :

    • Application Key
    • Application Secret
    • Consumer Key

Créer le Secret Kubernetes

yaml
# ovh-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
  name: ovh-credentials
  namespace: traefik
type: Opaque
stringData:
  OVH_ENDPOINT: "ovh-eu"
  OVH_APPLICATION_KEY: "votre-application-key"
  OVH_APPLICATION_SECRET: "votre-application-secret"
  OVH_CONSUMER_KEY: "votre-consumer-key"
sh
kubectl apply -f ovh-credentials.yaml

Configuration Traefik avec ACME DNS-01

Via Helm (recommandé)

yaml
# traefik-values.yaml
deployment:
  replicas: 1

ingressRoute:
  dashboard:
    enabled: true

logs:
  general:
    level: INFO
  access:
    enabled: true

# Entrypoints
ports:
  web:
    port: 80
    exposedPort: 80
    redirectTo:
      port: websecure  # Redirection HTTP → HTTPS
  websecure:
    port: 443
    exposedPort: 443
    tls:
      enabled: true

# Configuration ACME (Let's Encrypt)
certificatesResolvers:
  letsencrypt:
    acme:
      email: votre-email@example.com
      storage: /data/acme.json
      dnsChallenge:
        provider: ovh
        delayBeforeCheck: 30
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

# Variables d'environnement pour OVH
env:
- name: OVH_ENDPOINT
  valueFrom:
    secretKeyRef:
      name: ovh-credentials
      key: OVH_ENDPOINT
- name: OVH_APPLICATION_KEY
  valueFrom:
    secretKeyRef:
      name: ovh-credentials
      key: OVH_APPLICATION_KEY
- name: OVH_APPLICATION_SECRET
  valueFrom:
    secretKeyRef:
      name: ovh-credentials
      key: OVH_APPLICATION_SECRET
- name: OVH_CONSUMER_KEY
  valueFrom:
    secretKeyRef:
      name: ovh-credentials
      key: OVH_CONSUMER_KEY

# Persistence pour stocker les certificats
persistence:
  enabled: true
  size: 128Mi
  path: /data
sh
helm upgrade --install traefik traefik/traefik \
  --namespace traefik \
  --values traefik-values.yaml

Via fichier de configuration statique

yaml
# traefik-config.yaml (ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
  name: traefik-config
  namespace: traefik
data:
  traefik.yaml: |
    api:
      dashboard: true
      insecure: true
    
    entryPoints:
      web:
        address: ":80"
        http:
          redirections:
            entryPoint:
              to: websecure
              scheme: https
      websecure:
        address: ":443"
    
    providers:
      kubernetesIngress: {}
      kubernetesCRD: {}
    
    certificatesResolvers:
      letsencrypt:
        acme:
          email: votre-email@example.com
          storage: /data/acme.json
          dnsChallenge:
            provider: ovh
            delayBeforeCheck: 30
            resolvers:
              - "1.1.1.1:53"
              - "8.8.8.8:53"
    
    log:
      level: INFO

Déployer une application avec HTTPS

yaml
# app-https.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
spec:
  rules:
  - host: myapp.votredomaine.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp
            port:
              number: 80
  tls:
  - hosts:
    - myapp.votredomaine.com
    secretName: myapp-tls  # Traefik stocke le certificat ici

Certificat Wildcard

Pour un certificat *.votredomaine.com :

yaml
# wildcard-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wildcard-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
    traefik.ingress.kubernetes.io/router.tls.domains.0.main: "votredomaine.com"
    traefik.ingress.kubernetes.io/router.tls.domains.0.sans: "*.votredomaine.com"
spec:
  rules:
  - host: "*.votredomaine.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: default-backend
            port:
              number: 80
  tls:
  - hosts:
    - "*.votredomaine.com"
    - "votredomaine.com"

IngressRoute (CRD Traefik)

Traefik propose ses propres CRDs, plus expressifs que les Ingress standards.

Installer les CRDs

sh
# Inclus dans le chart Helm, sinon :
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.0/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml

Exemple IngressRoute basique

yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: myapp-ingressroute
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`myapp.votredomaine.com`)
    kind: Rule
    services:
    - name: myapp
      port: 80
  tls:
    certResolver: letsencrypt

IngressRoute avec middlewares

yaml
# Middleware : Basic Auth
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: basic-auth
spec:
  basicAuth:
    secret: auth-secret  # htpasswd format
---
# Middleware : Rate Limiting
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit
spec:
  rateLimit:
    average: 100
    burst: 50
---
# Middleware : Headers sécurité
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: security-headers
spec:
  headers:
    frameDeny: true
    contentTypeNosniff: true
    browserXssFilter: true
    stsSeconds: 31536000
    stsIncludeSubdomains: true
---
# IngressRoute avec middlewares
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: secure-app
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`secure.votredomaine.com`)
    kind: Rule
    middlewares:
    - name: basic-auth
    - name: rate-limit
    - name: security-headers
    services:
    - name: secure-app
      port: 80
  tls:
    certResolver: letsencrypt

Créer le secret pour Basic Auth

sh
# Générer le hash du mot de passe
htpasswd -nb admin monsuperpassword
# admin:$apr1$xxx...

# Créer le secret
kubectl create secret generic auth-secret \
  --from-literal=users='admin:$apr1$xxx...'

Exemple complet : Homelab avec Traefik

Architecture

                         Internet


                    ┌─────────────────┐
                    │   Box Internet  │
                    │  (port forward  │
                    │   80/443)       │
                    └────────┬────────┘


                    ┌─────────────────┐
                    │    Traefik      │
                    │   (NodePort/    │
                    │   HostNetwork)  │
                    └────────┬────────┘

        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
   ┌─────────┐          ┌─────────┐          ┌─────────┐
   │ Glance  │          │Vaultwarden│        │ Miniflux │
   │dashboard│          │  :30080  │          │  :30081 │
   │.home.com│          │.home.com │          │.home.com│
   └─────────┘          └─────────┘          └─────────┘

Configuration complète

yaml
# homelab-traefik.yaml

# Secret OVH
---
apiVersion: v1
kind: Secret
metadata:
  name: ovh-credentials
  namespace: traefik
type: Opaque
stringData:
  OVH_ENDPOINT: "ovh-eu"
  OVH_APPLICATION_KEY: "xxxxx"
  OVH_APPLICATION_SECRET: "xxxxx"
  OVH_CONSUMER_KEY: "xxxxx"

# IngressRoute pour Glance
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: glance
  namespace: glance
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`dashboard.votredomaine.com`)
    kind: Rule
    services:
    - name: glance
      port: 8080
  tls:
    certResolver: letsencrypt

# IngressRoute pour Vaultwarden
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: vaultwarden
  namespace: vaultwarden
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`vault.votredomaine.com`)
    kind: Rule
    services:
    - name: vaultwarden
      port: 80
  tls:
    certResolver: letsencrypt

# IngressRoute pour Miniflux
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: miniflux
  namespace: miniflux
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`rss.votredomaine.com`)
    kind: Rule
    services:
    - name: miniflux
      port: 8080
  tls:
    certResolver: letsencrypt

# Middleware redirect HTTP → HTTPS (global)
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: traefik
spec:
  redirectScheme:
    scheme: https
    permanent: true

Vérifier les certificats

sh
# Voir les certificats dans Traefik
kubectl exec -n traefik deploy/traefik -- cat /data/acme.json | jq '.letsencrypt.Certificates[].domain'

# Ou via le dashboard Traefik
kubectl port-forward -n traefik svc/traefik 9000:8080
# Aller sur http://localhost:9000/dashboard/#/tls

Debugging

Logs Traefik

sh
# Logs du pod Traefik
kubectl logs -n traefik deploy/traefik -f

# Augmenter le niveau de log
# Dans la config : log.level=DEBUG

Problèmes courants

Certificat non généré

sh
# Vérifier les logs ACME
kubectl logs -n traefik deploy/traefik | grep -i acme

# Erreurs courantes :
# - "unable to generate certificate" : vérifier les credentials OVH
# - "DNS propagation check failed" : augmenter delayBeforeCheck
# - "too many requests" : rate limit Let's Encrypt (5 certificats/semaine/domaine)

404 sur l'application

sh
# Vérifier l'Ingress
kubectl describe ingress myapp-ingress

# Vérifier les endpoints
kubectl get endpoints myapp

# Vérifier dans le dashboard Traefik
# HTTP Routers → vérifier que la route existe
# HTTP Services → vérifier que le service est vert

Tester le challenge DNS

sh
# Vérifier que Traefik peut créer l'enregistrement
# Dans les logs, chercher :
# "legolog: [INFO] acme: Checking DNS record propagation"
# "legolog: [INFO] acme: Waiting for DNS record propagation"

# Vérifier manuellement
dig TXT _acme-challenge.votredomaine.com

Autres providers DNS

Cloudflare

yaml
# Secret
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-credentials
  namespace: traefik
type: Opaque
stringData:
  CF_API_EMAIL: "votre-email@example.com"
  CF_DNS_API_TOKEN: "votre-api-token"

# Dans traefik-values.yaml
certificatesResolvers:
  letsencrypt:
    acme:
      dnsChallenge:
        provider: cloudflare

Gandi

yaml
stringData:
  GANDIV5_API_KEY: "votre-api-key"

# provider: gandiv5

Liste complète

Traefik supporte de nombreux providers : https://doc.traefik.io/traefik/https/acme/#providers


Récapitulatif

Commandes utiles

sh
# Voir les Ingress
kubectl get ingress -A

# Voir les IngressRoutes (CRD Traefik)
kubectl get ingressroute -A

# Voir les middlewares
kubectl get middleware -A

# Voir les certificats stockés
kubectl get secret -A | grep tls

# Logs Traefik
kubectl logs -n traefik deploy/traefik -f --tail=100

# Dashboard Traefik
kubectl port-forward -n traefik svc/traefik 9000:8080

Checklist déploiement HTTPS

  • [ ] Traefik installé et fonctionnel
  • [ ] Secret avec credentials DNS provider créé
  • [ ] certificatesResolvers configuré avec ACME
  • [ ] DNS pointant vers l'IP de Traefik
  • [ ] Ingress/IngressRoute avec certResolver: letsencrypt
  • [ ] Attendre ~2 minutes pour la génération du certificat
  • [ ] Vérifier les logs si problème

Bonnes pratiques

  1. Staging d'abord : testez avec acme.caServer: https://acme-staging-v02.api.letsencrypt.org/directory pour éviter les rate limits
  2. Persistence : activez la persistence pour ne pas re-demander les certificats à chaque redémarrage
  3. Wildcard : utilisez un certificat wildcard pour simplifier (*.domain.com)
  4. Monitoring : surveillez l'expiration des certificats (Traefik les renouvelle automatiquement 30 jours avant)