diff --git a/docs/26-nginx-lua-guide.md b/docs/26-nginx-lua-guide.md new file mode 100644 index 0000000..76f8314 --- /dev/null +++ b/docs/26-nginx-lua-guide.md @@ -0,0 +1,1671 @@ +# NGINX Lua 模块深度指南 + +## 概述 + +NGINX Lua 模块(ngx_http_lua_module)是 OpenResty 平台的核心组件,它将 Lua 脚本语言嵌入 NGINX,使 NGINX 具备强大的动态脚本能力。本文档深入介绍 Lua 模块的核心概念、API 和最佳实践。 + +--- + +## 1. OpenResty 简介 + +### 1.1 什么是 OpenResty + +OpenResty 是一个基于 NGINX 的高性能 Web 平台,由 agentzh(章亦春)创建。它在标准 NGINX 基础上集成了: + +- **LuaJIT**:高性能 Lua 解释器,执行速度接近原生代码 +- **ngx_lua**:NGINX Lua 嵌入模块 +- **丰富的 Lua 库**:Redis、MySQL、Memcached 等客户端库 +- **协程调度器**:异步非阻塞 I/O 支持 + +### 1.2 OpenResty 与传统 NGINX 的区别 + +| 特性 | 标准 NGINX | OpenResty | +|------|-----------|-----------| +| 脚本能力 | 有限(NJS) | 强大(完整 Lua 支持) | +| 性能 | 高 | 更高(LuaJIT) | +| 生态系统 | 模块扩展 | 丰富的 Lua 库 | +| 学习曲线 | 平缓 | 中等 | +| 典型应用 | 反向代理 | API 网关、WAF、边缘计算 | + +### 1.3 安装 OpenResty + +#### 使用包管理器安装 + +**Ubuntu/Debian:** +```bash +# 添加 OpenResty 仓库 +wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add - +sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" + +# 安装 +sudo apt-get update +sudo apt-get install -y openresty +``` + +**CentOS/RHEL:** +```bash +# 添加仓库 +sudo yum install -y yum-utils +sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo + +# 安装 +sudo yum install -y openresty +``` + +**macOS:** +```bash +brew install openresty +``` + +#### 从源码编译安装 + +```bash +# 下载源码 +wget https://openresty.org/download/openresty-1.25.3.1.tar.gz +tar -xzf openresty-1.25.3.1.tar.gz +cd openresty-1.25.3.1 + +# 配置编译选项 +./configure \ + --prefix=/usr/local/openresty \ + --with-http_ssl_module \ + --with-http_v2_module \ + --with-http_v3_module \ + --with-http_realip_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-pcre-jit \ + --with-luajit + +# 编译安装 +make -j$(nproc) +sudo make install + +# 添加到环境变量 +echo 'export PATH=/usr/local/openresty/bin:$PATH' >> ~/.bashrc +source ~/.bashrc +``` + +#### 验证安装 + +```bash +# 检查版本 +openresty -v +# 输出:nginx version: openresty/1.25.3.1 + +# 检查 LuaJIT +openresty -V 2>&1 | grep luajit +# 确认包含 --with-luajit + +# 启动 OpenResty +sudo openresty + +# 测试 +curl http://localhost/ +``` + +--- + +## 2. ngx_lua 核心指令 + +ngx_lua 提供了多个执行阶段指令,允许在不同请求处理阶段执行 Lua 代码。 + +### 2.1 执行阶段概览 + +``` +请求处理流程: + +┌─────────────────────────────────────────┐ +│ init_by_lua │ ← NGINX 启动时 +├─────────────────────────────────────────┤ +│ init_worker_by_lua │ ← 每个 worker 启动时 +├─────────────────────────────────────────┤ +│ ssl_certificate_by_lua │ ← SSL 证书阶段(可选) +├─────────────────────────────────────────┤ +│ set_by_lua │ 设置变量值 │ +├─────────────────────────────────────────┤ +│ rewrite_by_lua │ URL 重写 │ +├─────────────────────────────────────────┤ +│ access_by_lua │ 访问控制 │ +├─────────────────────────────────────────┤ +│ content_by_lua │ 生成响应内容 │ +├─────────────────────────────────────────┤ +│ header_filter_by_lua │ 处理响应头 │ +├─────────────────────────────────────────┤ +│ body_filter_by_lua │ 处理响应体 │ +├─────────────────────────────────────────┤ +│ log_by_lua │ 日志阶段 │ +└─────────────────────────────────────────┘ +``` + +### 2.2 指令详解 + +#### init_by_lua + +在 NGINX 启动时执行,用于全局初始化。 + +```nginx +http { + # 加载 Lua 模块,预编译代码 + init_by_lua_block { + require "cjson" + require "resty.redis" + require "resty.mysql" + + -- 预编译正则表达式 + local regex = [[\d+]] + local m, err = ngx.re.match("hello 123", regex, "jo") + + -- 全局配置 + config = { + redis_host = "127.0.0.1", + redis_port = 6379, + cache_ttl = 300 + } + + -- 打印启动信息 + ngx.log(ngx.NOTICE, "OpenResty initialized with LuaJIT") + } +} +``` + +**适用场景:** +- 预加载常用模块 +- 初始化全局变量/配置 +- 建立数据库连接池 +- 编译正则表达式 + +#### init_worker_by_lua + +在每个 worker 进程启动时执行。 + +```nginx +http { + init_worker_by_lua_block { + local delay = 5 -- 5秒间隔 + local handler + + -- 定时任务:健康检查 + handler = function(premature) + if premature then + return + end + + -- 执行健康检查 + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:8080/health", { + method = "GET", + timeout = 2000 + }) + + if res and res.status == 200 then + ngx.log(ngx.INFO, "Health check passed") + else + ngx.log(ngx.ERR, "Health check failed: ", err) + end + + -- 重新注册定时器 + local ok, err = ngx.timer.at(delay, handler) + if not ok then + ngx.log(ngx.ERR, "Failed to create timer: ", err) + end + end + + -- 启动定时器 + ngx.timer.at(delay, handler) + + ngx.log(ngx.NOTICE, "Worker ", ngx.worker.id(), " started") + } +} +``` + +**适用场景:** +- 启动后台定时任务 +- worker 级别的初始化 +- 定时数据采集 +- 缓存预热 + +#### set_by_lua + +设置 NGINX 变量值。 + +```nginx +location /api { + set_by_lua_block $api_backend { + local version = ngx.var.http_x_api_version + + if version == "v2" then + return "backend_v2" + else + return "backend_v1" + end + } + + proxy_pass http://$api_backend; +} +``` + +**限制:** +- 不能使用阻塞 I/O +- 不能使用 `ngx.sleep` +- 不能使用 cosocket + +#### rewrite_by_lua + +在 rewrite 阶段执行,用于 URL 重写和重定向。 + +```nginx +location / { + rewrite_by_lua_block { + local uri = ngx.var.uri + local args = ngx.var.args + + -- 统一处理尾部斜杠 + if uri ~= "/" and uri:sub(-1) == "/" then + uri = uri:sub(1, -2) + end + + -- 旧 URL 兼容处理 + if uri:match("^/old%-api/") then + local new_uri = uri:gsub("^/old%-api", "/api/v1") + return ngx.redirect(new_uri .. (args and "?" .. args or ""), 301) + end + + -- 设置内部变量 + ngx.var.target_service = "user_service" + } + + proxy_pass http://backend; +} +``` + +#### access_by_lua + +在 access 阶段执行,用于访问控制和认证。 + +```nginx +location /admin { + access_by_lua_block { + local token = ngx.var.http_authorization + + if not token then + ngx.header["WWW-Authenticate"] = "Bearer" + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- JWT 验证 + local jwt = require "resty.jwt" + local jwt_obj = jwt:verify("secret_key", token:gsub("Bearer ", "")) + + if not jwt_obj.verified then + ngx.log(ngx.ERR, "JWT verification failed: ", jwt_obj.reason) + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- 设置用户信息到变量 + ngx.var.user_id = jwt_obj.payload.sub + ngx.var.user_role = jwt_obj.payload.role + } + + proxy_pass http://admin_backend; +} +``` + +#### content_by_lua + +生成响应内容的核心指令。 + +```nginx +location /api/status { + content_by_lua_block { + local cjson = require "cjson" + + local status = { + nginx_version = ngx.var.nginx_version, + lua_version = _VERSION, + worker_id = ngx.worker.id(), + worker_count = ngx.worker.count(), + time = ngx.time(), + connections = ngx.var.connections_active + } + + ngx.header["Content-Type"] = "application/json" + ngx.say(cjson.encode(status)) + } +} +``` + +#### header_filter_by_lua + +修改响应头。 + +```nginx +location / { + proxy_pass http://backend; + + header_filter_by_lua_block { + -- 添加安全头部 + ngx.header["X-Frame-Options"] = "SAMEORIGIN" + ngx.header["X-XSS-Protection"] = "1; mode=block" + ngx.header["X-Content-Type-Options"] = "nosniff" + + -- 移除敏感头部 + ngx.header["Server"] = nil + ngx.header["X-Powered-By"] = nil + + -- 根据条件设置缓存 + if ngx.var.uri:match("^/api/") then + ngx.header["Cache-Control"] = "no-store, no-cache, must-revalidate" + end + } +} +``` + +#### body_filter_by_lua + +修改响应体(基于流式处理)。 + +```nginx +location / { + proxy_pass http://backend; + + body_filter_by_lua_block { + local chunk = ngx.arg[1] + local eof = ngx.arg[2] + + -- 累积响应体 + if ngx.ctx.body then + ngx.ctx.body = ngx.ctx.body .. chunk + else + ngx.ctx.body = chunk + end + + -- 最后一块数据处理 + if eof then + local body = ngx.ctx.body + + -- 敏感信息脱敏 + body = body:gsub("\"phone\":\"(%d%d%d)%d%d%d%d(\d%d%d%d)\"", + "\"phone\":\"%1****%2\"") + body = body:gsub("\"email\":\"([^@]+)@[^\"]+\"", + "\"email\":\"%1@***\"") + + ngx.arg[1] = body + else + -- 非最后一块,输出空并标记不完成 + ngx.arg[1] = nil + ngx.arg[2] = false + end + } +} +``` + +#### log_by_lua + +日志阶段处理。 + +```nginx +http { + log_by_lua_block { + local uri = ngx.var.uri + local status = ngx.var.status + local request_time = ngx.var.request_time + + -- 慢请求记录 + if tonumber(request_time) > 1.0 then + ngx.log(ngx.WARN, "Slow request: ", uri, + " status=", status, + " time=", request_time) + end + + -- 统计信息发送到监控 + if status >= 500 then + local statsd = require "resty.statsd" + statsd.increment("nginx.error." .. status) + end + } +} +``` + +### 2.3 指令上下文支持 + +| 指令 | http | server | location | upstream | +|------|------|--------|----------|----------| +| `init_by_lua` | ✓ | ✗ | ✗ | ✗ | +| `init_worker_by_lua` | ✓ | ✗ | ✗ | ✗ | +| `set_by_lua` | ✗ | ✓ | ✓ | ✗ | +| `rewrite_by_lua` | ✓ | ✓ | ✓ | ✗ | +| `access_by_lua` | ✓ | ✓ | ✓ | ✗ | +| `content_by_lua` | ✗ | ✗ | ✓ | ✗ | +| `header_filter_by_lua` | ✓ | ✓ | ✓ | ✗ | +| `body_filter_by_lua` | ✓ | ✓ | ✓ | ✗ | +| `log_by_lua` | ✓ | ✓ | ✓ | ✗ | +| `balancer_by_lua` | ✗ | ✗ | ✗ | ✓ | + +--- + +## 3. Lua 共享字典(ngx.shared.DICT) + +共享字典是在所有 worker 进程间共享的内存缓存。 + +### 3.1 定义共享字典 + +```nginx +http { + # 语法:lua_shared_dict + lua_shared_dict cache 10m; # 通用缓存 + lua_shared_dict sessions 5m; # 会话存储 + lua_shared_dict rate_limit 1m; # 限流计数 + lua_shared_dict locks 1m; # 锁存储 +} +``` + +**内存估算:** +- 每个 key-value 对约占用 60-80 字节(小数据) +- 1MB 可存储约 12,000-16,000 个简单键值对 + +### 3.2 基本操作 + +```lua +-- 获取字典实例 +local cache = ngx.shared.cache + +-- 存储数据(支持过期时间) +cache:set("key", "value", 300) -- 300秒后过期 +cache:set("key", "value", 0) -- 永不过期 + +-- 带过期时间的存储 +cache:set("session:123", user_data, 1800) -- 30分钟会话 + +-- 获取数据 +local value, flags = cache:get("key") +if value then + ngx.say("Value: ", value) +else + ngx.say("Cache miss") +end + +-- 删除数据 +cache:delete("key") + +-- 原子递增(用于计数器) +local new_val, err = cache:incr("counter", 1, 0) -- 从0开始,每次+1 + +-- 批量获取 +cache:set("user:1", "Alice") +cache:set("user:2", "Bob") +cache:set("user:3", "Charlie") + +local keys = {"user:1", "user:2", "user:3"} +local values = cache:get(keys) +``` + +### 3.3 高级操作 + +```lua +local cache = ngx.shared.cache + +-- 安全添加(仅当 key 不存在时设置) +local success, err, forcible = cache:add("key", "value", 300) +if not success then + ngx.log(ngx.ERR, "Failed to add: ", err) +end + +-- 安全替换(仅当 key 存在时设置) +local success = cache:replace("key", "new_value") + +-- 原子操作:如果不存在则设置 +local ok = cache:add("lock:process", "1", 10) +if ok then + -- 获取到锁 + -- 执行操作 + cache:delete("lock:process") +end + +-- 获取过期时间 +local ttl, err = cache:ttl("key") +if ttl then + ngx.say("TTL: ", ttl, " seconds") +end + +-- 获取信息 +local info = cache:get_keys(0) -- 0 表示获取所有 keys +ngx.say("Keys count: ", #info) + +-- 清空字典(慎用) +cache:flush_all() + +-- 过期数据清理 +cache:flush_expired(100) -- 清理最多100个过期条目 +``` + +### 3.4 应用场景 + +#### 分布式限流 + +```nginx +http { + lua_shared_dict rate_limit 10m; + + server { + location /api { + access_by_lua_block { + local limit = ngx.shared.rate_limit + local key = "rate:" .. ngx.var.binary_remote_addr + + -- 令牌桶算法实现 + local now = ngx.time() + local rate = 10 -- 每秒10个请求 + local burst = 20 -- 突发容量 + + local last = limit:get(key .. ":last") + local tokens = limit:get(key .. ":tokens") + + if not last then + tokens = burst + else + local elapsed = now - last + tokens = math.min(burst, tokens + elapsed * rate) + end + + if tokens < 1 then + return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) + end + + tokens = tokens - 1 + limit:set(key .. ":tokens", tokens) + limit:set(key .. ":last", now) + } + + proxy_pass http://api_backend; + } + } +} +``` + +#### 会话存储 + +```nginx +http { + lua_shared_dict sessions 20m; + + server { + location /login { + content_by_lua_block { + local cjson = require "cjson" + local sessions = ngx.shared.sessions + + -- 验证用户名密码 + local username = ngx.var.arg_username + local password = ngx.var.arg_password + + if not authenticate(username, password) then + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- 创建会话 + local session_id = ngx.md5(ngx.time() .. ngx.var.remote_addr) + local session_data = { + username = username, + login_time = ngx.time(), + ip = ngx.var.remote_addr + } + + sessions:set("session:" .. session_id, + cjson.encode(session_data), 3600) + + -- 设置 Cookie + ngx.header["Set-Cookie"] = "session=" .. session_id .. + "; Path=/; HttpOnly; Secure" + ngx.say("Login successful") + } + } + + location /profile { + access_by_lua_block { + local cjson = require "cjson" + local sessions = ngx.shared.sessions + + -- 获取 session + local cookie = ngx.var.cookie_session + if not cookie then + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + local data = sessions:get("session:" .. cookie) + if not data then + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- 解析会话数据 + local session = cjson.decode(data) + ngx.var.user_name = session.username + } + + proxy_pass http://backend; + } + } +} +``` + +#### 缓存穿透防护 + +```lua +-- 缓存穿透防护(防止缓存击穿和雪崩) +local function get_with_lock(cache, key, ttl, fetch_func) + -- 1. 尝试从缓存获取 + local value = cache:get(key) + if value then + return value + end + + local lock_key = "lock:" .. key + local lock_ttl = 10 -- 锁超时时间 + + -- 2. 尝试获取锁 + local ok = cache:add(lock_key, "1", lock_ttl) + if not ok then + -- 3. 未获取到锁,等待后重试 + ngx.sleep(0.1) + return cache:get(key) + end + + -- 4. 获取到锁,从数据源加载 + local ok2, result = pcall(fetch_func) + if ok2 and result then + cache:set(key, result, ttl) + end + + -- 5. 释放锁 + cache:delete(lock_key) + + return result +end + +-- 使用示例 +local value = get_with_lock(ngx.shared.cache, "user:123", 300, function() + -- 从数据库获取 + return fetch_from_db("user", 123) +end) +``` + +--- + +## 4. Cosocket API(非阻塞网络 I/O) + +Cosocket 是 OpenResty 提供的非阻塞网络 I/O 接口,支持 TCP、UDP 和 Unix Domain Socket。 + +### 4.1 TCP Cosocket + +```lua +-- 创建 TCP socket +local sock = ngx.socket.tcp() + +-- 设置超时 +sock:settimeout(5000) -- 5秒超时 + +-- 连接服务器 +local ok, err = sock:connect("127.0.0.1", 6379) +if not ok then + ngx.log(ngx.ERR, "Failed to connect: ", err) + return +end + +-- 发送数据 +local bytes, err = sock:send("PING\r\n") +if not bytes then + ngx.log(ngx.ERR, "Failed to send: ", err) + return +end + +-- 接收数据 +local line, err = sock:receive("*l") -- 接收一行 +if not line then + ngx.log(ngx.ERR, "Failed to receive: ", err) + return +end + +ngx.say("Response: ", line) + +-- 关闭连接 +sock:close() +``` + +### 4.2 高级用法 + +```lua +local sock = ngx.socket.tcp() + +-- 连接池复用 +sock:setkeepalive(60000, 100) -- 60秒超时,最多100个连接 + +-- 指定模式接收 +local data, err = sock:receive(1024) -- 接收最多1024字节 +local data, err = sock:receive("*a") -- 接收所有数据 +local data, err = sock:receiveuntil("\r\n") -- 接收直到指定分隔符 + +-- 批量发送 +local ok, err = sock:send({ + "GET / HTTP/1.1\r\n", + "Host: example.com\r\n", + "Connection: close\r\n", + "\r\n" +}) +``` + +### 4.3 UDP Cosocket + +```lua +local sock = ngx.socket.udp() + +-- 设置超时 +sock:settimeout(2000) + +-- 设置目标 +local ok, err = sock:setpeername("127.0.0.1", 53) +if not ok then + ngx.log(ngx.ERR, "Failed to set peer: ", err) + return +end + +-- 发送 DNS 查询 +local query = build_dns_query("example.com") +local ok, err = sock:send(query) + +-- 接收响应 +local data, err = sock:receive(512) +if data then + local result = parse_dns_response(data) + ngx.say("IP: ", result) +end + +sock:close() +``` + +### 4.4 异步 HTTP 请求 + +使用 `resty.http` 库进行异步 HTTP 请求: + +```bash +# 安装 lua-resty-http +luarocks install lua-resty-http +``` + +```lua +local http = require "resty.http" + +-- 简单 GET 请求 +local httpc = http.new() +local res, err = httpc:request_uri("http://api.example.com/data", { + method = "GET", + headers = { + ["Accept"] = "application/json" + } +}) + +if res then + ngx.status = res.status + ngx.say(res.body) +else + ngx.status = 502 + ngx.say("Request failed: ", err) +end +``` + +#### 高级 HTTP 请求 + +```lua +local http = require "resty.http" + +local httpc = http.new() + +-- 配置超时 +httpc:set_timeout(5000) + +-- 建立连接(连接复用) +local ok, err = httpc:connect("api.example.com", 443) +if not ok then + return ngx.exit(ngx.HTTP_BAD_GATEWAY) +end + +-- SSL 握手 +local session, err = httpc:ssl_handshake(false, "api.example.com", false) + +-- 发送请求 +local res, err = httpc:request({ + method = "POST", + path = "/v1/users", + headers = { + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. token + }, + body = [[{"name":"John","email":"john@example.com"}]] +}) + +if not res then + ngx.log(ngx.ERR, "Request failed: ", err) + return ngx.exit(ngx.HTTP_BAD_GATEWAY) +end + +-- 流式读取响应 +local reader = res.body_reader +repeat + local chunk, err = reader(8192) + if err then + ngx.log(ngx.ERR, "Read error: ", err) + break + end + if chunk then + ngx.print(chunk) + end +until not chunk + +-- 保持连接复用 +local ok, err = httpc:set_keepalive(60000, 100) +``` + +--- + +## 5. 与 Redis/MySQL 集成 + +### 5.1 Redis 集成 + +```bash +# 安装 lua-resty-redis(OpenResty 已内置) +``` + +#### 基础操作 + +```lua +local redis = require "resty.redis" + +-- 创建连接 +local red = redis:new() +red:set_timeout(1000) -- 1秒超时 + +-- 连接 +local ok, err = red:connect("127.0.0.1", 6379) +if not ok then + ngx.log(ngx.ERR, "Failed to connect: ", err) + return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) +end + +-- 基本操作 +red:set("key", "value") +red:set("key", "value", 300) -- 带过期时间 + +local res, err = red:get("key") +if res == ngx.null then + ngx.say("Key not found") +else + ngx.say("Value: ", res) +end + +-- 列表操作 +red:lpush("queue", "task1") +red:lpush("queue", "task2") +local task = red:rpop("queue") + +-- 哈希操作 +red:hset("user:1001", "name", "Alice") +red:hset("user:1001", "age", "30") +local user = red:hgetall("user:1001") + +-- 事务 +red:multi() +red:incr("counter") +red:lpush("log", "new entry") +local res, err = red:exec() + +-- 连接池 +local ok, err = red:set_keepalive(60000, 100) +``` + +#### Redis 连接池封装 + +```lua +-- redis_pool.lua +local redis = require "resty.redis" +local _M = {} + +function _M.get_connection() + local red = redis:new() + red:set_timeout(1000) + + -- 使用 Unix socket(性能更好) + local ok, err = red:connect("unix:/var/run/redis/redis.sock") + if not ok then + -- 回退到 TCP + ok, err = red:connect("127.0.0.1", 6379) + if not ok then + return nil, err + end + end + + -- 认证(如有密码) + -- local res, err = red:auth("password") + + return red, nil +end + +function _M.return_connection(red) + if not red then + return + end + local ok, err = red:set_keepalive(60000, 100) + if not ok then + red:close() + end +end + +return _M +``` + +#### 缓存模式封装 + +```lua +-- cache.lua +local redis_pool = require "redis_pool" +local cjson = require "cjson" +local _M = {} + +function _M.get(key, ttl, fetch_func) + local red, err = redis_pool.get_connection() + if not red then + -- Redis 不可用,直接获取数据 + return fetch_func() + end + + -- 尝试从 Redis 获取 + local data, err = red:get("cache:" .. key) + if data and data ~= ngx.null then + redis_pool.return_connection(red) + return cjson.decode(data) + end + + -- 从数据源获取 + local result = fetch_func() + if result then + -- 异步写入 Redis + red:set("cache:" .. key, cjson.encode(result), "EX", ttl) + end + + redis_pool.return_connection(red) + return result +end + +return _M +``` + +### 5.2 MySQL 集成 + +```bash +# 安装 lua-resty-mysql(OpenResty 已内置) +``` + +#### 基础操作 + +```lua +local mysql = require "resty.mysql" + +-- 创建连接 +local db, err = mysql:new() +if not db then + ngx.log(ngx.ERR, "Failed to create mysql: ", err) + return +end + +db:set_timeout(1000) + +-- 连接数据库 +local ok, err, errcode, sqlstate = db:connect({ + host = "127.0.0.1", + port = 3306, + database = "test", + user = "root", + password = "password", + charset = "utf8mb4", + max_packet_size = 1024 * 1024, + pool = "mysqlpool" -- 连接池名称 +}) + +if not ok then + ngx.log(ngx.ERR, "Failed to connect: ", err) + return +end + +-- 查询 +local res, err, errcode, sqlstate = db:query("SELECT * FROM users WHERE id = 1") +if not res then + ngx.log(ngx.ERR, "Query failed: ", err) + return +end + +-- 处理结果 +for i, row in ipairs(res) do + ngx.say("User: ", row.name, ", Email: ", row.email) +end + +-- 插入/更新 +local res, err = db:query([[ + INSERT INTO users (name, email) + VALUES ('John', 'john@example.com') +]]) + +if res then + ngx.say("Inserted ID: ", res.insert_id) +end + +-- 事务 +local res, err = db:query("START TRANSACTION") +local res1, err1 = db:query("UPDATE accounts SET balance = balance - 100 WHERE id = 1") +local res2, err2 = db:query("UPDATE accounts SET balance = balance + 100 WHERE id = 2") + +if res1 and res2 then + db:query("COMMIT") +else + db:query("ROLLBACK") +end + +-- 放回连接池 +local ok, err = db:set_keepalive(60000, 100) +``` + +#### MySQL 连接池封装 + +```lua +-- mysql_pool.lua +local mysql = require "resty.mysql" +local cjson = require "cjson" + +local _M = { + config = { + host = "127.0.0.1", + port = 3306, + database = "app", + user = "app_user", + password = "app_pass", + charset = "utf8mb4", + max_packet_size = 1024 * 1024, + pool_size = 50 + } +} + +function _M.query(sql) + local db, err = mysql:new() + if not db then + return nil, err + end + + db:set_timeout(3000) + + local ok, err = db:connect({ + host = _M.config.host, + port = _M.config.port, + database = _M.config.database, + user = _M.config.user, + password = _M.config.password, + charset = _M.config.charset, + max_packet_size = _M.config.max_packet_size, + pool = "mysqlpool" + }) + + if not ok then + return nil, err + end + + local res, err = db:query(sql) + db:set_keepalive(60000, _M.config.pool_size) + + if not res then + return nil, err + end + + return res, nil +end + +-- 参数化查询(防 SQL 注入) +function _M.escape(str) + return ngx.quote_sql_str(str) +end + +return _M +``` + +### 5.3 综合示例:用户信息查询 + +```nginx +location /api/user { + content_by_lua_block { + local cjson = require "cjson" + local user_id = ngx.var.arg_id + + if not user_id or not user_id:match("^%d+$") then + ngx.status = 400 + ngx.say("Invalid user ID") + return + end + + -- 1. 尝试从 Redis 获取 + local redis = require "resty.redis" + local red = redis:new() + red:set_timeout(1000) + + local ok, err = red:connect("127.0.0.1", 6379) + if ok then + local data = red:get("user:" .. user_id) + if data and data ~= ngx.null then + ngx.header["Content-Type"] = "application/json" + ngx.header["X-Cache"] = "HIT" + ngx.say(data) + red:set_keepalive(60000, 100) + return + end + red:set_keepalive(60000, 100) + end + + -- 2. 从 MySQL 查询 + local mysql = require "resty.mysql" + local db = mysql:new() + db:set_timeout(2000) + + local ok, err = db:connect({ + host = "127.0.0.1", + port = 3306, + database = "users", + user = "readonly", + password = "readonly", + charset = "utf8mb4" + }) + + if not ok then + ngx.status = 500 + ngx.say("Database error") + return + end + + local sql = "SELECT id, name, email, created_at FROM users WHERE id = " .. user_id + local res, err = db:query(sql) + db:set_keepalive(60000, 50) + + if not res or #res == 0 then + ngx.status = 404 + ngx.say("User not found") + return + end + + local user = res[1] + local response = cjson.encode(user) + + -- 3. 异步写入 Redis(不阻塞响应) + local ok, err = red:connect("127.0.0.1", 6379) + if ok then + red:set("user:" .. user_id, response, "EX", 300) -- 缓存5分钟 + red:set_keepalive(60000, 100) + end + + ngx.header["Content-Type"] = "application/json" + ngx.header["X-Cache"] = "MISS" + ngx.say(response) + } +} +``` + +--- + +## 6. 性能优化技巧 + +### 6.1 LuaJIT 优化 + +```lua +-- 1. 使用局部变量缓存全局变量 +local ngx = ngx +local cjson = require "cjson" +local http = require "resty.http" + +-- 2. 避免在循环中创建函数 +local function process_item(item) + return item * 2 +end + +for i = 1, 1000 do + process_item(i) -- 比内联函数更高效 +end + +-- 3. 使用 table.new 预分配(OpenResty 扩展) +local new_tab = require "table.new" +local t = new_tab(100, 0) -- 预分配100个数组元素 + +-- 4. 字符串拼接使用 table.concat +local parts = {} +for i = 1, 100 do + parts[i] = "item" .. i +end +local result = table.concat(parts, ",") + +-- 5. 使用 ngx.re 而不是 Lua 正则 +-- 高效 +local m, err = ngx.re.match(str, [[\d+]], "jo") +-- 低效 +local m = str:match("%d+") + +-- 6. 避免使用 pairs/ipairs 进行数值索引遍历 +-- 高效 +for i = 1, #arr do + local v = arr[i] +end +-- 低效 +for i, v in ipairs(arr) do +end +``` + +### 6.2 连接池优化 + +```lua +-- 合理设置连接池大小 +local pool_size = 100 +local keepalive_timeout = 60000 -- 60秒 + +-- Redis +red:set_keepalive(keepalive_timeout, pool_size) + +-- MySQL +db:set_keepalive(keepalive_timeout, pool_size) + +-- HTTP +httpc:set_keepalive(keepalive_timeout, pool_size) +``` + +### 6.3 缓存策略 + +| 策略 | 适用场景 | 实现方式 | +|------|---------|---------| +| **本地缓存** | 热点数据、配置信息 | `ngx.shared.DICT` | +| **Redis 缓存** | 分布式缓存、会话 | `resty.redis` | +| **多级缓存** | 高并发读取 | L1(本地)+ L2(Redis) | +| **缓存预热** | 系统启动时 | `init_worker_by_lua` | +| **缓存穿透防护** | 防止缓存击穿 | 互斥锁 + 空值缓存 | + +### 6.4 Worker 间通信 + +```lua +-- 使用共享字典实现 worker 间通信 +http { + lua_shared_dict ipc 1m; + + init_worker_by_lua_block { + local ipc = ngx.shared.ipc + + -- 订阅消息 + local check_message + check_message = function(premature) + if premature then return end + + -- 获取消息 + local msg = ipc:get("broadcast") + if msg then + -- 处理消息 + ngx.log(ngx.INFO, "Worker ", ngx.worker.id(), + " received: ", msg) + ipc:delete("broadcast") + end + + ngx.timer.at(0.1, check_message) + end + + ngx.timer.at(0.1, check_message) + } +} +``` + +### 6.5 内存管理 + +```lua +-- 1. 及时释放大对象 +local large_data = fetch_large_data() +-- 处理数据 +process(large_data) +large_data = nil -- 显式释放引用 + +-- 2. 使用弱引用表(缓存场景) +local weak_cache = setmetatable({}, { + __mode = "v" +}) + +-- 3. 避免闭包捕获大对象 +local function create_handler(config) + -- 只捕获需要的字段 + local timeout = config.timeout + return function() + -- 使用 timeout,不持有整个 config + end +end + +-- 4. 控制字符串创建 +-- 使用 string.sub 而不是正则提取 +local prefix = str:sub(1, 10) +``` + +--- + +## 7. 完整配置示例 + +### 7.1 API 网关配置 + +```nginx +user openresty; +worker_processes auto; +error_log /var/log/openresty/error.log warn; +pid /run/openresty.pid; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + include /usr/local/openresty/nginx/conf/mime.types; + default_type application/octet-stream; + + # Lua 共享字典 + lua_shared_dict cache 50m; + lua_shared_dict rate_limit 10m; + lua_shared_dict sessions 20m; + lua_shared_dict locks 5m; + + # Lua 库路径 + lua_package_path "/usr/local/openresty/site/lualib/?.lua;;"; + lua_package_cpath "/usr/local/openresty/site/lualib/?.so;;"; + + # 全局初始化 + init_by_lua_block { + require "cjson" + require "resty.redis" + require "resty.mysql" + + -- 全局配置 + CONFIG = { + redis = { host = "127.0.0.1", port = 6379 }, + mysql = { + host = "127.0.0.1", port = 3306, + database = "api_db", + user = "api_user", password = "api_pass" + }, + jwt_secret = "your-secret-key", + rate_limit = { rps = 100, burst = 200 } + } + } + + # Worker 初始化 + init_worker_by_lua_block { + -- 定期清理过期缓存 + local flush_expired + flush_expired = function(premature) + if premature then return end + ngx.shared.cache:flush_expired(100) + ngx.timer.at(30, flush_expired) + end + ngx.timer.at(30, flush_expired) + } + + # 日志格式 + log_format api_log '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"' + ' cache=$upstream_http_x_cache'; + + access_log /var/log/openresty/access.log api_log; + + # 性能优化 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # 限流区域 + limit_req_zone $binary_remote_addr zone=ip:10m rate=10r/s; + + # 上游服务器 + upstream api_backend { + server 127.0.0.1:8080 weight=5; + server 127.0.0.1:8081 weight=5; + keepalive 100; + } + + # 主服务器 + server { + listen 80; + server_name api.example.com; + return 301 https://$server_name$request_uri; + } + + server { + listen 443 ssl http2; + server_name api.example.com; + + # SSL 配置 + ssl_certificate /etc/ssl/certs/api.crt; + ssl_certificate_key /etc/ssl/private/api.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # 安全头部 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # 全局访问控制 + location / { + # IP 白名单检查 + access_by_lua_block { + local whitelist = { ["10.0.0.0/24"] = true } + -- 实现 IP 检查逻辑 + } + + proxy_pass http://api_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 健康检查 + location /health { + access_log off; + content_by_lua_block { + local cjson = require "cjson" + local health = { + status = "healthy", + time = ngx.time(), + worker = ngx.worker.id() + } + ngx.header["Content-Type"] = "application/json" + ngx.say(cjson.encode(health)) + } + } + + # API 路由 + location /api/v1/ { + # 限流 + limit_req zone=ip burst=20 nodelay; + + access_by_lua_block { + -- JWT 认证 + local token = ngx.var.http_authorization + if not token then + return ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- 验证 JWT(简化示例) + -- 实际使用 resty.jwt 库 + } + + header_filter_by_lua_block { + ngx.header["X-API-Version"] = "v1" + } + + proxy_pass http://api_backend; + } + + # 登录接口 + location /api/auth/login { + limit_req zone=ip burst=5 nodelay; + + content_by_lua_block { + local cjson = require "cjson" + + ngx.req.read_body() + local data = ngx.req.get_body_data() + if not data then + ngx.status = 400 + ngx.say(cjson.encode({ error = "No body" })) + return + end + + local args = cjson.decode(data) + -- 验证用户名密码 + -- 生成 JWT + + ngx.header["Content-Type"] = "application/json" + ngx.say(cjson.encode({ + token = "jwt_token_here", + expires_in = 3600 + })) + } + } + + # 静态资源 + location /static/ { + alias /var/www/static/; + expires 30d; + add_header Cache-Control "public, immutable"; + } + } +} +``` + +### 7.2 WAF(Web 应用防火墙)配置 + +```nginx +http { + lua_shared_dict waf_rules 10m; + lua_shared_dict waf_block 50m; + + init_by_lua_block { + -- 加载 WAF 规则 + local cjson = require "cjson" + local rules = { + { + id = "1001", + name = "SQL Injection", + pattern = [[(?:union|select|insert|update|delete|drop|create)\s+]], + severity = "high", + action = "block" + }, + { + id = "1002", + name = "XSS Attack", + pattern = [[]*>[\s\S]*?]], + severity = "high", + action = "block" + }, + { + id = "1003", + name = "Path Traversal", + pattern = [[\.\./\.\.]], + severity = "medium", + action = "block" + } + } + + -- 存储到共享字典 + local waf_rules = ngx.shared.waf_rules + for _, rule in ipairs(rules) do + waf_rules:set(rule.id, cjson.encode(rule)) + end + } + + server { + listen 80; + server_name protected.example.com; + + location / { + access_by_lua_block { + local cjson = require "cjson" + local waf_rules = ngx.shared.waf_rules + local waf_block = ngx.shared.waf_block + + local ip = ngx.var.remote_addr + + -- 检查 IP 是否被封锁 + if waf_block:get("block:" .. ip) then + return ngx.exit(ngx.HTTP_FORBIDDEN) + end + + -- 获取所有规则 + local rules = waf_rules:get_keys(0) + local matched = false + local matched_rule = nil + + -- 检查请求 + local check_string = ngx.var.request_uri .. " " .. + (ngx.var.http_user_agent or "") .. " " .. + (ngx.var.http_cookie or "") + + for _, rule_id in ipairs(rules) do + local rule_data = waf_rules:get(rule_id) + if rule_data then + local rule = cjson.decode(rule_data) + local m, err = ngx.re.match(check_string, rule.pattern, "ijo") + if m then + matched = true + matched_rule = rule + break + end + end + end + + if matched then + -- 记录攻击 + ngx.log(ngx.ERR, "WAF blocked request from ", ip, + " Rule: ", matched_rule.id, " - ", matched_rule.name) + + -- 增加计数 + local count, err = waf_block:incr("count:" .. ip, 1, 0) + if count and count > 100 then + -- 封锁 IP + waf_block:set("block:" .. ip, "1", 3600) + ngx.log(ngx.ERR, "IP blocked: ", ip) + end + + return ngx.exit(ngx.HTTP_FORBIDDEN) + end + } + + proxy_pass http://backend; + } + } +} +``` + +--- + +## 8. 参考文档 + +- [OpenResty 官方文档](https://openresty.org/en/) +- [LuaJIT 文档](http://luajit.org/) +- [lua-nginx-module](https://github.com/openresty/lua-nginx-module) +- [lua-resty-core](https://github.com/openresty/lua-resty-core) +- [lua-resty-redis](https://github.com/openresty/lua-resty-redis) +- [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql) +- [lua-resty-http](https://github.com/ledgetech/lua-resty-http) +- [lua-resty-jwt](https://github.com/cdbattags/lua-resty-jwt) +- [Lua 5.1 参考手册](https://www.lua.org/manual/5.1/) diff --git a/docs/27-nginx-security-deep-dive.md b/docs/27-nginx-security-deep-dive.md new file mode 100644 index 0000000..63e1695 --- /dev/null +++ b/docs/27-nginx-security-deep-dive.md @@ -0,0 +1,1538 @@ +# NGINX 安全深度指南 + +## 目录 + +1. [Bot 检测与防护](#1-bot-检测与防护) +2. [WAF 配置深度指南(ModSecurity)](#2-waf-配置深度指南modsecurity) +3. [DDoS 防护策略](#3-ddos-防护策略) +4. [OWASP Top 10 防护](#4-owasp-top-10-防护) +5. [安全响应头完整配置](#5-安全响应头完整配置) +6. [CVE 历史漏洞与修复版本](#6-cve-历史漏洞与修复版本) +7. [安全审计日志配置](#7-安全审计日志配置) +8. [完整安全配置示例](#8-完整安全配置示例) + +--- + +## 1. Bot 检测与防护 + +### 1.1 基于 User-Agent 的 Bot 识别 + +```nginx +map $http_user_agent $bot_type { + default "human"; + + # 善意爬虫(白名单) + ~*googlebot "good_bot"; + ~*bingbot "good_bot"; + ~*duckduckbot "good_bot"; + ~*baiduspider "good_bot"; + ~*yandexbot "good_bot"; + ~*facebookexternalhit "good_bot"; + ~*twitterbot "good_bot"; + ~*linkedinbot "good_bot"; + ~*whatsapp "good_bot"; + + # 恶意爬虫/扫描器 + ~*sqlmap "bad_bot"; + ~*nikto "bad_bot"; + ~*masscan "bad_bot"; + ~*zgrab "bad_bot"; + ~*nmap "bad_bot"; + ~*nessus "bad_bot"; + ~*burp "bad_bot"; + ~*gobuster "bad_bot"; + ~*dirbuster "bad_bot"; + ~*wfuzz "bad_bot"; + ~*crawler4j "bad_bot"; + ~*scrapy "bad_bot"; + ~*python-requests "bad_bot"; + + # 自动化工具 + ~*curl "tool"; + ~*wget "tool"; + ~*python-urllib "tool"; + ~*java "tool"; + ~*libwww-perl "tool"; + ~*httpclient "tool"; +} + +# 基于行为的检测 +map $http_user_agent $suspicious_behavior { + default 0; + ~*bot 1; + ~*spider 1; + ~*crawl 1; + ~*scan 1; + ~*probe 1; +} +``` + +### 1.2 验证爬虫真实性 + +```nginx +server { + listen 80; + server_name example.com; + + # 白名单爬虫验证(反向 DNS 验证) + location / { + # 仅对白名单爬虫进行验证 + if ($bot_type = "good_bot") { + # 验证 Googlebot + if ($http_user_agent ~* googlebot) { + # 实际生产环境需使用 Lua/NJS 进行反向 DNS 验证 + # 此处仅示例 + access_log /var/log/nginx/verified_bots.log; + } + } + + # 拦截恶意爬虫 + if ($bot_type = "bad_bot") { + return 444; # 无响应断开连接 + } + + # 限制自动化工具 + if ($bot_type = "tool") { + limit_req zone=tool_limit burst=1 nodelay; + } + + proxy_pass http://backend; + } +} +``` + +### 1.3 基于行为的 Bot 检测 + +```nginx +# 定义 Bot 检测区域 +limit_req_zone $binary_remote_addr zone=bot_detect:10m rate=1r/s; + +server { + # 无 User-Agent 检测 + if ($http_user_agent = "") { + return 403; + } + + # 高频请求检测 + location / { + # 检查请求频率异常 + limit_req zone=bot_detect burst=5 nodelay; + + # 检测无 referer 的 POST 请求(常见于 CSRF/Bot) + if ($request_method = POST) { + set $post_check 1; + } + if ($http_referer = "") { + set $post_check "${post_check}1"; + } + if ($post_check = "11") { + return 403 "Suspicious request detected"; + } + + # 检测异常 header 组合 + if ($http_accept = "") { + return 403; + } + + proxy_pass http://backend; + } +} +``` + +### 1.4 挑战-响应机制(使用 Lua) + +```nginx +# 需要 ngx_http_lua_module +server { + location / { + access_by_lua_block { + local bot_type = ngx.var.bot_type + + if bot_type == "suspicious" then + -- 发送 JavaScript 挑战 + ngx.header.content_type = "text/html" + ngx.say([[ + + + + ]]) + ngx.exit(ngx.HTTP_OK) + end + + -- 验证 cookie + local cookies = ngx.var.http_cookie + if not cookies or not cookies:match("js_enabled=1") then + ngx.exit(ngx.HTTP_FORBIDDEN) + end + } + + proxy_pass http://backend; + } +} +``` + +### 1.5 Bot 管理策略表 + +| 类别 | 特征 | 处理方式 | +|------|------|----------| +| **善意爬虫** | Google/Bing/Baidu 等搜索引擎 | 验证真实性后放行,设置独立 rate limit | +| **监控 Bot** | Uptime 检查、SEO 工具 | IP 白名单,设置宽松的限制 | +| **恶意爬虫** | 扫描器、暴力破解工具 | 立即阻断,记录日志 | +| **自动化工具** | curl/wget/脚本 | 严格限制频率,验证码挑战 | +| **未知 Bot** | 未识别的 User-Agent | 行为分析,可疑则挑战验证 | + +--- + +## 2. WAF 配置深度指南(ModSecurity) + +### 2.1 ModSecurity 编译安装 + +```bash +# 安装依赖 +apt-get install -y libpcre3-dev libxml2-dev libcurl4-openssl-dev \ + liblua5.3-dev libssl-dev libyajl-dev + +# 下载 ModSecurity v3 +cd /usr/src +git clone --depth 1 -b v3/master https://github.com/SpiderLabs/ModSecurity +cd ModSecurity +git submodule init +git submodule update + +# 编译安装 +./build.sh +./configure --with-yajl --with-ssdeep --with-lua --with-maxmind +make -j$(nproc) +make install + +# 下载 NGINX 连接器 +cd /usr/src +git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx + +# 编译 NGINX 模块(需 NGINX 源码) +cd /usr/src/nginx-1.24.0 +./configure --add-module=/usr/src/ModSecurity-nginx [其他配置选项] +make modules +``` + +### 2.2 核心配置 + +```nginx +# modsecurity.conf - 主配置文件 +load_module modules/ngx_http_modsecurity_module.so; + +http { + # 全局 ModSecurity 配置 + modsecurity on; + modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf; + + server { + listen 80; + server_name example.com; + + location / { + # 可为特定 location 启用/禁用 + modsecurity on; + + # 内联规则 + modsecurity_rules ' + SecRuleEngine On + SecRule REQUEST_URI "@contains /admin" \ + "id:1000,phase:1,deny,status:403,msg:'Admin access restricted'" + '; + + proxy_pass http://backend; + } + + # 静态资源跳过 WAF + location ~* \\.(jpg|jpeg|png|gif|css|js|woff|woff2)$ { + modsecurity off; + proxy_pass http://backend; + } + } +} +``` + +### 2.3 ModSecurity 核心规则集(CRS) + +```ini +# /etc/nginx/modsecurity/crs-setup.conf + +# 规则引擎模式 +SecRuleEngine DetectionOnly # 仅检测模式(测试阶段) +# SecRuleEngine On # 阻断模式(生产环境) + +# 异常评分阈值 +SecAction "id:900001,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=5" +SecAction "id:900002,phase:1,nolog,pass,t:none,setvar:tx.outbound_anomaly_score_threshold=4" + +# 检测 Paranoia Level(0-4,越高越严格) +SecAction "id:900003,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=2" + +# 允许特定路径(排除规则) +SecRule REQUEST_URI "@beginsWith /api/webhook" \ + "id:1001,phase:1,pass,nolog,ctl:ruleEngine=Off" + +# 自定义白名单 +SecRule REQUEST_HEADERS:User-Agent "@contains MyInternalApp" \ + "id:1002,phase:1,pass,nolog,ctl:ruleRemoveById=913100" + +# 自定义规则 - SQL 注入增强 +SecRule REQUEST_COOKIES|REQUEST_COOKIES_NAMES|REQUEST_FILENAME|ARGS_NAMES|ARGS|XML:/* \ + "@rx (?i:(?:select\\s*\\*\\s*from|union\\s*select.*from|insert\\s+into|delete\\s+from|drop\\s+table))" \ + "id:1003,phase:2,deny,status:403,msg:'SQL Injection Detected',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}'" +``` + +### 2.4 规则集加载配置 + +```nginx +# 在 nginx.conf 中加载 CRS +modsecurity_rules_file /etc/nginx/modsecurity/crs-setup.conf; +modsecurity_rules ' + # 加载 CRS 规则 + Include /usr/share/modsecurity-crs/crs-setup.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-901-INITIALIZATION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-903.9001-DRUPAL-EXCLUSION-RULES.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-905-COMMON-EXCEPTIONS.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-910-IP-REPUTATION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-911-METHOD-ENFORCEMENT.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-912-DOS-PROTECTION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-913-SCANNER-DETECTION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-921-PROTOCOL-ATTACK.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-934-APPLICATION-ATTACK-GENERIC.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-944-APPLICATION-ATTACK-JAVA.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-950-DATA-LEAKAGES.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-959-BLOCKING-EVALUATION.conf + Include /usr/share/modsecurity-crs/rules/RESPONSE-980-CORRELATION.conf + Include /usr/share/modsecurity-crs/rules/REQUEST-999-EXCLUSION-RULES-AFTER-CRS.conf +'; +``` + +### 2.5 虚拟补丁配置 + +```ini +# /etc/nginx/modsecurity/virtual-patches.conf + +# CVE-2021-44228 (Log4j) +SecRule REQUEST_HEADERS|REQUEST_HEADERS_NAMES|REQUEST_BODY|ARGS|ARGS_NAMES \ + "@rx \\$\\{.*jndi:(ldap|ldaps|dns|rmi|iiop|http|https|corba|nis|nds)" \ + "id:2001,phase:2,deny,status:403,msg:'CVE-2021-44228 Log4j RCE attempt',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}'" + +# CVE-2017-9805 (Struts REST XStream) +SecRule REQUEST_HEADERS:Content-Type "@contains application/x-www-form-urlencoded" \ + "id:2002,phase:1,deny,status:403,msg:'CVE-2017-9805 Struts REST XStream',chain" + SecRule REQUEST_BODY "@contains &1 | grep -oP 'nginx/\\K[0-9.]+') +LATEST=$(curl -s https://nginx.org/en/download.html | grep -oP 'Stable version.*?nginx-[0-9.]+' | grep -oP '[0-9.]+$' | head -1) + +if [ "$CURRENT" != "$LATEST" ]; then + echo "警告: NGINX 版本 $CURRENT 需要更新到 $LATEST" + # 发送通知... +fi +``` + +### 6.3 安全更新流程 + +```bash +# 1. 备份当前配置 +cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d) +tar -czf /backup/nginx-config-$(date +%Y%m%d).tar.gz /etc/nginx/ + +# 2. 测试新配置 +nginx -t + +# 3. 平滑升级(热升级) +# 下载并编译新版本 +make upgrade + +# 或使用包管理器 +apt-get update && apt-get install --only-upgrade nginx + +# 4. 验证升级 +nginx -v +systemctl status nginx + +# 5. 回滚准备(保留旧二进制) +mv /usr/sbin/nginx /usr/sbin/nginx.old +``` + +--- + +## 7. 安全审计日志配置 + +### 7.1 安全审计日志格式 + +```nginx +# /etc/nginx/conf.d/audit-log.conf + +# 安全审计专用日志格式 +log_format security '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time" ' + 'ssl_protocol=$ssl_protocol ssl_cipher=$ssl_cipher ' + 'req_id=$request_id ' + 'xff=$http_x_forwarded_for ' + 'real_ip=$http_x_real_ip ' + 'cc=$geoip_country_code ' + 'city=$geoip_city '; + +# 攻击检测日志格式 +log_format attack '$time_iso8601|$remote_addr|$request_method|$request_uri|' + '$status|$http_user_agent|$http_referer|' + '$request_time|$upstream_response_time|' + '$http_x_forwarded_for|$geoip_country_code|' + '$connection_requests|$msec'; + +# 安全事件日志 +log_format security_event 'time=$time_iso8601 ' + 'client=$remote_addr ' + 'method=$request_method ' + 'uri="$request_uri" ' + 'status=$status ' + 'request_length=$request_length ' + 'body_bytes_sent=$body_bytes_sent ' + 'referer="$http_referer" ' + 'user_agent="$http_user_agent" ' + 'cookie=$http_cookie ' + 'authorization=$http_authorization ' + 'content_type=$content_type ' + 'content_length=$content_length ' + 'host=$host ' + 'server_name=$server_name ' + 'request_id=$request_id ' + 'ssl_session_id=$ssl_session_id ' + 'ssl_session_reused=$ssl_session_reused'; +``` + +### 7.2 条件日志记录 + +```nginx +http { + # 仅记录特定状态码 + map $status $loggable { + ~^[23] 0; # 2xx/3xx 不记录 + default 1; # 其他记录 + } + + # 仅记录慢请求 + map $request_time $slow_request { + ~^[0-2]\\. 0; # < 3s 不记录 + default 1; # >= 3s 记录 + } + + # 合并条件 + map "${loggable}${slow_request}" $security_log { + "00" 0; + default 1; + } + + server { + access_log /var/log/nginx/security.log security if=$security_log; + + location / { + proxy_pass http://backend; + } + } +} +``` + +### 7.3 安全事件过滤 + +```nginx +# 记录特定安全事件 +map "$status:$request_method:$http_user_agent" $security_event { + default 0; + + # 记录所有 401/403 + "~^401" 1; + "~^403" 1; + "~^404" 1; + + # 记录攻击特征 + "~*sqlmap" 1; + "~*nikto" 1; + "~*nmap" 1; + + # 记录 POST 到敏感路径 + "~*:POST:.*/(admin|config|setup|install|phpmyadmin)" 1; +} + +server { + access_log /var/log/nginx/security-events.log attack if=$security_event; + + # 错误日志分离 + error_log /var/log/nginx/security-error.log warn; +} +``` + +### 7.4 日志轮转与保留 + +```bash +# /etc/logrotate.d/nginx-security +/var/log/nginx/security*.log { + daily + missingok + rotate 90 + compress + delaycompress + notifempty + create 0640 www-data adm + sharedscripts + postrotate + if [ -f /var/run/nginx.pid ]; then + kill -USR1 $(cat /var/run/nginx.pid) + fi + endscript +} + +# 攻击日志长期保留 +/var/log/nginx/attack*.log { + weekly + missingok + rotate 520 # 保留 10 年 + compress + delaycompress + create 0640 www-data adm + sharedscripts + postrotate + kill -USR1 $(cat /var/run/nginx.pid) + endscript +} +``` + +### 7.5 实时安全监控脚本 + +```bash +#!/bin/bash +# /usr/local/bin/nginx-security-monitor.sh + +LOG_FILE="/var/log/nginx/security.log" +ALERT_THRESHOLD=100 # 每分钟请求数阈值 +BLOCK_THRESHOLD=10 # 触发封禁的异常请求数 + +# 实时监控 +tail -F $LOG_FILE | while read line; do + # 提取 IP + ip=$(echo "$line" | awk '{print $1}') + + # 检查攻击模式 + if echo "$line" | grep -qE "(union|select|script|alert|eval\\(|system\\()"; then + echo "$(date): SQL/XSS 攻击检测: $ip - $line" >> /var/log/nginx/alerts.log + # 可选:自动添加到黑名单 + # echo "deny $ip;" >> /etc/nginx/conf.d/auto-block.conf + fi + + # 检查扫描行为 + if echo "$line" | grep -qE "(\\.env|\\.git|config\\.xml|phpmyadmin|wp-admin)"; then + echo "$(date): 敏感路径扫描: $ip - $line" >> /var/log/nginx/alerts.log + fi +done +``` + +--- + +## 8. 完整安全配置示例 + +### 8.1 生产环境安全配置 + +```nginx +# /etc/nginx/nginx.conf - 安全强化主配置 + +user nginx; +worker_processes auto; +worker_rlimit_nofile 65535; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +# 加载动态模块 +load_module modules/ngx_http_geoip_module.so; +load_module modules/ngx_http_modsecurity_module.so; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + # 基础设置 + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 隐藏版本号 + server_tokens off; + + # 字符编码 + charset utf-8; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + log_format security '$remote_addr - $time_iso8601 - "$request" - $status - ' + '"$http_user_agent" - "$http_referer" - ' + 'req_time=$request_time - xff="$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # 性能优化 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + + # 客户端限制 + client_max_body_size 10m; + client_body_buffer_size 128k; + client_header_buffer_size 1k; + large_client_header_buffers 4 8k; + client_body_timeout 12s; + client_header_timeout 12s; + + # 缓冲区设置 + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + + # GeoIP + geoip_country /usr/share/GeoIP/GeoIP.dat; + geoip_city /usr/share/GeoIP/GeoLiteCity.dat; + + # 限流区域 + limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=50r/s; + limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/m; + limit_conn_zone $binary_remote_addr zone=conn_limit:10m; + + # Bot 检测 + map $http_user_agent $bot_type { + default "human"; + ~*googlebot "good_bot"; + ~*bingbot "good_bot"; + ~*sqlmap "bad_bot"; + ~*nikto "bad_bot"; + ~*nmap "bad_bot"; + ~*curl "tool"; + ~*wget "tool"; + } + + # 国家代码映射 + map $geoip_country_code $allowed_country { + default yes; + CN yes; + US yes; + JP yes; + RU no; + KP no; + IR no; + } + + # 包含其他配置 + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} +``` + +### 8.2 HTTPS 站点安全配置 + +```nginx +# /etc/nginx/sites-available/example.com + +server { + listen 80; + server_name example.com www.example.com; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name example.com www.example.com; + + # SSL 证书 + ssl_certificate /etc/nginx/ssl/example.com.crt; + ssl_certificate_key /etc/nginx/ssl/example.com.key; + + # SSL 配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_dhparam /etc/nginx/ssl/dhparam.pem; + + # SSL 会话 + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # OCSP Stapling + ssl_stapling on; + ssl_stapling_verify on; + ssl_trusted_certificate /etc/nginx/ssl/example.com.chain.crt; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + + # 安全响应头 + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; connect-src 'self' https://api.example.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self';" always; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + + # 根目录 + root /var/www/example.com; + index index.html index.htm; + + # 日志 + access_log /var/log/nginx/example.com-access.log main; + error_log /var/log/nginx/example.com-error.log warn; + + # 全局限制 + limit_conn conn_limit 20; + limit_req zone=req_limit burst=20 nodelay; + + # 安全过滤 + if ($bad_ip) { + return 444; + } + + if ($allowed_country = no) { + return 403; + } + + # 敏感文件保护 + location ~ /\\. { + deny all; + return 404; + } + + location ~* \\.(git|svn|env|config|log|sql|backup|bak|swp|old|orig|save)$ { + deny all; + return 404; + } + + # 静态资源 + location ~* \\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header X-Frame-Options "SAMEORIGIN" always; + access_log off; + } + + # API 路由 + location /api/ { + limit_req zone=api_limit burst=50 nodelay; + + # 内容类型验证 + if ($content_type !~ ^(application/json|application/x-www-form-urlencoded|multipart/form-data)$) { + return 415; + } + + # 方法限制 + if ($request_method !~ ^(GET|POST|PUT|DELETE|PATCH|OPTIONS)$) { + return 405; + } + + # CORS + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin "https://example.com" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always; + add_header Access-Control-Allow-Credentials "true" always; + add_header Access-Control-Max-Age 86400 always; + return 204; + } + + add_header Access-Control-Allow-Origin "https://example.com" always; + add_header Access-Control-Allow-Credentials "true" always; + + proxy_pass http://api_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Request-ID $request_id; + } + + # 认证路由 + location /auth/ { + limit_req zone=login_limit burst=3 nodelay; + + proxy_pass http://auth_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 主应用 + location / { + try_files $uri $uri/ /index.html; + + # 安全响应头(静态页面) + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + } +} +``` + +### 8.3 WAF 完整配置 + +```nginx +# /etc/nginx/conf.d/waf.conf + +# ModSecurity 全局配置 +modsecurity on; +modsecurity_rules_file /etc/nginx/modsecurity/main.conf; + +# 特定路径规则 +server { + location / { + # 启用完整 CRS + modsecurity_rules ' + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyLimit 524288 + SecResponseBodyLimitAction ProcessPartial + + # 文件上传限制 + SecRequestBodyLimit 13107200 + SecRequestBodyNoFilesLimit 131072 + + # 允许文件上传类型 + SecRule REQUEST_FILENAME "@rx \\.(jpg|jpeg|png|gif|pdf|doc|docx)$" \ + "id:2000,phase:1,pass,nolog,ctl:requestBodyLimit=52428800" + + # 加载 CRS + Include /usr/share/modsecurity-crs/crs-setup.conf + Include /usr/share/modsecurity-crs/rules/*.conf + + # 自定义排除 + SecRule REQUEST_URI "@beginsWith /api/webhook" \ + "id:9000,phase:1,pass,nolog,ctl:ruleRemoveById=920350" + '; + + proxy_pass http://backend; + } + + # 静态资源跳过 WAF + location ~* \\.(jpg|jpeg|png|gif|css|js|woff|woff2|ttf|eot|svg|ico)$ { + modsecurity off; + expires 1y; + access_log off; + proxy_pass http://backend; + } +} +``` + +### 8.4 安全配置检查清单 + +```markdown +## 部署前检查清单 + +### 基础安全 +- [ ] 使用最新稳定版 NGINX +- [ ] server_tokens 设置为 off +- [ ] 禁用不安全的 SSL 协议(SSLv2/SSLv3/TLSv1.0/TLSv1.1) +- [ ] 配置安全的加密套件 +- [ ] 启用 HSTS +- [ ] 配置所有安全响应头 + +### 访问控制 +- [ ] 管理后台 IP 白名单 +- [ ] 敏感文件/目录访问限制 +- [ ] 地理位置访问控制(如需要) +- [ ] User-Agent 过滤 + +### 请求限制 +- [ ] 请求速率限制(通用) +- [ ] API 专用限流 +- [ ] 登录接口严格限流 +- [ ] 连接数限制 +- [ ] 带宽限制 + +### WAF 防护 +- [ ] ModSecurity 安装配置 +- [ ] CRS 规则集加载 +- [ ] 虚拟补丁规则 +- [ ] 自定义业务规则 +- [ ] 排除规则测试 + +### 日志监控 +- [ ] 安全审计日志配置 +- [ ] 日志轮转设置 +- [ ] 实时监控脚本 +- [ ] fail2ban 集成 + +### 性能与安全平衡 +- [ ] 静态资源缓存策略 +- [ ] 安全头对静态资源的影响 +- [ ] WAF 对性能的损耗评估 +- [ ] 限流阈值合理性测试 + +### 测试验证 +- [ ] SSL Labs A+ 评分 +- [ ] Security Headers 检查 +- [ ] OWASP ZAP 扫描 +- [ ] 渗透测试 +- [ ] 性能基准测试 +``` + +--- + +## 附录:常用安全测试命令 + +```bash +# SSL/TLS 测试 +openssl s_client -connect example.com:443 -tls1_2 +openssl s_client -connect example.com:443 -tls1_3 +testssl.sh example.com + +# 安全响应头检查 +curl -I -s https://example.com | grep -E "^(Strict-Transport-Security|Content-Security-Policy|X-Frame-Options)" + +# 漏洞扫描基础 +curl -s https://example.com/.env && echo "DANGER: .env exposed!" +curl -s https://example.com/.git/HEAD && echo "DANGER: .git exposed!" +curl -s https://example.com/config.xml && echo "DANGER: config exposed!" + +# 压力测试(限流验证) +ab -n 1000 -c 100 https://example.com/api/test +siege -c 50 -t 30s https://example.com/ +``` diff --git a/docs/28-nginx-api-gateway.md b/docs/28-nginx-api-gateway.md new file mode 100644 index 0000000..e5a771a --- /dev/null +++ b/docs/28-nginx-api-gateway.md @@ -0,0 +1,1527 @@ +# NGINX API 网关配置指南 + +## 1. API 网关概述 + +### 什么是 API 网关 + +API 网关是微服务架构中的关键组件,作为单一入口点统一管理和暴露后端服务。NGINX 作为高性能反向代理,非常适合构建功能完善的 API 网关。 + +### API 网关核心功能 + +| 功能 | 说明 | +|------|------| +| **请求路由** | 根据路径、方法、Header 路由到不同后端服务 | +| **负载均衡** | 在多个服务实例间分发请求 | +| **认证授权** | JWT、OAuth2、API Key 验证 | +| **限流熔断** | 防止过载和服务雪崩 | +| **协议转换** | HTTP/HTTPS、WebSocket、gRPC 转换 | +| **请求/响应转换** | 修改请求头、响应体、路径重写 | +| **缓存加速** | 缓存常用 API 响应 | +| **日志监控** | 统一收集 API 调用日志和指标 | + +### API 网关架构图 + +``` + ┌─────────────────┐ + │ API Gateway │ + │ (NGINX) │ + └────────┬────────┘ + │ + ┌─────────┬───────┴───────┬─────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ User Svc │ │ Order Svc│ │PaymentSvc│ │ SearchSvc│ + └──────────┘ └──────────┘ └──────────┘ └──────────┘ +``` + +--- + +## 2. API 路由设计模式 + +### 2.1 基于路径的路由 + +最常见的路由方式,根据 URL 路径前缀路由到不同服务。 + +```nginx +http { + upstream user_service { + server user-svc:8080; + } + + upstream order_service { + server order-svc:8081; + } + + upstream payment_service { + server payment-svc:8082; + } + + server { + listen 80; + server_name api.example.com; + + # 用户服务路由 + location /api/v1/users/ { + proxy_pass http://user_service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # 订单服务路由 + location /api/v1/orders/ { + proxy_pass http://order_service/; + proxy_set_header Host $host; + } + + # 支付服务路由 + location /api/v1/payments/ { + proxy_pass http://payment_service/; + proxy_set_header Host $host; + } + } +} +``` + +### 2.2 基于请求方法的路由 + +同一路径根据 HTTP 方法路由到不同服务。 + +```nginx +server { + listen 80; + server_name api.example.com; + + # 查询操作路由到只读服务 + location /api/v1/data/ { + if ($request_method ~ ^(GET|HEAD)$) { + proxy_pass http://readonly_backend; + break; + } + + # 写入操作路由到主服务 + if ($request_method ~ ^(POST|PUT|PATCH|DELETE)$) { + proxy_pass http://write_backend; + break; + } + + return 405; # Method Not Allowed + } +} +``` + +### 2.3 基于 Header 的路由 + +根据请求头内容路由,常用于 A/B 测试、金丝雀发布。 + +```nginx +http { + map $http_x_api_version $backend_pool { + default "stable_backend"; + "v2" "beta_backend"; + "v2-beta" "beta_backend"; + } + + upstream stable_backend { + server api-v1:8080; + } + + upstream beta_backend { + server api-v2:8080; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + proxy_pass http://$backend_pool; + proxy_set_header Host $host; + } + } +} +``` + +### 2.4 基于 Cookie 的路由 + +```nginx +http { + map $cookie_app_version $backend_node { + default "stable"; + "beta" "canary"; + } + + upstream stable { + server api-stable:8080; + } + + upstream canary { + server api-canary:8080; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + proxy_pass http://$backend_node; + } + } +} +``` + +### 2.5 流量分流路由(百分比) + +```nginx +http { + # 使用 split_clients 进行百分比分流 + split_clients "${remote_addr}${http_user_agent}" $variant { + 10% canary; # 10% 流量到新版本 + * stable; # 90% 流量到稳定版 + } + + upstream stable { + server api-v1:8080; + } + + upstream canary { + server api-v2:8080; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + proxy_pass http://$variant; + proxy_set_header X-Variant $variant; + } + } +} +``` + +### 2.6 组合路由策略 + +```nginx +http { + # 定义映射变量 + map $http_x_env $target_backend { + default prod; + "staging" staging; + "dev" dev; + } + + map $http_x_tenant_id $tenant_shard { + default shard1; + ~^1[0-5] shard1; # 租户 10-15 + ~^1[6-9]|^2[0] shard2; # 租户 16-20 + } + + upstream prod { + server api-prod-1:8080; + server api-prod-2:8080; + } + + upstream staging { + server api-staging:8080; + } + + upstream dev { + server api-dev:8080; + } + + upstream shard1 { + server db-shard1:8080; + } + + upstream shard2 { + server db-shard2:8080; + } + + server { + listen 80; + server_name api.example.com; + + # 环境路由 + location /api/ { + proxy_pass http://$target_backend; + proxy_set_header X-Tenant-Shard $tenant_shard; + + # 记录路由决策 + access_log /var/log/nginx/api-access.log detailed; + } + + # 租户数据路由 + location /api/tenant-data/ { + proxy_pass http://$tenant_shard; + } + } +} +``` + +--- + +## 3. 请求/响应转换 + +### 3.1 请求头转换 + +#### 添加请求头 + +```nginx +server { + listen 80; + server_name api.example.com; + + location /api/ { + # 传递客户端信息 + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # 添加网关标识 + proxy_set_header X-API-Gateway "nginx"; + proxy_set_header X-Request-ID $request_id; + + # 添加时间戳 + proxy_set_header X-Request-Time $msec; + + proxy_pass http://backend; + } +} +``` + +#### 删除请求头 + +```nginx +server { + location /api/ { + # 删除敏感请求头,防止信息泄露 + proxy_set_header Authorization ""; + proxy_set_header Cookie ""; + proxy_set_header X-Internal-Token ""; + + proxy_pass http://backend; + } +} +``` + +#### 修改请求头 + +```nginx +server { + location /api/ { + # 重写 Host 头 + proxy_set_header Host backend.internal.com; + + # 基于条件设置 + if ($http_x_client_type = "mobile") { + proxy_set_header X-Device-Type "mobile"; + } + + proxy_pass http://backend; + } +} +``` + +### 3.2 响应头转换 + +#### 添加安全响应头 + +```nginx +server { + listen 80; + server_name api.example.com; + + # 添加 API 响应头 + add_header X-API-Version "v1" always; + add_header X-RateLimit-Limit "1000" always; + + # 安全头部 + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header X-XSS-Protection "1; mode=block" always; + + # CORS 头 + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID" always; + + location /api/ { + proxy_pass http://backend; + + # 暴露额外响应头给前端 + expose_headers X-Request-ID X-RateLimit-Remaining; + } +} +``` + +#### 隐藏响应头 + +```nginx +server { + location /api/ { + proxy_pass http://backend; + + # 隐藏后端服务器信息 + proxy_hide_header X-Powered-By; + proxy_hide_header X-Runtime; + proxy_hide_header X-Version; + proxy_hide_header Server; + } +} +``` + +### 3.3 请求体转换 + +#### 请求体大小限制 + +```nginx +http { + # 全局请求体限制 + client_max_body_size 10m; + client_body_buffer_size 128k; + + server { + listen 80; + server_name api.example.com; + + # 上传接口允许更大请求体 + location /api/v1/upload/ { + client_max_body_size 100m; + proxy_pass http://upload_backend; + } + + # Webhook 接口限制较小 + location /api/v1/webhooks/ { + client_max_body_size 1m; + proxy_pass http://webhook_backend; + } + + # 普通 API + location /api/ { + client_max_body_size 5m; + proxy_pass http://api_backend; + } + } +} +``` + +### 3.4 响应体转换(sub_filter) + +#### 修改响应内容 + +```nginx +server { + listen 80; + server_name api.example.com; + + location /api/ { + proxy_pass http://backend; + + # 替换响应中的内部 URL 为外部 URL + sub_filter_once off; + sub_filter_types application/json; + + # 替换后端地址为网关地址 + sub_filter 'http://backend-internal:8080' 'https://api.example.com'; + + # 替换版本标识 + sub_filter '"version": "internal"' '"version": "public"'; + } +} +``` + +#### JSON 字段脱敏 + +```nginx +server { + location /api/users/ { + proxy_pass http://user_backend; + + # 脱敏手机号(示例:隐藏中间4位) + sub_filter_once off; + sub_filter_types application/json; + sub_filter '([0-9]{3})[0-9]{4}([0-9]{4})' '$1****$2'; + } +} +``` + +### 3.5 URL 重写与转换 + +#### 路径重写 + +```nginx +server { + listen 80; + server_name api.example.com; + + # 旧版本路径兼容 + location /api/v0/ { + rewrite ^/api/v0/(.*)$ /api/v1/$1 permanent; + } + + # 内部路径映射 + location /api/public/ { + rewrite ^/api/public/(.*)$ /internal/api/$1 break; + proxy_pass http://backend; + } + + # 带参数重写 + location /api/search/ { + rewrite ^/api/search/(.*)$ /search?q=$1 break; + proxy_pass http://search_backend; + } +} +``` + +#### 查询参数处理 + +```nginx +server { + location /api/ { + # 添加默认参数 + if ($arg_version = "") { + set $args $args&version=v1; + } + + # 移除敏感参数 + if ($arg_api_key) { + set $args ''; + # 或者保留其他参数 + set $args $arg_foo=$arg_foo; + } + + proxy_pass http://backend; + } +} +``` + +--- + +## 4. JWT 验证 + +### 4.1 通过 auth_request 进行 JWT 验证 + +使用外部认证服务验证 JWT Token。 + +```nginx +http { + upstream api_backend { + server api:8080; + } + + upstream auth_service { + server auth:8080; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + # JWT 验证 + auth_request /auth_verify; + + # 从认证响应中提取用户信息 + auth_request_set $auth_user $upstream_http_x_user_id; + auth_request_set $auth_role $upstream_http_x_user_role; + auth_request_set $auth_tenant $upstream_http_x_tenant_id; + + # 传递给后端 + proxy_set_header X-User-ID $auth_user; + proxy_set_header X-User-Role $auth_role; + proxy_set_header X-Tenant-ID $auth_tenant; + + proxy_pass http://api_backend; + } + + # JWT 验证子请求 + location = /auth_verify { + internal; + proxy_pass http://auth_service/verify; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + + # 传递 Authorization 头 + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Client-IP $remote_addr; + + proxy_connect_timeout 3s; + proxy_read_timeout 3s; + } + + # 认证失败处理 + error_page 401 = @unauthorized; + location @unauthorized { + default_type application/json; + return 401 '{"error":"Unauthorized","code":"INVALID_TOKEN"}'; + } + + error_page 403 = @forbidden; + location @forbidden { + default_type application/json; + return 403 '{"error":"Forbidden","code":"INSUFFICIENT_PERMISSIONS"}'; + } + } +} +``` + +### 4.2 使用 Lua 进行 JWT 验证(OpenResty) + +```nginx +http { + lua_package_path "/usr/local/lib/lua/?.lua;;"; + + server { + listen 80; + server_name api.example.com; + + location /api/ { + access_by_lua_block { + local jwt = require "resty.jwt" + local validators = require "resty.jwt-validators" + + -- 获取 token + local auth_header = ngx.var.http_authorization + if not auth_header then + ngx.status = 401 + ngx.say('{"error":"Missing Authorization header"}') + ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + local token = string.gsub(auth_header, "Bearer ", "") + + -- 验证 JWT + local jwt_obj = jwt:verify( + ngx.var.jwt_secret, -- JWT 密钥 + token, + { + iss = "https://auth.example.com", + validators = { + exp = validators.opt_is_not_expired(), + iat = validators.opt_is_not_before_now(), + } + } + ) + + if not jwt_obj.verified then + ngx.status = 401 + ngx.say('{"error":"Invalid token","details":"' .. jwt_obj.reason .. '"}') + ngx.exit(ngx.HTTP_UNAUTHORIZED) + end + + -- 设置变量供后续使用 + ngx.var.jwt_sub = jwt_obj.payload.sub + ngx.var.jwt_role = jwt_obj.payload.role or "user" + } + + proxy_set_header X-User-ID $jwt_sub; + proxy_set_header X-User-Role $jwt_role; + proxy_pass http://api_backend; + } + } +} +``` + +### 4.3 使用 NJS(NGINX JavaScript)进行 JWT 验证 + +```nginx +load_module modules/ngx_http_js_module.so; + +http { + js_import /etc/nginx/jwt_verify.js; + + server { + listen 80; + server_name api.example.com; + + location /api/ { + # 调用 NJS 验证 + js_set $jwt_payload jwt_verify.verify; + + # 验证失败时拒绝 + if ($jwt_payload = "") { + return 401 '{"error":"Unauthorized"}'; + } + + proxy_set_header X-JWT-Payload $jwt_payload; + proxy_pass http://api_backend; + } + } +} +``` + +**jwt_verify.js**: + +```javascript +function verify(r) { + var auth = r.headersIn['Authorization']; + if (!auth || !auth.startsWith('Bearer ')) { + return ''; + } + + var token = auth.substring(7); + + try { + // 简单 JWT 解析(base64 解码 payload) + var parts = token.split('.'); + if (parts.length !== 3) { + return ''; + } + + // 解码 payload + var payload = parts[1]; + // base64url 解码 + var decoded = Buffer.from(payload, 'base64url').toString(); + var claims = JSON.parse(decoded); + + // 验证过期时间 + if (claims.exp && claims.exp < Math.floor(Date.now() / 1000)) { + return ''; + } + + return JSON.stringify(claims); + } catch (e) { + return ''; + } +} + +export default {verify}; +``` + +### 4.4 JWT 验证配置参考表 + +| 验证方式 | 优点 | 缺点 | 适用场景 | +|----------|------|------|----------| +| **auth_request** | 灵活、业务逻辑外置、支持复杂验证 | 额外网络请求、延迟增加 | 需要与认证中心实时交互 | +| **Lua (OpenResty)** | 高性能、功能丰富、社区成熟 | 需要 OpenResty 或 lua-nginx-module | 复杂验证逻辑、本地处理 | +| **NJS** | NGINX 官方支持、无需额外模块 | 功能相对简单、性能略低 | 简单验证、官方生态优先 | + +--- + +## 5. 限流与配额管理 + +### 5.1 基础限流配置 + +```nginx +http { + # 按 IP 限流区域 + limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s; + + # 按用户限流区域 + limit_req_zone $http_x_user_id zone=user_limit:10m rate=100r/m; + + # 全局 API 限流 + limit_req_zone $server_name zone=api_global:10m rate=1000r/s; + + server { + listen 80; + server_name api.example.com; + + # 全局限流 + limit_req zone=api_global burst=200 nodelay; + + location /api/ { + # IP 级限流 + limit_req zone=ip_limit burst=20 nodelay; + + proxy_pass http://api_backend; + } + + location /api/v1/users/ { + # 用户级限流 + limit_req zone=user_limit burst=10 nodelay; + + proxy_pass http://user_backend; + } + } +} +``` + +### 5.2 分层限流策略 + +```nginx +http { + # 不同层级的限流区域 + limit_req_zone $binary_remote_addr zone=ip:10m rate=30r/s; + limit_req_zone $http_x_api_key zone=api_key:10m rate=100r/s; + limit_req_zone $http_x_user_id zone=user:10m rate=60r/m; + limit_req_zone $server_name zone=global:10m rate=5000r/s; + + server { + listen 80; + server_name api.example.com; + + # 全局保护 + limit_req zone=global burst=1000 nodelay; + + # 健康检查端点不限流 + location /health { + limit_req off; + access_log off; + return 200 '{"status":"ok"}'; + } + + # 公开 API - 仅 IP 限流 + location /api/public/ { + limit_req zone=ip burst=50 nodelay; + proxy_pass http://public_backend; + } + + # 认证 API - IP + API Key 双重限流 + location /api/v1/ { + limit_req zone=ip burst=30 nodelay; + limit_req zone=api_key burst=100 nodelay; + proxy_pass http://api_backend; + } + + # 用户级 API - 三层限流 + location /api/v1/user/ { + limit_req zone=ip burst=20 nodelay; + limit_req zone=api_key burst=50 nodelay; + limit_req zone=user burst=10 nodelay; + proxy_pass http://user_backend; + } + } +} +``` + +### 5.3 配额管理(基于连接数) + +```nginx +http { + # 按 API Key 限制并发连接 + limit_conn_zone $http_x_api_key zone=conn_by_key:10m; + + # 按用户限制并发连接 + limit_conn_zone $http_x_user_id zone=conn_by_user:10m; + + server { + listen 80; + server_name api.example.com; + + location /api/v1/stream/ { + # 每个 API Key 最多 10 个并发连接 + limit_conn conn_by_key 10; + + # 每个用户最多 5 个并发连接 + limit_conn conn_by_user 5; + + proxy_pass http://streaming_backend; + proxy_buffering off; + } + } +} +``` + +### 5.4 限流响应自定义 + +```nginx +http { + limit_req_zone $binary_remote_addr zone=limit:10m rate=10r/s; + + server { + listen 80; + server_name api.example.com; + + location /api/ { + limit_req zone=limit burst=20 nodelay; + + # 自定义限流响应 + limit_req_status 429; # Too Many Requests + + proxy_pass http://api_backend; + } + + # 自定义 429 响应 + error_page 429 @rate_limited; + location @rate_limited { + default_type application/json; + add_header Retry-After 60 always; + return 429 '{"error":"Rate limit exceeded","retry_after":60,"limit":10}'; + } + } +} +``` + +### 5.5 基于路径的差异化限流 + +```nginx +http { + # 不同限流区域 + limit_req_zone $binary_remote_addr zone=light:10m rate=100r/m; + limit_req_zone $binary_remote_addr zone=medium:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=heavy:10m rate=1r/s; + + map $uri $rate_limit_zone { + default ""; + ~*^/api/health "none"; + ~*^/api/search "heavy"; + ~*^/api/reports "heavy"; + ~*^/api/export "heavy"; + ~*^/api/webhooks "light"; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + # 根据路径应用不同限流 + if ($rate_limit_zone = "none") { + limit_req off; + } + + if ($rate_limit_zone = "light") { + limit_req zone=light burst=5 nodelay; + } + + if ($rate_limit_zone = "heavy") { + limit_req zone=heavy burst=3 nodelay; + } + + proxy_pass http://api_backend; + } + } +} +``` + +### 5.6 限流指标暴露 + +```nginx +http { + limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s; + + server { + listen 80; + server_name api.example.com; + + location /api/ { + limit_req zone=api burst=200 nodelay; + + # 添加限流相关响应头 + add_header X-RateLimit-Limit 100 always; + add_header X-RateLimit-Window 1s always; + + proxy_pass http://api_backend; + } + + # 限流状态端点 + location /api/status/ratelimit { + limit_req off; + stub_status on; + access_log off; + } + } +} +``` + +--- + +## 6. API 版本控制策略 + +### 6.1 URL 路径版本控制 + +```nginx +http { + upstream api_v1 { + server api-v1:8080; + } + + upstream api_v2 { + server api-v2:8080; + } + + server { + listen 80; + server_name api.example.com; + + # v1 路由 + location /api/v1/ { + proxy_pass http://api_v1/; + proxy_set_header X-API-Version "v1"; + } + + # v2 路由 + location /api/v2/ { + proxy_pass http://api_v2/; + proxy_set_header X-API-Version "v2"; + } + + # 默认版本(向后兼容) + location /api/ { + rewrite ^/api/(.*)$ /api/v2/$1 break; + proxy_pass http://api_v2; + } + } +} +``` + +### 6.2 Header 版本控制 + +```nginx +http { + upstream api_v1 { + server api-v1:8080; + } + + upstream api_v2 { + server api-v2:8080; + } + + # 根据 Accept-Version 头路由 + map $http_accept_version $api_version { + default "v2"; + "1" "v1"; + "2" "v2"; + "v1" "v1"; + "v2" "v2"; + } + + server { + listen 80; + server_name api.example.com; + + location /api/ { + # 版本不存在时返回 400 + if ($api_version = "") { + return 400 '{"error":"Unsupported API version"}'; + } + + proxy_pass http://api_$api_version; + proxy_set_header X-API-Version $api_version; + } + } +} +``` + +### 6.3 内容协商版本控制 + +```nginx +http { + upstream api_v1 { + server api-v1:8080; + } + + upstream api_v2 { + server api-v2:8080; + } + + server { + listen 80; + server_name api.example.com; + + location /api/users { + # 检查 Accept 头中的版本媒体类型 + if ($http_accept ~ "application/vnd\.api\.v2\+json") { + proxy_pass http://api_v2; + break; + } + + if ($http_accept ~ "application/vnd\.api\.v1\+json") { + proxy_pass http://api_v1; + break; + } + + # 默认版本 + proxy_pass http://api_v2; + } + } +} +``` + +### 6.4 版本弃用与 Sunset + +```nginx +http { + server { + listen 80; + server_name api.example.com; + + location /api/v1/ { + # 添加弃用警告头 + add_header Deprecation "true" always; + add_header Sunset "Sun, 31 Dec 2024 23:59:59 GMT" always; + add_header Link '; rel="successor-version"' always; + + proxy_pass http://api_v1; + } + } +} +``` + +--- + +## 7. OpenAPI/Swagger 集成 + +### 7.1 Swagger UI 托管 + +```nginx +http { + server { + listen 80; + server_name api.example.com; + + # Swagger UI 静态文件 + location /docs/ { + alias /var/www/swagger-ui/; + try_files $uri $uri/ /docs/index.html; + } + + # OpenAPI 规范文件 + location /api-docs/ { + alias /etc/nginx/api-docs/; + default_type application/json; + + # CORS + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, OPTIONS" always; + } + + # 特定服务文档 + location /api-docs/users.yaml { + alias /etc/nginx/api-docs/users.yaml; + default_type text/yaml; + } + + location /api-docs/orders.yaml { + alias /etc/nginx/api-docs/orders.yaml; + default_type text/yaml; + } + } +} +``` + +### 7.2 多服务 API 文档聚合 + +```nginx +http { + server { + listen 80; + server_name api.example.com; + + # 聚合文档入口 + location /api-docs { + default_type application/json; + return 200 '{ + "openapi": "3.0.0", + "info": { + "title": "API Gateway", + "version": "1.0.0" + }, + "servers": [ + {"url": "https://api.example.com"} + ], + "paths": { + "/api/v1/users": {"$ref": "/api-docs/users.yaml#/paths/~1users"}, + "/api/v1/orders": {"$ref": "/api-docs/orders.yaml#/paths/~1orders"} + } + }'; + } + + # 代理到各服务文档 + location /api-docs/users/ { + proxy_pass http://user_service/docs/; + } + + location /api-docs/orders/ { + proxy_pass http://order_service/docs/; + } + } +} +``` + +### 7.3 API 文档访问控制 + +```nginx +http { + server { + listen 80; + server_name api.example.com; + + # 公开文档 + location /docs/public/ { + alias /var/www/docs/public/; + } + + # 内部文档(需认证) + location /docs/internal/ { + auth_basic "Internal API Docs"; + auth_basic_user_file /etc/nginx/.htpasswd-docs; + + alias /var/www/docs/internal/; + } + + # API 定义文件保护 + location ~ ^/api-docs/.*\.(yaml|json)$ { + # 只允许特定 referer + valid_referers server_names *.example.com; + + if ($invalid_referer) { + return 403; + } + + alias /etc/nginx/api-docs/; + } + } +} +``` + +--- + +## 8. 完整 API 网关配置示例 + +### 8.1 生产级 API 网关配置 + +```nginx +# /etc/nginx/nginx.conf + +user nginx; +worker_processes auto; +worker_rlimit_nofile 65535; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/json; + + # 日志格式 + log_format api_log '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time" ' + 'req_id="$request_id" api_key="$http_x_api_key" ' + 'user_id="$jwt_sub" tenant="$jwt_tenant"'; + + access_log /var/log/nginx/access.log api_log; + + # 性能优化 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # 客户端限制 + client_max_body_size 10m; + client_body_buffer_size 128k; + client_header_buffer_size 4k; + large_client_header_buffers 4 8k; + + # 限流区域 + limit_req_zone $binary_remote_addr zone=ip:10m rate=30r/s; + limit_req_zone $http_x_api_key zone=api_key:10m rate=100r/s; + limit_req_zone $server_name zone=global:10m rate=10000r/s; + + # 连接限制 + limit_conn_zone $binary_remote_addr zone=addr:10m; + limit_conn_zone $http_x_api_key zone=api_conn:10m; + + # 上游服务定义 + upstream user_service { + zone user_service 64k; + least_conn; + server user-svc-1:8080 weight=5; + server user-svc-2:8080 weight=5; + server user-svc-3:8080 backup; + keepalive 32; + } + + upstream order_service { + zone order_service 64k; + least_conn; + server order-svc-1:8080; + server order-svc-2:8080; + keepalive 32; + } + + upstream payment_service { + zone payment_service 64k; + server payment-svc-1:8080 max_fails=3 fail_timeout=30s; + server payment-svc-2:8080 max_fails=3 fail_timeout=30s; + keepalive 16; + } + + upstream auth_service { + zone auth_service 64k; + server auth-svc:8080; + keepalive 16; + } + + # 变量映射 + map $http_x_api_version $api_version { + default "v1"; + "v1" "v1"; + "v2" "v2"; + } + + map $uri $rate_limit_tier { + default "medium"; + ~*^/api/health "none"; + ~*^/api/metrics "none"; + ~*^/api/export "strict"; + ~*^/api/search "strict"; + ~*^/api/webhooks "relaxed"; + } + + # API Gateway 服务器 + server { + listen 80; + listen [::]:80; + server_name api.example.com; + + # 返回 444 关闭非指定域名访问 + location / { + return 444; + } + + # 健康检查(无认证、无限流) + location = /health { + access_log off; + limit_req off; + return 200 '{"status":"healthy","gateway":"nginx"}'; + } + + # Prometheus 指标端点 + location = /metrics { + access_log off; + limit_req off; + stub_status on; + } + + # API 文档 + location /docs/ { + alias /var/www/api-docs/; + try_files $uri $uri/ =404; + + # 缓存文档 + expires 1h; + add_header Cache-Control "public, immutable"; + } + + # 主 API 入口 + location /api/ { + # 请求 ID 生成 + add_header X-Request-ID $request_id always; + + # 全局限流 + limit_req zone=global burst=2000 nodelay; + + # 分层限流 + limit_req zone=ip burst=50 nodelay; + limit_req zone=api_key burst=100 nodelay; + + # 连接限制 + limit_conn addr 50; + limit_conn api_conn 100; + + # JWT 验证(可选,根据路径) + location ~ ^/api/(v1|v2)/(users|orders|payments)/ { + auth_request /auth/verify; + auth_request_set $jwt_sub $upstream_http_x_user_id; + auth_request_set $jwt_role $upstream_http_x_user_role; + auth_request_set $jwt_tenant $upstream_http_x_tenant_id; + + # 认证失败处理 + error_page 401 = @auth_error; + error_page 403 = @forbidden_error; + + # 子路径路由 + location ~ ^/api/(v1|v2)/users/ { + rewrite ^/api/(v1|v2)/(.*)$ /$2 break; + proxy_pass http://user_service; + } + + location ~ ^/api/(v1|v2)/orders/ { + rewrite ^/api/(v1|v2)/(.*)$ /$2 break; + proxy_pass http://order_service; + } + + location ~ ^/api/(v1|v2)/payments/ { + rewrite ^/api/(v1|v2)/(.*)$ /$2 break; + proxy_pass http://payment_service; + } + } + + # 公开 API(无需认证) + location ~ ^/api/(v1|v2)/public/ { + rewrite ^/api/(v1|v2)/(.*)$ /$2 break; + proxy_pass http://public_service; + } + } + + # 认证子请求 + location = /auth/verify { + internal; + proxy_pass http://auth_service/verify; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Original-URI $request_uri; + proxy_set_header X-Original-Method $request_method; + proxy_connect_timeout 3s; + proxy_read_timeout 3s; + } + + # WebSocket 支持 + location /ws/ { + proxy_pass http://websocket_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } + + # 错误处理 + error_page 500 502 503 504 @api_error; + location @api_error { + internal; + default_type application/json; + add_header X-Error-Source "gateway" always; + return 500 '{"error":"Internal Server Error","code":"GATEWAY_ERROR"}'; + } + + location @auth_error { + internal; + default_type application/json; + return 401 '{"error":"Unauthorized","code":"AUTH_REQUIRED"}'; + } + + location @forbidden_error { + internal; + default_type application/json; + return 403 '{"error":"Forbidden","code":"ACCESS_DENIED"}'; + } + + location @rate_limited { + internal; + default_type application/json; + add_header Retry-After 60 always; + return 429 '{"error":"Rate limit exceeded","code":"RATE_LIMITED","retry_after":60}'; + } + } + + # HTTPS 服务器 + server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name api.example.com; + + # SSL 配置 + ssl_certificate /etc/nginx/ssl/api.example.com.crt; + ssl_certificate_key /etc/nginx/ssl/api.example.com.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # HSTS + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + + # 安全头部 + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # 重用 HTTP 配置 + include /etc/nginx/api-locations.conf; + } +} +``` + +### 8.2 配置结构建议 + +``` +/etc/nginx/ +├── nginx.conf # 主配置 +├── conf.d/ +│ ├── 00-upstreams.conf # 上游服务定义 +│ ├── 10-limits.conf # 限流配置 +│ ├── 20-maps.conf # 变量映射 +│ └── 30-ssl.conf # SSL 通用配置 +├── sites-enabled/ +│ └── api-gateway.conf # API 网关服务器配置 +├── api-docs/ # API 文档 +│ ├── openapi.yaml +│ ├── users.yaml +│ └── orders.yaml +└── ssl/ + ├── api.example.com.crt + └── api.example.com.key +``` + +### 8.3 常用指令速查表 + +| 类别 | 指令 | 说明 | +|------|------|------| +| **路由** | `proxy_pass` | 代理到后端 | +| | `rewrite` | URL 重写 | +| | `map` | 变量映射 | +| **请求头** | `proxy_set_header` | 设置代理请求头 | +| | `proxy_hide_header` | 隐藏响应头 | +| | `add_header` | 添加响应头 | +| **认证** | `auth_request` | 外部认证 | +| | `auth_request_set` | 提取认证变量 | +| **限流** | `limit_req_zone` | 定义限流区域 | +| | `limit_req` | 应用限流 | +| | `limit_conn_zone` | 定义连接限制区域 | +| | `limit_conn` | 应用连接限制 | +| **转换** | `sub_filter` | 响应内容替换 | +| | `client_max_body_size` | 请求体大小限制 | + +--- + +## 9. 最佳实践 + +### 9.1 安全最佳实践 + +1. **始终使用 HTTPS**:生产环境强制 TLSv1.2+ +2. **隐藏后端信息**:移除 Server、X-Powered-By 等响应头 +3. **启用 HSTS**:防止降级攻击 +4. **实施认证**:敏感接口必须认证 +5. **输入验证**:限制请求体大小和方法 + +### 9.2 性能最佳实践 + +1. **启用 keepalive**:减少连接建立开销 +2. **合理配置缓冲区**:平衡内存使用和响应速度 +3. **使用缓存**:对读多写少的 API 启用缓存 +4. **连接池**:配置上游 keepalive 连接 +5. **启用 gzip**:压缩 JSON 响应 + +### 9.3 可观测性最佳实践 + +1. **结构化日志**:包含请求 ID、用户 ID、耗时等 +2. **健康检查端点**:便于负载均衡器探测 +3. **指标暴露**:使用 stub_status 或 nginx-module-vts +4. **分布式追踪**:传递 Trace ID 到后端 +5. **告警配置**:对错误率和延迟设置告警 + +### 9.4 部署检查清单 + +- [ ] 配置文件语法验证 (`nginx -t`) +- [ ] 限流阈值合理性验证 +- [ ] SSL 证书有效性检查 +- [ ] 后端服务连通性测试 +- [ ] 认证流程端到端测试 +- [ ] 错误响应格式验证 +- [ ] 性能基准测试 +- [ ] 安全扫描(端口、Header 等) diff --git a/docs/29-nginx-dynamic-config.md b/docs/29-nginx-dynamic-config.md new file mode 100644 index 0000000..ff86469 --- /dev/null +++ b/docs/29-nginx-dynamic-config.md @@ -0,0 +1,1184 @@ +# NGINX 动态配置与服务发现指南 + +## 1. 动态配置概述 + +### 为什么需要动态配置 + +传统 NGINX 配置是静态的,修改配置需要重载甚至重启服务。在微服务架构和云原生环境中,这种静态模式面临挑战: + +- **服务实例频繁变更**:容器化部署中 Pod 动态扩缩容 +- **配置变更频繁**:路由规则、权重、限流策略需要实时调整 +- **零 downtime 要求**:传统 reload 会导致连接中断 +- **多环境管理**:开发、测试、生产环境配置快速切换 + +### 动态配置核心能力 + +| 能力 | 说明 | 适用场景 | +|------|------|----------| +| **动态 Upstream** | 运行时修改后端服务器列表 | 服务发现、蓝绿部署 | +| **动态 SSL** | 运行时加载/更新证书 | 多租户、自动化证书管理 | +| **动态路由** | 基于外部数据的路由决策 | A/B 测试、灰度发布 | +| **配置热重载** | 不中断服务的配置更新 | 日常配置变更 | + +### 动态配置方案对比 + +| 方案 | 实现方式 | 优点 | 缺点 | +|------|----------|------|------| +| **DNS 服务发现** | resolver + 域名解析 | 原生支持,无需模块 | TTL 延迟,无法精细控制 | +| **dyups 模块** | 通过 HTTP API 修改 upstream | 精确控制,即时生效 | 需要第三方模块 | +| **Lua 脚本** | OpenResty + Lua | 灵活性高 | 依赖 OpenResty | +| **NJS + 外部存储** | JavaScript + etcd/Consul | 官方支持,现代化 | 需要编写脚本逻辑 | +| **nginx-unit** | 动态应用服务器 | API 驱动,语言无关 | 架构不同,迁移成本高 | + +--- + +## 2. DNS 动态服务发现 + +### resolver 指令详解 + +NGINX 内置 DNS 解析支持,通过 `resolver` 实现基于域名的动态服务发现。 + +```nginx +http { + # 配置 DNS 服务器 + resolver 10.0.0.1 10.0.0.2 valid=300s ipv6=off; + resolver_timeout 5s; + + upstream backend { + # 使用域名,NGINX 会按 TTL 重新解析 + server api.example.com:8080 resolve; + server api-backup.example.com:8080 backup; + } +} +``` + +### resolver 参数说明 + +| 参数 | 说明 | 默认值 | +|------|------|--------| +| `address` | DNS 服务器地址,可配置多个 | - | +| `valid=time` | 覆盖 DNS 返回的 TTL | DNS TTL | +| `ipv6=on/off` | 是否解析 IPv6 地址 | on | +| `status_zone=zone` | 启用 DNS 查询统计 (Plus) | - | + +### server 的 resolve 参数 + +```nginx +upstream backend { + zone backend 64k; # 必需:共享内存区 + resolver 10.0.0.1 valid=10s; # 可选:独立的 resolver + + server api.example.com resolve; # 监控域名解析变化 + server 192.168.1.1; +} +``` + +**注意**:使用 `resolve` 需要: +1. 配置 `zone` 共享内存区 +2. NGINX Plus 或 1.11.3+ 商业版本/开源版本(某些功能受限) + +### 动态域名配置示例 + +```nginx +http { + resolver 8.8.8.8 8.8.4.4 valid=60s; + + server { + listen 80; + server_name ~^(?.+)\.example\.com$; + + location / { + # 动态目标地址 + proxy_pass http://$subdomain.internal; + proxy_set_header Host $host; + } + } +} +``` + +--- + +## 3. 使用 etcd/Consul 进行服务发现 + +### 架构设计 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ NGINX │────▶│ NJS/Consul │────▶│ Consul │ +│ │ │ Template │ │ /etcd │ +│ │ │ │ │ │ +│ proxy_pass │◀────│ upstream │◀────│ 服务注册中心 │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ + ▼ + ┌─────────────┐ + │ 微服务集群 │ + └─────────────┘ +``` + +### 方案一:NJS + Consul API + +使用 NJS 模块从 Consul 获取服务列表并动态路由。 + +```javascript +// consul.js - NJS 脚本 +function discover_backend(r) { + // Consul HTTP API 查询服务 + var service = r.variables.arg_service || 'web'; + var consul_url = 'http://consul:8500/v1/health/service/' + service; + + r.subrequest('/_consul_query', { + method: 'GET', + args: 'url=' + encodeURIComponent(consul_url) + }, function(reply) { + if (reply.status !== 200) { + r.return(502, 'Service discovery failed'); + return; + } + + var services = JSON.parse(reply.body); + if (services.length === 0) { + r.return(503, 'No healthy instances'); + return; + } + + // 选择第一个健康实例 + var instance = services[0].Service; + var target = instance.Address + ':' + instance.Port; + + // 内部重写到实际地址 + r.internalRedirect('/_proxy/' + target); + }); +} + +export default { discover_backend }; +``` + +```nginx +# nginx.conf +load_module modules/ngx_http_js_module.so; + +http { + js_import /etc/nginx/consul.js; + js_set $backend_target consul.discover_backend; + + # Consul 查询代理 + location /_consul_query { + internal; + proxy_pass $arg_url; + proxy_connect_timeout 2s; + proxy_read_timeout 2s; + } + + # 动态代理入口 + location /api/ { + js_content consul.discover_backend; + } + + # 实际代理位置 + location ~ ^/_proxy/(?.+)$ { + internal; + proxy_pass http://$addr; + proxy_set_header Host $host; + } +} +``` + +### 方案二:confd 模板渲染 + +confd 监听 etcd/Consul 变更,自动渲染 NGINX 配置并 reload。 + +```toml +# /etc/confd/conf.d/nginx.toml +[template] +src = "nginx.tmpl" +dest = "/etc/nginx/conf.d/upstreams.conf" +keys = [ + "/services/web/*", + "/services/api/*" +] +reload_cmd = "/usr/sbin/nginx -s reload" +``` + +``` +# /etc/confd/templates/nginx.tmpl +{{range $service := lsdir "/services"}} +upstream {{base $service}} { + {{$servers := getvs (printf "/services/%s/*" $service)}} + {{range $server := $servers}} + server {{$server}}; + {{end}} +} +{{end}} + +server { + listen 80; + {{range $service := lsdir "/services"}} + location /{{base $service}}/ { + proxy_pass http://{{base $service}}; + } + {{end}} +} +``` + +### 方案三:Consul Template + +HashiCorp 官方工具,专用于 Consul 集成。 + +```hcl +# template.ctmpl +{{range service "web"}} +server {{.Address}}:{{.Port}};{{end}} +``` + +```bash +# 启动 consul-template +consul-template \ + -consul-addr=consul:8500 \ + -template="template.ctmpl:/etc/nginx/conf.d/web.conf:/usr/sbin/nginx -s reload" +``` + +--- + +## 4. dyups 模块使用 + +### 模块简介 + +dyups(Dynamic Upstream)是淘宝开源的 NGINX 模块,提供 HTTP API 动态管理 upstream。 + +**功能特性**: +- 运行时添加、删除、修改 upstream +- 无需 reload 即可更新后端服务器 +- 支持查看当前 upstream 状态 + +### 安装编译 + +```bash +# 下载模块 +git clone https://github.com/yzprofile/ngx_http_dyups_module.git + +# 编译 NGINX 时添加模块 +cd nginx-1.24.0 +./configure \ + --add-module=/path/to/ngx_http_dyups_module \ + --with-http_ssl_module +make && make install +``` + +### 基础配置 + +```nginx +http { + # 加载 dyups 模块 + dyups_shm_zone_size 10m; # 共享内存大小 + + # dyups API 接口(需要限制访问) + server { + listen 127.0.0.1:8081; + server_name dyups_admin; + + location / { + dyups_interface; # 启用 dyups 接口 + } + } + + # 初始 upstream 定义(可为空) + upstream backend { + server 127.0.0.1:8080; # 占位服务器 + } + + server { + listen 80; + location / { + proxy_pass http://backend; + } + } +} +``` + +### HTTP API 详解 + +#### 更新/添加 Upstream + +```bash +# 更新整个 upstream(替换原有配置) +curl -X POST \ + -H "Content-Type: text/plain" \ + -d "server 192.168.1.1:8080 weight=5; +server 192.168.1.2:8080; +server 192.168.1.3:8080 backup;" \ + http://127.0.0.1:8081/upstream/backend +``` + +#### 删除 Upstream + +```bash +# 删除指定 upstream +curl -X DELETE http://127.0.0.1:8081/upstream/backend +``` + +#### 查询 Upstream 状态 + +```bash +# 获取所有 upstream 列表 +curl http://127.0.0.1:8081/list + +# 获取特定 upstream 详情 +curl http://127.0.0.1:8081/detail +``` + +**返回示例**: +```json +{ + "backend": [ + { + "server": "192.168.1.1:8080", + "weight": 5, + "max_fails": 1, + "fail_timeout": "10s" + }, + { + "server": "192.168.1.2:8080", + "weight": 1, + "backup": true + } + ] +} +``` + +### 完整动态配置示例 + +```nginx +http { + dyups_shm_zone_size 20m; + + # 管理接口(严格限制访问) + server { + listen 127.0.0.1:8081; + + # 只允许本地访问 + allow 127.0.0.1; + deny all; + + location / { + dyups_interface; + } + } + + # 对外服务 + server { + listen 80; + server_name api.example.com; + + location / { + proxy_pass http://dynamic_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + + # 连接超时设置 + proxy_connect_timeout 5s; + proxy_send_timeout 10s; + proxy_read_timeout 30s; + } + } +} +``` + +```bash +# 服务注册脚本 +#!/bin/bash + +UPSTREAM_NAME="dynamic_backend" +ADMIN_URL="http://127.0.0.1:8081" + +# 从服务发现获取实例列表 +SERVERS=$(curl -s http://consul:8500/v1/health/service/web | \ + jq -r '.[].Service | "server \(.Address):\(.Port);"') + +# 更新 NGINX upstream +curl -X POST \ + -H "Content-Type: text/plain" \ + -d "$SERVERS" \ + "$ADMIN_URL/upstream/$UPSTREAM_NAME" + +echo "Upstream updated: $UPSTREAM_NAME" +``` + +--- + +## 5. nginx-unit 简介 + +### 什么是 nginx-unit + +nginx-unit 是 NGINX 推出的动态应用服务器,专为微服务架构设计,支持通过 API 动态配置应用部署。 + +**核心特性**: + +| 特性 | 说明 | +|------|------| +| **动态配置** | 通过 REST API 实时配置,无需重启 | +| **多语言支持** | Go、Python、PHP、Perl、Ruby、Node.js、Java | +| **语言无关路由** | 统一的路由配置,与应用语言解耦 | +| **零停机更新** | 平滑的应用版本切换 | +| **静态文件服务** | 内置高效的静态资源服务 | + +### 架构对比 + +``` +传统 NGINX: nginx-unit: +┌──────────┐ ┌─────────────────┐ +│ nginx │──▶ php-fpm │ unit │ +│ │ │ ┌───────────┐ │ +│ proxy │──▶ uwsgi │ │ router │ │ +│ │ │ └─────┬─────┘ │ +│ static │◀── files │ ┌─────┴─────┐ │ +└──────────┘ │ │ lang │ │ + │ │ modules │ │ + │ │ ┌─┬─┬─┐ │ │ + │ │ │ │ │ │ │ │ + │ │ └─┴─┴─┘ │ │ + │ └───────────┘ │ + └─────────────────┘ +``` + +### 安装与启动 + +```bash +# macOS +brew install nginx-unit + +# Ubuntu/Debian +curl -X PUT --data-binary @unit.deb http://nginx.org/... +sudo dpkg -i unit.deb + +# 启动服务 +sudo unitd --log /var/log/unit.log +``` + +### 核心 API + +#### 配置监听器 + +```bash +# 创建 HTTP 监听器 +curl -X PUT http://localhost:8000/config/listeners/127.0.0.1:80 \ + -d '{"pass": "routes"}' +``` + +#### 配置应用 + +```bash +# 配置 PHP 应用 +curl -X PUT http://localhost:8000/config/applications/php_app \ + -d '{ + "type": "php", + "root": "/var/www/php-app", + "script": "index.php", + "processes": { + "max": 20, + "spare": 5 + } + }' + +# 配置 Python (ASGI) 应用 +curl -X PUT http://localhost:8000/config/applications/python_app \ + -d '{ + "type": "python", + "path": "/var/www/python-app", + "module": "wsgi", + "callable": "app" + }' +``` + +#### 配置路由 + +```bash +# 配置路由规则 +curl -X PUT http://localhost:8000/config/routes \ + -d '[ + { + "match": {"uri": "/api/*"}, + "action": {"pass": "applications/python_app"} + }, + { + "match": {"uri": "*.php"}, + "action": {"pass": "applications/php_app"} + }, + { + "action": {"share": "/var/www/static"} + } + ]' +``` + +### 完整配置示例 + +```json +{ + "listeners": { + "*:80": { + "pass": "routes/main" + }, + "*:443": { + "pass": "routes/main", + "tls": { + "certificate": "bundle" + } + } + }, + + "routes": { + "main": [ + { + "match": {"host": "api.example.com"}, + "action": {"pass": "applications/api"} + }, + { + "match": {"uri": "/admin/*"}, + "action": {"pass": "applications/admin"} + }, + { + "match": {"uri": ["*.jpg", "*.png", "*.css", "*.js"]}, + "action": {"share": "/var/www/static"} + }, + { + "action": {"pass": "applications/frontend"} + } + ] + }, + + "applications": { + "api": { + "type": "python", + "path": "/var/www/api", + "module": "app", + "callable": "application", + "processes": 10 + }, + "admin": { + "type": "php", + "root": "/var/www/admin", + "script": "index.php", + "processes": {"max": 10, "spare": 3} + }, + "frontend": { + "type": "node", + "working_directory": "/var/www/frontend", + "executable": "server.js", + "processes": 4 + } + }, + + "settings": { + "http": { + "header_read_timeout": 30, + "body_read_timeout": 30, + "send_timeout": 30, + "idle_timeout": 180 + } + } +} +``` + +### NGINX 与 nginx-unit 集成 + +```nginx +# NGINX 作为反向代理,unit 运行动态应用 +upstream unit_backend { + server 127.0.0.1:8000; + keepalive 32; +} + +server { + listen 80; + server_name app.example.com; + + location / { + proxy_pass http://unit_backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + } +} +``` + +--- + +## 6. 动态 SSL 证书加载 + +### SSL 证书管理挑战 + +- 多租户 SaaS:每个租户需要独立证书 +- 自动化证书续期:Let's Encrypt 等需要动态更新 +- 证书数量大:数万证书无法全部预加载 + +### 方案一:变量驱动 SSL 证书 + +```nginx +http { + # 证书映射 + map $ssl_server_name $ssl_cert { + default /etc/nginx/certs/default.crt; + app1.example.com /etc/nginx/certs/app1.crt; + app2.example.com /etc/nginx/certs/app2.crt; + } + + map $ssl_server_name $ssl_key { + default /etc/nginx/certs/default.key; + app1.example.com /etc/nginx/certs/app1.key; + app2.example.com /etc/nginx/certs/app2.key; + } + + server { + listen 443 ssl; + server_name app1.example.com app2.example.com; + + ssl_certificate $ssl_cert; + ssl_certificate_key $ssl_key; + + location / { + proxy_pass http://backend; + } + } +} +``` + +**限制**:标准 NGINX 不支持变量形式的 `ssl_certificate`。 + +### 方案二:OpenResty Lua 方案 + +```lua +-- ssl_certificate.lua +local ssl = require "ngx.ssl" +local cert_cache = require "resty.lrucache" + +local function load_certificate(domain) + -- 从 Redis/Consul 获取证书 + local cert_data = get_cert_from_storage(domain) + + if not cert_data then + return nil, "certificate not found" + end + + local ok, err = ssl.clear_certs() + if not ok then + return nil, "failed to clear certs: " .. err + end + + local ok, err = ssl.set_der_cert(cert_data.cert) + if not ok then + return nil, "failed to set cert: " .. err + end + + local ok, err = ssl.set_der_priv_key(cert_data.key) + if not ok then + return nil, "failed to set key: " .. err + end + + return true +end + +-- 在 ssl_certificate_by_lua_block 中调用 +local domain = ssl.server_name() +load_certificate(domain) +``` + +```nginx +server { + listen 443 ssl; + + ssl_certificate_by_lua_file /etc/nginx/ssl_certificate.lua; + + # 占位证书(首次连接需要) + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; +} +``` + +### 方案三:NGINX Plus 动态证书 + +NGINX Plus 支持 `ssl_certificate` 和 `ssl_certificate_key` 使用变量: + +```nginx +# NGINX Plus 配置 +server { + listen 443 ssl; + server_name ~^(?.+)$; + + ssl_certificate /etc/nginx/certs/$domain.crt; + ssl_certificate_key /etc/nginx/certs/$domain.key; + + # 证书数据存储在共享内存 + ssl_session_cache shared:SSL:10m; +} +``` + +### 方案四:密钥管理存储(KMS)集成 + +```nginx +# 使用 NJS 从外部 KMS 获取证书 +js_import /etc/nginx/ssl_manager.js; + +server { + listen 443 ssl; + + ssl_certificate /tmp/dynamic.crt; + ssl_certificate_key /tmp/dynamic.key; + + # 定期更新证书 + location /_ssl_update { + internal; + js_content ssl_manager.update_cert; + } +} +``` + +--- + +## 7. 配置热重载策略 + +### reload 机制详解 + +``` +进程变化流程: + +时间线 ──────────────────────────────────────────────▶ + +Master ├─────────┬─────────┬─────────┬──────────┤ + │ │ │ │ +Worker-1 ├─────────┴────┬────┴────────┤ │ + graceful stop +Worker-2 ├───────────────┴─────────┤ + new worker +``` + +### 优雅重载配置 + +```bash +#!/bin/bash +# safe-reload.sh - 安全重载脚本 + +NGINX_BIN="/usr/local/nginx/sbin/nginx" +CONFIG_FILE="/etc/nginx/nginx.conf" + +# 1. 测试配置有效性 +echo "Testing configuration..." +$NGINX_BIN -t -c $CONFIG_FILE +if [ $? -ne 0 ]; then + echo "Configuration test failed! Aborting reload." + exit 1 +fi + +# 2. 优雅重载 +echo "Reloading NGINX..." +$NGINX_BIN -s reload + +# 3. 验证重载成功 +sleep 1 +NEW_PID=$(cat /var/run/nginx.pid) +echo "New master PID: $NEW_PID" + +# 4. 检查 worker 进程 +WORKER_COUNT=$(ps aux | grep "nginx: worker" | grep -v grep | wc -l) +echo "Active workers: $WORKER_COUNT" +``` + +### 配置版本管理 + +```nginx +http { + # 在响应头中暴露配置版本 + add_header X-Config-Version "v2.3.1" always; + + # 或者使用变量(NJS 设置) + js_set $config_version get_config_version; + add_header X-Config-Version $config_version always; +} +``` + +```javascript +// config_version.js +var configVersion = "2.3.1"; +var configTimestamp = Date.now(); + +function get_config_version(r) { + return configVersion + "-" + configTimestamp; +} + +export default { get_config_version }; +``` + +### 金丝雀重载策略 + +```bash +#!/bin/bash +# canary-reload.sh - 金丝雀重载 + +# 步骤1:启动测试实例 +echo "Starting canary instance..." +nginx -c /etc/nginx/nginx.conf \ + -p /var/run/nginx-canary \ + -g "pid /var/run/nginx-canary.pid;" + +# 步骤2:健康检查 +sleep 2 +if ! curl -f http://localhost:8080/health; then + echo "Canary health check failed!" + kill $(cat /var/run/nginx-canary.pid) + exit 1 +fi + +# 步骤3:切换流量(通过负载均衡器或 DNS) +echo "Switching traffic to canary..." + +# 步骤4:观察一段时间后正式重载主实例 +sleep 60 +nginx -s reload + +# 步骤5:停止金丝雀实例 +kill $(cat /var/run/nginx-canary.pid) +``` + +### 自动回滚机制 + +```python +# reload_monitor.py +import subprocess +import time +import requests + +def reload_nginx(): + """执行 NGINX 重载并监控状态""" + + # 记录重载前状态 + before_metrics = collect_metrics() + + # 执行重载 + result = subprocess.run(['nginx', '-s', 'reload'], capture_output=True) + + if result.returncode != 0: + print("Reload failed:", result.stderr) + return False + + # 监控窗口期 + time.sleep(5) + + # 检查关键指标 + after_metrics = collect_metrics() + + if after_metrics['error_rate'] > before_metrics['error_rate'] * 2: + print("Error rate increased! Rolling back...") + rollback() + return False + + if after_metrics['5xx_count'] > 100: + print("5xx errors detected! Rolling back...") + rollback() + return False + + return True + +def rollback(): + """回滚到上一个配置版本""" + subprocess.run(['cp', '/etc/nginx/nginx.conf.backup', + '/etc/nginx/nginx.conf']) + subprocess.run(['nginx', '-s', 'reload']) + +def collect_metrics(): + """收集性能指标""" + # 实现指标收集逻辑 + pass +``` + +--- + +## 8. 完整动态配置示例 + +### 微服务网关配置 + +```nginx +# /etc/nginx/nginx.conf +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +load_module modules/ngx_http_js_module.so; + +events { + worker_connections 4096; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'upstream=$upstream_addr ' + 'config=$config_version'; + + access_log /var/log/nginx/access.log main; + + # 动态配置模块 + js_import /etc/nginx/dynamic_config.js; + js_set $config_version dynamic_config.get_version; + js_set $backend dynamic_config.resolve_backend; + + # 上游状态共享内存 + upstream_zone upstreams 64m; + + # === 动态 Upstream 管理 === + # 使用 dyups 或 Consul 模板生成 + include /etc/nginx/conf.d/upstreams/*.conf; + + # === 服务发现 API === + server { + listen 127.0.0.1:8081; + location / { + # dyups 管理接口 + dyups_interface; + + # 或自定义 NJS 接口 + # js_content dynamic_config.admin_api; + } + } + + # === 主网关服务 === + server { + listen 80; + listen 443 ssl http2; + server_name gateway.example.com; + + # 动态 SSL(NGINX Plus) + # ssl_certificate /etc/nginx/certs/$ssl_server_name.crt; + # ssl_certificate_key /etc/nginx/certs/$ssl_server_name.key; + + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Config-Version $config_version always; + + # 限流 + limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s; + + # 健康检查 + location /health { + access_log off; + return 200 "healthy\n"; + } + + # API 路由(动态后端解析) + location /api/ { + limit_req zone=api burst=200 nodelay; + + proxy_pass http://$backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_connect_timeout 5s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + proxy_next_upstream error timeout http_502 http_503; + } + + # 静态内容 + location / { + root /var/www/static; + try_files $uri $uri/ /index.html; + expires 1h; + } + } +} +``` + +```javascript +// /etc/nginx/dynamic_config.js - NJS 动态配置脚本 +var version = "2.0.0"; +var backends = { + "api": "api_upstream", + "user": "user_service_upstream", + "order": "order_service_upstream" +}; + +function get_version(r) { + return version; +} + +function resolve_backend(r) { + var service = r.variables.arg_service || "api"; + var upstream = backends[service]; + + if (!upstream) { + r.error("Unknown service: " + service); + return "fallback_upstream"; + } + + return upstream; +} + +// 从 Consul/ETCD 刷新配置 +function refresh_from_consul(r) { + r.subrequest('/_consul/services', { + method: 'GET' + }, function(reply) { + if (reply.status == 200) { + var services = JSON.parse(reply.body); + // 更新后端映射 + // 实际实现需要原子更新 + version = Date.now().toString(); + r.return(200, "Config refreshed to " + version); + } else { + r.return(502, "Failed to fetch from Consul"); + } + }); +} + +export default { + get_version, + resolve_backend, + refresh_from_consul +}; +``` + +### 动态配置管理脚本 + +```bash +#!/bin/bash +# /usr/local/bin/nginx-dynamic-manager + +CONFIG_DIR="/etc/nginx/conf.d/upstreams" +ADMIN_URL="http://127.0.0.1:8081" +CONSUL_URL="http://consul:8500" + +# 从 Consul 同步 upstream +sync_from_consul() { + local service=$1 + + # 查询健康实例 + local instances=$(curl -s "$CONSUL_URL/v1/health/service/$service" | \ + jq -r '.[] | select(.Checks[].Status == "passing") | + "server \(.Service.Address):\(.Service.Port) weight=1;"') + + if [ -z "$instances" ]; then + echo "No healthy instances for $service" + return 1 + fi + + # 更新 dyups + curl -X POST \ + -H "Content-Type: text/plain" \ + -d "$instances" \ + "$ADMIN_URL/upstream/${service}_upstream" + + echo "Updated $service upstream with:" + echo "$instances" +} + +# 批量更新所有服务 +sync_all() { + local services=$(curl -s "$CONSUL_URL/v1/catalog/services" | jq -r 'keys[]') + + for service in $services; do + sync_from_consul $service + done +} + +# 查看当前 upstream 状态 +status() { + curl -s "$ADMIN_URL/detail" | jq . +} + +# 主入口 +case "$1" in + sync) + if [ -n "$2" ]; then + sync_from_consul "$2" + else + sync_all + fi + ;; + status) + status + ;; + *) + echo "Usage: $0 {sync [service]|status}" + exit 1 + ;; +esac +``` + +### 配置自动同步服务 + +```systemd +# /etc/systemd/system/nginx-consul-sync.service +[Unit] +Description=NGINX Consul Sync +After=network.target nginx.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/consul-template \ + -consul-addr=consul:8500 \ + -template="/etc/consul-templates/nginx-upstreams.ctmpl:$CONFIG_DIR/upstreams.conf:/usr/local/bin/nginx-dynamic-manager sync" +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +```bash +# /etc/consul-templates/nginx-upstreams.ctmpl +{{range service "api"}} +server {{.Address}}:{{.Port}} weight={{.Weights.Passing}} max_fails=3 fail_timeout=30s; +{{end}} +``` + +--- + +## 9. 监控与运维 + +### 动态配置监控指标 + +| 指标 | 采集方式 | 告警阈值 | +|------|----------|----------| +| upstream 变更次数 | access_log 分析 | 突变检测 | +| 服务发现延迟 | 自定义 metrics | > 5s | +| 证书过期时间 | 定时检查 | < 7 天 | +| reload 失败次数 | 脚本监控 | > 0 | + +### 关键日志字段 + +```nginx +log_format dynamic '$remote_addr [$time_local] ' + 'svc=$service ' + 'upstream=$upstream_addr ' + 'ups_resp_time=$upstream_response_time ' + 'cfg_ver=$config_version ' + 'discover_latency=$discover_time'; +``` + +--- + +## 总结 + +NGINX 动态配置能力从简单的 DNS 解析到完整的 API 驱动配置,为现代云原生架构提供了灵活的解决方案: + +| 场景 | 推荐方案 | 复杂度 | +|------|----------|--------| +| 简单服务发现 | DNS resolver | 低 | +| 动态 upstream 管理 | dyups 模块 | 中 | +| 多语言微服务 | nginx-unit | 中 | +| 复杂路由逻辑 | OpenResty/NJS | 高 | +| 企业级服务网格 | Consul + NGINX Plus | 高 | + +选择方案时需权衡功能需求、运维复杂度和团队技术栈,从简单方案开始逐步演进。 diff --git a/docs/README.md b/docs/README.md index e37b1ef..1c694df 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,10 @@ | 23 | [特殊功能模块](./23-nginx-special-modules.md) | WebDAV/图像过滤/FLV/MP4/HLS 流媒体/XSLT 转换 | | 24 | [核心与事件模块](./24-nginx-core-events.md) | worker_processes/events/epoll/kqueue/连接数计算 | | 25 | [内置变量速查表](./25-nginx-variables-reference.md) | HTTP/Stream/SSL/Upstream 变量完整列表(150+个) | +| 26 | [Lua 模块深度指南](./26-nginx-lua-guide.md) | OpenResty、ngx_lua 指令、共享字典、cosocket API | +| 27 | [安全深度指南](./27-nginx-security-deep-dive.md) | WAF/ModSecurity、DDoS 防护、OWASP Top 10、安全头部 | +| 28 | [API 网关配置](./28-nginx-api-gateway.md) | API 路由设计、JWT 验证、限流配额、版本控制 | +| 29 | [动态配置与服务发现](./29-nginx-dynamic-config.md) | 动态 upstream、etcd/Consul、dyups、nginx-unit | --- @@ -63,6 +67,15 @@ ### 扩展与第三方 - [第三方扩展模块](./22-nginx-third-party-modules.md) - NJS, Lua, Brotli, RTMP 等 +- [Lua 模块深度指南](./26-nginx-lua-guide.md) - OpenResty、ngx_lua、cosocket + +### 安全深度 +- [安全与访问控制](./09-nginx-security.md) - 综合安全配置 +- [安全深度指南](./27-nginx-security-deep-dive.md) - WAF、DDoS、OWASP + +### API 与动态配置 +- [API 网关配置](./28-nginx-api-gateway.md) - API 路由、JWT、限流配额 +- [动态配置与服务发现](./29-nginx-dynamic-config.md) - 动态 upstream、etcd/Consul ### 参考手册 - [内置变量速查表](./25-nginx-variables-reference.md) - 150+ 个变量完整列表 diff --git a/docs/nginx-docs-analysis.md b/docs/nginx-docs-analysis.md new file mode 100644 index 0000000..beddf21 --- /dev/null +++ b/docs/nginx-docs-analysis.md @@ -0,0 +1,288 @@ +# NGINX 文档完善建议 + +基于对 nginx.org 官方文档的深度分析,对比 docs/ 目录下现有的 25 个文档,识别出以下可完善的部分。 + +--- + +## 一、缺失或需要新增的文档 + +### 1. NGINX Lua 模块深度指南 +**优先级:高** + +现有 `22-nginx-third-party-modules.md` 对 NJS/Lua 有简要介绍,但 Lua 模块功能强大,值得独立文档。 + +**建议内容:** +- OpenResty 环境搭建 +- ngx_lua 核心指令(content_by_lua、access_by_lua、rewrite_by_lua) +- Lua 共享字典(ngx.shared.DICT) +- cosocket API(非阻塞网络 I/O) +- 与 Redis/MySQL 集成 +- 性能优化技巧 + +### 2. NGINX 作为 API 网关 +**优先级:中** + +现代架构中 nginx 常作为 API 网关使用。 + +**建议内容:** +- API 路由设计模式 +- 请求/响应转换 +- JWT 验证(通过 Lua 或 NJS) +- 限流与配额管理 +- API 版本控制策略 +- OpenAPI/Swagger 集成 + +### 3. NGINX 动态配置 +**优先级:中** + +现代部署需要动态配置能力。 + +**建议内容:** +- 动态 upstream(nginx-plus 或开源方案) +- 使用 etcd/Consul 进行服务发现 +- dyups 模块使用 +- nginx-unit 简介 +- 动态 SSL 证书加载 + +### 4. NGINX 安全最佳实践(增强版) +**优先级:高** + +现有 `09-nginx-security.md` 内容良好,可扩展: + +**建议补充:** +- Bot 检测与防护 +- WAF 配置深度指南(ModSecurity) +- DDoS 防护策略 +- OWASP Top 10 防护 +- 安全响应头完整配置 +- CVE 历史漏洞与修复版本 + +--- + +## 二、现有文档可扩展的内容 + +### 15-nginx-advanced-features.md +**当前:** 仅 94 行,内容相对简略 +**建议扩展:** + +1. **调试与诊断** + - debug 日志级别 + - debug_points 指令 + - worker_debug_connection + +2. **错误处理** + - error_page 高级用法 + - 自定义错误页面 + - try_files 与 error_page 配合 + +3. **请求拦截** + - post_action 指令 + - 日志记录后操作 + +### 16-nginx-internal-redirect.md +**当前:** 119 行,内容较好 +**建议扩展:** + +1. **SSI(服务端包含)详解** + - SSI 指令列表 + - 虚拟包含 vs 文件包含 + - 条件执行 + +2. **命名 location 深度解析** + - 命名 location 语法 + - 与 error_page 配合 + - 重定向链追踪 + +### 17-nginx-mirror-slice.md +**当前:** 143 行,内容较好 +**建议扩展:** + +1. **高级镜像场景** + - 条件镜像(基于请求头、路径) + - 镜像流量采样 + - 镜像目标选择策略 + +2. **slice 与缓存高级配置** + - 多级缓存配置 + - 缓存预热策略 + - 缓存失效策略 + +### 19-nginx-http-modules-detail.md +**当前:** 约 1200 行,内容丰富 +**建议补充:** + +1. **ngx_http_addition_module** + - before/after 内容追加 + - 与 SSI 配合使用 + +2. **ngx_http_sub_module 高级用法** + - 多规则替换 + - 正则替换 + - 变量替换 + +--- + +## 三、新增文档建议清单 + +| 序号 | 文档名称 | 优先级 | 预计行数 | +|------|----------|--------|----------| +| 26 | nginx-lua-guide.md | 高 | 800+ | +| 27 | nginx-api-gateway.md | 中 | 600+ | +| 28 | nginx-dynamic-config.md | 中 | 500+ | +| 29 | nginx-security-deep-dive.md | 高 | 700+ | +| 30 | nginx-troubleshooting.md | 中 | 400+ | +| 31 | nginx-observability.md | 中 | 500+ | + +--- + +## 四、文档质量建议 + +### 统一格式 +- 所有文档使用统一的标题层级 +- 配置示例添加语法高亮标记 +- 表格格式统一 + +### 交叉引用 +- 添加相关文档链接 +- 引用官方文档链接 + +### 版本标注 +- 功能版本要求标注 +- 已废弃功能标注 + +--- + +## 五、NGINX HTTP 模块深度分析(Agent 分析结果) + +### 5.1 HTTP 模块完整列表(按类别) + +#### 核心模块 +| 模块名称 | 功能描述 | 依赖关系 | +|----------|----------|----------| +| `ngx_http_core_module` | HTTP 核心功能 | 无(必需) | +| `ngx_http_log_module` | 访问日志记录 | core | +| `ngx_http_upstream_module` | 上游服务器负载均衡 | core | + +#### 请求处理与路由模块 +| 模块名称 | 功能描述 | 依赖关系 | +|----------|----------|----------| +| `ngx_http_rewrite_module` | URI 重写(支持正则) | core | +| `ngx_http_proxy_module` | 反向代理 | upstream | +| `ngx_http_fastcgi_module` | FastCGI 协议代理 | upstream | +| `ngx_http_uwsgi_module` | uWSGI 协议代理 | upstream | +| `ngx_http_scgi_module` | SCGI 协议代理 | upstream | + +#### 安全与访问控制模块 +| 模块名称 | 功能描述 | 依赖关系 | +|----------|----------|----------| +| `ngx_http_access_module` | IP 访问控制 | core | +| `ngx_http_auth_basic_module` | HTTP 基本认证 | core | +| `ngx_http_auth_request_module` | 子请求认证 | proxy | +| `ngx_http_ssl_module` | SSL/TLS 支持 | core | +| `ngx_http_limit_req_module` | 请求速率限制 | core | +| `ngx_http_limit_conn_module` | 连接数限制 | core | +| `ngx_http_realip_module` | 真实 IP 替换 | core | + +#### 压缩与优化模块 +| 模块名称 | 功能描述 | 依赖关系 | +|----------|----------|----------| +| `ngx_http_gzip_module` | GZIP 压缩 | core | +| `ngx_http_gunzip_module` | GZIP 解压 | gzip | +| `ngx_http_headers_module` | 响应头处理 | core | + +#### 上游负载均衡算法模块 +| 模块名称 | 功能描述 | 依赖关系 | +|----------|----------|----------| +| `ngx_http_upstream_hash_module` | 一致性哈希负载均衡 | upstream | +| `ngx_http_upstream_ip_hash_module` | IP 哈希负载均衡 | upstream | +| `ngx_http_upstream_least_conn_module` | 最少连接负载均衡 | upstream | +| `ngx_http_upstream_keepalive_module` | 上游 keepalive 连接 | upstream | + +### 5.2 最常用的 15 个指令 + +| 排名 | 指令 | 用途 | +|------|------|------| +| 1 | `listen` | 端口监听 | +| 2 | `server_name` | 虚拟主机 | +| 3 | `location` | 路由匹配 | +| 4 | `root` | 根目录 | +| 5 | `proxy_pass` | 代理目标 | +| 6 | `try_files` | 文件尝试 | +| 7 | `rewrite` | URL 重写 | +| 8 | `return` | 返回响应 | +| 9 | `index` | 索引文件 | +| 10 | `error_page` | 错误页面 | +| 11 | `client_max_body_size` | 上传限制 | +| 12 | `keepalive_timeout` | 连接保持 | +| 13 | `gzip` | 压缩开关 | +| 14 | `ssl_certificate` | SSL 证书 | +| 15 | `access_log` | 访问日志 | + +--- + +## 六、NGINX Stream 模块深度分析(Agent 分析结果) + +### 6.1 Stream 核心模块指令 + +| 指令 | 语法 | 上下文 | 说明 | +|------|------|--------|------| +| `stream` | `stream { ... }` | main | 定义 stream 配置块 | +| `server` | `server { ... }` | stream | 定义虚拟服务器 | +| `listen` | `listen address:port [options]` | server | 监听端口配置 | +| `preread_buffer_size` | `preread_buffer_size size` | stream, server | 预读取缓冲区大小 | +| `preread_timeout` | `preread_timeout timeout` | stream, server | 预读取超时时间 | + +### 6.2 Stream 子模块完整列表 + +| 模块名称 | 功能描述 | +|----------|----------| +| **核心模块** | | +| ngx_stream_core_module | Stream 核心功能 | +| **代理模块** | | +| ngx_stream_proxy_module | TCP/UDP 代理转发 | +| ngx_stream_ssl_module | SSL/TLS 支持 | +| ngx_stream_ssl_preread_module | SSL 预读取(SNI 路由) | +| **上游模块** | | +| ngx_stream_upstream_module | 上游服务器管理 | +| ngx_stream_hash_module | 一致性哈希负载均衡 | +| ngx_stream_least_conn_module | 最少连接负载均衡 | +| ngx_stream_random_module | 随机负载均衡 | +| **访问控制** | | +| ngx_stream_access_module | 允许/拒绝访问控制 | +| ngx_stream_limit_conn_module | 连接数限制 | +| ngx_stream_geo_module | 基于 IP 的地理位置变量 | +| ngx_stream_geoip_module | GeoIP 数据库支持 | +| **日志与监控** | | +| ngx_stream_log_module | 日志记录 | +| ngx_stream_return_module | 返回指定值并关闭连接 | + +### 6.3 现有文档 10-nginx-stream-tcp-udp.md 对比 + +现有文档已覆盖: +- ✅ TCP/UDP 代理基础配置 +- ✅ 负载均衡算法 +- ✅ SSL 终止 +- ✅ PROXY 协议 +- ✅ 速率限制 + +建议补充: +- 🔧 健康检查详细配置(active health check) +- 🔧 stream 日志格式自定义 +- 🔧 UDP 响应数配置(proxy_responses) +- 🔧 SSL preread 模块(SNI 路由) + +--- + +## 七、与 Lolly 项目的关系 + +基于 docs/plan.md,Lolly 是一个类似 nginx 的 Go 实现。文档完善有助于: + +1. **功能对齐**:识别 nginx 功能,作为 Lolly 开发参考 +2. **配置翻译**:nginx 配置 → YAML 配置设计参考 +3. **测试用例**:文档中的配置示例可作为测试用例 + +建议在 Lolly 开发过程中,同步更新 nginx 文档作为功能对照。 + +--- + +*生成时间:2026-04-03* \ No newline at end of file