Handmatig certificaat management is een recept voor outages. Certificaten verlopen om 3 uur ’s nachts in een feestdagenweekend. Renewal processen leven in tribal knowledge. Teams deployen services zonder HTTPS omdat “het te ingewikkeld is.”

cert-manager automatiseert alles. Definieer welke certificaten je nodig hebt, en cert-manager regelt uitgifte, vernieuwing en Kubernetes Secret management. Voor altijd.

Dit is een van de eerste dingen die ik installeer in elk cluster.

Hoe cert-manager Werkt

flowchart TD
    subgraph cluster["Kubernetes Cluster"]
        CM["cert-manager"]
        CERT["Certificate<br/>Resource"]
        SECRET["TLS Secret"]
        INGRESS["Ingress"]
    end

    subgraph external["Extern"]
        LE["Let's Encrypt<br/>ACME Server"]
        DNS["DNS Provider"]
    end

    CERT -->|"watched"| CM
    CM -->|"maakt"| SECRET
    CM <-->|"ACME protocol"| LE
    CM <-->|"DNS challenge"| DNS
    SECRET -->|"mount"| INGRESS
  1. Je maakt een Certificate resource
  2. cert-manager vraagt een certificaat aan bij de issuer (Let’s Encrypt, Vault, etc.)
  3. cert-manager voltooit de challenge (HTTP-01 of DNS-01)
  4. cert-manager slaat het certificaat op in een Kubernetes Secret
  5. Je Ingress/Gateway gebruikt de Secret voor TLS

Vernieuwing gebeurt automatisch 30 dagen voor expiratie.

Installatie

helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

Voor GitOps met ArgoCD:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.14.0
    helm:
      values: |
        installCRDs: true
        prometheus:
          servicemonitor:
            enabled: true
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    syncOptions:
      - CreateNamespace=true

Issuers

Een Issuer vertelt cert-manager waar certificaten vandaan komen. Twee scopes:

  • Issuer — Werkt in één namespace
  • ClusterIssuer — Werkt over alle namespaces

Let’s Encrypt (ACME)

De meest voorkomende setup — gratis certificaten van Let’s Encrypt:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            class: nginx

Belangrijk: Begin met staging om rate limits te vermijden:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
      - http01:
          ingress:
            class: nginx

HTTP-01 vs DNS-01 Challenges

HTTP-01: Let’s Encrypt verifieert dat je het domein beheert door een bestand te plaatsen op /.well-known/acme-challenge/:

solvers:
  - http01:
      ingress:
        class: nginx

Werkt voor publiek toegankelijke services. Snel en simpel.

DNS-01: Let’s Encrypt verifieert via DNS TXT record:

solvers:
  - dns01:
      cloudflare:
        email: admin@example.com
        apiTokenSecretRef:
          name: cloudflare-api-token
          key: api-token

Vereist voor:

  • Wildcard certificaten (*.example.com)
  • Interne services die niet publiek toegankelijk zijn
  • Split-horizon DNS

Cloudflare DNS-01 Setup

# API Token Secret
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
  namespace: cert-manager
type: Opaque
stringData:
  api-token: "your-cloudflare-api-token"
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-cloudflare
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-cloudflare-account
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token

Cloudflare API token heeft Zone:DNS:Edit permissie nodig.

Private CA

Voor interne services, gebruik je eigen CA:

# Maak CA key pair
apiVersion: v1
kind: Secret
metadata:
  name: ca-key-pair
  namespace: cert-manager
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-ca-cert>
  tls.key: <base64-encoded-ca-key>
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: internal-ca
spec:
  ca:
    secretName: ca-key-pair

Of laat cert-manager een self-signed CA maken:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: my-ca
  secretName: my-ca-secret
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: my-ca-issuer
spec:
  ca:
    secretName: my-ca-secret

HashiCorp Vault

Integreer met Vault voor PKI:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: vault-issuer
spec:
  vault:
    server: https://vault.vault:8200
    path: pki/sign/my-role
    auth:
      kubernetes:
        role: cert-manager
        mountPath: /v1/auth/kubernetes
        secretRef:
          name: cert-manager-vault-token
          key: token

Certificaten Aanvragen

Certificate Resource

Vraag expliciet een certificaat aan:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-example-com
  namespace: production
spec:
  secretName: api-example-com-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - api.example.com
    - api-v2.example.com
  duration: 2160h  # 90 dagen
  renewBefore: 720h  # Vernieuw 30 dagen voor expiratie

Ingress Annotatie (Automatisch)

Laat cert-manager automatisch certificaten maken vanuit Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - api.example.com
      secretName: api-example-com-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

cert-manager ziet de annotatie, maakt een Certificate resource, en vult de Secret.

Gateway API

Voor Gateway API in plaats van Ingress:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: cilium
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-com-tls

Wildcard Certificaten

