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 LoadBalancer reciban 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.
  • kubectl configurado 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.com y api.example.com: sustitúyelos por los tuyos.

Comprueba el acceso al clúster antes de empezar:

kubectl get nodes
helm version

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

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: Prefix la coincidencia es por segmentos completos: /api captura /api y /api/usuarios, pero no /apiv2. Con pathType: Exact solo 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 /api o usa un middleware stripPrefix de 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

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/

Solución de problemas

  • EXTERNAL-IP en <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 traefik muestra el puerto (por ejemplo 80:31080) y puedes probar con curl -H "Host: app.example.com" http://ip_de_un_nodo:31080.
  • El Ingress no tiene ADDRESS o devuelve 404 para todo: comprueba que ingressClassName coincide con kubectl get ingressclass y revisa los logs del controlador con kubectl 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 demo y que port en el Ingress sea el puerto del Service, no el del contenedor.
  • El certificado se queda en READY False: sigue la cadena kubectl describe certificate demo-tls -n demo, luego kubectl get challenges -n demo y kubectl 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.