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
- Je maakt een Certificate resource
- cert-manager vraagt een certificaat aan bij de issuer (Let’s Encrypt, Vault, etc.)
- cert-manager voltooit de challenge (HTTP-01 of DNS-01)
- cert-manager slaat het certificaat op in een Kubernetes Secret
- 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.
