Apache APISIX es una puerta de enlace de API de código abierto basada en Nginx y OpenResty. Guarda su configuración en etcd, de modo que las rutas, los upstreams y los plugins que creas con su Admin API se aplican al instante en todos los nodos, sin recargas. En este tutorial desplegarás APISIX y etcd con Docker Compose en Ubuntu 24.04, publicarás un servicio de prueba y lo protegerás con autenticación por clave, un límite de peticiones y 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. curlyjq: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).
Conceptos de APISIX
| Objeto | Qué representa |
|---|---|
| Upstream | Un grupo de nodos backend con su algoritmo de balanceo y sus comprobaciones de salud. |
| Route | La regla que asocia peticiones (URI, host, método) con un upstream y con plugins. |
| Consumer | Un cliente de la API, con sus credenciales. |
| Plugin | Funcionalidad (autenticación, límites, reescritura) aplicada a una ruta, un servicio, un consumidor o de forma global. |
| SSL | Un certificado y su clave, asociados a uno o varios nombres (SNI). |
APISIX escucha en el puerto 9080 (HTTP) y 9443 (HTTPS) para el tráfico de clientes, y en el 9180 para la Admin API, que da control total sobre el gateway y nunca debe quedar expuesta a Internet.
Paso 1: Preparar el proyecto y la clave de administración
Crea un directorio para el despliegue:
mkdir -p ~/apisix && cd ~/apisix
Genera una clave aleatoria para la Admin API y cópiala; la usarás en el archivo de configuración:
openssl rand -hex 16
3f9c2b7e6a1d48c0b5e2f7a9d1c3e8b4
Guárdala en una variable de entorno para usarla en los comandos de este tutorial, sustituyendo your_admin_key por el valor generado:
export ADMIN_KEY=your_admin_key
Paso 2: Escribir la configuración de APISIX
Crea el archivo de configuración:
nano ~/apisix/config.yaml
apisix:
node_listen: 9080
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key_required: true
admin_key:
- name: admin
key: your_admin_key
role: admin
allow_admin:
- 0.0.0.0/0
etcd:
host:
- "http://etcd:2379"
prefix: "/apisix"
timeout: 30
admin_key: sustituyeyour_admin_keypor la clave generada en el paso 1. Todas las llamadas a la Admin API deberán enviarla en la cabeceraX-API-KEY.allow_admin: permite la Admin API desde cualquier IP del lado del contenedor. Es seguro solo porque en el paso siguiente el puerto 9180 se publica únicamente en127.0.0.1del servidor.etcd: APISIX guarda la configuración bajo el prefijo/apisixdel etcd definido en Compose.
El archivo contiene la clave, pero el contenedor de APISIX, que no se ejecuta con tu usuario, necesita leerlo. En lugar de restringir el archivo, restringe el directorio para que otros usuarios del servidor no puedan acceder a él:
chmod 700 ~/apisix
Paso 3: Definir los contenedores
Crea el archivo de Compose:
nano ~/apisix/compose.yaml
services:
etcd:
image: quay.io/coreos/etcd:v3.5.21
restart: unless-stopped
command:
- etcd
- --name=etcd0
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd:2379
volumes:
- etcd-data:/etcd-data
apisix:
image: apache/apisix:3.12.0-debian
restart: unless-stopped
depends_on:
- etcd
volumes:
- ./config.yaml:/usr/local/apisix/conf/config.yaml:ro
ports:
- "80:9080"
- "443:9443"
- "127.0.0.1:9180:9180"
httpbin:
image: mccutchen/go-httpbin
restart: unless-stopped
volumes:
etcd-data:
- etcd no publica puertos: solo APISIX accede a él por la red interna de Compose.
- El tráfico de clientes llega por los puertos 80 y 443 del servidor; la Admin API solo desde el propio servidor.
httpbines una aplicación de prueba que devuelve los datos de cada petición. En producción la sustituirás por tus servicios.
Consulta en Docker Hub la última versión estable de apache/apisix con sufijo -debian si quieres una más reciente que la del ejemplo.
Importantelos puertos publicados por Docker no pasan por las reglas de UFW. Por eso la Admin API se enlaza explícitamente a
127.0.0.1.
Paso 4: Arrancar la pila
docker compose up -d
docker compose ps
NAME IMAGE STATUS
apisix-apisix-1 apache/apisix:3.12.0-debian Up 10 seconds
apisix-etcd-1 quay.io/coreos/etcd:v3.5.21 Up 11 seconds
apisix-httpbin-1 mccutchen/go-httpbin Up 11 seconds
Comprueba que la Admin API responde con tu clave y que aún no hay rutas:
curl -s http://127.0.0.1:9180/apisix/admin/routes -H "X-API-KEY: $ADMIN_KEY" | jq
{
"total": 0,
"list": []
}
Sin la cabecera, o con una clave incorrecta, la respuesta es 401. Una petición al proxy devuelve 404 porque no hay ninguna ruta:
curl -s http://localhost/
{"error_msg":"404 Route Not Found"}
Paso 5: Crear un upstream y una ruta
Crea un upstream llamado httpbin con balanceo round robin y una comprobación de salud activa. Para añadir más réplicas de la aplicación bastaría con añadir nodos a la lista nodes:
curl -s -X PUT http://127.0.0.1:9180/apisix/admin/upstreams/httpbin \
-H "X-API-KEY: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "roundrobin",
"nodes": [
{ "host": "httpbin", "port": 8080, "weight": 1 }
],
"checks": {
"active": {
"type": "http",
"http_path": "/status/200",
"healthy": { "interval": 5, "successes": 2 },
"unhealthy": { "interval": 5, "http_failures": 3 }
}
}
}' | jq '.value | {id, type}'
{
"id": "httpbin",
"type": "roundrobin"
}
Las rutas se van a ir ampliando con plugins, así que es más cómodo mantenerlas en un archivo JSON y enviarlo completo con PUT, que crea o reemplaza el objeto:
nano ~/apisix/route-demo.json
{
"name": "demo",
"uri": "/demo/*",
"upstream_id": "httpbin",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/demo/(.*)", "/$1"]
}
}
}
El plugin proxy-rewrite elimina el prefijo /demo, de modo que /demo/get llega a la aplicación como /get. Crea la ruta:
curl -s -X PUT http://127.0.0.1:9180/apisix/admin/routes/demo \
-H "X-API-KEY: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d @route-demo.json | jq '.value | {id, uri}'
Prueba la ruta a través del proxy:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/demo/status/418
418
httpbin responde en /status/<código> con el código que se le pide. Recibir un 418 confirma que la petición ha llegado a la aplicación como /status/418, sin el prefijo. Para enrutar por dominio, añade "host": "api.your_domain" al JSON de la ruta.
Paso 6: Añadir autenticación y límite de peticiones
Crea un consumidor con una clave de API. El nombre de un consumidor solo admite letras, números y guiones bajos:
API_KEY=$(openssl rand -hex 20)
curl -s -X PUT http://127.0.0.1:9180/apisix/admin/consumers \
-H "X-API-KEY: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d "{\"username\": \"app_client\", \"plugins\": {\"key-auth\": {\"key\": \"$API_KEY\"}}}" \
| jq '.value.username'
echo "$API_KEY"
Guarda la clave que muestra el último comando: es la que entregarás al cliente.
Edita la ruta para añadir los plugins key-auth y limit-count:
nano ~/apisix/route-demo.json
{
"name": "demo",
"uri": "/demo/*",
"upstream_id": "httpbin",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/demo/(.*)", "/$1"]
},
"key-auth": {},
"limit-count": {
"count": 5,
"time_window": 60,
"key_type": "var",
"key": "remote_addr",
"rejected_code": 429,
"policy": "local"
}
}
}
key-authexige la clave en la cabeceraapikey.limit-countpermite 5 peticiones por minuto y por IP de origen, un valor bajo para poder probarlo. La políticalocalcuenta en memoria de cada nodo; con varios nodos, usaredispara compartir el contador.
Aplica la nueva versión de la ruta:
curl -s -X PUT http://127.0.0.1:9180/apisix/admin/routes/demo \
-H "X-API-KEY: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d @route-demo.json | jq '.value.plugins | keys'
[
"key-auth",
"limit-count",
"proxy-rewrite"
]
Comprueba que sin clave la petición se rechaza y que con clave pasa hasta alcanzar el límite:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/demo/get
for i in $(seq 1 6); do
curl -s -o /dev/null -w '%{http_code}\n' -H "apikey: $API_KEY" http://localhost/demo/get
done
401
200
200
200
200
200
429
Las respuestas incluyen las cabeceras X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset para que el cliente sepa cuánto margen le queda:
curl -s -D - -o /dev/null -H "apikey: $API_KEY" http://localhost/demo/get | grep -i ratelimit
Por defecto la cabecera apikey también llega al backend. Si no quieres que la aplicación vea la clave del cliente, configura el plugin como "key-auth": {"hide_credentials": true}.
Paso 7: Añadir un certificado TLS
APISIX elige el certificado por SNI entre los objetos ssls guardados en etcd. Construye el JSON con jq, que se encarga de escapar los saltos de línea del PEM, y envíalo a la Admin API. La clave privada solo es legible por root, por eso jq se ejecuta con sudo:
sudo jq -n \
--rawfile cert /etc/letsencrypt/live/your_domain/fullchain.pem \
--rawfile key /etc/letsencrypt/live/your_domain/privkey.pem \
--arg sni your_domain \
'{cert: $cert, key: $key, snis: [$sni]}' \
| curl -s -X PUT http://127.0.0.1:9180/apisix/admin/ssls/your_domain \
-H "X-API-KEY: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d @- | jq '.value.snis'
[
"your_domain"
]
Comprueba desde tu equipo que APISIX presenta el certificado. Como curl valida el certificado, un 200 confirma que es correcto (si acabas de agotar el límite del paso 6, espera un minuto):
curl -s -o /dev/null -w '%{http_code}\n' -H "apikey: your_api_key" https://your_domain/demo/get
200
Cuando renueves el certificado, repite el mismo comando: el PUT con el mismo identificador lo reemplaza sin reiniciar nada. Puedes automatizarlo con un hook de despliegue de Certbot en /etc/letsencrypt/renewal-hooks/deploy/.
Paso 8: Revisar la configuración guardada
Lista todas las rutas y los upstreams con sus identificadores:
curl -s http://127.0.0.1:9180/apisix/admin/routes -H "X-API-KEY: $ADMIN_KEY" \
| jq '.list[].value | {id, uri, plugins: (.plugins | keys)}'
curl -s http://127.0.0.1:9180/apisix/admin/upstreams -H "X-API-KEY: $ADMIN_KEY" \
| jq '.list[].value | {id, type, nodes}'
Toda la configuración vive en el volumen etcd-data. Incluye ese volumen (o una instantánea con etcdctl snapshot save) en tus copias de seguridad, y guarda los archivos JSON de las rutas en un repositorio Git para poder recrearlas.
Solución de problemas
- APISIX se reinicia en bucle: mira
docker compose logs apisix. Los errores habituales son YAML mal indentado enconfig.yamlo que etcd no está disponible todavía; en el segundo caso, APISIX se recupera solo en unos segundos. 401en la Admin API: la cabeceraX-API-KEYno coincide conadmin_keyenconfig.yaml. Tras cambiar la clave, aplica los cambios condocker compose restart apisix.404 Route Not Found: ninguna ruta coincide. Revisauriyhostcon el comando del paso 8; recuerda que/demo/*no coincide con/demosin barra final.502 Bad Gateway: APISIX no llega al upstream. Comprueba el nombre y puerto de los nodos y revisadocker compose logs apisix | grep -i upstream.
Conclusión
APISIX funciona con etcd como almacén de configuración, publica un servicio a través de una ruta con reescritura de URI, exige claves de API, limita las peticiones y sirve HTTPS con tu certificado, todo configurado en caliente desde una Admin API accesible solo en local. Como siguientes pasos, puedes añadir más nodos al upstream, activar el plugin prometheus para exportar métricas o levantar un segundo nodo de APISIX contra el mismo etcd para tener alta disponibilidad.
