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. curlyjqpara 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:
| Objeto | Qué representa |
|---|---|
| Service | Una API o aplicación de destino (su URL interna). |
| Route | La regla que decide qué peticiones entrantes van a un Service (ruta, host, método). |
| Consumer | Un cliente de la API, al que se asocian credenciales. |
| Plugin | Funcionalidad 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-migrationscrea 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. httpbines 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.
Importantelos puertos que publica Docker no pasan por las reglas de UFW. Por eso la Admin API se enlaza explícitamente a
127.0.0.1en lugar de confiar en el cortafuegos.
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
Advertenciael archivo exportado contiene las credenciales de los consumidores, como las claves de API. Guárdalo con permisos restrictivos y no lo subas a un repositorio público.
Solución de problemas
kongno arranca ykong-migrationssale con error: revisadocker 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 condocker compose down -vy vuelve a empezar.no Route matched with those values: la ruta no coincide. Lista las rutas concurl -s http://127.0.0.1:8001/routes | jq '.data[] | {name, paths, hosts}'.502 Bad GatewayoAn invalid response was received from the upstream server: Kong no llega al servicio. Comprueba la URL concurl -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 upy despuésdocker 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.
