cert-manager for Automatic TLS in Kubernetes

cert-manager is a Kubernetes-native certificate management tool that automates provisioning and renewal of certificado TLSs. Esta guía cubre Helm installation, configuring Issuers and ClusterIssuers, using Let's Encrypt for free certificates, managing Certificate resources, and implementing automatic TLS for ingreso on your VPS and baremetal Kubernetes infrastructure.

Tabla de contenidos

cert-manager Overview

What is cert-manager?

cert-manager is a Kubernetes operator that:

  • Automates certificate provisioning from CA providers
  • Manages certificate renewal
  • Injects certificates into secrets
  • Validates certificates
  • Supports multiple CAs (Let's Encrypt, Vault, HashiCorp, ACME)

Key Components

cert-manager controller: Main controller managing certificates

Webhook: Validates certificate resources

CA Injector: Injects CA certificates into webhooks

Instalación

Requisitos previos

  • Kubernetes v1.16+
  • kubectl configured
  • Helm 3+

Instalaing cert-manager with Helm

# Add Helm repository
helm repo add jetstack https://charts.jetstack.io
helm repo update

# Create namespace
kubectl create namespace cert-manager

# Install CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.crds.yaml

# Install cert-manager
helm install cert-manager jetstack/cert-manager \
  -n cert-manager \
  --set installCRDs=true \
  --set global.leaderElection.namespace=cert-manager

Wait for installation:

kubectl rollout status deployment/cert-manager -n cert-manager
kubectl rollout status deployment/cert-manager-webhook -n cert-manager

Verifica la instalación:

kubectl get pods -n cert-manager
kubectl api-resources | grep cert-manager

Helm Configuration

Production values file:

# cert-manager-values.yaml
global:
  leaderElection:
    namespace: cert-manager

cert-manager:
  serviceAccount:
    create: true
  
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi
  
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app.kubernetes.io/name
              operator: In
              values:
              - cert-manager
          topologyKey: kubernetes.io/hostname

webhook:
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi

cainjector:
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi

Instala with custom values:

helm install cert-manager jetstack/cert-manager \
  -n cert-manager \
  --create-namespace \
  -f cert-manager-values.yaml

Issuers and ClusterIssuers

Issuer vs ClusterIssuer

Issuer: Namespace-scoped certificate issuer

ClusterIssuer: Cluster-wide certificate issuer

ACME Issuer with 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: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx
    - dns01:
        route53:
          region: us-east-1
          hostedZoneID: Z1234567890ABC
          accessKeyID: AKIAIOSFODNN7EXAMPLE
          secretAccessKeySecretRef:
            name: aws-route53-secret
            key: secret-access-key

Staging Let's Encrypt Issuer

For testing (raises rate limit):

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging-key
    solvers:
    - http01:
        ingress:
          class: nginx

Vault Issuer

For internal certificate management:

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

Self-Signed Issuer

For development:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: self-signed
spec:
  selfSigned: {}

Certificate Resources

Certificate Resource

Explicitly request a certificate:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-cert
  namespace: production
spec:
  secretName: my-app-tls
  commonName: my-app.example.com
  dnsNames:
  - my-app.example.com
  - www.my-app.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  renewBefore: 720h  # Renew 30 days before expiry
  duration: 2160h    # 90 days validity

Create certificate:

kubectl apply -f certificate.yaml
kubectl get certificate -n production
kubectl describe certificate my-app-cert -n production

Certificate with Wildcard

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

Certificate with Custom Duration

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: long-lived-cert
spec:
  secretName: long-lived-tls
  commonName: api.example.com
  duration: 8760h    # 1 year
  renewBefore: 720h  # Renew 30 days before expiry
  issuerRef:
    name: vault-issuer
    kind: ClusterIssuer

Let's Encrypt Integration

HTTP-01 Challenge

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-http
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-key
    solvers:
    - http01:
        ingress:
          class: nginx

DNS-01 Challenge

For wildcard certificates:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-dns-key
    solvers:
    - dns01:
        cloudflare:
          email: [email protected]
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token

Create Cloudflare secret:

kubectl create secret generic cloudflare-api-token \
  -n cert-manager \
  --from-literal=api-token=YOUR_CLOUDFLARE_API_TOKEN

Ingress Integration

Automatic Certificate Creation

Add annotation to ingreso:

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

Multiple Domains

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-domain
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - example.com
    - www.example.com
    secretName: example-tls
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
  - host: www.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api
            port:
              number: 8080

Certificate Renewal

Automatic Renewal

cert-manager automatically renews certificates before expiry. Configuration:

spec:
  renewBefore: 720h  # Renew 30 days before expiry
  duration: 2160h    # Valid for 90 days

Manual Renewal Trigger

# Delete the certificate to trigger immediate renewal
kubectl delete secret my-app-tls -n production

# cert-manager will recreate the certificate
kubectl get certificate -n production -w

Supervisión Certificate Expiry

# View certificate details
kubectl get certificate -n production -o wide

# Check expiration dates
kubectl get secret -n production -o json | \
  jq '.items[] | select(.type=="kubernetes.io/tls") | {name:.metadata.name, cert:.data."tls.crt"}' | \
  while read cert; do
    echo $cert | jq -r '.cert' | base64 -d | openssl x509 -noout -dates
  done

Practical Examples

Ejemplo: Complete Ingress with Auto TLS

---
# ClusterIssuer for 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: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
# Namespace with cert-manager webhook enabled
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    cert-manager.io/inject-ca-certificates: "true"
---
# Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: my-app-cert
  namespace: production
spec:
  secretName: my-app-tls
  commonName: my-app.example.com
  dnsNames:
  - my-app.example.com
  - www.my-app.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  renewBefore: 720h
---
# Ingress using certificate
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - my-app.example.com
    - www.my-app.example.com
    secretName: my-app-tls
  rules:
  - host: my-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80
  - host: www.my-app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80

Ejemplo: Wildcard Certificate with DNS Challenge

---
# Cloudflare secret for DNS validation
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
  namespace: cert-manager
type: Opaque
stringData:
  api-token: YOUR_CLOUDFLARE_API_TOKEN
---
# ClusterIssuer with DNS challenge
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-dns-key
    solvers:
    - dns01:
        cloudflare:
          email: [email protected]
          apiTokenSecretRef:
            name: cloudflare-api-token
            key: api-token
---
# Wildcard certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
  namespace: production
spec:
  secretName: wildcard-tls
  commonName: "*.example.com"
  dnsNames:
  - "*.example.com"
  - example.com
  issuerRef:
    name: letsencrypt-dns
    kind: ClusterIssuer
  renewBefore: 720h

Conclusión

cert-manager automates certificado TLS management in Kubernetes, eliminating manual certificate renewal and providing automatic provisioning from trusted CAs like Let's Encrypt. By implementing cert-manager with proper Issuer configuration, automatic ingreso integration, and renewal policies, you ensure your applications always have valid certificado TLSs. Start with a Let's Encrypt staging issuer for testing, migrate to production when comfortable, and monitor certificate expiry dates. The combination of cert-manager and nginx-ingreso provides a complete, automated TLS solution for your VPS and baremetal Kubernetes infrastructure.