☰
LVS+Keepalived+Nginx高可用反向代理架构实战
2026/10/4 4:55:09 网站建设 项目流程

1. 为什么需要LVS加Nginx这套组合

先说我自己的经历。第一次在生产环境被流量打崩,是我当时只部署了一台Nginx做所有域名的反向代理,某个大促流量一下来,Nginx的并发连接数先爆,紧接着后端服务也连带超时。那时候才意识到,反向代理这层看着简单,真要扛住「入口不能挂、转发不能断、后端能扩容」这三个要求,单机Nginx远远不够。

很多人听到高可用,第一反应就是上K8s,但如果你只是要解决对外入口和反向代理层的高可用问题,LVS加Nginx其实是更轻量、更可控的一套组合。LVS在四层做入口负载,Nginx在七层做业务转发,Keepalived在这两者之间负责VIP漂移和健康检查。这个方案不依赖任何云厂商的负载均衡,纯软件就能搭起来,在物理机、虚拟机、私有云环境都能跑。

它适合谁用?适合已经有Nginx反向代理经验、但被单点问题困扰的开发或运维同学。也适合那些正在从「一台机器跑所有站点」转向「多节点横向扩展」的小团队。如果你还在用单台Nginx管理多个网站、多个域名,先别急着上容器集群,把LVS加Nginx这套组合理解透,你会发现很多架构问题都能在四层和七层之间优雅解决。

1.1 单机Nginx的瓶颈到底在哪

单台Nginx能支撑的QPS其实不低,正常情况下几千QPS都能扛,真正的问题不在性能,而在「单点」两个字。机器挂了、进程被误杀、内存泄漏导致系统卡死、机房网络抖动,任何一个因素都能让整个服务不可用。而且单机Nginx在处理大量连接时,还受几个硬性指标限制:文件描述符上限、worker_connections、内核的TCP参数,比如net.ipv4.tcp_max_syn_backlog和net.core.somaxconn。

我压测过一台默认配置的Nginx,并发到一万左右的时候,开始出现大量connect timeout,日志里全是「upstream timed out」。这时候你才会意识到,一个看起来配置很简单的反向代理,背后牵涉到的内核参数、连接队列、worker进程调度远比想象中复杂。更麻烦的是,当业务一多,你会在Nginx里堆一堆server块、一堆location规则,既有HTTP转发又有TCP转发,还有SSL证书、限流、跨域头,日志全都混在一起,出了问题都不知道从哪里查。

1.2 高可用到底高在哪几个层面

「高可用」不是一句口号,落到架构上,至少要解决四个层面的问题。

第一层是入口高可用。所有流量先到达一个虚拟IP,这个VIP由Keepalived管理,主节点挂了VIP自动漂移到备用节点,保证入口不中断。第二层是转发层高可用。Nginx反代节点可以有多台,LVS用负载均衡算法把请求分发到不同Nginx节点,单台Nginx故障不影响整体流量。第三层是后端高可用。Nginx的upstream本身支持多后端节点和健康检查,后端某台实例挂了,Nginx自动把流量切到其他实例。第四层是会话保持。对需要登录态的Web应用,可以用IP_hash或Cookie粘性,保证同一个用户的请求尽量落在同一台后端。

这套架构的真正价值,是把「网络入口」和「业务转发」拆成两个独立环节,每一层都做冗余,任何单点故障都不会让服务整体不可用。

1.3 什么场景适合这套方案,什么场景不适合

先说适合的场景:业务流量已经超过单台Nginx的承载上限,需要把Nginx横向扩成多台;对外入口有多个域名、多个API路由,希望统一从四层入口进;后端服务本身是多实例部署,需要一个可靠的负载均衡器;团队有基本的Linux运维能力,不想依赖云厂商的LB产品。

不适合的场景也很明显:如果只是两三个低流量站点,一台Nginx绰绰有余,硬上LVS反而增加维护成本;如果后端业务本身就是单点,比如只有一个MySQL主库,那再怎么搞入口高可用也没用,先解决后端可用性是关键;如果环境已经全面容器化,K8s的Service和Ingress已经帮你处理了负载和高可用问题,那也不必绕回LVS。

我在实际选择方案时有个判断标准:能用一层解决的,绝不用两层;但一旦流量模型复杂到单层处理不了,那就必须把每一层的职责划清楚。

2. 整体架构与核心设计拆解

