Un recurso Ingress define reglas HTTP y HTTPS (por nombre de dominio y por ruta) para llevar tráfico externo hasta los Services de un clúster de Kubernetes. Las reglas no hacen nada por sí solas: necesitan un controlador de Ingress que las lea y actúe como proxy inverso. En este tutorial instalarás Traefik como controlador, publicarás dos aplicaciones bajo el mismo punto de entrada con enrutamiento por dominio y por ruta, y activarás HTTPS con certificados de Let's Encrypt gestionados por cert-manager.
Requisitos previos
- Un clúster de Kubernetes 1.30 o posterior en el que los Services de tipo
LoadBalancerreciban una IP pública (un clúster gestionado con balanceador integrado, o MetalLB en servidores propios). Si solo tienes NodePort, puedes seguir la guía usando la IP de un nodo y el puerto asignado. kubectlconfigurado contra el clúster con permisos de administrador (hay que crear recursos a nivel de clúster, como IngressClass y ClusterIssuer).- Helm 3 o posterior en la máquina desde la que administras el clúster. En Ubuntu 24.04 puedes instalarlo con
sudo snap install helm --classic. - Un dominio con acceso a su DNS. Los ejemplos usan
app.example.comyapi.example.com: sustitúyelos por los tuyos.
Comprueba el acceso al clúster antes de empezar:
kubectl get nodes
helm version
Notael proyecto comunitario ingress-nginx dejó de mantenerse en marzo de 2026 y ya no recibe parches de seguridad. Para instalaciones nuevas usa otro controlador, como Traefik (el de esta guía), o migra a Gateway API. Los recursos Ingress que escribas aquí son estándar y sirven con cualquier controlador que los soporte.
Paso 1: Instalar Traefik como controlador de Ingress
Añade el repositorio oficial de charts de Traefik e instálalo en su propio namespace:
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik --namespace traefik --create-namespace
El chart crea un Deployment con Traefik, un Service de tipo LoadBalancer que escucha en los puertos 80 y 443, y una IngressClass llamada traefik. Comprueba que el pod está en marcha:
kubectl get pods -n traefik
NAME READY STATUS RESTARTS AGE
traefik-6d8b9f7c5b-kx2lp 1/1 Running 0 45s
Comprueba que existe la IngressClass que referenciarán tus Ingress:
kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 50s
Notasi tu clúster es k3s, Traefik ya viene instalado en el namespace
kube-systemcon la IngressClasstraefik. Salta este paso y usakube-systemdonde la guía dicetraefik.
Paso 2: Obtener la IP externa y configurar el DNS
Consulta la IP que el balanceador ha asignado al Service de Traefik. Puede tardar uno o dos minutos en aparecer:
kubectl get service traefik -n traefik
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.43.112.45 203.0.113.25 80:31080/TCP,443:31443/TCP 2m
Si EXTERNAL-IP se queda en <pending>, tu clúster no tiene un balanceador de carga; revisa la solución de problemas al final.
Crea en tu proveedor de DNS un registro A para cada dominio (app.example.com y api.example.com) apuntando a esa IP. Guárdala también en una variable para las pruebas:
export INGRESS_IP=203.0.113.25
Paso 3: Desplegar dos aplicaciones de ejemplo
Para ver el enrutamiento en acción, despliega dos aplicaciones en un namespace demo: web, un Nginx normal, y api, con la imagen traefik/whoami, que responde con los datos de la petición que recibe.
Crea el archivo apps.yaml:
nano apps.yaml
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.28
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: demo
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: demo
spec:
selector:
app: api
ports:
- port: 80
targetPort: 80
Aplícalo y comprueba que los Services tienen endpoints (pods detrás):
kubectl apply -f apps.yaml
kubectl get pods,endpointslices -n demo
Los dos Services son de tipo ClusterIP: no son accesibles desde fuera del clúster. El Ingress será la única puerta de entrada.
Paso 4: Enrutar tráfico por nombre de dominio
Crea un Ingress que envíe app.example.com al Service web y api.example.com al Service api:
nano ingress-hosts.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo
namespace: demo
spec:
ingressClassName: traefik
rules:
- host: app.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: 80
ingressClassName indica qué controlador debe atender este Ingress. Aplícalo:
kubectl apply -f ingress-hosts.yaml
kubectl get ingress -n demo
NAME CLASS HOSTS ADDRESS PORTS AGE
demo traefik app.example.com,api.example.com 203.0.113.25 80 10s
Prueba las dos reglas. La cabecera Host permite hacerlo aunque el DNS aún no se haya propagado:
curl -s -H "Host: app.example.com" http://$INGRESS_IP/ | grep -o '<title>.*</title>'
curl -s -H "Host: api.example.com" http://$INGRESS_IP/ | head -n 1
<title>Welcome to nginx!</title>
Hostname: api-7b9c6d5f8-4jz2q
Una petición con un Host que no coincide con ninguna regla recibe un 404 de Traefik.
Paso 5: Enrutar tráfico por ruta
También puedes servir varias aplicaciones bajo un mismo dominio, separadas por ruta. Modifica la regla de app.example.com para que /api vaya al Service api y el resto a web. Edita ingress-hosts.yaml y deja la primera regla así:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Aplica el cambio y pruébalo:
kubectl apply -f ingress-hosts.yaml
curl -s -H "Host: app.example.com" http://$INGRESS_IP/api/usuarios | grep GET
GET /api/usuarios HTTP/1.1
Ten en cuenta dos detalles:
- Con
pathType: Prefixla coincidencia es por segmentos completos:/apicaptura/apiy/api/usuarios, pero no/apiv2. ConpathType: Exactsolo coincide la ruta exacta. - El backend recibe la ruta original (
/api/usuarios). La especificación de Ingress no incluye reescritura de rutas; si tu aplicación espera/usuarios, configúrala para servir bajo/apio usa un middlewarestripPrefixde Traefik.
Paso 6: Instalar cert-manager
cert-manager solicita y renueva certificados TLS automáticamente y los guarda como Secrets de Kubernetes. Instálalo con Helm, incluyendo sus CRD:
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
Comprueba que sus tres componentes están en marcha:
kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-5c9d8879fd-q7x2m 1/1 Running 0 60s
cert-manager-cainjector-6cc9b5f678-p4lbn 1/1 Running 0 60s
cert-manager-webhook-7bb7b75848-9d2kc 1/1 Running 0 60s
Paso 7: Crear un emisor de Let's Encrypt
Un ClusterIssuer indica a cert-manager cómo obtener certificados. Este usa el desafío HTTP-01 de Let's Encrypt: cert-manager crea temporalmente un Ingress con la clase traefik para responder al desafío en /.well-known/acme-challenge/. Sustituye [email protected] por tu dirección, a la que Let's Encrypt enviará avisos sobre tus certificados:
nano cluster-issuer.yaml
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-account-key
solvers:
- http01:
ingress:
ingressClassName: traefik
Aplícalo y comprueba que queda listo:
kubectl apply -f cluster-issuer.yaml
kubectl get clusterissuer
NAME READY AGE
letsencrypt-prod True 15s
Consejomientras hagas pruebas, crea un segundo emisor igual con el servidor
https://acme-staging-v02.api.letsencrypt.org/directory. Los certificados de staging no son de confianza, pero no cuentan para los límites de emisión de Let's Encrypt.
Paso 8: Activar HTTPS en el Ingress
El DNS de tus dominios debe apuntar ya a la IP del paso 2, porque Let's Encrypt validará el dominio conectándose a él. Compruébalo:
dig +short app.example.com
Añade al Ingress la anotación que lo vincula al emisor y una sección tls con los dominios y el nombre del Secret donde se guardará el certificado. Edita ingress-hosts.yaml para que la cabecera quede así (las reglas no cambian):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo
namespace: demo
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- app.example.com
- api.example.com
secretName: demo-tls
rules:
Aplica el cambio y sigue la emisión del certificado:
kubectl apply -f ingress-hosts.yaml
kubectl get certificate -n demo --watch
NAME READY SECRET AGE
demo-tls False demo-tls 5s
demo-tls True demo-tls 48s
Cuando READY pase a True, pulsa Ctrl+C y prueba el acceso por HTTPS:
curl -sI https://app.example.com
HTTP/2 200
content-type: text/html
server: nginx/1.28.0
cert-manager renovará el certificado automáticamente antes de que caduque, sin que tengas que hacer nada.
Paso 9: Redirigir HTTP a HTTPS (opcional)
Por defecto Traefik sigue sirviendo también por HTTP. Para redirigir todo el tráfico del puerto 80 al 443, activa la redirección en el punto de entrada web con un archivo de valores del chart:
nano traefik-values.yaml
ports:
web:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
Aplícalo sobre la instalación existente:
helm upgrade traefik traefik/traefik --namespace traefik --reuse-values -f traefik-values.yaml
Comprueba la redirección:
curl -sI http://app.example.com
HTTP/1.1 301 Moved Permanently
Location: https://app.example.com/
Notaesta sintaxis corresponde a las versiones recientes del chart (34 o posteriores). Si usas una versión antigua, consulta el archivo
values.yamlde tu versión conhelm show values traefik/traefik. La renovación HTTP-01 sigue funcionando con la redirección activa, porque Let's Encrypt sigue redirecciones a HTTPS.
Solución de problemas
EXTERNAL-IPen<pending>: el clúster no tiene balanceador de carga. Instala MetalLB con un rango de IPs disponibles, o accede por NodePort:kubectl get svc traefik -n traefikmuestra el puerto (por ejemplo80:31080) y puedes probar concurl -H "Host: app.example.com" http://ip_de_un_nodo:31080.- El Ingress no tiene
ADDRESSo devuelve 404 para todo: comprueba queingressClassNamecoincide conkubectl get ingressclassy revisa los logs del controlador conkubectl logs -n traefik deployment/traefik. - 502 o 503 en un dominio concreto: el Service no tiene pods listos o el puerto no coincide. Revisa
kubectl get endpointslices -n demoy queporten el Ingress sea el puerto del Service, no el del contenedor. - El certificado se queda en
READY False: sigue la cadenakubectl describe certificate demo-tls -n demo, luegokubectl get challenges -n demoykubectl describe challenge <nombre> -n demo. La causa habitual es que el DNS no apunta a la IP del balanceador o que el puerto 80 no es accesible desde Internet.
Conclusión
Has instalado Traefik como controlador de Ingress, has publicado dos aplicaciones detrás de una única IP con reglas por dominio y por ruta, y has automatizado los certificados HTTPS con cert-manager y Let's Encrypt. Como siguientes pasos, puedes aumentar las réplicas del controlador con helm upgrade --set deployment.replicas=2 para que no sea un punto único de fallo, explorar los middlewares de Traefik (autenticación, límites de peticiones, cabeceras) o migrar tus rutas a Gateway API, el sucesor de Ingress en Kubernetes.
