Cert-Manager
Panduan lengkap instalasi dan konfigurasi Cert-Manager untuk automatic SSL/TLS certificate management di Kubernetes.
Pengenalan
Cert-Manager adalah native Kubernetes certificate management controller yang secara otomatis mengelola dan menerbitkan TLS certificates dari berbagai issuing sources seperti Let's Encrypt, HashiCorp Vault, Venafi, dan private CA.
Mengapa Cert-Manager?
Tanpa Cert-Manager, Anda harus:
- Manual generate dan renew certificates
- Manual manage certificate expiration
- Manual distribute certificates ke pods/services
- Dealing dengan complex certificate workflows
Dengan Cert-Manager:
- ✅ Automatic issuance: Otomatis request dan install certificates
- ✅ Automatic renewal: Auto-renew sebelum expired
- ✅ Multiple issuers: Support Let's Encrypt, private CA, dll
- ✅ Kubernetes native: Menggunakan CRDs dan annotations
- ✅ Production ready: Digunakan oleh ribuan cluster production
Arsitektur
┌─────────────────────────────────────┐
│ Ingress/Gateway │
│ (request certificate via │
│ annotation) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Cert-Manager │
│ ┌────────────────────────────┐ │
│ │ Certificate Controller │ │
│ └────────────┬───────────────┘ │
│ │ │
│ ┌────────────▼───────────────┐ │
│ │ Issuer/ClusterIssuer │ │
│ └────────────┬───────────────┘ │
│ │ │
│ ┌────────────▼───────────────┐ │
│ │ ACME Client │ │
│ └────────────┬───────────────┘ │
└───────────────┼─────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Let's Encrypt / CA │
│ (issues certificate) │
└─────────────────────────────────────┘
Instalasi
Prasyarat
# Kubernetes cluster (minimal v1.22)
kubectl version --short
# Helm 3
helm version
Metode 1: Helm (Recommended)
# Add Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Install Cert-Manager dengan CRDs
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.0 \
--set installCRDs=true
Metode 2: Kubectl dengan Manifest
# Install CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.crds.yaml
# Install Cert-Manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
Verifikasi Instalasi
# Check pods
kubectl get pods -n cert-manager
# Output:
# NAME READY STATUS RESTARTS AGE
# cert-manager-7d9f7f8c8-xxxxx 1/1 Running 0 1m
# cert-manager-cainjector-5c6f7d8c8-xxxxx 1/1 Running 0 1m
# cert-manager-webhook-5c6f7d8c8-xxxxx 1/1 Running 0 1m
# Check CRDs
kubectl get crd | grep cert-manager
# Test installation
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager-test
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: test-selfsigned
namespace: cert-manager-test
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: selfsigned-cert
namespace: cert-manager-test
spec:
dnsNames:
- example.com
secretName: selfsigned-cert-tls
issuerRef:
name: test-selfsigned
EOF
# Check certificate
kubectl get certificate -n cert-manager-test
# Cleanup test
kubectl delete namespace cert-manager-test
Konfigurasi Custom
Helm Values Customization
Buat file values.yaml:
# Resource limits
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
# Replica count untuk HA
replicaCount: 2
# Enable Prometheus metrics
prometheus:
enabled: true
servicemonitor:
enabled: true
# Webhook configuration
webhook:
replicaCount: 2
resources:
requests:
cpu: 10m
memory: 32Mi
# CA Injector configuration
cainjector:
replicaCount: 2
resources:
requests:
cpu: 10m
memory: 32Mi
# Global configuration
global:
leaderElection:
namespace: cert-manager
logLevel: 2 # 0=panic, 1=fatal, 2=error, 3=warning, 4=info, 5=debug
# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
Install dengan custom values:
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.0 \
--set installCRDs=true \
-f values.yaml
Issuers
1. ClusterIssuer vs Issuer
- Issuer: Namespaced, hanya bisa digunakan dalam namespace yang sama
- ClusterIssuer: Cluster-wide, bisa digunakan dari namespace manapun (recommended)
2. Let's Encrypt (Production)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
# Production Let's Encrypt server
server: https://acme-v02.api.letsencrypt.org/directory
# Email untuk notifications
email: admin@example.com
# Secret untuk menyimpan private key
privateKeySecretRef:
name: letsencrypt-prod
# ACME challenge solvers
solvers:
# HTTP-01 challenge (untuk domain publik)
- http01:
ingress:
class: nginx
3. Let's Encrypt (Staging untuk Testing)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# Staging server (rate limit lebih tinggi)
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
4. DNS-01 Challenge (untuk Wildcard Certificates)
Cloudflare DNS:
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: "your-cloudflare-api-token"
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns-cloudflare
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-dns-cloudflare
solvers:
- dns01:
cloudflare:
email: admin@example.com
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
AWS Route53:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns-route53
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-dns-route53
solvers:
- dns01:
route53:
region: us-east-1
accessKeyID: AKIAIOSFODNN7EXAMPLE
secretAccessKeySecretRef:
name: route53-credentials-secret
key: secret-access-key
5. Self-Signed Issuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
6. CA Issuer (Private CA)
# Create CA certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-ca-certificate
namespace: cert-manager
spec:
isCA: true
commonName: my-ca
secretName: my-ca-secret
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
---
# Create CA Issuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: my-ca-issuer
spec:
ca:
secretName: my-ca-secret
Membuat Certificates
1. Basic Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: default
spec:
# Secret name untuk menyimpan certificate
secretName: example-com-tls
# Duration dan renewal
duration: 2160h # 90 days
renewBefore: 360h # 15 days before expiration
# Issuer reference
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
# Common name
commonName: example.com
# DNS names (SANs)
dnsNames:
- example.com
- www.example.com
2. Wildcard Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: default
spec:
secretName: wildcard-example-com-tls
issuerRef:
name: letsencrypt-dns-cloudflare
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com
3. Certificate dengan Multiple Domains
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: multi-domain-cert
namespace: default
spec:
secretName: multi-domain-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
- api.example.com
- admin.example.com
Integration dengan Ingress
Nginx Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
namespace: production
annotations:
# Cert-Manager annotation
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# Nginx annotations
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
- www.example.com
secretName: example-com-tls # Cert-Manager akan create secret ini
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Traefik Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
Gateway API (Kubernetes Gateway)
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: example-gateway
namespace: default
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: wildcard-example-com-tls
Monitoring dan Troubleshooting
Check Certificate Status
# List all certificates
kubectl get certificates --all-namespaces
# Describe certificate
kubectl describe certificate example-com -n default
# Check certificate details
kubectl get certificate example-com -n default -o yaml
# Check secret
kubectl get secret example-com-tls -n default -o yaml
Check Certificate Ready Status
# Wait for certificate to be ready
kubectl wait --for=condition=ready certificate/example-com -n default --timeout=300s
# Check if secret exists and is valid
kubectl get secret example-com-tls -n default -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout
Common Issues
Issue 1: Certificate Pending/Not Ready
# Check certificate status
kubectl describe certificate example-com -n default
# Check certificate request
kubectl get certificaterequest -n default
# Check order (ACME)
kubectl get order -n default
# Check challenge
kubectl get challenge -n default
# Detailed challenge info
kubectl describe challenge <challenge-name> -n default
Solusi umum:
- Pastikan DNS record sudah correct
- Check ingress controller berjalan
- Verify firewall allow port 80 (HTTP-01)
- Check API token valid (DNS-01)
Issue 2: Rate Limiting Let's Encrypt
# Error: too many certificates already issued
Solusi:
- Gunakan staging issuer untuk testing
- Wait rate limit reset (biasanya 1 minggu)
- Gunakan DNS-01 challenge (rate limit lebih tinggi)
Issue 3: DNS Propagation Issues (DNS-01)
# Check DNS propagation
dig TXT _acme-challenge.example.com
# Wait longer
kubectl describe challenge <challenge-name>
Logs
# Cert-Manager controller logs
kubectl logs -n cert-manager -l app=cert-manager -f
# Webhook logs
kubectl logs -n cert-manager -l app=webhook -f
# CA Injector logs
kubectl logs -n cert-manager -l app=cainjector -f
Debug Mode
Enable verbose logging:
helm upgrade cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set global.logLevel=4 \
--reuse-values
Monitoring dengan Prometheus
ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: cert-manager
namespace: cert-manager
spec:
selector:
matchLabels:
app.kubernetes.io/name: cert-manager
endpoints:
- port: tcp-prometheus-servicemonitor
interval: 30s
Key Metrics
# Certificate expiration time
certmanager_certificate_expiration_timestamp_seconds
# Certificate renewal time
certmanager_certificate_renewal_timestamp_seconds
# Certificate ready status
certmanager_certificate_ready_status
# ACME client requests
certmanager_http_acme_client_request_count
Grafana Dashboard
Import dashboard ID: 11001 (Cert-Manager Official)
Best Practices
1. Use ClusterIssuer
✅ Gunakan:
kind: ClusterIssuer # Reusable across namespaces
❌ Hindari:
kind: Issuer # Harus create per namespace
2. Separate Staging dan Production
# Testing
issuerRef:
name: letsencrypt-staging
# Production
issuerRef:
name: letsencrypt-prod
3. Set Proper Duration dan Renewal
spec:
duration: 2160h # 90 days
renewBefore: 720h # Renew 30 days before expiration
4. Use DNS-01 untuk Wildcard
# ✅ Gunakan DNS-01
dnsNames:
- "*.example.com"
solvers:
- dns01:
cloudflare: {}
5. Monitor Certificate Expiration
# Create PrometheusRule
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cert-manager-alerts
namespace: cert-manager
spec:
groups:
- name: cert-manager
interval: 30s
rules:
- alert: CertificateExpiringSoon
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 604800
for: 1h
labels:
severity: warning
annotations:
summary: "Certificate {{ $labels.name }} expiring soon"
description: "Certificate {{ $labels.name }} in namespace {{ $labels.namespace }} expires in less than 7 days"
6. Resource Requests/Limits
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
7. High Availability
# Multiple replicas
replicaCount: 2
# Pod anti-affinity
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: cert-manager
topologyKey: kubernetes.io/hostname
Advanced Configuration
Custom Certificate Template
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: advanced-cert
spec:
secretName: advanced-cert-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
# Private key configuration
privateKey:
algorithm: RSA
encoding: PKCS1
size: 4096
rotationPolicy: Always
# Certificate usage
usages:
- digital signature
- key encipherment
- server auth
# Subject alternative names
dnsNames:
- example.com
ipAddresses:
- "192.168.1.100"
uris:
- "spiffe://cluster.local/ns/default/sa/myapp"
# Email addresses
emailAddresses:
- admin@example.com
Certificate dengan Annotations
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: annotated-cert
spec:
secretName: annotated-cert-tls
secretTemplate:
annotations:
my-annotation: "my-value"
labels:
my-label: "my-value"
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
Backup dan Restore
Backup Certificates
# Backup all certificates
kubectl get certificates --all-namespaces -o yaml > certificates-backup.yaml
# Backup secrets
kubectl get secrets --all-namespaces -l cert-manager.io/certificate-name -o yaml > secrets-backup.yaml
# Backup issuers
kubectl get clusterissuers -o yaml > clusterissuers-backup.yaml
kubectl get issuers --all-namespaces -o yaml > issuers-backup.yaml
Restore
kubectl apply -f clusterissuers-backup.yaml
kubectl apply -f issuers-backup.yaml
kubectl apply -f secrets-backup.yaml
kubectl apply -f certificates-backup.yaml
Upgrade Cert-Manager
# Backup current state
kubectl get certificates --all-namespaces -o yaml > certs-backup.yaml
# Check current version
helm list -n cert-manager
# Update repo
helm repo update
# Upgrade
helm upgrade cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.14.0 \
--reuse-values
# Verify
kubectl get pods -n cert-manager
Uninstall
# Delete certificates first
kubectl delete certificates --all --all-namespaces
# Uninstall helm release
helm uninstall cert-manager -n cert-manager
# Delete CRDs (optional)
kubectl delete crd certificates.cert-manager.io
kubectl delete crd certificaterequests.cert-manager.io
kubectl delete crd challenges.acme.cert-manager.io
kubectl delete crd clusterissuers.cert-manager.io
kubectl delete crd issuers.cert-manager.io
kubectl delete crd orders.acme.cert-manager.io
# Delete namespace
kubectl delete namespace cert-manager
Kesimpulan
Cert-Manager adalah tool essential untuk managing TLS certificates di Kubernetes. Dengan automatic issuance dan renewal, Anda tidak perlu lagi worry tentang certificate expiration.
Kapan Menggunakan Cert-Manager?
✅ Gunakan untuk:
- Automatic certificate management
- Let's Encrypt integration
- Wildcard certificates
- Multi-domain certificates
- Production workloads