← Back to list

Cert-Manager: Certificados TLS automáticos en Kubernetes con Let’s Encrypt.

Aprenderemos cómo automatizar la emisión y renovación de certificados TLS en Kubernetes usando cert-manager y Let’s Encrypt, validando el…

Ismael Aguilera · 2026-07-24 22:49 · 0 claps · 5.2 min read
#kubernetes #devops #cert-manager #lets-encrypt #api-gateway
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

Cert Manager: Certificados TLS automáticos en Kubernetes con Let’s Encrypt.

Aprenderemos cómo automatizar la emisión y renovación de certificados TLS en Kubernetes usando cert-manager y Let’s Encrypt, validando el dominio vía DNS-01 en Cloudflare y exponiendo la app con Gateway API.

Cuando empecé a exponer aplicaciones en Kubernetes a través de Ingress, generar y renovar certificados TLS a mano era de las tareas que más postergaba: un proceso manual, fácil de olvidar y que en algún momento termina en un certificado vencido en producción. Este es el artículo que me hubiera gustado tener cuando resolví automatizarlo por primera vez, y espero que te ahorre la misma vuelta que a mí.

Antes de entrar al laboratorio hay un cambio importante que quiero mencionar: en artículos anteriores usé ingress-nginx para exponer mis aplicaciones, pero ese controlador fue retirado el 31 de marzo de 2026 — ya no recibe parches de seguridad ni soporte para versiones nuevas de Kubernetes. El Steering Committee de Kubernetes recomienda migrar a Gateway API, así que este laboratorio ya lo arma sobre el nuevo estándar.

¿Qué es cert-manager?

Es un controlador nativo de Kubernetes que automatiza la emisión, renovación y gestión de certificados TLS, integrándose con autoridades certificadoras como Let’s Encrypt, Vault o una CA privada. Una vez configurado, cert-manager observa tus recursos Gateway (o Ingress, si todavía lo usas) y se encarga de todo el ciclo de vida del certificado sin intervención manual.

¿Qué es un ClusterIssuer?

Es el recurso que le indica a cert-manager cómo y con quién emitir certificados: la autoridad certificadora, las credenciales y el método de validación (challenge). A diferencia de un Issuer, que vive en un namespace, un ClusterIssuer está disponible para todo el cluster.

Para este laboratorio voy a usar el challenge DNS-01 en lugar del más común HTTP-01. La diferencia es clave: HTTP-01 necesita que tu servicio sea alcanzable desde internet para validar el dominio, mientras que DNS-01 solo necesita que puedas crear un registro TXT en tu zona DNS. Esto significa que voy a terminar con un certificado real, emitido por Let’s Encrypt y confiado por cualquier navegador, sirviendo una aplicación que solo existe en mi cluster local de OrbStack.

¿Por qué Gateway API en vez de Ingress?

Gateway API es el sucesor oficial de Ingress dentro de Kubernetes: un estándar más expresivo, con mejor soporte multi-tenant y menos dependiente de anotaciones específicas de cada controlador. Para este lab voy a usar NGINX Gateway Fabric como implementación — seguimos usando NGINX como motor, solo que ahora habla el estándar Gateway API en lugar del recurso Ingress clásico.

Entorno

  • Cluster local con OrbStack
  • Dominio propio en Cloudflare
  • Token de API de Cloudflare con permiso Zone / DNS / Edit, restringido a la zona del dominio

Todo el código de este laboratorio está en mi repositorio cert-manager-lab.

1. Instalar las CRDs de Gateway API

kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml

2. Instalar NGINX Gateway Fabric

kubectl create namespace nginx-gateway
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --namespace nginx-gateway
kubectl wait --timeout=5m -n nginx-gateway deployment/ngf-nginx-gateway-fabric --for=condition=Available

El chart crea automáticamente un GatewayClass llamado nginx, que vamos a referenciar más adelante.

3. Instalar cert-manager

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true \
  --set config.gatewayAPI.enabled=true \
  --set extraArgs="{--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=8.8.8.8:53\,1.1.1.1:53}"
kubectl get pods -n cert-manager

4. Cargar el token de Cloudflare

kubectl create secret generic cloudflare-api-token-secret \
  --namespace cert-manager \
  --from-literal=api-token=<TU_TOKEN_DE_CLOUDFLARE>

5. Crear el ClusterIssuer

cluster-issuers/letsencrypt-staging.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    email: tu-email@ejemplo.com
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-staging-key
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token

