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-1

Slug : 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/hosts ou 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 :

  1. ClusterIssuer solver dns01.route53 (region ca-central-1, hostedZoneID)
  2. Role IAM : route53:ChangeResourceRecordSets limité à la zone lab
  3. 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 :

  1. ClusterIssuer prod + staging séparés
  2. Alertes sur Certificate non Ready / expiration < 14 j (Prometheus rules ou cron kubectl)
  3. RBAC : qui peut créer ClusterIssuer ?
  4. NetworkPolicy sur cert-manager namespace
  5. Pin chart + revue upgrades CRD
  6. Redirect HTTPS + HSTS côté Ingress
  7. 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

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

← Catalogue Tutoriels

Pour aller plus loin — hubs live

Retour parcours Catalogue Tutoriels — hub de la série et leçons sœurs.