跳到主要内容

部署与高可用

前面几篇都是在单机上玩配置,这一篇解决「上生产」的问题:机器怎么初始化、权限怎么给、日志怎么转、系统参数怎么调;单机扛不住时怎么用多实例 + keepalived 做 VIP 高可用;配置怎么用 Git 管起来、怎么回滚;以及不重启服务换二进制(平滑升级)的完整流程。示例环境 Ubuntu 22.04 + nginx 1.24。

生产部署 checklist​

上生产前按这个清单过一遍,能挡掉一半的低级事故。

运行用户与目录权限​

user  www-data www-data;
worker_processes auto;
  • worker 用 www-data 跑,不要用 root。master 需要 root 来绑 80/443,这是 Nginx 的标准做法。
  • 静态目录权限:目录 755、文件 644,属主 www-data。给成 777 是给自己埋雷。
  • 证书私钥 600、属主 root,只要 worker 能读就行;位于 /etc/nginx/certs 时注意目录本身至少 755。
  • 日志目录 /var/log/nginx 属主 www-data,否则 reload 后新开的日志文件写不进去,error.log 会刷 Permission denied。

日志轮转​

包安装自带 /etc/logrotate.d/nginx,源码编译的要自己加。确认配置里有 postrotate 发 USR1:

/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}

USR1 让 Nginx 重新打开日志文件。别用 copytruncate——大流量下会丢日志,而且 Nginx 持有的是旧 inode。细节和验证方法在第 5 篇。

系统调优​

# 1. 文件描述符上限,systemd 托管的服务要写在 unit 里(覆盖 override.conf)
sudo mkdir -p /etc/systemd/system/nginx.service.d
sudo tee /etc/systemd/system/nginx.service.d/override.conf <<'EOF'
[Service]
LimitNOFILE=65535
EOF
sudo systemctl daemon-reload

# 2. 内核网络参数
sudo tee -a /etc/sysctl.d/99-nginx.conf <<'EOF'
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
fs.file-max = 1000000
EOF
sudo sysctl --system

配套的 Nginx 侧配置:

worker_rlimit_nofile 65535;

events {
worker_connections 10240;
multi_accept on;
}

http {
# 与 somaxconn 对齐,否则内核队列白白浪费
backlog 65535;

keepalive_timeout 65;
keepalive_requests 1000;
server_tokens off; # 不暴露版本号
client_max_body_size 20m;
client_header_timeout 15s;
client_body_timeout 15s;
send_timeout 15s;
}

proxy_cache_path / proxy_temp_path 用的磁盘目录也要单独挂盘,别让缓存把根分区写满。

上线前最后一遍​

  • nginx -T 输出里没有测试环境的残留配置(比如 listen 8080 的调试站点)。
  • server_tokens off、默认站点已删除或改成 444。
  • 证书有效期足够长,续期脚本有 cron。
  • curl -I 打一遍 HTTPS,确认 HSTS、跳转链路都对。
  • 灰度前先压测一轮,wrk -t4 -c400 -d60s 看 QPS 和错误率。
默认 server 挡扫描

Nginx 会把第一个 server 当默认主机,任何不认识域名的请求都落到它身上。生产上显式加一个兜底,直接断开未匹配的请求:

server {
listen 80 default_server;
server_name _;
return 444;
}

多实例 + keepalived 高可用​

单机 Nginx 再稳也是单点。最常见的开源高可用方案是两台机器各跑一个 Nginx,前面用 keepalived 共用一个 VIP,VIP 漂到哪台,流量就走哪台。

                    用户
│
VIP 10.0.0.100
┌──────┴──────┐
│ │
┌───────▼──────┐ ┌────▼─────────┐
│ node1 (MASTER)│ │ node2 (BACKUP)│
│ nginx 1.24 │ │ nginx 1.24 │
│ keepalived │ │ keepalived │
└───────┬───────┘ └──────┬────────┘
└──────┬─────────┘
▼
upstream 后端集群

两台都装 Nginx​

两台机器配置完全一致(用同一份 Git 仓库下发,下一节讲),各自监听 80/443 和后端的 upstream 不变。

keepalived 配置​

sudo apt install -y keepalived

主节点 /etc/keepalived/keepalived.conf:

global_defs {
router_id nginx_node1
script_user root
enable_script_security
}

# 健康检查脚本:Nginx 不通就降优先级
vrrp_script chk_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -30
fall 3
rise 2
}

vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1

authentication {
auth_type PASS
auth_pass nginx_ha_2024
}

virtual_ipaddress {
10.0.0.100/24 dev eth0
}

track_script {
chk_nginx
}
}

备节点只有三处不同:router_id 换成 nginx_node2、state BACKUP、priority 90(数值要比主节点低)。virtual_router_id、auth_pass、VIP 必须一致。

健康检查脚本 /etc/keepalived/check_nginx.sh:

#!/bin/bash
# 本机 Nginx 健康检查:状态码 200 才算健康
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://127.0.0.1/health)
[ "$code" = "200" ]
sudo chmod 755 /etc/keepalived/check_nginx.sh
sudo systemctl enable --now keepalived

配套的 /health 最好放在 Nginx 本地直接返回,不依赖后端:

location = /health {
access_log off;
return 200 "ok\n";
}

验证:

# 主节点上能看到 VIP 绑在网卡上
ip addr show eth0 | grep 10.0.0.100

