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.
  • curl y jq: 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

ObjetoQué representa
UpstreamUn grupo de nodos backend con su algoritmo de balanceo y sus comprobaciones de salud.
RouteLa regla que asocia peticiones (URI, host, método) con un upstream y con plugins.
ConsumerUn cliente de la API, con sus credenciales.
PluginFuncionalidad (autenticación, límites, reescritura) aplicada a una ruta, un servicio, un consumidor o de forma global.
SSLUn 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: sustituye your_admin_key por la clave generada en el paso 1. Todas las llamadas a la Admin API deberán enviarla en la cabecera X-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 en 127.0.0.1 del servidor.
  • etcd: APISIX guarda la configuración bajo el prefijo /apisix del 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.
  • httpbin es 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.

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-auth exige la clave en la cabecera apikey.
  • limit-count permite 5 peticiones por minuto y por IP de origen, un valor bajo para poder probarlo. La política local cuenta en memoria de cada nodo; con varios nodos, usa redis para 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 en config.yaml o que etcd no está disponible todavía; en el segundo caso, APISIX se recupera solo en unos segundos.
  • 401 en la Admin API: la cabecera X-API-KEY no coincide con admin_key en config.yaml. Tras cambiar la clave, aplica los cambios con docker compose restart apisix.
  • 404 Route Not Found: ninguna ruta coincide. Revisa uri y host con el comando del paso 8; recuerda que /demo/* no coincide con /demo sin barra final.
  • 502 Bad Gateway: APISIX no llega al upstream. Comprueba el nombre y puerto de los nodos y revisa docker 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.