LVS+Keepalived+Nginx高可用反向代理架构实战
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做业务反代职责分开这套架构拓扑我用文字描述一下你脑海里可以想象成三段式客户端 - VIPLVS节点上漂移 - LVS主备节点 - Nginx反代节点1/2 - 后端应用实例1/2/3LVS工作在四层也就是TCP/UDP层面它不关心HTTP协议内容不解析域名、URI、Cookie只按负载均衡算法把TCP请求原样分发给后端的Nginx节点。这样做的优势是转发效率极高LVS本身不会成为性能瓶颈内核态的IPVS转发比用户态的Nginx代理要快得多。Nginx则工作在七层它拿到LVS转发来的请求后再根据server_name、location路径、Header等信息做精细路由。比如同一个VIP的80端口LVS先平均分发到两台NginxNginx再根据域名把请求转发给后端的Java服务、Node服务、静态文件服务等。这就是四层和七层各司其职的标准玩法。关键的是响应流量路径。在DR模式下LVS只负责把请求数据包转发给Nginx节点Nginx节点处理完请求后直接把响应报文回给客户端不需要再绕回LVS。这一点非常重要因为响应数据往往比请求数据大得多如果响应也要走LVSLVS很快就会被带宽打满。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或TUNLVS有三种工作模式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直连RSVIP冲突、访问异常这些诡异问题就全来了。这是DR模式最大的坑后面配置细节我会单独强调。3. 环境准备与基础部署3.1 服务器角色规划与网络要求我以一个最小可用的环境为例5台节点加1个VIP。这里的IP都是内网示例地址你可以根据自己的网段替换。节点IP角色lvs-master192.168.1.10Keepalived主LVSlvs-backup192.168.1.11Keepalived备LVSnginx-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两个节点都安装Keepalivedyum install -y keepalived ipvsadmKeepalived的主配置逻辑是先在全局定义一个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改成BACKUPpriority改成比主节点低比如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必须写成DRNAT模式和这个配置完全不同。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返回502LVS并不能感知。更好的做法是写一个外部检查脚本用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 0Forward一列为Route这就说明DR模式生效了。4.2 RS节点的VIP绑定与ARP抑制RS也就是Nginx节点上光有Nginx还不够必须把VIP绑定到本地回环接口lo:0同时在sysctl里抑制ARP响应。如果没有这两步DR模式的整个链路是走不通的。先把VIP绑定到lo:0ifconfig 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_ignore1表示只回答目标IP是本接口IP的ARP请求因为VIP虽然绑在lo上但lo接口不会接受来自其他设备的ARP请求这样就避免了RS对外宣告自己拥有VIParp_announce1表示尽量使用本地接口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 weight3; server 192.168.1.31:8080 weight3; 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上添加RSKeepalived探活成功后会重新把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脚本退出码为1Keepalived就把这台Nginx摘掉。相比TCP_CHECK这种方式能感知应用层异常后端大量超时、Nginx响应502时都能及时被识别。写脚本有两个经验。第一不要第一次检查失败就立刻置为不健康最好连续失败2到3次才摘除否则后端服务偶尔抖动一下就被误判反而触发大规模切换。第二脚本不要做太重的检查比如在脚本里curl一个耗时的完整页面那会把Keepalived的检查线程拖死。健康检查接口要轻量只做最基本的依赖探活。5.4 后端应用故障的联动这套架构的第三层高可用是指后端某台业务实例挂了Nginx能自动跳过它。在Nginx的upstream配置中如果某台后端TCP建连失败或HTTP返回错误Nginx会在请求级别自动尝试下一台可用后端。可以做一个简单的联动验证停掉backend-1的Web服务然后用curl反复请求VIP观察响应时间。正常情况下部分请求会短暂变慢但不会出现大量失败。因为LVS把请求分到了两台NginxNginx再把请求分到后端backend-1挂了Nginx会把请求全部转发给backend-2。这里要注意upstream的fail_timeout和max_fails参数。默认max_fails1fail_timeout10秒也就是10秒内失败1次就标记该后端不可用。对某些业务来说这个阈值太敏感可以适当调大比如max_fails3、fail_timeout30避免后端重启瞬间被误判为永久故障。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 nginx4curl -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 1tcp_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里带上AuthorizationNginx可以做最外层拦截比如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导致返回405Nginx又没做拦截浏览器就会报CORS失败。常见写法是Nginx里直接拦截OPTIONS并返回204if ($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 -tKeepalived配置用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逻辑更容易在故障时排查。每次调整架构前我都会先问自己是入口的问题还是转发的问题还是后端可用性的问题。这样一想大部分定位路径就会变得很清楚。我自己的习惯是在测试环境留一套一模一样的脚本任何升级、证书替换、参数调优先在测试环境跑完整流程再上生产。这套规范看起来慢但长期下来能省下的时间和夜间告警足够抵消那点额外成本。