# 停掉主节点 nginx,看 VIP 是否漂到备节点
sudo systemctl stop nginx
ip addr show eth0 | grep 10.0.0.100 # 在备节点上执行
keepalived 的三个坑
  1. 脑裂:两台同时认为自己是 MASTER,VIP 冲突。检查 virtual_router_id 是否重复、心跳网段有没有被防火墙挡住(VRRP 是组播 224.0.0.18,协议号 112)。
  2. 抢占抖动:主节点刚恢复就抢回 VIP,导致连接闪断。可以加 nopreempt(注意这要求初始 state 都是 BACKUP)。
  3. 云环境不适用:阿里云、腾讯云等 VPC 里默认禁组播,keepalived 可能起不来。云上直接用 SLB/CLB 挂两台 Nginx,健康检查交给云产品,省事得多。

配置版本管理​

线上直接 vi /etc/nginx/conf.d/xxx.conf 是事故之源:改了没记录、改错没回滚、两台机器配置不一致。

推荐结构:配置放 Git 仓库,机器上只做 checkout + reload。

nginx-conf/
├── nginx.conf
├── conf.d/
│ ├── app.conf
│ ├── api.conf
│ └── static.conf
├── snippets/
│ ├── proxy-headers.conf
│ └── ssl.conf
├── certs/ # 只放公钥证书,私钥走密钥管理,不进 Git
└── deploy.sh
#!/bin/bash
# deploy.sh:部署 + 校验 + 回滚
set -euo pipefail

REPO=/srv/nginx-conf
TARGET=/etc/nginx
BACKUP=/srv/nginx-backup/$(date +%Y%m%d%H%M%S)

cd "$REPO"
git pull --ff-only

# 1. 备份当前配置
sudo cp -a "$TARGET/nginx.conf" "$BACKUP/"
sudo cp -a "$TARGET/conf.d" "$BACKUP/"

# 2. 同步并校验
sudo rsync -a --delete "$REPO/nginx.conf" "$TARGET/nginx.conf"
sudo rsync -a --delete "$REPO/conf.d/" "$TARGET/conf.d/"

if sudo nginx -t; then
sudo nginx -s reload
echo "deploy ok"
else
echo "config test failed, rolling back"
sudo cp -a "$BACKUP/nginx.conf" "$TARGET/"
sudo rsync -a --delete "$BACKUP/conf.d/" "$TARGET/conf.d/"
exit 1
fi

三条纪律:

  • --delete 配 rsync,保证机器上没有仓库外的「野生配置」。
  • nginx -t 通过才 reload,脚本里这个判断不能省。
  • 变更留痕:commit message 写清「改了什么、为什么、影响哪些域名」,半年后回看时这就是证据。

平滑升级​

信号速查​

信号命令作用
HUPnginx -s reload重载配置,平滑换 worker
USR1nginx -s reopen重开日志文件(logrotate 用)
USR2无快捷命令平滑升级:启动新 master + 新 worker
WINCH无优雅关闭老 worker
QUITnginx -s quit优雅退出
TERM / INTnginx -s stop快速退出

只改配置​

sudo nginx -t && sudo nginx -s reload

reload 的判断逻辑:master 读新配置、起新 worker、给老 worker 发 WINCH,老 worker 处理完存量连接后自行退出。不断服,但新老 worker 会短暂共存,内存翻倍一小会儿。

换二进制(版本升级)​

这是不重启进程换掉 nginx 可执行文件的标准流程:

# 1. 备份当前二进制
sudo cp /usr/sbin/nginx /usr/sbin/nginx.old

# 2. 用新包覆盖二进制(apt 升级的话这一步自动完成)
sudo apt install --only-upgrade nginx

# 3. 用新二进制校验配置(注意:此时跑的还是老进程)
sudo nginx -t

# 4. 启动新 master(会自动 fork 新 worker,端口由 master 传递)
sudo kill -USR2 $(cat /var/run/nginx.pid)

# 5. 观察:新旧 worker 并存,pid 文件变成 nginx.pid.oldbin
ps -ef | grep nginx

# 6. 确认新版本工作正常后,优雅关闭老 worker
sudo kill -WINCH $(cat /var/run/nginx.pid.oldbin)

# 7. 再观察一段时间,没问题就彻底关闭老 master
sudo kill -QUIT $(cat /var/run/nginx.pid.oldbin)

任何一步发现问题,回滚方式:

# 老 master 还在(只是 worker 被 WINCH 了),发 HUP 让它重新拉起老 worker
sudo kill -HUP $(cat /var/run/nginx.pid.oldbin)

# 然后让新 master 退出
sudo kill -QUIT $(cat /var/run/nginx.pid)
第 6 步别省

直接 QUIT 老 master 也能完成升级,但一旦新版本有兼容问题(模块缺失、配置行为变了),连接已经切走,回滚就有损。先 WINCH 观察、再 QUIT 才是「有退路」的做法。

小结​

  • 生产清单四件事:降权运行、日志轮转走 USR1、LimitNOFILE 和内核参数对齐、nginx -T 复核生效配置。
  • 高可用最省事的开源方案:两台 Nginx + keepalived VIP + 健康检查脚本;云上优先用 SLB,别硬扛组播限制。
  • 配置用 Git 管,部署脚本必须 nginx -t 通过才 reload,失败自动回滚,私钥不进仓库。
  • 平滑升级记住顺序:备份二进制 → -t 校验 → USR2 起新 master → WINCH 停老 worker → 观察 → QUIT 老 master,中间任何一步都能回退。

服务跑稳了,下一步是「看得见」:日志怎么切、状态怎么采、指标怎么接进 Prometheus,第 5 篇展开。