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
LoadBalancerykubectlconfigurado 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
sudoen 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
Advertenciael canal en memoria (InMemoryChannel) pierde los eventos pendientes si se reinicia su pod. Sirve para desarrollo y pruebas; en producción usa un broker respaldado por Kafka o RabbitMQ, que Knative ofrece como componentes aparte.
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.
