cert-manager + Let’s Encrypt en prod
À la fin de ce tutoriel, vous installerez cert-manager, créerez un ClusterIssuer ACME Let’s Encrypt (staging puis prod), émettrez un certificat HTTP-01 via Ingress nginx, et comprendrez le chemin DNS-01 (wildcard, Route53 en
ca-central-1) — sans jamais committer de clé privée.Niveau : Intermédiaire · Temps estimé : 55–70 min · Versions cibles : Kubernetes 1.29+ · Helm 3 · cert-manager v1.16+ (chart Jetstack) · ingress-nginx · ACME Let’s Encrypt · Dernière vérification : 2026-09-11 · Region cloud (DNS-01) :
ca-central-1Slug :
wow-cert-manager-letsencrypt· Série : WOW (14/50) · Mot-clé SEO : cert-manager letsencrypt · Publish : HOLD → push Maître← Précédent : External Secrets Operator · Aussi : Ingress + Gateway API · Secrets K8s · Helm · Argo CD
Prérequis
- Cluster lab (kubeadm, k3s, EKS/AKS) +
kubectl+ Helm 3 - Contrôleur Ingress HTTP (idéalement ingress-nginx) exposé sur 80/443
- Un FQDN qui résout vers le LoadBalancer / NodePort du contrôleur (lab :
/etc/hostsou DNS staging) - Bases Secrets TLS — kubernetes-secrets
- Option DNS-01 : compte AWS lab, zone Route53, région
ca-central-1 - Coût : Let’s Encrypt = gratuit ; hors LB cloud / Route53 queries
kubectl version --client
helm version --short
kubectl get nodes
kubectl get ingressclass
Ce que nous allons construire
Lab TLS cert-manager (WOW 14/50)
├── Helm Jetstack : cert-manager + CRDs
├── ClusterIssuer ACME staging (HTTP-01)
├── App démo + Ingress annoté → Secret TLS auto
├── Ressource Certificate (explicite)
├── DNS-01 Route53 ca-central-1 (wildcard — overview)
├── Passage staging → prod + rate limits
└── Day-2 : renew, events, teardown
(Schéma — alt : « cert-manager : ClusterIssuer Let’s Encrypt, HTTP-01 Ingress, Secret TLS, DNS-01 Route53 ».)
Étape 1 — Pourquoi cert-manager en prod ?
Sans automate, les équipes collent des .pem à la main, ratent les renouvellements, et ouvrent des incidents TLS à 3 h du matin. cert-manager observe des ressources Kubernetes (Issuer / ClusterIssuer, Certificate) et parle ACME à Let’s Encrypt (ou une CA interne) pour écrire un Secret kubernetes.io/tls.
| Concept | Portée | Usage |
|---|---|---|
| Issuer | Namespace | Équipe / env isolé |
| ClusterIssuer | Cluster | Plateforme : un issuer « letsencrypt-prod » pour tous |
| Certificate | Namespace | Demande explicite (hosts, secretName, duration) |
| Annotation Ingress | Ingress | Raccourci : cert-manager crée le Certificate pour vous |
Angle DEH : le Secret TLS est un artefact cluster, pas un fichier Git. GitOps versionne Issuer/Certificate/Ingress ; jamais tls.key.
Trois anti-patterns : (1) ClusterIssuer prod dès le premier lab — rate limit LE ; (2) HTTP-01 sans chemin 80 ouvert depuis Internet ; (3) wildcards en HTTP-01 (impossible — il faut DNS-01).
Étape 2 — Installer cert-manager (Helm)
Repo officiel Jetstack :
helm repo add jetstack https://charts.jetstack.io
helm repo update
kubectl create namespace cert-manager
helm upgrade --install cert-manager jetstack/cert-manager
--namespace cert-manager
--version v1.16.2
--set crds.enabled=true
--wait
Vérifiez les pods (cert-manager, cainjector, webhook) Running :
kubectl get pods -n cert-manager
kubectl get crd | grep cert-manager.io
Pinnez la version chart en GitOps (Argo CD). crds.enabled=true au premier install ; les upgrades CRD suivent la doc Jetstack.
Étape 3 — ClusterIssuer staging (HTTP-01)
Toujours staging d’abord : https://acme-staging-v02.api.letsencrypt.org/directory. Les navigateurs ne feront pas confiance, mais vous validez le flux sans brûler le quota prod.
# clusterissuer-le-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: lab@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- http01:
ingress:
class: nginx
kubectl apply -f clusterissuer-le-staging.yaml
kubectl describe clusterissuer letsencrypt-staging
Attendu : Ready=True. Adaptez ingress.class à votre IngressClass (nginx, traefik, …).
Étape 4 — App démo + Ingress annoté
Déployez un backend minimal et un Ingress qui demande le certificat :
# demo-tls.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-tls
namespace: default
spec:
replicas: 1
selector:
matchLabels: { app: hello-tls }
template:
metadata:
labels: { app: hello-tls }
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata:
name: hello-tls
namespace: default
spec:
selector: { app: hello-tls }
ports: [{ name: http, port: 80, targetPort: 80 }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-tls
namespace: default
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: nginx
tls:
- hosts: [hello.lab.example.com]
secretName: hello-tls-secret
rules:
- host: hello.lab.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-tls
port: { name: http }
kubectl apply -f demo-tls.yaml
kubectl get certificate,certificaterequest,order,challenge -A
kubectl describe certificate hello-tls-secret -n default
Quand Certificate est Ready, le Secret hello-tls-secret contient tls.crt / tls.key. Testez HTTPS (certificat staging = warning navigateur normal).
Points clés HTTP-01 : LE doit atteindre http://<host>/.well-known/acme-challenge/... via le même Ingress. Firewall, WAF, ou auth basique sur / cassent souvent le challenge.
Étape 5 — Ressource Certificate explicite
L’annotation Ingress est pratique ; en plateforme, préférez un Certificate GitOps-visible :
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: hello-tls
namespace: default
spec:
secretName: hello-tls-secret
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
dnsNames:
- hello.lab.example.com
Même Secret, même renew. Utile pour mTLS interne, jobs hors Ingress, ou multi-hosts sans dupliquer les annotations.
Étape 6 — DNS-01 et wildcards (Route53 ca-central-1)
HTTP-01 ne couvre pas *.apps.example.com. DNS-01 crée un enregistrement _acme-challenge TXT. Sur AWS, solver Route53 + IAM least privilege (souvent via IRSA / Pod Identity, pas de clé longue durée dans un Secret Git).
Schéma mental :
- ClusterIssuer solver
dns01.route53(regionca-central-1, hostedZoneID) - Role IAM :
route53:ChangeResourceRecordSetslimité à la zone lab - Certificate avec
dnsNames: ["*.apps.example.com", "apps.example.com"]
Ne collez jamais AWS_SECRET_ACCESS_KEY dans un manifeste versionné. Preferez IRSA (EKS) ou External Secrets — External Secrets quand disponible.
Étape 7 — Passage prod et rate limits
Quand staging est vert :
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: platform@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
Basculez l’annotation / issuerRef vers letsencrypt-prod, changez éventuellement secretName pour forcer une nouvelle émission, activez ssl-redirect. Surveillez les rate limits Let’s Encrypt (échecs répétés, trop de certificats / domaine). Staging reste le filet de sécurité des CI.
Étape 8 — Day-2 : renew, observabilité, durcissement
cert-manager renouvelle avant expiration (souvent ~30 jours avant). Surveillez :
kubectl get certificate -A
kubectl describe certificate -n default hello-tls
kubectl get events -n default --field-selector involvedObject.kind=Certificate
Check-list prod DEH :
- ClusterIssuer prod + staging séparés
- Alertes sur
Certificatenon Ready / expiration < 14 j (Prometheus rules ou cronkubectl) - RBAC : qui peut créer ClusterIssuer ?
- NetworkPolicy sur
cert-managernamespace - Pin chart + revue upgrades CRD
- Redirect HTTPS + HSTS côté Ingress
- Aucun Secret TLS dans Git ; rotation = delete CertificateRequest / re-issue contrôlée
Troubleshooting
| Symptôme | Piste |
|---|---|
| Challenge pending | DNS Host ≠ Ingress ; port 80 fermé ; class Ingress faux |
| Order failed | Rate limit LE ; email / Terms ; CAA DNS |
| Secret vide | Certificate not Ready — kubectl describe Order/Challenge |
| Webhook timeout | CNI / NetworkPolicy bloque cert-manager-webhook |
| Wildcard KO | HTTP-01 utilisé → passer DNS-01 |
| Staging OK, prod KO | Quota / CAA / solver différent |
FAQ
Issuer ou ClusterIssuer ?
Plateforme multi-équipes : ClusterIssuer. Isolation stricte par ns : Issuer.
Annotation Ingress vs Certificate ?
Annotation = DX rapide. Certificate = contrat GitOps explicite (durée, renew, usages).
Puis-je utiliser une CA interne ?
Oui (ca issuer). Let’s Encrypt pour public Internet ; CA privée pour mTLS interne.
k3s / Traefik ?
Même modèle : adaptez ingress.class / solver. Voir aussi homelab k3s.
Où vivent les comptes ACME ?
Secrets privateKeySecretRef du ClusterIssuer — cluster only, backup contrôlé.
Quiz (5 questions)
1. HTTP-01 exige surtout :
– A. Un enregistrement TXT · B. Un chemin /.well-known/acme-challenge joignable en HTTP · C. Un wildcard DNS
2. Pour *.apps.example.com il faut :
– A. HTTP-01 · B. DNS-01 · C. Un Secret manuel openssl
3. On commence toujours par :
– A. Le serveur ACME prod · B. Le staging Let’s Encrypt · C. Un Issuer sans solver
4. Le Secret TLS doit être :
– A. Commité dans Git · B. Créé/renouvelé dans le cluster · C. Stocké en ConfigMap
5. crds.enabled=true sert à :
– A. Installer les CRD cert-manager · B. Activer Gateway API · C. Remplacer ingress-nginx
Réponses : 1‑B · 2‑B · 3‑B · 4‑B · 5‑A
Nettoyage lab
kubectl delete ingress hello-tls --ignore-not-found
kubectl delete certificate,secret -l app=hello-tls --ignore-not-found
kubectl delete -f demo-tls.yaml --ignore-not-found
kubectl delete clusterissuer letsencrypt-staging letsencrypt-prod --ignore-not-found
# Optionnel : helm uninstall cert-manager -n cert-manager
Pour aller plus loin
- Docs : cert-manager · ACME · Let’s Encrypt rate limits
- DEH : Ingress nginx / Gateway API · Helm · Secrets · Argo CD · NetworkPolicies
Maillage Platform / Sécurité K8s
| Ingress / TLS | kubernetes-ingress-gateway-api |
| Secrets | kubernetes-secrets · External Secrets (WOW) |
| GitOps | argocd-gitops-kubernetes |
| Homelab | wow-homelab-k3s |
Meta publication (SEO) — Publish: HOLD → WP-CLI Maître
- Title SEO : cert-manager + Let’s Encrypt en prod : HTTP-01, DNS-01, Ingress (2026)
- Meta description : TLS auto sur Kubernetes avec cert-manager et Let’s Encrypt : ClusterIssuer staging→prod, HTTP-01 Ingress nginx, DNS-01 Route53 ca-central-1, renew et FAQ FR.
- Focus keyword : cert-manager letsencrypt
- Schemas : Article + HowTo + FAQ
- Image mise en avant :
assets/web/devopelastichayway/cover-wow-cert-manager-letsencrypt-1200x630.webp(alt : cert-manager et Let’s Encrypt — TLS automatique Kubernetes) - Catégorie : Kubernetes / Platform · Niveau : Intermédiaire
- Slug / URL :
wow-cert-manager-letsencrypt→/tutoriels/wow-cert-manager-letsencrypt/ - Region lab DNS :
ca-central-1
Pour aller plus loin — hubs live
Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.