Skip to main content

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

Resources