2.1 LVS做入口,Nginx做业务反代,职责分开

这套架构拓扑我用文字描述一下,你脑海里可以想象成三段式:

客户端 -> VIP(LVS节点上漂移) -> LVS主备节点 -> Nginx反代节点1/2 -> 后端应用实例1/2/3

LVS工作在四层,也就是TCP/UDP层面,它不关心HTTP协议内容,不解析域名、URI、Cookie,只按负载均衡算法把TCP请求原样分发给后端的Nginx节点。这样做的优势是转发效率极高,LVS本身不会成为性能瓶颈,内核态的IPVS转发比用户态的Nginx代理要快得多。

Nginx则工作在七层,它拿到LVS转发来的请求后,再根据server_name、location路径、Header等信息做精细路由。比如同一个VIP的80端口,LVS先平均分发到两台Nginx,Nginx再根据域名把请求转发给后端的Java服务、Node服务、静态文件服务等。这就是四层和七层各司其职的标准玩法。

关键的是响应流量路径。在DR模式下,LVS只负责把请求数据包转发给Nginx节点,Nginx节点处理完请求后,直接把响应报文回给客户端,不需要再绕回LVS。这一点非常重要,因为响应数据往往比请求数据大得多,如果响应也要走LVS,LVS很快就会被带宽打满。

2.2 四个核心组件及各自分工

要让这套架构跑起来,至少涉及四个核心组件,它们的角色必须从一开始就分得清。

LVS本身是一个内核模块加ipvsadm工具,负责维护负载均衡规则。你在LVS节点上用ipvsadm定义虚拟服务VIP:PORT,然后再挂上多个真实服务器RS,就是那几台Nginx节点的IP:PORT。每个RS可以设置权重,LVS会按权重进行调度。你可以用ipvsadm -L -n实时查看当前规则和连接状态。

Keepalived负责两件事:一是VRRP协议实现VIP漂移,主LVS节点挂了,备用节点几秒内接管VIP;二是对LVS规则里的RS做健康检查,发现某台Nginx节点异常,自动把它从LVS转发列表里摘除。Keepalived的配置是整个架构里最需要细抠的部分,后面会展开讲。

Nginx反代节点跑的是标准Nginx,但它的upstream指向真实的业务后端。这里的后端口是HTTP服务还是其他TCP协议服务,决定了你用http模块还是stream模块。绝大多数Web业务走http模块就够,如果后端是数据库中间件、Redis等TCP协议,则要用stream。

最后是业务后端,也就是真正提供服务的应用实例,一般部署多台,由Nginx的upstream做负载均衡和健康检查。业务后端是否无状态,决定了会话保持要不要开启。

2.3 为什么选DR模式而不是NAT或TUN

LVS有三种工作模式:NAT、DR、TUN。使用NAT模式时,请求和响应都要经过LVS节点,LVS需要对数据包做地址转换,再转发给后端,后端也要把网关指向LVS。这种方式实现逻辑简单,但LVS节点本身会成为带宽和连接数的瓶颈,因为我前面说了,响应数据量通常比请求大得多,全部走LVS,负载压力很夸张。

TUN模式通过IP隧道封装数据包,要求后端节点支持隧道协议,而且网络环境需要特殊配置,日常维护容易踩坑,一般数据中心环境不推荐。

DR模式则完全不一样。LVS通过改写数据帧的目标MAC地址,把请求直接转发给同一二层网络里的RS节点,RS节点直接处理请求并将响应返回给客户端。LVS只负责入站请求,不承担响应流量,性能上和可扩展性上都是最优的。

代价是RS节点必须把VIP绑定到自己的本地回环接口lo:0上,并关闭对这个VIP的ARP响应。如果不做ARP抑制,RS节点会直接响应客户端的ARP请求,导致客户端绕过LVS直连RS,VIP冲突、访问异常这些诡异问题就全来了。这是DR模式最大的坑,后面配置细节我会单独强调。

3. 环境准备与基础部署

3.1 服务器角色规划与网络要求

我以一个最小可用的环境为例,5台节点加1个VIP。这里的IP都是内网示例地址,你可以根据自己的网段替换。

节点IP角色
lvs-master192.168.1.10Keepalived主+LVS
lvs-backup192.168.1.11Keepalived备+LVS
nginx-node1192.168.1.20Nginx反代
nginx-node2192.168.1.21Nginx反代
backend-1192.168.1.30后端应用实例
backend-2192.168.1.31后端应用实例
VIP192.168.1.100对外统一入口

