Knative añade a Kubernetes un modelo serverless: con Knative Serving despliegas un contenedor y obtienes URL, autoescalado por peticiones (incluido el escalado a cero) y revisiones inmutables entre las que repartir tráfico; con Knative Eventing conectas productores y consumidores de eventos en formato CloudEvents. En esta guía instalarás ambos componentes con Kourier como capa de red, sobre un clúster k3s en Ubuntu 24.04 o sobre cualquier clúster existente, y los probarás con un despliegue canary y un broker de eventos.

Requisitos previos

  • Un clúster de Kubernetes con soporte para servicios LoadBalancer y kubectl configurado con permisos de administrador. Si no tienes uno, el paso 1 crea un clúster k3s de un nodo en un servidor con Ubuntu 24.04, por ejemplo un VPS de CubePath con al menos 4 GB de RAM y 2 vCPU.
  • Un usuario no root con privilegios sudo en ese servidor.
  • Opcional: un dominio en el que puedas crear un registro DNS comodín. Para pruebas se usa sslip.io, que no requiere configurar DNS.

Knative publica cada versión con una lista de versiones de Kubernetes soportadas. Esta guía usa Knative 1.18; comprueba en las notas de la última versión si hay una más reciente y qué versión mínima de Kubernetes necesita.

Paso 1: Crear un clúster k3s (opcional)

Si ya tienes un clúster, salta al paso 2. k3s es una distribución ligera de Kubernetes que se instala con un script. Se desactiva Traefik, el ingress que incluye por defecto, porque Kourier necesita los puertos 80 y 443 del nodo.

Configura el cortafuegos. Además de SSH, HTTP y HTTPS, k3s necesita que UFW permita el tráfico de las redes internas de pods (10.42.0.0/16) y de servicios (10.43.0.0/16); sin estas reglas los pods no pueden comunicarse entre sí. La API de Kubernetes (6443) queda cerrada: gestiona el clúster desde el propio servidor o por un túnel SSH.

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
sudo ufw enable

Descarga el script de instalación, revísalo y ejecútalo:

curl -sfL https://get.k3s.io -o install-k3s.sh
less install-k3s.sh
sh install-k3s.sh --disable traefik

Configura kubectl para tu usuario (k3s instala el binario en /usr/local/bin/kubectl):

mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$USER": ~/.kube/config
chmod 600 ~/.kube/config
kubectl get nodes
NAME      STATUS   ROLES                  AGE   VERSION
knative   Ready    control-plane,master   40s   v1.33.4+k3s1

Paso 2: Instalar Knative Serving

Define la versión en una variable para no repetirla en cada URL:

export KNATIVE_VERSION=knative-v1.18.0

Instala primero las definiciones de recursos (CRD) y después los componentes principales:

kubectl apply -f https://github.com/knative/serving/releases/download/${KNATIVE_VERSION}/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/${KNATIVE_VERSION}/serving-core.yaml

Espera a que todos los componentes estén listos:

kubectl wait --for=condition=Available deployment --all -n knative-serving --timeout=300s
kubectl get pods -n knative-serving
NAME                          READY   STATUS    RESTARTS   AGE
activator-6cf5b8d7c9-7lq2x    1/1     Running   0          60s
autoscaler-58b8c6d8f7-t2k4m   1/1     Running   0          60s
controller-7d9f6c7b8d-xz9vn   1/1     Running   0          60s
webhook-5f8b9c6d4b-q8wzl      1/1     Running   0          60s

Paso 3: Instalar Kourier como capa de red

Knative necesita una capa de red que reciba las peticiones y las enrute a la revisión correcta. Kourier es la opción más ligera y la recomendada si no usas ya Istio o Contour. Instálala:

kubectl apply -f https://github.com/knative/net-kourier/releases/download/${KNATIVE_VERSION}/kourier.yaml

Indica a Knative Serving que use Kourier:

kubectl patch configmap/config-network \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'

Comprueba que el servicio de Kourier tiene una IP externa asignada:

kubectl get service kourier -n kourier-system
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
kourier   LoadBalancer   10.43.112.57   203.0.113.10    80:31080/TCP,443:31443/TCP   30s

Si EXTERNAL-IP se queda en <pending>, tu clúster no tiene un balanceador que atienda servicios LoadBalancer. En k3s lo proporciona ServiceLB; en otros clústeres necesitarás MetalLB o el balanceador de tu proveedor.

Paso 4: Configurar el dominio

Cada servicio de Knative recibe una URL con el formato <servicio>.<namespace>.<dominio>. Elige una de estas dos opciones.

Para pruebas, con sslip.io. Knative incluye un Job que configura el dominio <IP>.sslip.io, un DNS público que resuelve cualquier nombre que contenga una IP a esa IP:

kubectl apply -f https://github.com/knative/serving/releases/download/${KNATIVE_VERSION}/serving-default-domain.yaml

Para producción, con tu dominio. Crea en tu proveedor DNS un registro A comodín *.your_domain que apunte a la EXTERNAL-IP de Kourier y configura el dominio en Knative:

