Kong Gateway es una puerta de enlace de API de código abierto construida sobre Nginx y OpenResty. Se coloca delante de tus servicios y centraliza el enrutamiento, la autenticación, el límite de peticiones y los logs mediante plugins que se configuran en caliente a través de su Admin API. En este tutorial desplegarás Kong con PostgreSQL usando Docker Compose en Ubuntu 24.04, publicarás un servicio de prueba, lo protegerás con claves de API y un límite de peticiones y añadirás un certificado TLS.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker, con tu usuario en el grupo docker.
  • curl y jq para trabajar con la Admin API: sudo apt install curl jq.
  • Opcional, para el paso 7: un dominio apuntando al servidor y un certificado para él (por ejemplo, emitido con Certbot).

Cómo se organiza Kong

Antes de empezar conviene conocer los cuatro objetos con los que vas a trabajar:

ObjetoQué representa
ServiceUna API o aplicación de destino (su URL interna).
RouteLa regla que decide qué peticiones entrantes van a un Service (ruta, host, método).
ConsumerUn cliente de la API, al que se asocian credenciales.
PluginFuncionalidad que se aplica de forma global, a un Service, a una Route o a un Consumer.

Kong escucha en dos puertos principales: el proxy (8000 para HTTP y 8443 para HTTPS dentro del contenedor), que recibe el tráfico de los clientes, y la Admin API (8001), que usas para configurarlo. La Admin API da control total sobre el gateway y nunca debe quedar expuesta a Internet.

Paso 1: Preparar el proyecto

Crea un directorio para el despliegue:

mkdir -p ~/kong && cd ~/kong

Genera una contraseña aleatoria para PostgreSQL y guárdala en un archivo .env, que Docker Compose lee automáticamente:

echo "KONG_PG_PASSWORD=$(openssl rand -hex 24)" > .env
chmod 600 .env

Paso 2: Definir los contenedores

Crea el archivo de Compose:

nano ~/kong/compose.yaml
services:
  kong-db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: ${KONG_PG_PASSWORD}
    volumes:
      - kong-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong", "-d", "kong"]
      interval: 5s
      timeout: 5s
      retries: 10

  kong-migrations:
    image: kong:3.9
    command: kong migrations bootstrap
    depends_on:
      kong-db:
        condition: service_healthy
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: ${KONG_PG_PASSWORD}
    restart: on-failure

  kong:
    image: kong:3.9
    restart: unless-stopped
    depends_on:
      kong-migrations:
        condition: service_completed_successfully
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: ${KONG_PG_PASSWORD}
      KONG_PROXY_LISTEN: "0.0.0.0:8000, 0.0.0.0:8443 ssl http2"
      KONG_ADMIN_LISTEN: "0.0.0.0:8001"
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ERROR_LOG: /dev/stderr
    ports:
      - "80:8000"
      - "443:8443"
      - "127.0.0.1:8001:8001"
    healthcheck:
      test: ["CMD", "kong", "health"]
      interval: 10s
      timeout: 5s
      retries: 10

  httpbin:
    image: mccutchen/go-httpbin
    restart: unless-stopped

volumes:
  kong-db:

Puntos importantes:

  • kong-migrations crea el esquema en PostgreSQL y termina. Kong solo arranca cuando ha terminado correctamente.
  • El proxy se publica en los puertos 80 y 443 del servidor. La Admin API se publica únicamente en 127.0.0.1:8001, así que solo es accesible desde el propio servidor.
  • httpbin es una aplicación de prueba que responde con los datos de la petición. No publica puertos: solo Kong puede llegar a ella por la red interna de Compose. En producción la sustituirás por tus servicios reales.

Paso 3: Arrancar Kong

Levanta la pila:

docker compose up -d

Comprueba el estado de los contenedores. kong-migrations debe aparecer como terminado con código 0 y kong como healthy pasados unos segundos:

docker compose ps -a
NAME                     IMAGE                  STATUS
kong-httpbin-1           mccutchen/go-httpbin   Up 40 seconds
kong-kong-1              kong:3.9               Up 25 seconds (healthy)
kong-kong-db-1           postgres:16            Up 40 seconds (healthy)
kong-kong-migrations-1   kong:3.9               Exited (0) 27 seconds ago

Consulta la versión y el estado de la base de datos a través de la Admin API:

curl -s http://127.0.0.1:8001 | jq -r .version
curl -s http://127.0.0.1:8001/status | jq .database
3.9.1
{
  "reachable": true
}

Una petición al proxy devuelve 404 porque todavía no hay rutas:

curl -i http://localhost/
HTTP/1.1 404 Not Found
...
{
  "message":"no Route matched with those values",
  "request_id":"..."
}

Paso 4: Crear un servicio y una ruta

Registra httpbin como servicio. Kong resolverá el nombre httpbin dentro de la red de Compose:

curl -s -X POST http://127.0.0.1:8001/services \
  --data name=httpbin \
  --data url=http://httpbin:8080 | jq '{id, name, host, port}'
{
  "id": "5a1c7f2e-...",
  "name": "httpbin",
  "host": "httpbin",
  "port": 8080
}

Crea una ruta que envíe al servicio todo lo que empiece por /demo. Con strip_path=true (el valor por defecto) Kong elimina el prefijo, así que /demo/get llega al servicio como /get:

curl -s -X POST http://127.0.0.1:8001/services/httpbin/routes \
  --data name=demo \
  --data 'paths[]=/demo' | jq '{name, paths, strip_path}'

Prueba la ruta a través del proxy:

curl -s http://localhost/demo/get | jq '.headers["X-Forwarded-Prefix"]'
[
  "/demo"
]

La respuesta es la de httpbin: Kong ha recibido /demo/get, ha quitado el prefijo y ha añadido la cabecera X-Forwarded-Prefix con la parte eliminada.