DR模式要求LVS节点和所有RS节点在同一个二层网络,也就是同一台交换机或者同一个虚拟网络内,否则MAC直接转发无法生效。这个要先确认,别配置完了才发现跨网段,DR根本就走不通。

系统初始化建议统一做这几件事:用chrony或NTP同步时间,避免健康检查时间戳和日志时间对不上;关闭firewalld或精确放行VIP端口,否则流量到不了Nginx;安装必要工具,比如ipvsadm、curl、tcpdump。生产环境千万别忘了给Keepalived和Nginx配置systemd托管,别裸跑进程。

3.2 LVS节点内核准备

LVS节点上要先保证内核支持IPVS模块,通常安装ipvsadm时会自动加载。你可以手动执行命令确认:

modprobe ip_vs lsmod | grep ip_vs

如果模块没有自动加载,就写进/etc/modules-load.d/ipvs.conf里:

ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh

这些模块对应不同调度算法,RR是轮询,WRR是加权轮询,SH是源地址哈希。DR模式下LVS节点不需要开启IP转发,与NAT模式不同,这一点不要弄混。但RS节点要做特殊的VIP绑定和ARP抑制,这在后面的配置会详细说明。

3.3 安装Keepalived并做基本配置

LVS两个节点都安装Keepalived:

yum install -y keepalived ipvsadm

Keepalived的主配置逻辑,是先在全局定义一个VRRP实例,实例里声明VIP漂移的网卡、优先级、认证方式和track_script,再定义virtual_server转发规则和RS健康检查。下面是我实际用过的主节点配置框架:

! /etc/keepalived/keepalived.conf global_defs { router_id LVS_MASTER } vrrp_script check_lvs { script "/usr/local/bin/check_lvs.sh" interval 2 fall 2 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 1111 } virtual_ipaddress { 192.168.1.100/32 dev eth0 label eth0:0 } track_script { check_lvs } }

备用节点的配置几乎一样,把state改成BACKUP,priority改成比主节点低,比如90,其他保持不变。

真实服务器的转发规则我放在下一部分讲,因为它是整个DR配置的核心。这里先说明一个实际心得:Keepalived的VRRP广告间隔默认是1秒,主备切换大概在2到3秒内完成,对于一般业务来说可以接受。如果你需要更快的切换,可以将advert_int调到0.5秒,但会相应增加VRRP报文数量和网络开销,底层网络太差时反而容易误判。

4. 核心配置细节与参数解析

4.1 LVS DR模式核心配置精解

继续说Keepalived里的virtual_server配置,这是DR模式的灵魂。在同一个keepalived.conf里,加上如下内容:

virtual_server 192.168.1.100 80 { delay_loop 6 lb_algo wrr lb_kind DR protocol TCP nat_mask 255.255.255.255 real_server 192.168.1.20 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.21 80 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }

这里的参数我逐一解释。delay_loop是健康检查的周期,单位秒,6秒一次比较常规。lb_algo是调度算法,wrr表示加权轮询,如果两台Nginx配置不同,可以通过weight字段调整权重。lb_kind必须写成DR,NAT模式和这个配置完全不同。nat_mask在DR模式下几个文档里都会写着255.255.255.255,官方配置模板默认如此,保留即可。

real_server下面的TCP_CHECK是Keepalived对RS的端口探活机制,它会主动建立TCP连接到RS的指定端口,connect_timeout是连接超时时间,nb_get_retry是失败重试次数,delay_before_retry是重试间隔。只有当TCP连接失败达到阈值,Keepalived才会把这个RS从LVS转发规则中摘除。

我需要提醒一个实际经验:TCP_CHECK只检查端口通不通,不检查应用是不是真的健康。Nginx进程活着但后端应用全部超时、Nginx返回502,LVS并不能感知。更好的做法是写一个外部检查脚本,用curl访问RS的/healthz接口,只有返回200才算健康。这个脚本后面单独写。

配置完成后,在LVS节点启动Keepalived并检查规则:

systemctl enable keepalived systemctl start keepalived ipvsadm -L -n

看到类似输出就说明规则已经加载:

IP Virtual Server version 1.2.1 Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.100:80 wrr -> 192.168.1.20:80 Route 1 0 0 -> 192.168.1.21:80 Route 1 0 0