Voor *.example.com heb je DNS-01 nodig:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: production
spec:
  secretName: wildcard-example-com-tls
  issuerRef:
    name: letsencrypt-cloudflare
    kind: ClusterIssuer
  dnsNames:
    - "*.example.com"
    - "example.com"

Let op: Includeer zowel *.example.com als example.com — wildcard dekt niet het apex domein.

Cross-Namespace Secrets

Certificaten leven in één namespace, maar je hebt ze misschien elders nodig. Opties:

Reflector (Externe Controller)

Gebruik kubernetes-reflector:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: shared-cert
  namespace: cert-manager
  annotations:
    reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
    reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "production,staging"
spec:
  secretName: shared-cert-tls
  # ...

trust-manager

cert-manager’s officiële oplossing voor CA bundle distributie:

helm install trust-manager jetstack/trust-manager \
  --namespace cert-manager
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: my-ca-bundle
spec:
  sources:
    - secret:
        name: my-ca-secret
        key: ca.crt
  target:
    configMap:
      key: ca-bundle.crt
    namespaceSelector:
      matchLabels:
        trust-bundle: enabled

Monitoring en Troubleshooting

Check Certificaat Status

kubectl get certificates -A
kubectl describe certificate api-example-com -n production

Check Certificate Requests

kubectl get certificaterequests -A
kubectl describe certificaterequest api-example-com-xyz -n production

Check Orders en Challenges

kubectl get orders -A
kubectl get challenges -A

# Debug een vastgelopen challenge
kubectl describe challenge api-example-com-xyz-123 -n production

Prometheus Metrics

cert-manager exporteert metrics voor alerting:

# Alert op verlopende certificaten
- alert: CertificateExpiringSoon
  expr: certmanager_certificate_expiration_timestamp_seconds - time() < 604800
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "Certificaat {{ $labels.name }} verloopt binnen 7 dagen"

# Alert op gefaalde vernieuwingen
- alert: CertificateRenewalFailed
  expr: certmanager_certificate_ready_status{condition="False"} == 1
  for: 1h
  labels:
    severity: critical
  annotations:
    summary: "Certificaat {{ $labels.name }} is niet klaar"

Veelvoorkomende Problemen

Challenge blijft pending:

  • HTTP-01: Ingress routeert niet naar cert-manager solver
  • DNS-01: API credentials verkeerd of missende permissies

Rate limited:

  • Te veel certificaten aangevraagd bij Let’s Encrypt
  • Gebruik staging issuer voor testen
  • Consolideer in wildcard certificaten

Secret updatet niet:

  • Check Certificate resource status
  • Check CertificateRequest en Order resources
  • Bekijk cert-manager logs: kubectl logs -n cert-manager deploy/cert-manager

Best Practices

1. Gebruik ClusterIssuers

Tenzij je namespace isolatie nodig hebt, reduceren ClusterIssuers duplicatie:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
# Beschikbaar voor alle namespaces

2. Scheid Staging en Productie

Heb altijd beide issuers:

# letsencrypt-staging voor testen
# letsencrypt-prod voor productie

3. Monitor Expiratie

Zelfs met automatisering, monitor als vangnet:

certmanager_certificate_expiration_timestamp_seconds - time() < 604800

4. Gebruik DNS-01 voor Interne Services

Als je services niet publiek toegankelijk zijn, werkt HTTP-01 niet.

5. Standaardiseer Secret Namen

Conventie zoals ${service}-tls maakt het voorspelbaar:

secretName: api-example-com-tls

Mijn Productie Setup

# ClusterIssuer voor Let's Encrypt met Cloudflare DNS
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: certs@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
        selector:
          dnsZones:
            - "example.com"

---
# Wildcard certificaat voor alle services
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: cert-manager
spec:
  secretName: wildcard-example-com-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - "*.example.com"
    - "example.com"

Belangrijke keuzes:

  • DNS-01 via Cloudflare — Werkt voor alles, inclusief wildcards
  • Wildcard certificaat — Eén cert voor alle subdomeinen, minder rate limit zorgen
  • Centrale namespace — Certificaat in cert-manager namespace, gereflecteerd waar nodig
  • Prometheus monitoring — Alert voordat dingen kapotgaan

Waarom Dit Ertoe Doet

TLS is niet meer optioneel. Browsers waarschuwen bij HTTP. APIs vereisen HTTPS. Interne services hebben encryptie nodig voor zero trust.

Handmatig certificaat management schaalt niet:

  • Vernieuwingsdata worden vergeten
  • Verschillende teams gebruiken verschillende processen
  • Nieuwe services lanceren zonder HTTPS

cert-manager maakt TLS de default:

  • Definieer eenmaal, vernieuw voor altijd
  • Hetzelfde proces voor elk team
  • Integreert met GitOps

Dit is automatisering waar het het meest ertoe doet — security die werkt zonder eraan te denken.


De beste security is de security die je niet hoeft te onthouden. cert-manager maakt TLS automatisch, zodat je je kunt focussen op wat er echt toe doet.