Si quieres enrutar por dominio en lugar de por ruta, añade --data 'hosts[]=api.your_domain' al crear la ruta.

Paso 5: Exigir una clave de API

Activa el plugin key-auth en el servicio. A partir de ese momento cada petición debe incluir una clave válida en la cabecera apikey:

curl -s -X POST http://127.0.0.1:8001/services/httpbin/plugins \
  --data name=key-auth \
  --data 'config.key_names[]=apikey' | jq '{name, enabled}'

Comprueba que una petición sin clave es rechazada:

curl -i http://localhost/demo/get
HTTP/1.1 401 Unauthorized
...
{
  "message":"No API key found in request",
  "request_id":"..."
}

Crea un consumidor y deja que Kong genere una clave aleatoria para él:

curl -s -X POST http://127.0.0.1:8001/consumers --data username=app-client | jq '{username}'
API_KEY=$(curl -s -X POST http://127.0.0.1:8001/consumers/app-client/key-auth | jq -r .key)
echo "$API_KEY"

Repite la petición con la clave:

curl -s -o /dev/null -w '%{http_code}\n' -H "apikey: $API_KEY" http://localhost/demo/get
200

Kong añade las cabeceras X-Consumer-Username y X-Consumer-ID a la petición que llega al backend, de modo que tu aplicación sabe qué cliente está llamando.

Paso 6: Limitar las peticiones

El plugin rate-limiting cuenta las peticiones por consumidor (o por IP si no hay consumidor autenticado). Actívalo en el servicio con un límite de 5 peticiones por minuto para poder probarlo fácilmente:

curl -s -X POST http://127.0.0.1:8001/services/httpbin/plugins \
  --data name=rate-limiting \
  --data config.minute=5 \
  --data config.policy=local | jq '{name, config: {minute: .config.minute, policy: .config.policy}}'

La política local guarda los contadores en memoria de cada nodo de Kong. Si ejecutas varios nodos detrás de un balanceador, usa redis para que compartan el contador.

Lanza siete peticiones seguidas:

for i in $(seq 1 7); do
  curl -s -o /dev/null -w '%{http_code}\n' -H "apikey: $API_KEY" http://localhost/demo/get
done
200
200
200
200
200
429
429

Las respuestas incluyen cabeceras que indican al cliente cuánto margen le queda:

curl -s -D - -o /dev/null -H "apikey: $API_KEY" http://localhost/demo/get | grep -i ratelimit
X-RateLimit-Limit-Minute: 5
X-RateLimit-Remaining-Minute: 0
RateLimit-Limit: 5
RateLimit-Remaining: 0
RateLimit-Reset: 38

Para dar a un cliente concreto un límite distinto, crea el mismo plugin sobre el consumidor (POST /consumers/app-client/plugins). La configuración más específica tiene prioridad sobre la del servicio.

Paso 7: Añadir un certificado TLS

Kong sirve HTTPS en el puerto 443 con un certificado autofirmado hasta que le das uno válido. Sube tu certificado y asócialo al dominio (SNI). La clave privada solo es legible por root, por eso se lee con sudo:

sudo curl -s -X POST http://127.0.0.1:8001/certificates \
  -F "cert=@/etc/letsencrypt/live/your_domain/fullchain.pem" \
  -F "key=@/etc/letsencrypt/live/your_domain/privkey.pem" \
  -F "snis[]=your_domain" | jq '{id, snis}'

Comprueba desde tu equipo que Kong presenta el certificado correcto:

curl -s -o /dev/null -w '%{http_code}\n' -H "apikey: your_api_key" https://your_domain/demo/get
200

Como curl valida el certificado por defecto, un 200 confirma que la cadena es correcta. Sustituye your_api_key por la clave generada en el paso 5.

Cuando renueves el certificado, actualízalo con PATCH /certificates/<id> usando los mismos parámetros cert y key.

Paso 8: Exportar la configuración

Toda la configuración está en PostgreSQL. Además de hacer copias de la base de datos, puedes exportarla a un archivo declarativo legible, útil para revisarla o versionarla en Git:

docker compose exec kong kong config db_export /tmp/kong.yaml
docker compose cp kong:/tmp/kong.yaml ./kong-export.yaml
grep -A3 '^services:' kong-export.yaml
services:
- connect_timeout: 60000
  enabled: true
  host: httpbin

Solución de problemas

  • kong no arranca y kong-migrations sale con error: revisa docker compose logs kong-migrations. Suele ser una contraseña de PostgreSQL distinta de la que se usó al crear el volumen; si es una instalación nueva, borra el volumen con docker compose down -v y vuelve a empezar.
  • no Route matched with those values: la ruta no coincide. Lista las rutas con curl -s http://127.0.0.1:8001/routes | jq '.data[] | {name, paths, hosts}'.
  • 502 Bad Gateway o An invalid response was received from the upstream server: Kong no llega al servicio. Comprueba la URL con curl -s http://127.0.0.1:8001/services/httpbin | jq '{host, port, path}' y que el backend esté en la misma red o sea accesible desde el contenedor.
  • Actualizar Kong: cambia la etiqueta de la imagen, ejecuta docker compose run --rm kong kong migrations up y después docker compose up -d. Lee antes las notas de la versión.

Conclusión

Tienes Kong Gateway funcionando con PostgreSQL, un servicio publicado a través de una ruta, autenticación por clave de API, límite de peticiones por consumidor y un certificado TLS, con la Admin API accesible solo desde el propio servidor. Como siguientes pasos, puedes añadir plugins como cors, ip-restriction o prometheus, pasar a la política redis del límite de peticiones si escalas a varios nodos, y gestionar la configuración de forma declarativa con decK en tu pipeline de CI.