Forward一列为Route,这就说明DR模式生效了。

4.2 RS节点的VIP绑定与ARP抑制

RS也就是Nginx节点上,光有Nginx还不够,必须把VIP绑定到本地回环接口lo:0,同时在sysctl里抑制ARP响应。如果没有这两步,DR模式的整个链路是走不通的。

先把VIP绑定到lo:0:

ifconfig lo:0 192.168.1.100 netmask 255.255.255.255 broadcast 192.168.1.100 up

注意netmask是255.255.255.255,不是255.255.255.0,否则会产生路由冲突。为了让机器重启后配置不丢,建议写入网卡配置文件或者写一个systemd oneshot服务。

然后配置ARP抑制,编辑/etc/sysctl.conf,在Nginx节点上添加:

net.ipv4.conf.all.arp_announce = 1 net.ipv4.conf.all.arp_ignore = 1 net.ipv4.conf.lo.arp_announce = 1 net.ipv4.conf.lo.arp_ignore = 1

执行sysctl -p让配置生效。这两个参数的含义分别是:arp_ignore=1表示只回答目标IP是本接口IP的ARP请求,因为VIP虽然绑在lo上,但lo接口不会接受来自其他设备的ARP请求,这样就避免了RS对外宣告自己拥有VIP;arp_announce=1表示尽量使用本地接口IP作为ARP请求的源地址,避免通过VIP去发送ARP请求。

这一步的坑我曾经踩得很惨:当时RS绑定了VIP但没做ARP抑制,客户端偶尔访问成功,偶尔超时,抓包后发现RS在响应VIP的ARP请求,数据走了RS直连,而LVS那边还在继续分发连接,整个网络混乱不堪。所以这个配置不是可选,是必须。

4.3 Nginx反代参数与实际配置

Nginx节点上,除了监听VIP的高可用逻辑,真正的转发全部靠Nginx完成。我给出一个比较完整的上游配置模板:

upstream backend_http { server 192.168.1.30:8080 weight=3; server 192.168.1.31:8080 weight=3; keepalive 32; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_http; 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_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; proxy_next_upstream http_502 http_503 error timeout; } }

简单解读一下几个容易被忽略的点。

proxy_set_header这段极其重要。Host必须透传客户端的原始域名,否则后端应用拿不到正确主机名,很多基于Host做虚拟路由或生成绝对URL的业务会出问题。X-Real-IP和X-Forwarded-For是给后端传递客户端真实IP,这个在做日志分析和反爬时必须要有。X-Forwarded-Proto是为了让后端知道客户端是HTTP还是HTTPS,防止后端强制跳转时把HTTPS跳成HTTP。

keepalive 32这个参数很多人会漏。它的作用是让Nginx与后端上游之间保持长连接,避免每个请求都重新建立TCP连接。我在压力测试中发现,不加keepalive时TIME_WAIT连接数居高不下,加了之后连接复用率明显提升,系统负载降了一截。

proxy_next_upstream用于当上游返回502、503或者超时时自动切换到下一台后端。注意它默认不会在HTTP 504超时时重试,如果你希望后端超时也走重试,需要显式加上timeout。但要谨慎使用:非幂等请求如POST在某些业务场景下不能随便重试,否则可能造成重复下单。这个取舍要结合业务自己在代码层面对幂等性的保证。

4.4 多端口多站点与开发环境自定义域名配置

我在一些开发环境里最常被问到的,是怎么在一个Nginx上配多个站点、多个端口,然后自定义域名访问。比如你在本地用虚拟机跑了一个Nginx,虚拟机IP是192.168.1.20,你想用dev.app1.com访问端口8080的站点,用dev.app2.com访问端口8081的站点。

先在开发机的hosts文件里加一行:

192.168.1.20 dev.app1.com dev.app2.com

然后Nginx里这样写两个server块:

server { listen 8080; server_name dev.app1.com; root /var/www/app1; } server { listen 8081; server_name dev.app2.com; root /var/www/app2; }

如果你的多个站点都要走80端口,那就不能依赖端口区分,得靠server_name区分:

server { listen 80; server_name dev.app1.com; location / { proxy_pass http://127.0.0.1:8080; } } server { listen 80; server_name dev.app2.com; location / { proxy_pass http://127.0.0.1:8081; } }

用可视化工具的话,1Panel这类面板可以把Nginx配置拆到conf.d下的独立小文件里,每个站点一个文件,清晰很多,批量添加多个反代站点非常方便。但我要提醒,面板能帮你管理配置,不代表你可以不懂底层Nginx的server块和location匹配优先级。一旦遇到复杂路由,不懂底层逻辑还是会抓瞎。

4.5 443转发、SSL证书替换和常见证书错误

这一块是生产环境绕不开的痛点。先说Nginx上替换SSL证书不生效的典型原因。

很多人改完证书文件后直接感觉应该生效,但浏览器里看到的还是旧证书,原因往往就是Nginx进程还在用旧证书,没有重新加载。正确操作是替换证书后先执行:

nginx -t nginx -s reload

注意nginx -t只检查语法,真正让新证书生效的是reload。如果你用了软链接,比如link当前目录下两年前的证书软链,新证书放到了别的位置,那软链指向没变化,reload后自然还是旧证书。排查方法是执行nginx -T,这个命令会导出当前实际生效的配置和证书路径,一眼就看明白。

还有一个非常常见的报错,浏览器提示net::ERR_CERT_COMMON_NAME_INVALID,意思就是证书里的CN或SAN字段跟你访问的域名不匹配。我之前遇到一个部署,Nginx做HTTPS反代,listen 443端口,却把证书写成了后端内网IP的证书,用户访问公网域名自然报错。这种情况必须在Nginx这层终止SSL,也就是说Nginx配置的证书必须是用户访问域名的证书,因为Nginx对外讲的是HTTPS,对后端则通常是HTTP内网转发。如果你确实需要从Nginx到后端也走HTTPS,则要在proxy_pass里指定https协议,并且可能需要配置proxy_ssl_trusted_certificate或proxy_ssl_verify off来跳过校验,测试环境可以这么做,生产环境还是建议把后端证书链配好。

排查证书问题时,我强烈推荐用openssl命令直接查看证书内容:

openssl x509 -in fullchain.pem -noout -subject -dates -ext subjectAltName

再用这个命令模拟浏览器校验某个域名:

openssl s_client -connect 192.168.1.100:443 -servername api.example.com

看输出里的subjectAltName和SSL verify result,基本能锁定问题方向。

5. 故障演练与切换验证

5.1 主LVS宕机,验证VIP漂移

方案搭好了,不演练等于白搭。生产环境最怕的就是从没测过切换流程,真到故障时发现备用节点起不来。

第一步验证Keepalived主备切换。在主LVS节点上直接停掉Keepalived服务:

systemctl stop keepalived

然后到备用节点上执行:

ip addr show eth0

正常情况下备用节点的eth0上会多出192.168.1.100这个VIP,整个过程在2到3秒内完成。还可以看备用节点的Keepalived日志:

journalctl -u keepalived -f

日志里会有一条类似「Transition to MASTER STATE」的记录,同时通过ipvsadm -L -n确认转发规则已经在备用节点上生效。

这里有个隐藏问题:如果两台LVS节点都要手动配置同样的virtual_server规则,那就没问题。如果当时只在主节点上写了规则,备用节点没写,那么切换过去后VIP有了但规则是空的,流量全部进黑洞。解决办法是把统一配置抽象成模板,两个节点保持完全一致的Keepalived配置,只改state和priority。

5.2 模拟Nginx反代节点故障

第二层验证,模拟某台Nginx节点挂掉。最直接的方式是停掉Nginx服务:

systemctl stop nginx

此时Keepalived会通过TCP_CHECK发现192.168.1.20:80端口不可达,经过几秒的失败重试后,会把这个RS从LVS转发列表中摘除。你可以用ipvsadm -L -n观察,RealServer列表里192.168.1.20那一行会消失,或者状态变为不可用。

再回到客户端用curl连续访问VIP,观察请求是否全部落到存活的那台Nginx上。如果你在Nginx access_log里加了协议头、后端地址等字段,可以很直观地看到流量只到了192.168.1.21。

恢复的时候直接启动Nginx服务,不用手动去LVS上添加RS,Keepalived探活成功后会重新把RS加回列表。这是Keepalived做得比较省心的地方。

5.3 健康检查脚本的写法和取舍

前面说过TCP_CHECK只检查端口,不够可靠。建议在Keepalived里用MISC_CHECK替代,或者用TCP_CHECK配合外部脚本做更精确的检查。

在实际项目中,我更推荐在Nginx上暴露一个简化版健康检查接口,并用外部脚本确保该接口确实能判断服务健康,脚本内容类似:

#!/bin/bash code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 http://127.0.0.1/healthz) if [ "$code" = "200" ]; then exit 0 fi exit 1

这个脚本挂在Keepalived的real_server检查里,用MISC_CHECK声明。当接口返回非200,脚本退出码为1,Keepalived就把这台Nginx摘掉。相比TCP_CHECK,这种方式能感知应用层异常,后端大量超时、Nginx响应502时都能及时被识别。

写脚本有两个经验。第一,不要第一次检查失败就立刻置为不健康,最好连续失败2到3次才摘除,否则后端服务偶尔抖动一下就被误判,反而触发大规模切换。第二,脚本不要做太重的检查,比如在脚本里curl一个耗时的完整页面,那会把Keepalived的检查线程拖死。健康检查接口要轻量,只做最基本的依赖探活。

5.4 后端应用故障的联动

这套架构的第三层高可用,是指后端某台业务实例挂了,Nginx能自动跳过它。在Nginx的upstream配置中,如果某台后端TCP建连失败或HTTP返回错误,Nginx会在请求级别自动尝试下一台可用后端。

可以做一个简单的联动验证:停掉backend-1的Web服务,然后用curl反复请求VIP,观察响应时间。正常情况下部分请求会短暂变慢,但不会出现大量失败。因为LVS把请求分到了两台Nginx,Nginx再把请求分到后端,backend-1挂了,Nginx会把请求全部转发给backend-2。

这里要注意upstream的fail_timeout和max_fails参数。默认max_fails=1,fail_timeout=10秒,也就是10秒内失败1次就标记该后端不可用。对某些业务来说这个阈值太敏感,可以适当调大,比如max_fails=3、fail_timeout=30,避免后端重启瞬间被误判为永久故障。

6. 常见问题与排查经验

6.1 VIP不通、访问超时、抓包看不到SYN

这套架构里,VIP不通是第一大坑。排查思路要按链路逐层来。

先确认VIP在谁身上。登录LVS主备节点,分别执行ip addr show eth0,看到VIP的节点是当前MASTER。如果VIP不在任何节点上,说明Keepalived主备都没起来,看journalctl -u keepalived日志里有没有VRRP错误。

如果VIP在主节点上,但客户端访问不了,抓包用tcpdump在LVS节点上看有没有SYN包。没有SYN包,通常是网络设备、防火墙或路由不放行VIP。有SYN包,但RS不回包或者响应有问题,就要看RS节点的ARP抑制有没有配好。我前面反复强调的ARP坑,经常就是这种表现:LVS转发正常,但RS也响应了VIP的ARP请求,造成去客户端的路径上出现两个MAC地址,交换机的MAC表就会紊乱。

还有一种隐蔽情况:Keepalived配置里virtual_router_id和网段里其他VRRP实例冲突,导致主备节点互相抢占,VIP在两边飘来飘去。遇到类似现象,检查所有节点上的virtual_router_id和auth_pass是否一致,不要跟别人共用网段的VRID。

6.2 SSL证书替换后仍然显示旧证书

这个问题我在第4.5部分提过,这里再补一个实际排查清单,按顺序操作能大幅缩短定位时间:

步骤命令或操作目的
1nginx -T查看实际加载的证书路径
2openssl x509 -in cert.pem -noout -dates -subject确认新证书内容和有效期
3ss -tlnpgrep nginx
4curl -vk https://域名 或 openssl s_client看浏览器视角实际收到的证书
5替换证书后执行 nginx -s reload确保Nginx加载新证书

替换证书不生效还有一个被忽视的点:证书链文件没更新。很多人只替换了server证书和私钥,但没有更新中间的CA证书链,虽然浏览器刷新不一定报错,但移动端或某些严格校验的客户端就会突然开始报证书链不完整。稳妥做法是每次替换证书时,把完整链fullchain.pem、私钥key.pem、以及可能是中级的ca-bundle.pem一起更新,再用openssl verify验证一遍。

6.3 TCP最大连接数、TIME_WAIT和Nginx连接数上限

搜索关键词里有一个高频问题,就是Nginx作为反向代理的TCP最大连接数。先明确一点,Nginx能支撑的连接数不等于无限,它受限于worker_connections乘以worker_processes,同时还受系统文件描述符上限影响。

linux默认单进程文件描述符软限是1024,如果Nginx配置了高并发但没提高ulimit,大量请求会报too many open files。需要在systemd里设置LimitNOFILE,或者在/etc/security/limits.conf里调整。

如果再配合大流量反代,系统层面还会出现大量TIME_WAIT。TIME_WAIT太多不是致命的,但会消耗内存和端口资源。可以调整:

net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_tw_reuse = 1

tcp_tw_reuse开启后,处于TIME_WAIT的连接可以被安全复用,主要适用于出站连接主动方。但对NAT环境要谨慎,有些场景下复用TIME_WAIT可能造成TCP序列号冲突。稳妥做法还是优化upstream keepalive长连接,从根源上减少连接建立和销毁频率。

另外别忘了net.core.somaxconn和net.ipv4.tcp_max_syn_backlog,这两个是TCP连接队列深度的关键参数,生产环境我会调整到65535左右,否则突发连接容易因为SYN队列满了而丢包。

6.4 反向代理Ollama或其他本地AI服务的配置细节

这个词条也很典型:nginx 反代 ollama 设置apikey cherrystudio。现在很多开发者在本地或内网跑Ollama这类大模型服务,然后用Nginx把它暴露给团队内部或者特定Web应用使用。单独反代一个Ollama服务,配置其实不复杂,但有几个细节要注意。

Ollama的API默认监听127.0.0.1:11434,先要让Ollama监听在一个可以被Nginx访问的地址,比如0.0.0.0:11434,或者直接把Nginx放在同一台机器上通过localhost转发。然后Nginx配置一个server块,把符合路径的请求转发过去:

server { listen 11435; server_name ai.internal; location / { proxy_pass http://127.0.0.1:11434; 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; proxy_read_timeout 600s; proxy_send_timeout 600s; } }

这里的proxy_read_timeout特别重要。大模型生成是一次流式输出,推理时间可能超过普通Web接口的默认30秒或60秒,如果超时时间太短,客户端会经常看到中间断流的报错。我建议至少设置到300秒以上,要根据你的模型规模和推理耗时来定。

关于API Key的校验,像Cherry Studio这类客户端会在请求Header里带上Authorization,Nginx可以做最外层拦截,比如:

location /v1 { if ($http_authorization !~ "^Bearer ") { return 401; } proxy_pass http://127.0.0.1:11434; }

但Nginx的if有些场景下容易踩坑,尤其是被多个location配置干扰时。更严谨的方式是用map模块配合变量判断,或者直接交给Ollama后端的鉴权插件处理。我的建议是,Nginx这层做基础拦截,真正的授权校验还是放在更靠近模型服务的地方,否则面面具到会让Nginx配置失控。

6.5 CORS跨域问题的Nginx排查思路

搜索词里还有nginx invalid cors request。这类问题在前后端分离架构里几乎人人都会遇到。现象通常是浏览器控制台报CORS错误,但用curl测试后端接口又是正常的。

先明确一个基本事实:CORS是浏览器行为,不是HTTP协议强制要求。所以用curl测不出来正常,只有浏览器会因为同源策略拦截。

Nginx反代场景里的CORS问题,常见原因有两个。第一,后端服务本身返回了Access-Control-Allow-Origin,但Nginx又用add_header追加了一组CORS头,两者不一致,浏览器无法判断就报错。解决办法是一端控制,后端返回了就不要再在Nginx重复添加,或者统一在Nginx这层去掉后端的CORS头再做统一追加。

第二,OPTIONS预检请求没处理好。跨域请求如果带了复杂Header或非简单方法,浏览器会先发一个OPTIONS请求,后端服务如果忽略OPTIONS导致返回405,Nginx又没做拦截,浏览器就会报CORS失败。常见写法是Nginx里直接拦截OPTIONS并返回204:

if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization, Content-Type"; return 204; }

这里有个小细节,add_header要加always参数才保证无论响应状态码如何都会输出,否则在204响应下某些版本的Nginx可能不输出CORS头,浏览器还是会拦截。我在配置里通常统一写成add_header xxx always。

6.6 Nginx官方镜像和alpine挂载配置的坑

用Docker部署Nginx时,经常遇到的一个问题是挂载conf.d目录报错或者容器内Nginx起不来。比如这种报错:

[emerg] 1#1: open() "/etc/nginx/conf.d/default.conf" failed (13: Permission denied)

通常不是Nginx配置写错了,而是宿主机挂载目录的SELinux标签和容器内进程权限不匹配。在SELinux启用的环境,挂载时应该加Z参数:

docker run -d -p 80:80 -v /path/conf.d:/etc/nginx/conf.d:Z nginx:alpine

如果你用的是nginx:alpine镜像,还有一个隐藏问题:官方alpine镜像的nginx二进制可能是精简编译的,某些模块比如stream、http_v2_module可能没有被完整启用。如果你需要四层TCP转发,在容器里挂出stream.conf但启动报unknown directive "stream",那就是镜像本身不带该模块。解决办法是换用nginx官方完整版镜像,或者自己构建包含所需模块的镜像。

挂载整个conf.d目录还有一个坑:如果挂载的是空目录,会覆盖镜像内默认的/etc/nginx/conf.d目录,Nginx启动后完全没有默认服务器,直接404或者连接拒绝。这不是异常,而是因为默认的default.conf被空目录替换了,需要检查一下挂载源目录里是否真的有对应的配置文件。

6.7 K8s环境还需要LVS吗

搜索词里出现kubernetes 部署nginx,所以把这个场景也提一嘴。在K8s集群里,如果你用Nginx Ingress Controller作为对外入口,集群Service本身已经解决了后端Pod的负载问题,Ingress Controller有了一个新的外部流量入口,此时还需要LVS吗?

我的看法是分场景。在云上,直接用云平台的四层负载均衡器对接Ingress Controller即可,没必要自己搭LVS。但在裸机或私有云环境,没有云LB可用时,LVS加Keepalived依然是稳定且经济的入口方案。它可以把集群外部的流量分发到多台运行Nginx Ingress的节点上,然后再由Ingress Controller完成七层路由。

另一种场景是K8s集群内已经用了MetalLB或NodePort暴露服务,此时LVS的价值不大。所以在K8s里是否引入LVS,要看有没有「集群外部也要有统一VIP」的硬性需求,有,就用,没有,就别给系统增加复杂度。

7. 停机演练与个人踩坑总结

最后的这一部分,分享几个我用血换来的经验和建议。

第一,任何变更前都要先备份。这个备份不是只备份你要改的那个文件,而是把keepalived.conf、nginx.conf整个配置目录连同证书一起打包。我说的是完整打包,因为你可能改完Nginx配置发现问题,想回滚却忘了原文件长什么样。用git管理配置文件,每次变更后commit,是最不费力的方式。

第二,变更后先看配置语法,再看服务状态,最后看业务指标。Nginx配置用nginx -t,Keepalived配置用keepalived -t,这两步能挡住90%的低级错误。之前我有一次改完Keepalived直接重启,结果因为花括号没闭合,服务起不来,线上入口直接断了几分钟。从那以后我形成了肌肉记忆,配置改完不检查绝不上线。

第三,别在生产环境第一次演练。所有故障切换流程,我建议先在开发或测试环境完整跑一遍。第一遍跑的时候你才会发现自己对架构的理解还有哪些盲区,比如备用节点没同步规则,比如健康检查脚本需要root权限但Keepalived的MISC_CHECK执行用户不对,比如RS节点的ARP抑制在别的网卡上生效了但在主网卡上没配全,这些问题都会在演练中暴露出来。

第四,关注keepalived日志和Nginx错误日志。很多人配置完这套架构就再也不看日志,结果VIP漂移了、后端摘除了、证书过期了,全都不知道。可以用简单的脚本或者日志采集工具把关键错误关键字抓出来,比如日志里出现「VRRP_Instance」「invalid」「unhealthy」时及时告警。

最后说一个真实感受,LVS加Nginx这套组合,看起来配置项多,但只要把四层与七层的职责边界想清楚,把VIP绑定、ARP抑制、健康检查这三个核心点做扎实,它就比K8s那套复杂的Service、Endpoint、kube-proxy逻辑更容易在故障时排查。每次调整架构前,我都会先问自己:是入口的问题,还是转发的问题,还是后端可用性的问题。这样一想,大部分定位路径就会变得很清楚。

我自己的习惯是在测试环境留一套一模一样的脚本,任何升级、证书替换、参数调优,先在测试环境跑完整流程,再上生产。这套规范看起来慢,但长期下来能省下的时间和夜间告警,足够抵消那点额外成本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询