En la lección anterior publicamos www.rutasnorte.example y api.rutasnorte.example con un único Ingress, y cerramos señalando el problema que no admite demora: todo viaja en HTTP en claro. El nombre, el DNS, el teléfono y el correo que un cliente escribe para reservar un billete, y los datos que van hacia la pasarela de pagos, están cruzando internet sin cifrar. Esta lección pone HTTPS en los dos dominios: primero a mano con un certificado autofirmado para entender las piezas, y después de forma automática con cert-manager, que emitirá y renovará certificados de Let's Encrypt sin que nadie tenga que acordarse de nada.
Advertencia de seguridad. Esta lección enseña la mecánica de Kubernetes para gestionar certificados. La configuración criptográfica real de una plataforma en producción —conjuntos de cifrado, versiones de TLS permitidas, políticas de HSTS, custodia de claves privadas, requisitos de cumplimiento normativo— debe revisarla y aprobarla un profesional de seguridad. Los valores que aparecen aquí son didácticos y razonables en 2026, pero envejecen: no los copies a producción sin validarlos con quien corresponda en tu organización.
Contenido
- Por qué HTTPS es obligatorio en Rutas Norte
- Dónde termina el TLS en un clúster
- El Secret
kubernetes.io/tlsy el bloquetls:del Ingress - Certificado autofirmado para desarrollo
- cert-manager: qué problema resuelve e instalación
- Los objetos de cert-manager y el flujo de emisión
- ACME con Let's Encrypt:
HTTP-01frente aDNS-01 - Emisión automática desde el Ingress
- El entorno de staging y los límites de emisión
- Renovación automática y comprobación del estado
- Redirección a HTTPS y HSTS
- Más allá: mTLS y mallas de servicio
- Por qué HTTPS es obligatorio en Rutas Norte
No es una buena práctica: es un requisito.
| Motivo | Qué pasa sin TLS |
|---|---|
| Datos personales | Nombre, DNI, teléfono y correo viajan legibles por cualquier red intermedia. En el RGPD, el cifrado en tránsito es una medida técnica esperable |
| Pagos | PCI DSS exige cifrado del canal. Ninguna pasarela seria acepta integrarse por HTTP |
| Sesiones | Un JWT o una cookie interceptada en una wifi pública es una cuenta robada |
| Integridad | Un intermediario puede modificar la respuesta: inyectar scripts, cambiar el importe |
| Navegadores y SEO | Marcan "No seguro", bloquean APIs (geolocalización, service workers) y los buscadores penalizan HTTP. La tienda parecería fraudulenta |
Y una precisión: TLS no solo cifra, también autentica. El certificado demuestra que quien responde en www.rutasnorte.example es realmente Rutas Norte y no alguien que ha secuestrado el DNS. Sin esa parte, el cifrado sería con un desconocido.
- Dónde termina el TLS en un clúster
"Terminar el TLS" significa descifrar: el punto donde el tráfico deja de estar cifrado. Hay tres opciones.
flowchart LR
CA["Cliente"] -->|HTTPS| IA["A. Ingress:<br/>descifra aqui"]
IA -->|"HTTP en claro"| SA["Pods"]
CB["Cliente"] -->|HTTPS| IB["B. Ingress: descifra<br/>y vuelve a cifrar"]
IB -->|HTTPS| SB["Pods con certificado propio"]
CC["Cliente"] -->|HTTPS| IC["C. Ingress: NO descifra,<br/>reenvia por SNI"]
IC -->|HTTPS| SC["El pod termina el TLS"]
| Modelo | Ventajas | Inconvenientes | Cuándo |
|---|---|---|---|
| A. Terminación en el Ingress | Un solo sitio con certificados; las aplicaciones no saben de TLS; el controlador puede enrutar por ruta y cabeceras | El tráfico interno va en claro dentro del clúster | Por defecto, y lo que usa Rutas Norte |
| B. Reencriptado | Cifrado también dentro del clúster | Cada aplicación gestiona su certificado | Entornos regulados, redes no confiables |
| C. Passthrough | El certificado nunca sale de la aplicación | El Ingress no ve HTTP: sin enrutado por ruta, sin reescritura | mTLS estricto, protocolos opacos |
Rutas Norte usa A. El tráfico interno en claro se compensa con las políticas de red de 04-06, y si hiciera falta cifrado dentro del clúster, la respuesta sería una malla de servicio (08-04).
- El Secret
kubernetes.io/tls y el bloque tls: del Ingress
kubernetes.io/tls y el bloque tls: del IngressUn certificado en Kubernetes es un Secret de tipo kubernetes.io/tls con exactamente dos claves: tls.crt, la cadena de certificados en PEM (el del servidor primero, luego los intermedios), y tls.key, la clave privada en PEM. El tipo obliga a que ambas existan.
Recuerda de 03-02: eso es base64, no cifrado, y cualquiera con permiso de lectura sobre Secrets en ese namespace tiene la clave privada de la plataforma; es el argumento más contundente para el RBAC de 08-01 y para el cifrado en reposo de etcd. El Ingress lo consume con el bloque tls::
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rutas-norte
namespace: rutas-norte-pro
spec:
ingressClassName: nginx
tls:
- hosts:
- www.rutasnorte.example
- api.rutasnorte.example
secretName: rutasnorte-tls # Secret del MISMO namespace que el Ingress
rules:
# ... las mismas reglas de 04-04, sin cambiosTres reglas que rompen esto si no se cumplen: el Secret debe estar en el mismo namespace que el Ingress (no hay referencias entre namespaces, así que publicar los tres entornos exige el certificado en los tres, y cert-manager puede emitirlo en cada uno); los hosts del bloque tls deben coincidir con los del certificado, o el navegador dará ERR_CERT_COMMON_NAME_INVALID; y se pueden poner varias entradas tls, cada una con su Secret, eligiendo el controlador por SNI, el nombre que el cliente anuncia al iniciar el saludo TLS.
- Certificado autofirmado para desarrollo
Para rutas-norte-dev no pedimos certificados a una autoridad pública: el dominio no existe y el clúster es local. Generamos uno propio.
# Clave privada de 2048 bits y certificado autofirmado a 365 dias
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout /tmp/rutasnorte-dev.key \
-out /tmp/rutasnorte-dev.crt \
-subj "/C=ES/ST=Cantabria/L=Santander/O=Rutas Norte S.L./CN=www.rutasnorte.example" \
-addext "subjectAltName=DNS:www.rutasnorte.example,DNS:api.rutasnorte.example"Desglose: -x509 genera un certificado directamente en vez de una solicitud (CSR); -nodes deja la clave privada sin contraseña, imprescindible porque nadie va a teclearla al arrancar; -newkey rsa:2048 crea clave y certificado en un paso; -subj da los datos del titular sin preguntar; y -addext subjectAltName añade el campo que de verdad importa.
Sobre subjectAltName (SAN): los navegadores llevan años ignorando el CN y validando exclusivamente las SAN, de modo que un certificado sin SAN se rechaza aunque el CN sea perfecto —es el error número uno al generar certificados a mano—. Aquí necesitamos dos, una por dominio, para que el mismo certificado sirva a la tienda y a la API.
kubectl create secret tls rutasnorte-tls \
--cert=/tmp/rutasnorte-dev.crt --key=/tmp/rutasnorte-dev.key -n rutas-norte-dev
# Verificar que las SAN son las esperadas
kubectl get secret rutasnorte-tls -n rutas-norte-dev \
-o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -text \
| grep -A1 "Subject Alternative Name"
curl -k -s -o /dev/null -w '%{http_code}\n' https://www.rutasnorte.example/
curl -s https://www.rutasnorte.example/ 2>&1 | head -1X509v3 Subject Alternative Name:
DNS:www.rutasnorte.example, DNS:api.rutasnorte.example
200
curl: (60) SSL certificate problem: self-signed certificatekubectl create secret tls pone el tipo correcto y las dos claves con los nombres correctos.
Con -k funciona; sin -k, falla. Eso es correcto y es la lección: el certificado cifra igual de bien, pero nadie puede verificar quién lo emitió, y el navegador mostrará "La conexión no es privada" con NET::ERR_CERT_AUTHORITY_INVALID. Un cliente que viera ese aviso no compraría. Por eso los autofirmados sirven para desarrollo y pruebas internas, nunca para producción.
- cert-manager: qué problema resuelve e instalación
Con certificados de una autoridad pública, el proceso manual es: generar clave y CSR, demostrar que controlas el dominio, recibir el certificado, crear el Secret, y repetirlo cada 90 días, que es lo que duran los de Let's Encrypt. El resultado previsible es el incidente clásico: un sábado por la noche caduca el certificado, la tienda deja de funcionar en pleno puente, y nadie recuerda cómo se renovaba porque lo hizo una persona que ya no está.
cert-manager convierte eso en un bucle de reconciliación como cualquier otro: declaras que quieres un certificado y él lo obtiene, lo guarda en un Secret y lo renueva antes de que caduque, indefinidamente.
# Instalacion con los CRDs incluidos
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
kubectl get pods -n cert-manager
kubectl get crds | grep cert-manager.iocert-manager-6d8b9c7f4d-jk29p 1/1 Running 0 60s
cert-manager-cainjector-5f9b7d8c6-w4xr2 1/1 Running 0 60s
cert-manager-webhook-7c4d9f8b5-mn6vt 1/1 Running 0 60s
certificaterequests.cert-manager.io certificates.cert-manager.io
challenges.acme.cert-manager.io clusterissuers.cert-manager.io
issuers.cert-manager.io orders.acme.cert-manager.ioTres componentes: cert-manager es el controlador que observa los Certificate y ejecuta el flujo de emisión; cainjector inyecta paquetes de CA en webhooks y APIServices; y webhook valida y aplica valores por defecto a los recursos propios.
Seis CRDs (06-06): cert-manager extiende la API de Kubernetes con tipos propios y un controlador que los reconcilia. Es el patrón operador de 06-07 en estado puro, y probablemente el ejemplo más útil que vas a encontrar.
- Los objetos de cert-manager y el flujo de emisión
| Objeto | Ámbito | Quién lo crea | Qué es |
|---|---|---|---|
Issuer |
Namespace | Tú | Fuente de certificados válida solo en ese namespace |
ClusterIssuer |
Clúster | Tú (administrador) | Fuente utilizable desde cualquier namespace |
Certificate |
Namespace | Tú, o el Ingress con la anotación | "Quiero un certificado para estos dominios, en este Secret" |
CertificateRequest / Order / Challenge |
Namespace | cert-manager | La petición con su CSR, el pedido ACME y cada desafío a superar |
Los tres últimos son internos: no se escriben a mano, pero son donde se lee qué está fallando, siguiendo la cadena Certificate → CertificateRequest → Order → Challenge.
sequenceDiagram
participant U as Tu (o el Ingress)
participant CM as cert-manager
participant LE as Let's Encrypt (ACME)
participant K8S as API de Kubernetes
U->>K8S: crea Certificate (dominios + secretName)
CM->>K8S: observa el Certificate
CM->>CM: genera clave privada y CSR
CM->>K8S: crea CertificateRequest
CM->>K8S: crea Order
CM->>LE: solicita el pedido para los dominios
LE-->>CM: desafios pendientes (HTTP-01 o DNS-01)
CM->>K8S: crea Challenge
CM->>K8S: publica la prueba (Ingress temporal o registro TXT)
CM->>LE: "ya puedes validar"
LE->>LE: comprueba la prueba desde internet
LE-->>CM: validado; aqui tienes el certificado
CM->>K8S: crea/actualiza el Secret kubernetes.io/tls
CM->>K8S: Certificate con Ready=True
Note over CM: y programa la renovacion a los 60 dias
- ACME con Let's Encrypt:
HTTP-01 frente a DNS-01
HTTP-01 frente a DNS-01ACME (Automatic Certificate Management Environment) es el protocolo que automatiza la emisión. Su núcleo es una pregunta: ¿demuestras que controlas el dominio?
Desafío HTTP-01
Let's Encrypt pide publicar un fichero con un contenido concreto en http://<dominio>/.well-known/acme-challenge/<token>; cert-manager crea un pod y un Ingress temporal para servirlo, y lo borra al terminar.
# k8s/base/clusterissuer-letsencrypt-http01.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-produccion
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected] # avisos de caducidad y problemas
privateKeySecretRef:
name: letsencrypt-produccion-cuenta # clave de la CUENTA ACME, no del certificado
solvers:
- http01:
ingress:
ingressClassName: nginxRequisito imprescindible: el dominio debe resolver a tu Ingress y ser alcanzable desde internet en el puerto 80. Consecuencias directas: no funciona en minikube con dominios .example resueltos por /etc/hosts (por eso en dev usamos autofirmados); no funciona para servicios internos sin exposición pública; no puede emitir certificados comodín; y si rediriges todo HTTP a HTTPS debes excluir /.well-known/acme-challenge/ —los controladores modernos lo gestionan solos, pero con reglas propias es un fallo habitual.
Desafío DNS-01
Let's Encrypt pide un registro TXT en _acme-challenge.<dominio>. cert-manager lo crea usando la API del proveedor de DNS y lo borra después.
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-cuenta
solvers:
- dns01:
cloudflare:
email: [email protected]
apiTokenSecretRef:
name: cloudflare-token # Secret con el token de la API del DNS
key: token
selector:
dnsZones:
- rutasnorte.example # este solver solo para esta zonaHTTP-01 |
DNS-01 |
|
|---|---|---|
| Qué se publica | Un fichero en /.well-known/acme-challenge/ |
Un registro TXT |
| Requiere puerto 80 público | Sí | No |
| Funciona con servicios internos | No | Sí |
| Certificados comodín | No | Sí, obligatorio |
| Credenciales necesarias | Ninguna | Token de la API del DNS |
| Velocidad | Segundos | Minutos (propagación del DNS) |
| Riesgo | Ninguno especial | Un token con permiso para editar tu zona DNS |
Regla práctica: HTTP-01 por defecto, y DNS-01 cuando necesites comodines o el servicio no sea público. Rutas Norte usaría HTTP-01 para sus dos dominios; si mañana quisiera *.rutasnorte.example para dar un subdominio a cada agencia, tendría que pasar a DNS-01.
- Emisión automática desde el Ingress
Se puede crear un Certificate explícito, útil cuando se quieren controlar la duración, el algoritmo de clave o la rotación:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: rutasnorte-tls
namespace: rutas-norte-pro
spec:
secretName: rutasnorte-tls # el Secret que se creará
duration: 2160h # 90 días
renewBefore: 720h # renovar 30 días antes de caducar
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always # clave nueva en cada renovación
dnsNames: [www.rutasnorte.example, api.rutasnorte.example]
issuerRef:
name: letsencrypt-produccion
kind: ClusterIssuerPero lo cómodo es la anotación en el Ingress: cert-manager la detecta y genera el Certificate por ti a partir del bloque tls:.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rutas-norte
namespace: rutas-norte-pro
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-produccion"
# para un Issuer de namespace seria: cert-manager.io/issuer
spec:
ingressClassName: nginx
tls:
- hosts:
- www.rutasnorte.example
- api.rutasnorte.example
secretName: rutasnorte-tls # cert-manager creará este Secret
rules:
# ... las mismas reglas de 04-04Toda la gestión de certificados de Rutas Norte se reduce a una anotación: los dominios salen de tls.hosts y el destino, de tls.secretName.
kubectl apply -f k8s/entornos/pro/ingress-rutas-norte.yaml
kubectl get certificate,order,challenge -n rutas-norte-procertificate.cert-manager.io/rutasnorte-tls False rutasnorte-tls 12s
order.acme.cert-manager.io/rutasnorte-tls-1-2894732 pending 11s
challenge.acme.cert-manager.io/rutasnorte-tls-1-...-0 pending www.rutasnorte.example 10s
# ... unos segundos despues
certificate.cert-manager.io/rutasnorte-tls True rutasnorte-tls 47sREADY: True y los Order/Challenge desaparecidos: el certificado está en el Secret y el controlador de Ingress ya lo está sirviendo.
- El entorno de staging y los límites de emisión
Let's Encrypt impone límites estrictos: 50 certificados por dominio registrado y semana, 5 fallos de validación por cuenta, dominio y hora, 5 duplicados exactos por semana (mismo conjunto de dominios) y 100 nombres por certificado.
El de duplicados es el que muerde: cinco intentos con el mismo conjunto de dominios y quedas bloqueado una semana; depurando una configuración nueva se consumen en diez minutos y no hay forma de acelerarlo. Por eso existe el entorno de staging, con los mismos límites multiplicados por mucho y certificados emitidos por una CA de pruebas que los navegadores no reconocen.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory # la unica diferencia
email: [email protected]
privateKeySecretRef:
name: letsencrypt-staging-cuenta
solvers:
- http01:
ingress:
ingressClassName: nginxEl procedimiento correcto, sin excepciones: (1) configurar el Ingress con cert-manager.io/cluster-issuer: letsencrypt-staging; (2) comprobar kubectl get certificate hasta READY: True; (3) verificar con curl -k que el TLS funciona y que el emisor es (STAGING) Let's Encrypt; y (4) solo entonces pasar a producción:
kubectl annotate ingress rutas-norte -n rutas-norte-pro \
cert-manager.io/cluster-issuer=letsencrypt-produccion --overwrite
kubectl delete secret rutasnorte-tls -n rutas-norte-pro
kubectl get certificate -n rutas-norte-pro -wEse kubectl delete secret es necesario: sin él, cert-manager ve un certificado válido y no lo reemplaza hasta que toque renovar.
- Renovación automática y comprobación del estado
cert-manager renueva cuando queda menos de renewBefore para caducar (por defecto un tercio de la vida; con 90 días, a los 60). El proceso es idéntico al de emisión y el Secret se actualiza en su sitio, sin cambiar de nombre. ¿Hay que reiniciar los pods? No. El controlador de Ingress observa el Secret y recarga solo. Si un pod montara el certificado como volumen, el kubelet propaga el cambio en aproximadamente un minuto, aunque la aplicación tendría que releerlo.
Comprobaciones
Status:
Conditions:
Type: Ready
Status: True
Message: Certificate is up to date and has not expired
Not Before: 2026-07-30T09:14:23Z
Not After: 2026-10-28T09:14:22Z
Renewal Time: 2026-09-28T09:14:22Z
Events:
Normal Issued 21d cert-manager-certificates-issuing The certificate has been successfully issuedNot After y Renewal Time son los dos campos que hay que vigilar. Y el certificado servido de verdad:
echo | openssl s_client -connect www.rutasnorte.example:443 \
-servername www.rutasnorte.example 2>/dev/null \
| openssl x509 -noout -issuer -subject -datesissuer=C=US, O=Let's Encrypt, CN=R11
subject=CN=www.rutasnorte.example
notBefore=Jul 30 09:14:23 2026 GMT
notAfter=Oct 28 09:14:22 2026 GMTEl -servername es imprescindible: sin SNI, el controlador devolvería su certificado por defecto y verías uno equivocado.
Diagnóstico cuando READY es False
Sigue la cadena hacia abajo hasta el Challenge, con kubectl describe sobre certificate, certificaterequest, order y challenge, y por último kubectl logs -n cert-manager -l app=cert-manager.
Mensaje en el Challenge |
Causa |
|---|---|
Waiting for HTTP-01 challenge propagation: wrong status code '404' |
El Ingress temporal no es alcanzable, o el DNS público no apunta a tu clúster |
connection refused / timeout |
El puerto 80 no está abierto desde internet |
NXDOMAIN |
El dominio no existe en el DNS público |
too many certificates already issued |
Límite semanal alcanzado. Espera o usa staging |
Issuer not found |
Confusión entre Issuer y ClusterIssuer, o nombre mal escrito |
Ese último merece atención: cert-manager.io/issuer busca un Issuer en el namespace del Ingress; cert-manager.io/cluster-issuer busca un ClusterIssuer global. Usar la anotación equivocada da un error que parece de nombre y es de tipo.
- Redirección a HTTPS y HSTS
Publicar HTTPS no basta: hay que impedir el HTTP, porque un cliente que escriba el dominio sin esquema irá por HTTP. En ingress-nginx la redirección está activada por defecto en cuanto el Ingress tiene bloque tls, y se controla con las anotaciones ssl-redirect (HTTP → HTTPS) y force-ssl-redirect (incluso detrás de otro proxy):
Pero la redirección deja una ventana: la primera petición viaja en claro y puede ser interceptada. HSTS (HTTP Strict Transport Security) la cierra: una cabecera que ordena al navegador usar HTTPS para ese dominio durante un tiempo, sin preguntar y sin permitir saltarse el aviso.
metadata:
annotations:
nginx.ingress.kubernetes.io/hsts: "true"
nginx.ingress.kubernetes.io/hsts-max-age: "31536000" # 1 año
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
nginx.ingress.kubernetes.io/hsts-preload: "false"Advertencias serias sobre HSTS, porque es de las pocas cosas de esta lección que pueden dejarte fuera de tu propio dominio:
- No se puede revocar rápidamente. Los navegadores recuerdan la directiva hasta que expire
max-age. Si tu certificado falla, los usuarios no pueden entrar, ni aceptando el riesgo. includeSubDomainsafecta a TODOS los subdominios, incluidos los que aún no tienen HTTPS: unadmin.rutasnorte.exampleinterno por HTTP dejaría de ser accesible.preloades prácticamente irreversible: entrar en la lista precargada de los navegadores es fácil, salir tarda meses. Empieza conmax-agebajo (300) y súbelo progresivamente.
Y aquí toca recordar la advertencia inicial: la política de HSTS, las versiones de TLS aceptadas (ssl-protocols: "TLSv1.2 TLSv1.3") y los conjuntos de cifrado (ssl-ciphers), que son ajustes globales del ConfigMap del controlador y no anotaciones por Ingress, deben ser revisados por un profesional de seguridad antes de aplicarse en producción.
- Más allá: mTLS y mallas de servicio
Lo montado protege el tramo cliente ↔ Ingress; dentro del clúster, el tráfico entre el controlador y tienda-web, o entre api-reservas y postgres-reservas, sigue en claro. Dos mecanismos van más lejos. mTLS (TLS mutuo): además del servidor, el cliente presenta certificado, de modo que api-reservas no solo verifica con quién habla sino que demuestra quién es; es la base de la identidad criptográfica entre servicios y sustituye a la confianza basada en direcciones IP. Y las mallas de servicio (Istio, Linkerd, Cilium Service Mesh), que inyectan un proxy junto a cada pod —o lo integran en el kernel con eBPF— y establecen mTLS automáticamente entre todos los componentes, con certificados de corta vida y sin tocar el código.
Para Rutas Norte hoy es exagerado: cinco componentes en un clúster propio, con las políticas de red de 04-06 limitando quién puede hablar con quién. Se replantearía si la plataforma creciera a decenas de servicios o si un requisito normativo exigiera cifrado extremo a extremo. El análisis completo está en 08-04.
Errores Comunes y Consejos
- Empezar directamente en producción de Let's Encrypt. Cinco fallos y una semana bloqueado. Siempre staging primero.
- Certificado sin
subjectAltName. Los navegadores ignoran elCN. Sin SAN, el certificado es inútil. - Secret TLS en otro namespace. No hay referencias cruzadas: el Secret vive donde vive el Ingress.
- Confundir
IssuerconClusterIssueren la anotación. El error dice "no encontrado" y es de tipo. - Cambiar el issuer sin borrar el Secret. cert-manager ve un certificado válido y no reemplaza nada.
- Redirigir a HTTPS bloqueando
/.well-known/acme-challenge/. El desafíoHTTP-01deja de funcionar y la renovación falla en silencio hasta que caduca. - Activar HSTS con
preloaddesde el primer día. Prácticamente irreversible. Subemax-agepor etapas. - Intentar
HTTP-01en minikube con dominios de/etc/hosts, o pedir un comodín conHTTP-01: lo primero no es alcanzable desde Let's Encrypt y lo segundo es imposible por diseño (exigeDNS-01). - Consejo: vigila
certmanager_certificate_expiration_timestamp_secondsen Prometheus (07-03) y alerta con 21 días de margen. La automatización también falla. - Consejo: un
Certificatepor dominio en lugar de uno con muchos SAN limita el daño: un fallo de validación en un dominio no impide renovar los demás. - Consejo: guarda el
ClusterIssueren Git, pero nunca el Secret con la clave privada de la cuenta ACME ni el token del DNS. Aplica lo de 03-02: Sealed Secrets, ESO o Vault.
Ejercicios
Ejercicio 1: HTTPS en desarrollo con certificado autofirmado
Genera con openssl un certificado válido para www.rutasnorte.example y api.rutasnorte.example, créalo como Secret TLS en rutas-norte-dev, añádelo al Ingress y comprueba que HTTPS funciona con -k y falla sin él. Explica exactamente qué garantía falta.
Ejercicio 2: Instalar cert-manager y crear los dos ClusterIssuers
Instala cert-manager, crea letsencrypt-staging y letsencrypt-produccion con desafío HTTP-01, y anota el Ingress de producción con el de staging. Sigue la cadena Certificate → Order → Challenge y explica por qué en minikube el desafío no se completará.
Ejercicio 3: Diagnosticar un certificado que no se emite
Un Certificate lleva 20 minutos en READY: False. Diseña el procedimiento de diagnóstico completo, indicando qué comando usar en cada nivel y qué mensaje esperarías para tres causas distintas: dominio que no resuelve desde internet, puerto 80 cerrado y límite de emisión alcanzado.
Soluciones
Ejercicio 1
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout /tmp/dev.key -out /tmp/dev.crt \
-subj "/C=ES/O=Rutas Norte S.L./CN=www.rutasnorte.example" \
-addext "subjectAltName=DNS:www.rutasnorte.example,DNS:api.rutasnorte.example"
kubectl create secret tls rutasnorte-tls \
--cert=/tmp/dev.crt --key=/tmp/dev.key -n rutas-norte-dev
kubectl patch ingress rutas-norte -n rutas-norte-dev --type=merge -p '
spec:
tls:
- hosts: ["www.rutasnorte.example","api.rutasnorte.example"]
secretName: rutasnorte-tls'
curl -k -s -o /dev/null -w 'con -k: %{http_code}\n' https://www.rutasnorte.example/
curl -s -o /dev/null https://www.rutasnorte.example/ || echo "sin -k: fallo (esperado)"Lo que falta es la autenticación, no el cifrado. El canal está igual de cifrado, pero nadie de confianza avala que ese certificado pertenezca a Rutas Norte: un atacante que secuestrara el DNS podría presentar su propio autofirmado y el cliente no notaría la diferencia. La cadena de confianza es lo que aporta una CA pública.
Ejercicio 2
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
kubectl wait --for=condition=Available deploy --all -n cert-manager --timeout=180s
kubectl apply -f k8s/base/clusterissuer-letsencrypt-staging.yaml
kubectl apply -f k8s/base/clusterissuer-letsencrypt-produccion.yaml
kubectl get clusterissuer # ambos deben aparecer con READY True
kubectl annotate ingress rutas-norte -n rutas-norte-pro \
cert-manager.io/cluster-issuer=letsencrypt-staging --overwrite
kubectl describe challenge -n rutas-norte-pro | tail -12Reason: Waiting for HTTP-01 challenge propagation: failed to perform self check
GET request: Get "http://www.rutasnorte.example/.well-known/acme-challenge/xY3...":
dial tcp: lookup www.rutasnorte.example: no such hostEn minikube el dominio solo existe en tu /etc/hosts. Los servidores de Let's Encrypt no lo resuelven ni pueden alcanzar tu clúster, así que HTTP-01 no puede completarse por diseño. Es exactamente el motivo por el que en desarrollo se usan certificados autofirmados.
Ejercicio 3
NS=rutas-norte-pro
kubectl describe certificate rutasnorte-tls -n $NS | sed -n '/Status/,$p' # nivel 1
kubectl describe certificaterequest -n $NS | grep -A3 "Conditions" # nivel 2
kubectl describe order -n $NS | grep -A5 "Status" # nivel 3
kubectl describe challenge -n $NS | grep -A5 "Reason" # nivel 4: el mensaje util
kubectl logs -n cert-manager -l app=cert-manager --tail=80 | grep -i error # nivel 5| Causa | Mensaje esperado | Dónde aparece |
|---|---|---|
| Dominio que no resuelve | dial tcp: lookup www.rutasnorte.example: no such host |
Challenge |
| Puerto 80 cerrado | connection refused o context deadline exceeded tras el self check |
Challenge |
| Límite alcanzado | 429 urn:ietf:params:acme:error:rateLimited: too many certificates already issued |
Order y logs de cert-manager |
Para los dos primeros se arregla la infraestructura (DNS público, cortafuegos). Para el tercero no hay arreglo técnico: se espera a que pase la semana o se trabaja en staging.
Conclusión
Rutas Norte ya cifra su tráfico. Sabes por qué HTTPS no es opcional en una plataforma que maneja DNI, teléfonos y pagos, y que TLS aporta dos cosas distintas: cifrado e identidad. Conoces los tres modelos de terminación —en el Ingress, reencriptado y passthrough— y por qué la terminación en el Ingress es la elección correcta aquí. Dominas la pieza básica, el Secret de tipo kubernetes.io/tls con sus claves tls.crt y tls.key, y las tres reglas que lo gobiernan: mismo namespace que el Ingress, coincidencia entre los hosts del bloque tls y las SAN del certificado, y selección por SNI cuando hay varios.
Has generado un certificado autofirmado con openssl para desarrollo, con el detalle decisivo del subjectAltName que los navegadores validan e ignorando el CN, y has comprobado en tu propia terminal la diferencia entre "cifrado" y "verificable": funciona con -k, falla sin él, y esa diferencia es la razón de que los autofirmados nunca lleguen a producción.
Después has automatizado el problema con cert-manager, que es el patrón operador aplicado a certificados: seis CRDs, un controlador y un bucle de reconciliación. Sabes qué es un Issuer y en qué se diferencia de un ClusterIssuer, y cómo cert-manager encadena por debajo Certificate → CertificateRequest → Order → Challenge, que es también el orden en que se diagnostica cualquier fallo. Entiendes ACME y sus dos desafíos: HTTP-01, sencillo pero con el requisito de ser alcanzable desde internet en el puerto 80 y sin poder emitir comodines, y DNS-01, imprescindible para comodines y para servicios internos a cambio de confiar un token de la API del DNS. Y has reducido toda la gestión a una anotación en el Ingress, con la disciplina irrenunciable de empezar siempre en staging para no consumir los cinco intentos semanales de Let's Encrypt.
Sabes comprobar el estado con kubectl get certificate y openssl s_client -servername, que la renovación ocurre 30 días antes de caducar sin reiniciar nada, y cómo cerrar la puerta al HTTP con la redirección y con HSTS, midiendo bien sus riesgos porque no se revoca a voluntad. Y sabes qué queda fuera: el tráfico dentro del clúster sigue en claro, y eso son mTLS y mallas de servicio, materia de 08-04.
Nos queda un agujero de los grandes, el mismo que arrastramos desde el módulo 3: cualquier pod del clúster puede conectarse a postgres-reservas:5432. Un pod comprometido, una imagen con una dependencia maliciosa o un simple error de despliegue en rutas-norte-dev tienen acceso directo a la base de datos con los datos personales de todos los clientes. En 04-06, la última lección del módulo, construiremos el modelo de aislamiento de la plataforma: denegar todo por defecto, abrir DNS —el fallo clásico que tumba el clúster entero—, y autorizar una a una únicamente las conversaciones que Rutas Norte necesita.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
