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 :
| Type | Fonctionnement | Limite |
|---|---|---|
| NodePort | Expose sur un port du nœud (30000-32767) | URLs moches (http://ip:31234), pas de TLS natif |
| LoadBalancer | Provisionne un LB externe | 1 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 :
| Controller | Points forts |
|---|---|
| Traefik | Auto-discovery, Let's Encrypt natif, dashboard |
| NGINX Ingress | Mature, très configurable, standard de facto |
| HAProxy | Performant, entreprise |
| Contour | Envoy-based, moderne |
| Cilium | eBPF, 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)
# 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=INFOMéthode 2 : Manifestes 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# 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: 8080Vérifier l'installation
# 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
# 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: 80Créer un Ingress
# 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: 80Tester
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.comCertificats TLS avec Let's Encrypt
Le problème : HTTPS automatique
Pour avoir HTTPS, il faut des certificats TLS. Les options :
- Certificats manuels : achat, renouvellement manuel, galère
- 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 :
| Challenge | Fonctionnement | Cas d'usage |
|---|---|---|
| HTTP-01 | Let's Encrypt accède à http://domain/.well-known/acme-challenge/xxx | Serveur accessible depuis Internet sur port 80 |
| DNS-01 | Créer un enregistrement TXT _acme-challenge.domain | Wildcards, 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
- Un domaine géré par OVH
- Des credentials API OVH
Créer les credentials API OVH
Allez sur https://eu.api.ovh.com/createToken/
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/*
- Application name :
Notez les credentials :
- Application Key
- Application Secret
- Consumer Key
Créer le Secret Kubernetes
# 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"kubectl apply -f ovh-credentials.yamlConfiguration Traefik avec ACME DNS-01
Via Helm (recommandé)
# 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: /datahelm upgrade --install traefik traefik/traefik \
--namespace traefik \
--values traefik-values.yamlVia fichier de configuration statique
# 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: INFODéployer une application avec HTTPS
# 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 iciCertificat Wildcard
Pour un certificat *.votredomaine.com :
# 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
# 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.ymlExemple IngressRoute basique
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: letsencryptIngressRoute avec middlewares
# 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: letsencryptCréer le secret pour Basic Auth
# 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
# 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: trueVérifier les certificats
# 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/#/tlsDebugging
Logs Traefik
# Logs du pod Traefik
kubectl logs -n traefik deploy/traefik -f
# Augmenter le niveau de log
# Dans la config : log.level=DEBUGProblèmes courants
Certificat non généré
# 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
# 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 vertTester le challenge DNS
# 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.comAutres providers DNS
Cloudflare
# 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: cloudflareGandi
stringData:
GANDIV5_API_KEY: "votre-api-key"
# provider: gandiv5Liste complète
Traefik supporte de nombreux providers : https://doc.traefik.io/traefik/https/acme/#providers
Récapitulatif
Commandes utiles
# 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:8080Checklist déploiement HTTPS
- [ ] Traefik installé et fonctionnel
- [ ] Secret avec credentials DNS provider créé
- [ ]
certificatesResolversconfiguré 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
- Staging d'abord : testez avec
acme.caServer: https://acme-staging-v02.api.letsencrypt.org/directorypour éviter les rate limits - Persistence : activez la persistence pour ne pas re-demander les certificats à chaque redémarrage
- Wildcard : utilisez un certificat wildcard pour simplifier (
*.domain.com) - Monitoring : surveillez l'expiration des certificats (Traefik les renouvelle automatiquement 30 jours avant)