将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录, 提高文档组织性和可维护性。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1185 lines
28 KiB
Markdown
1185 lines
28 KiB
Markdown
# 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 ~^(?<subdomain>.+)\.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/(?<addr>.+)$ {
|
||
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 ~^(?<domain>.+)$;
|
||
|
||
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 | 高 |
|
||
|
||
选择方案时需权衡功能需求、运维复杂度和团队技术栈,从简单方案开始逐步演进。
|