kubectl patch configmap/config-domain \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"your_domain":""}}'

Con esta configuración, un servicio hola en el namespace default tendrá la URL http://hola.default.your_domain.

Paso 5: Instalar la CLI kn

kn es la CLI de Knative. No es imprescindible (todo se puede hacer con manifiestos y kubectl), pero simplifica mucho consultar revisiones y tráfico. Descárgala para tu arquitectura (usa kn-linux-arm64 en ARM):

curl -fsSLo kn https://github.com/knative/client/releases/download/${KNATIVE_VERSION}/kn-linux-amd64
sudo install -m 755 kn /usr/local/bin/kn
rm kn
kn version

Paso 6: Desplegar el primer servicio

Un Service de Knative (abreviado ksvc) describe el contenedor a ejecutar; Knative crea a partir de él la revisión, el deployment, el autoescalado y la ruta. Crea el manifiesto:

nano hola-v1.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hola
  namespace: default
spec:
  template:
    metadata:
      name: hola-v1
      annotations:
        autoscaling.knative.dev/min-scale: "0"
        autoscaling.knative.dev/max-scale: "10"
        autoscaling.knative.dev/target: "50"
    spec:
      containers:
        - image: ghcr.io/knative/helloworld-go:latest
          ports:
            - containerPort: 8080
          env:
            - name: TARGET
              value: "v1"
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              memory: 256Mi

Las anotaciones controlan el autoescalador de Knative (KPA):

  • min-scale: "0" permite escalar a cero cuando no hay tráfico.
  • max-scale: "10" pone un techo de réplicas.
  • target: "50" es la concurrencia objetivo: el autoescalador añade réplicas cuando cada una atiende de media más de 50 peticiones simultáneas.

El nombre de la revisión (hola-v1) es opcional, pero ponerlo facilita el reparto de tráfico del paso 8. Debe empezar por el nombre del servicio seguido de un guion.

Aplica el manifiesto y espera a que el servicio esté listo:

kubectl apply -f hola-v1.yaml
kubectl wait --for=condition=Ready ksvc/hola --timeout=120s
kn service list
NAME   URL                                          LATEST    AGE   CONDITIONS   READY   REASON
hola   http://hola.default.203.0.113.10.sslip.io    hola-v1   25s   3 OK / 3     True

Haz una petición:

curl "$(kubectl get ksvc hola -o jsonpath='{.status.url}')"
Hello v1!

Paso 7: Comprobar el escalado a cero

Observa los pods del servicio en una terminal:

kubectl get pods -l serving.knative.dev/service=hola -w

Tras aproximadamente un minuto sin peticiones, el pod pasa a Terminating y desaparece. Haz otra petición con curl desde otra terminal: verás cómo se crea un pod nuevo. Esa primera petición tarda algo más (arranque en frío) porque el activator de Knative la retiene hasta que el pod está listo; las siguientes son inmediatas.

El tiempo de espera antes de escalar a cero se configura de forma global en el ConfigMap config-autoscaler. Por ejemplo, para esperar dos minutos:

kubectl patch configmap/config-autoscaler \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"scale-to-zero-grace-period":"120s"}}'

Para un servicio concreto que no deba sufrir arranques en frío, pon autoscaling.knative.dev/min-scale: "1" en su plantilla.

Paso 8: Dividir tráfico entre revisiones

Cada cambio en spec.template crea una revisión nueva e inmutable. Por defecto, todo el tráfico va a la última, pero puedes repartirlo para hacer un despliegue canary. Crea la versión 2 enviándole solo el 20 % del tráfico:

nano hola-v2.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hola
  namespace: default
spec:
  template:
    metadata:
      name: hola-v2
      annotations:
        autoscaling.knative.dev/min-scale: "0"
        autoscaling.knative.dev/max-scale: "10"
        autoscaling.knative.dev/target: "50"
    spec:
      containers:
        - image: ghcr.io/knative/helloworld-go:latest
          ports:
            - containerPort: 8080
          env:
            - name: TARGET
              value: "v2"
  traffic:
    - revisionName: hola-v1
      percent: 80
      tag: estable
    - revisionName: hola-v2
      percent: 20
      tag: canary

Aplica el cambio y lanza varias peticiones:

kubectl apply -f hola-v2.yaml
kubectl wait --for=condition=Ready ksvc/hola --timeout=120s
URL=$(kubectl get ksvc hola -o jsonpath='{.status.url}')
for i in $(seq 1 10); do curl -s "$URL"; done

Aproximadamente dos de cada diez respuestas serán Hello v2!. Las etiquetas (tag) crean además una URL fija para cada revisión, útil para probar la versión canary directamente:

kn service describe hola
...
Revisions:
     20%  @latest (hola-v2) #canary [2] (30s)
     ...
     80%  hola-v1 #estable [1] (2m)
...

La revisión canary responde siempre en http://canary-hola.default.203.0.113.10.sslip.io. Cuando la valides, pasa todo el tráfico a la nueva revisión:

kn service update hola --traffic hola-v2=100

