cert-manager
Purpose: Automated TLS certificate management
Version: 1.17.2
Namespace: cert-manager
Description
Manages certificates from Let's Encrypt using DNS-01 challenges with Cloudflare. Automates certificate issuance and renewal.
Key features:
- Automatic certificate issuance from Let's Encrypt
- DNS-01 challenge validation via Cloudflare API
- Automatic renewal (30 days before expiry)
- Kubernetes Secret storage
- Ingress integration via annotations
Installation
Installed via ArgoCD from Helm chart: jetstack/cert-manager
Configuration file: cluster/dev/config.yaml
Configuration
ClusterIssuer
The ClusterIssuer is created automatically during bootstrap:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@ssdk8s.xyz
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Cloudflare Secret
The Cloudflare API token secret is created by the bootstrap script:
kubectl create secret generic cloudflare-api-token \
-n cert-manager \
--from-literal=api-token=YOUR_CF_API_TOKEN
The Cloudflare API token must have minimal permissions: Zone:DNS:Edit only. Do not use a token with broader permissions.
Usage
Annotations for Ingress
All ingresses should use cert-manager for TLS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.ssdk8s.xyz
secretName: myapp-tls
rules:
- host: myapp.ssdk8s.xyz
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
All ingresses MUST use cert-manager for TLS. Do not use ssl-passthrough unless absolutely required.
Certificate Resource (Optional)
For more control, create a Certificate resource:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-app-tls
namespace: default
spec:
secretName: my-app-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- myapp.ssdk8s.xyz
duration: 2160h # 90 days
renewBefore: 720h # 30 days before expiry
Components
| Component | Deployment | Purpose |
|---|---|---|
| cert-manager | cert-manager | Main controller |
| cert-manager-cainjector | cert-manager-cainjector | Injects CA certs into CRDs |
| cert-manager-webhook | cert-manager-webhook | Validation webhook for Certificate resources |
Certificate Lifecycle
| Property | Value |
|---|---|
| Issuer | Let's Encrypt Production |
| Duration | 90 days (default) |
| Renewal | Automatically at 30 days before expiry |
| Challenge | DNS-01 via Cloudflare API |
| Key Algorithm | RSA 2048-bit |
| Rate Limits | 50 certificates per week per registered domain |
DNS-01 Challenge Flow
Troubleshooting
Check Certificate Status
# List all certificates
kubectl get certificates -A
# Describe specific certificate
kubectl describe certificate <name> -n <namespace>
# Check certificate events
kubectl get events -n cert-manager --field-selector involvedObject.kind=Certificate
Check Challenge Status
# List all challenges
kubectl get challenges -A
# Describe specific challenge
kubectl describe challenge <name> -n <namespace>
# Check challenge logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager | grep -i challenge
Check Order Status
# List certificate orders
kubectl get certificateorders -A
# Describe order
kubectl describe certificateorder <name> -n <namespace>
Check Issuer Status
kubectl get clusterissuer letsencrypt-prod -o yaml
kubectl describe clusterissuer letsencrypt-prod
Common Issues
-
Cloudflare API token invalid
- Verify token has
Zone:DNS:Editpermissions - Check token hasn't expired
- Ensure correct zone is being used
- Verify token has
-
DNS propagation delay
- Wait 2-5 minutes for TXT record propagation
- Check with:
dig TXT _acme-challenge.yourdomain.com
-
Rate limiting
- Let's Encrypt limits: 50 certs/week per domain
- Use staging issuer for testing:
https://acme-staging-v02.api.letsencrypt.org/directory
-
Certificate not renewing
- Check
renewBeforesetting (should be ~30 days) - Verify cert-manager pods are running
- Check cert-manager logs for errors
- Check
-
Webhook connection refused
- Check webhook pod status:
kubectl get pods -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook - Verify webhook service:
kubectl get svc -n cert-manager cert-manager-webhook
- Check webhook pod status:
Debug Commands
# Check cert-manager pods
kubectl get pods -n cert-manager
# View cert-manager logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager --tail=100
# View webhook logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager-webhook --tail=50
# Check DNS resolution
dig TXT _acme-challenge.yourdomain.com @8.8.8.8
# Test Cloudflare API (replace TOKEN and ZONE_ID)
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer TOKEN"