Empezamos apuntando al servidor de staging de Let’s Encrypt. Producción tiene rate limits estrictos, y mientras estás probando el flujo es fácil chocar contra ellos. El ClusterIssuer de producción (letsencrypt-prod) es idéntico, solo cambia la URL del server y los nombres.

kubectl apply -f cluster-issuers/letsencrypt-staging.yaml
kubectl apply -f cluster-issuers/letsencrypt-prod.yaml

6. Desplegar la app de demo, el Gateway y el HTTPRoute

Para la demo voy a reutilizar Podinfo, la misma app que usé en este blog,

demo/gateway.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: cert-manager-lab
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  gatewayClassName: nginx
  listeners:
    - name: https
      hostname: cert-manager-lab.tudominio.cloud
      port: 443
      protocol: HTTPS
      tls:
        mode: Terminate
        certificateRefs:
          - name: cert-manager-lab-tls
            kind: Secret
      allowedRoutes:
        namespaces:
          from: Same

La anotación cert-manager.io/cluster-issuer en el Gateway es todo lo que necesita cert-manager para entrar en acción: al detectar este listener HTTPS, crea automáticamente un recurso Certificate, resuelve el challenge DNS-01 contra Cloudflare, y guarda el certificado resultante en el Secret cert-manager-lab-tls, el mismo que referencia certificateRefs.

demo/httproute.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: podinfo
spec:
  parentRefs:
    - name: cert-manager-lab
      sectionName: https
  hostnames:
    - cert-manager-lab.tudominio.cloud
  rules:
    - backendRefs:
        - name: podinfo
          port: 80

El HTTPRoute es quien realmente enruta el tráfico hacia el Service de Podinfo — el Gateway solo define el punto de entrada y la terminación TLS.

kubectl apply -f demo/deployment.yaml
kubectl apply -f demo/service.yaml
kubectl apply -f demo/gateway.yaml
kubectl apply -f demo/httproute.yaml

Verificamos el estado del certificado:

kubectl get certificate
kubectl describe certificate cert-manager-lab-tls

Cuando el campo Ready pasa a True, el certificado ya está emitido.

7. Apuntar el dominio al cluster local

NGINX Gateway Fabric crea dos Services distintos: uno de control-plane (ngf-nginx-gateway-fabric, namespace nginx-gateway), que usa mTLS entre sus propios componentes internos y no sirve tráfico de tu aplicación; y uno de data-plane, creado automáticamente por cada Gateway que apliques, en el mismo namespace de ese Gateway. Es este segundo el que necesitamos:

kubectl get svc -n default -l gateway.networking.k8s.io/gateway-name=cert-manager-lab

Va a tener un nombre parecido a cert-manager-lab-nginx. Dato útil: si alguna vez apuntas por error al Service de control-plane, el navegador muestra ERR_BAD_SSL_CLIENT_AUTH_CERT, porque ese Service exige un certificado de cliente TLS que tu navegador no tiene.

echo "<IP_DEL_SERVICIO_DATA_PLANE> cert-manager-lab.tudominio.cloud" | sudo tee -a /etc/hosts

En este punto el navegador va a marcar el certificado como no confiable — es esperado, todavía estamos usando el issuer de staging.

8. Pasar a producción

Con el flujo confirmado, cambiamos la anotación del Gateway:

cert-manager.io/cluster-issuer: letsencrypt-prod
kubectl apply -f demo/gateway.yaml
kubectl get certificate

Y listo! https://cert-manager-lab.ismaelaguilera.cloud ahora carga con un certificado real, válido y confiado por el navegador — sirviendo una app que nunca salió de mi laptop.

Con esto llegamos al final del laboratorio. cert-manager resuelve un problema que parece pequeño pero que en producción se paga caro cuando se descuida: la gestión del ciclo de vida de tus certificados. Y de paso, con el retiro de ingress-nginx, es un buen momento para dar el salto a Gateway API si todavía no lo hiciste. Espero que este artículo te sirva para automatizar ambas cosas en tus propios clusters.

Referencias


메타데이터
post_id
bbf12144b2ae
slug
cert-manager-certificados-tls-automáticos-en-kubernetes-con-lets-encrypt-bbf12144b2ae
url
https://medium.com/@ismaelaguilera_/cert-manager-certificados-tls-autom%C3%A1ticos-en-kubernetes-con-lets-encrypt-bbf12144b2ae
canonical_url
https://medium.com/@ismaelaguilera_/cert-manager-certificados-tls-autom%C3%A1ticos-en-kubernetes-con-lets-encrypt-bbf12144b2ae
author_url
https://medium.com/@ismaelaguilera_
status
ok
fetched_at
2026-08-10 11:51:09