Si algo va mal, volver atrás es igual de rápido: kn service update hola --traffic hola-v1=100. La revisión antigua sigue existiendo, así que no hay que reconstruir ni volver a desplegar nada.

Paso 9: Instalar Knative Eventing

Knative Eventing permite que los servicios reaccionen a eventos sin conocer quién los produce. Instala los CRD, el núcleo, un canal en memoria y el broker basado en canales:

kubectl apply -f https://github.com/knative/eventing/releases/download/${KNATIVE_VERSION}/eventing-crds.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/${KNATIVE_VERSION}/eventing-core.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/${KNATIVE_VERSION}/in-memory-channel.yaml
kubectl apply -f https://github.com/knative/eventing/releases/download/${KNATIVE_VERSION}/mt-channel-broker.yaml
kubectl wait --for=condition=Available deployment --all -n knative-eventing --timeout=300s

Paso 10: Enrutar eventos con un broker y un trigger

El flujo es: los productores envían eventos al broker, y cada trigger filtra por atributos (por ejemplo, el tipo de evento) y los entrega a un suscriptor. Crea los tres recursos en un solo manifiesto:

nano eventos.yaml
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
  name: default
  namespace: default
---
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: event-display
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/min-scale: "1"
    spec:
      containers:
        - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display
---
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: pedidos-creados
  namespace: default
spec:
  broker: default
  filter:
    attributes:
      type: com.ejemplo.pedido.creado
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: event-display

event-display es una imagen de ejemplo de Knative que imprime en su log cada evento recibido. Aplica el manifiesto y comprueba que el broker y el trigger están listos:

kubectl apply -f eventos.yaml
kubectl wait --for=condition=Ready broker/default trigger/pedidos-creados --timeout=120s
kubectl get broker default
NAME      URL                                                                        AGE   READY   REASON
default   http://broker-ingress.knative-eventing.svc.cluster.local/default/default   40s   True

El broker solo es accesible desde dentro del clúster. Envía un evento con un pod temporal de curl, usando las cabeceras Ce-* del formato binario de CloudEvents:

kubectl run curl --image=curlimages/curl --rm -it --restart=Never -- \
  -s -o /dev/null -w "%{http_code}\n" -X POST \
  http://broker-ingress.knative-eventing.svc.cluster.local/default/default \
  -H "Ce-Id: 1" \
  -H "Ce-Specversion: 1.0" \
  -H "Ce-Type: com.ejemplo.pedido.creado" \
  -H "Ce-Source: prueba-manual" \
  -H "Content-Type: application/json" \
  -d '{"pedidoId": "42"}'

El broker responde 202 al aceptar el evento. Revisa el log del suscriptor:

kubectl logs -l serving.knative.dev/service=event-display -c user-container --tail=15
Context Attributes,
  specversion: 1.0
  type: com.ejemplo.pedido.creado
  source: prueba-manual
  id: 1
  ...
Data,
  {
    "pedidoId": "42"
  }

Si envías un evento con otro Ce-Type, el broker lo acepta igualmente pero el trigger no lo entrega, porque no coincide con su filtro.

Actualizar servicios desde CI

En un pipeline de despliegue continuo basta con construir la imagen, subirla al registro y actualizar el servicio. Knative crea una revisión nueva y mueve el tráfico cuando está lista:

kn service update hola --image registry.your_domain/hola:1.4.0

Si gestionas los manifiestos con Git (Argo CD, Flux), cambia la imagen en el YAML del Service y deja que la herramienta lo aplique; el resultado es el mismo.

Solución de problemas

El ksvc se queda con READY en Unknown o False. Revisa el motivo con kubectl describe ksvc hola y el estado de la revisión con kubectl get revisions. Los errores más habituales son una imagen que no existe o que no se puede descargar, y un contenedor que no escucha en el puerto declarado.

curl a la URL del servicio no responde. Comprueba que Kourier tiene EXTERNAL-IP (kubectl get svc -n kourier-system), que los puertos 80 y 443 están abiertos en el cortafuegos y que el dominio configurado en config-domain resuelve a esa IP.

El servicio nunca escala a cero. Algo sigue enviando tráfico (una sonda externa de monitorización, por ejemplo) o la anotación min-scale es mayor que 0. Los logs del autoescalador ayudan a verlo: kubectl logs -n knative-serving deployment/autoscaler --tail=50.

El trigger no entrega eventos. Comprueba que el type del evento coincide exactamente con el filtro, que el trigger está READY con kubectl get triggers y los logs del broker con kubectl logs -n knative-eventing deployment/mt-broker-ingress --tail=50.

Conclusión

Has instalado Knative Serving con Kourier, desplegado un servicio que escala a cero, repartido tráfico entre dos revisiones con un despliegue canary y enrutado eventos con un broker y un trigger. Como siguientes pasos, activa HTTPS automático para tus servicios con la integración de cert-manager de Knative, sustituye el canal en memoria por un broker de Kafka o RabbitMQ para producción y conecta fuentes de eventos reales como PingSource o ApiServerSource.