L'erreur est survenue à 14h32 un mardi classique de production. Notre monitoring affichait soudain une cascade d'erreurs 503 Service Unavailable, tandis que les logs Nginx crachaient des lignes interminables :
2026/01/14 14:32:17 [error] 15234#0: *89234 lua tcp socket connect timed out
2026/01/14 14:32:17 [error] 15234#0: *89235 connection refused while connecting to upstream
2026/01/14 14:32:18 [error] 15234#0: *89236 upstream prematurely closed connection
2026/01/14 14:32:18 [error] 15234#0: *89237 no live upstreams while connecting to upstream
Un client enterprise avait déployé un nouveau script de scraping massif sans respecter nos quotas. En 90 secondes, plus de 12 000 requêtes submergeaient notre gateway. Mesure corrective immédiate : implémenter un système de rate limiting robuste avec Nginx et Lua. Voici comment j'ai résolu ce problème de manière définitive.
Pourquoi le Rate Limiting est Critique pour les APIs IA
En tant qu'architecte infrastructure qui a géré des millions de requêtes API mensuelles, je peux vous assurer : sans contrôle de flux, votre système est une bombe à retardement. Les fournisseurs comme HolySheep AI proposent des APIs performantes (<50ms latence moyenne), mais sans gestion inteligente du traffic, même leurs serveurs robustes peuvent faillir.
Le rate limiting protège contre :
- Les pics de traffic imprévus (viralité, attaques, bugs clients)
- Les coûts explodeifs (chaque requête génère des coûts de calcul)
- Les dégradations en cascade (un client affame les autres)
- Les abus et surexploitation (scraping, farming)
Architecture de Notre Solution Nginx Lua
Nous utilisons openresty (Nginx + LuaJIT 2.1) pour implémenter un rate limiter distribué avec Redis comme store partagé. Cette approche offre :
- Décision de refus en <1ms
- Synchronisation multi-nœuds en temps réel
- Persistance des compteurs même après restart
- Algorithmes configurables (Token Bucket, Leaky Bucket, Sliding Window)
Installation et Configuration
Prérequis
# Installation OpenResty sur Ubuntu 22.04
sudo apt-get update
sudo apt-get install -y gnupg2 ca-certificates lsb-core
Ajouter le repo OpenResty
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | \
sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt-get update
sudo apt-get install -y openresty redis-server lua-redis-parser
Vérification
openresty -v
nginx version: openresty/1.21.4.1
built with LuaJIT 2.1.0-beta3
Configuration Nginx avec Rate Limiting
# /etc/openresty/nginx.conf
Worker process doit tourner en root pour certaines ops
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/openresty.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/openresty/mime.types;
default_type application/json;
# Configuration Redis
lua_package_path "/etc/openresty/lua/?.lua;;";
lua_code_cache on;
# Initialisation Redis shared dict
lua_shared_dict ratelimit 10m;
lua_shared_dict ip_whitelist 1m;
init_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis connection failed: ", err)
end
-- Préchargement des configs
package.loaded.ratelimit_config = {
default_limit = 100, -- req/min par défaut
default_burst = 20, -- burst autorisé
api_key_limit = 1000, -- limite pour clés premium
ip_whitelist = {"10.0.0.0/8", "172.16.0.0/12"},
}
}
server {
listen 8080;
server_name _;
location /v1/ {
access_by_lua_file /etc/openresty/lua/ratelimit.lua;
# Proxy vers HolySheep AI
proxy_pass https://api.holysheep.ai/v1/;
proxy_set_header Host api.holysheep.ai;
proxy_set_header X-API-Key $http_x_api_key;
proxy_set_header Content-Type application/json;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Retry automatique
proxy_next_upstream error timeout invalid_header http_502;
proxy_next_upstream_tries 2;
}
# Endpoint santé
location /health {
content_by_lua_block {
ngx.say('{"status":"healthy","redis":"ok"}')
}
}
}
}
Script Lua de Rate Limiting
-- /etc/openresty/lua/ratelimit.lua
local redis = require "resty.redis"
local cjson = require "cjson"
local RATELIMIT_KEY_PREFIX = "rl:"
local WINDOW_SECONDS = 60
-- Extract API key from header
local function get_api_key()
local key = ngx.var.http_x_api_key or ngx.var.http_authorization
if key then
-- Supporter "Bearer xxx" et "xxx" directement
key = key:gsub("^Bearer%s+", "")
end
return key or "anonymous"
end
-- Vérifier si IP est whitelistée
local function is_ip_whitelisted(ip)
local whitelist = package.loaded.ratelimit_config.ip_whitelist
for _, cidr in ipairs(whitelist) do
if is_ip_in_cidr(ip, cidr) then
return true
end
end
return false
end
local function is_ip_in_cidr(ip, cidr)
local function ip_to_num(ip)
local parts = {}
for p in ip:gmatch("%d+") do
table.insert(parts, tonumber(p))
end
return (parts[1]*16777216) + (parts[2]*65536) + (parts[3]*256) + parts[4]
end
local ip_num = ip_to_num(ip)
local mask_bits = tonumber(cidr:match("/(%d+)$")) or 32
local mask = (2^32 - 1) - (2^(32 - mask_bits) - 1)
local base_ip = ip_to_num(cidr:match("^(%d+.%d+.%d+.%d+)"))
return (ip_num ~ base_ip) == (ip_num & ~mask)
end
-- Algorithme Sliding Window Counter
local function sliding_window_check(key, limit, window)
local red = redis:new()