相关文章:从0到1吃透 VIP:Keepalived、Nginx 与数据库高可用原理https://blog.csdn.net/weixin_45806267/article/details/166946810?sharetype=blogdetail&sharerId=166946810&sharerefer=PC&sharesource=weixin_45806267&spm=1011.2480.3001.8118

一、架构说明

该架构适用于:

TDengine 集群
MySQL 集群
Redis 集群
Web/API 服务集群
中间件集群
其他 HTTP/TCP 服务

假设规划如下:

Nginx-01:192.168.10.11
Nginx-02:192.168.10.12

VIP:192.168.10.10

后端节点:
192.168.10.21
192.168.10.22
192.168.10.23

网卡:ens33

整体架构:

                    客户端 / 应用系统
                           │
                           ↓
                    VIP:192.168.10.10
                           │
             ┌─────────────┴─────────────┐
             │                           │
      Nginx + Keepalived          Nginx + Keepalived
           Node01                      Node02
       192.168.10.11              192.168.10.12
          MASTER                      BACKUP
             │                           │
             └─────────────┬─────────────┘
                           │
                         Nginx
                           │
                        upstream
                           │
              ┌────────────┼────────────┐
              │            │            │
         Backend-01   Backend-02   Backend-03
       192.168.10.21 192.168.10.22 192.168.10.23

二、高可用原理

Keepalived 主要负责:

VIP
MASTER / BACKUP
VRRP
优先级
VIP 漂移
健康检查脚本调用

Nginx 主要负责:

监听业务端口
反向代理
负载均衡
后端节点转发

健康检查脚本负责:

检测 nginx 是否正常
nginx 异常时尝试自动恢复
持续失败时向 Keepalived 返回失败状态

整体逻辑:

Keepalived
    ↓
每隔几秒调用 check_nginx.sh
    ↓
检查 Nginx
    │
    ├── 正常
    │     ↓
    │   保持当前状态
    │
    └── 异常
          ↓
      尝试重启 Nginx
          │
          ├── 成功
          │     ↓
          │   继续服务
          │
          └── 持续失败
                ↓
           健康检查失败
                ↓
           当前节点降权
                ↓
           备用节点接管 VIP

所以可以简单记:

Keepalived
负责 VIP 在哪台 Nginx

Nginx
负责请求转发到哪个后端

后端集群
负责真正处理请求

你本次两套环境也是这个模式:Keepalived 通过 vrrp_script 和 track_script 调用 Nginx 检查脚本,Nginx 主配置再通过 include 加载业务代理配置。 粘贴的文本 (1)(10) 粘贴的文本 (1)(10)


三、目录规划

推荐:

/app/nginx1.3/
├── conf/
│   ├── nginx.conf
│   └── http.d/
│       └── http-app.conf
├── logs/
├── html/
└── sbin/
    └── nginx

Keepalived:

/etc/keepalived/
├── keepalived.conf
├── check_nginx.sh
└── nginx-attempts.txt

systemd:

/etc/systemd/system/nginx.service

四、Nginx 主配置文件

文件:

vi /app/nginx1.3/conf/nginx.conf

完整模板:

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 8192;
    multi_accept on;
}

http {
    server_tokens off;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    include mime.types;
    default_type application/octet-stream;

    keepalive_timeout 180s;
    keepalive_requests 1000;

    client_max_body_size 100M;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }

    proxy_connect_timeout 15s;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;

    proxy_buffering off;

    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 Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent '
                    '$request_time $upstream_response_time '
                    'upstream_addr="$upstream_addr"';

    include /app/nginx1.3/conf/http.d/*.conf;
}

修改完检查:

/app/nginx1.3/sbin/nginx -t

五、Nginx HTTP 业务代理配置文件

文件:

vi /app/nginx1.3/conf/http.d/http-app.conf

下面假设业务端口为:

6041
6043
6060

后端节点:

192.168.10.21
192.168.10.22
192.168.10.23

完整模板:

upstream service_6041 {
    least_conn;

    server 192.168.10.21:6041 max_fails=2 fail_timeout=10s;
    server 192.168.10.22:6041 max_fails=2 fail_timeout=10s;
    server 192.168.10.23:6041 max_fails=2 fail_timeout=10s;
}

upstream service_6043 {
    ip_hash;

    server 192.168.10.21:6043 max_fails=2 fail_timeout=10s;
    server 192.168.10.22:6043 max_fails=2 fail_timeout=10s;
    server 192.168.10.23:6043 max_fails=2 fail_timeout=10s;
}

upstream service_6060 {
    ip_hash;

    server 192.168.10.21:6060 max_fails=2 fail_timeout=10s;
    server 192.168.10.22:6060 max_fails=2 fail_timeout=10s;
    server 192.168.10.23:6060 max_fails=2 fail_timeout=10s;
}

server {
    listen 6041;
    server_name 192.168.10.10;

    access_log /app/nginx1.3/logs/service-6041.access.log main;
    error_log  /app/nginx1.3/logs/service-6041.error.log;

    location / {
        proxy_pass http://service_6041;

        proxy_set_header Host $host:$server_port;
        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 300;
        proxy_read_timeout 3600;
        proxy_send_timeout 300;
    }
}

server {
    listen 6043;
    server_name 192.168.10.10;

    access_log /app/nginx1.3/logs/service-6043.access.log main;
    error_log  /app/nginx1.3/logs/service-6043.error.log;

    location / {
        proxy_pass http://service_6043;

        proxy_set_header Host $host:$server_port;
        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 300;
        proxy_read_timeout 3600;
        proxy_send_timeout 300;
    }
}

server {
    listen 6060;
    server_name 192.168.10.10;

    access_log /app/nginx1.3/logs/service-6060.access.log main;
    error_log  /app/nginx1.3/logs/service-6060.error.log;

    location / {
        proxy_pass http://service_6060;

        proxy_set_header Host $host:$server_port;
        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 300;
        proxy_read_timeout 3600;
        proxy_send_timeout 300;
    }
}

这里必须重点检查:

listen 6041 → proxy_pass service_6041
listen 6043 → proxy_pass service_6043
listen 6060 → proxy_pass service_6060

你本次环境中曾出现过不同端口错误指向同一个 upstream 的问题,而 nginx -t 仍可能通过,所以语法检查之外必须做业务测试。 粘贴的文本 (1)(10)

修改后:

/app/nginx1.3/sbin/nginx -t
systemctl reload nginx

六、Nginx systemd 服务文件

文件:

vi /etc/systemd/system/nginx.service

完整模板:

[Unit]
Description=Nginx Reverse Proxy Service
After=network-online.target
Wants=network-online.target

[Service]
Type=forking

PIDFile=/app/nginx1.3/logs/nginx.pid

ExecStartPre=/app/nginx1.3/sbin/nginx -t -c /app/nginx1.3/conf/nginx.conf

ExecStart=/app/nginx1.3/sbin/nginx -c /app/nginx1.3/conf/nginx.conf

ExecReload=/bin/kill -s HUP $MAINPID

ExecStop=/bin/kill -s QUIT $MAINPID

TimeoutStopSec=30

Restart=no

[Install]
WantedBy=multi-user.target

注意:

不要配置 ExecQuit

systemd 不识别:

ExecQuit=

创建后:

systemctl daemon-reload
systemctl enable nginx
systemctl start nginx

检查:

systemctl is-enabled nginx
systemctl is-active nginx

正常:

enabled
active

七、安装 Keepalived

执行:

dnf install -y keepalived

或者:

yum install -y keepalived

确认:

keepalived --version

八、Keepalived 主节点配置文件

Node01:

192.168.10.11

文件:

vi /etc/keepalived/keepalived.conf

完整配置:

! Configuration File for keepalived

global_defs {
    router_id nginx-node01
    script_user root
    enable_script_security
}

vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 5
    weight -20
    fall 3
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP

    interface ens33

    virtual_router_id 51

    priority 100

    nopreempt

    unicast_src_ip 192.168.10.11

    unicast_peer {
        192.168.10.12
    }

    advert_int 1

    authentication {
        auth_type PASS
        auth_pass HaVip123
    }

    virtual_ipaddress {
        192.168.10.10/24 dev ens33 label ens33:1
    }

    track_script {
        chk_nginx
    }
}

九、Keepalived 备节点配置文件

Node02:

192.168.10.12

文件:

vi /etc/keepalived/keepalived.conf

完整配置:

! Configuration File for keepalived

global_defs {
    router_id nginx-node02
    script_user root
    enable_script_security
}

vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 5
    weight -20
    fall 3
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP

    interface ens33

    virtual_router_id 51

    priority 90

    nopreempt

    unicast_src_ip 192.168.10.12

    unicast_peer {
        192.168.10.11
    }

    advert_int 1

    authentication {
        auth_type PASS
        auth_pass HaVip123
    }

    virtual_ipaddress {
        192.168.10.10/24 dev ens33 label ens33:1
    }

    track_script {
        chk_nginx
    }
}

两台主要区别:

router_id
priority
unicast_src_ip
unicast_peer

必须保持一致:

virtual_router_id
VIP
网卡
auth_pass
健康检查脚本

十、Nginx 健康检查脚本

文件:

vi /etc/keepalived/check_nginx.sh

完整模板:

#!/bin/bash

LOG_DIR="/app/keepalived/logs"
LOG_FILE="${LOG_DIR}/nginx-check.log"

ATTEMPT_FILE="/etc/keepalived/nginx-attempts.txt"

MAX_RESTART_ATTEMPTS=3

mkdir -p "${LOG_DIR}"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "${LOG_FILE}"
}

get_attempts() {
    if [ -f "${ATTEMPT_FILE}" ]; then
        cat "${ATTEMPT_FILE}"
    else
        echo 0
    fi
}

set_attempts() {
    echo "$1" > "${ATTEMPT_FILE}"
}

if systemctl is-active --quiet nginx; then
    set_attempts 0
    exit 0
fi

log "Nginx service is not active."

attempts=$(get_attempts)

if [ "${attempts}" -lt "${MAX_RESTART_ATTEMPTS}" ]; then
    attempts=$((attempts + 1))

    log "Restart Nginx, attempt ${attempts}."

    systemctl restart nginx

    sleep 3

    if systemctl is-active --quiet nginx; then
        log "Nginx restart successful."
        set_attempts 0
        exit 0
    fi

    set_attempts "${attempts}"

    log "Nginx restart failed."

    exit 1
fi

log "Nginx restart failed ${MAX_RESTART_ATTEMPTS} times."

exit 1

赋权:

chmod 755 /etc/keepalived/check_nginx.sh
chown root:root /etc/keepalived/check_nginx.sh

创建计数文件:

echo 0 > /etc/keepalived/nginx-attempts.txt
chown root:root /etc/keepalived/nginx-attempts.txt

十一、为什么脚本不重启 Keepalived

不要在:

check_nginx.sh

里面执行:

systemctl restart keepalived

推荐职责:

check_nginx.sh
→ 检查 Nginx
→ 尝试恢复 Nginx
→ 返回 exit 0 / exit 1

Keepalived
→ 根据检查结果决定 VIP 是否漂移

这样逻辑更清晰。


十二、Keepalived 自启动与 systemd 管理

安装后:

systemctl enable keepalived
systemctl start keepalived

检查:

systemctl is-enabled keepalived
systemctl is-active keepalived

正常:

enabled
active

十三、Keepalived 配置检查

修改后:

keepalived -t

然后:

journalctl -u keepalived -n 50 --no-pager

BACKUP 常见:

Entering BACKUP STATE

MASTER 常见:

Entering MASTER STATE
Sending gratuitous ARP

十四、nopreempt 配置说明

配置:

nopreempt

作用:

原 MASTER 故障
↓
VIP 漂移到 BACKUP
↓
原 MASTER 恢复
↓
不主动抢回 VIP

这样可以避免 VIP 反复漂移。

因此:

使用 nopreempt

时,不建议再配置:

preempt_delay 300

否则可能出现:

preempt_delay is incompatible with nopreempt mode

十五、防火墙

业务端口按实际开放:

firewall-cmd --permanent --add-port=6041/tcp
firewall-cmd --permanent --add-port=6043/tcp
firewall-cmd --permanent --add-port=6060/tcp

VRRP:

firewall-cmd --permanent \
--add-rich-rule='rule protocol value="112" accept'

然后:

firewall-cmd --reload

检查:

firewall-cmd --zone=public --list-all

注意:

VRRP 是 IP Protocol 112
不是 TCP 112
不是 UDP 112

十六、标准启动顺序

先检查 Nginx:

/app/nginx1.3/sbin/nginx -t

启动:

systemctl start nginx

确认:

systemctl is-active nginx

两台都正常后,再启动 Keepalived:

systemctl start keepalived

十七、检查 VIP

Node01:

ip addr show ens33

正常情况下:

192.168.10.11
192.168.10.10

Node02:

ip addr show ens33

正常:

192.168.10.12

也就是:

MASTER
→ 有 VIP

BACKUP
→ 无 VIP

十八、如果两台同时出现 VIP

属于异常。

重点检查:

VRRP Protocol 112
virtual_router_id
unicast_src_ip
unicast_peer
interface
防火墙

必要时抓包:

tcpdump -ni ens33 proto 112

十九、后端检查

先测后端:

for ip in 192.168.10.21 192.168.10.22 192.168.10.23; do
    echo "===== $ip ====="

    curl -sS -o /dev/null \
    --connect-timeout 3 \
    -w "6041 HTTP=%{http_code}\n" \
    http://$ip:6041/

    timeout 3 bash -c "</dev/tcp/$ip/6043" \
    && echo "6043 TCP=OK" || echo "6043 TCP=FAIL"

    timeout 3 bash -c "</dev/tcp/$ip/6060" \
    && echo "6060 TCP=OK" || echo "6060 TCP=FAIL"
done

二十、VIP 业务验证

6041:

curl -sS -o /dev/null \
--connect-timeout 3 \
-w "6041 HTTP_CODE=%{http_code}\n" \
http://192.168.10.10:6041/

6060:

curl -sS -o /dev/null \
--connect-timeout 3 \
-w "6060 HTTP_CODE=%{http_code}\n" \
http://192.168.10.10:6060/

6043:

timeout 3 bash -c '</dev/tcp/192.168.10.10/6043' \
&& echo "6043 TCP=OK" || echo "6043 TCP=FAIL"

二十一、VIP 漂移测试

假设 VIP 当前在 Node01:

ip addr show ens33 | grep 192.168.10.10

在 Node01:

systemctl stop keepalived

Node02 检查:

ip addr show ens33 | grep 192.168.10.10

应该看到 VIP。

然后再次测试:

VIP:6041
VIP:6043
VIP:6060

都正常说明:

VIP 漂移正常
备用 Nginx 正常
代理服务正常

二十二、恢复原节点

Node01:

systemctl start keepalived

由于:

nopreempt

所以 VIP 应继续留在 Node02。

此时:

Node01 → BACKUP
Node02 → MASTER

属于正常。


二十三、Nginx 故障自动恢复测试

在维护窗口:

systemctl stop nginx

观察:

tail -f /app/keepalived/logs/nginx-check.log

应该看到类似:

Nginx service is not active
Restart Nginx
Nginx restart successful

如果 Nginx 成功恢复:

VIP 不漂移

如果连续失败:

Keepalived 健康检查失败
↓
当前节点降权
↓
备用节点接管 VIP

二十四、如果代理 MySQL 等 TCP 服务

如果后端是:

MySQL
Redis
某些 TCP 中间件

不能使用:

http {
}

需要使用:

stream {
}

例如:

stream {
    upstream mysql_backend {
        server 192.168.10.21:3306;
        server 192.168.10.22:3306;
    }

    server {
        listen 3306;

        proxy_connect_timeout 5s;
        proxy_timeout 3600s;

        proxy_pass mysql_backend;
    }
}

但这里要特别注意:

MySQL 主从、双主、MGR、Galera、双向复制等架构,不能简单把所有节点无差别加入 upstream。

必须结合:

读写角色
主从角色
是否允许双写
复制机制
冲突风险

单独设计代理策略。


二十五、日常检查命令

Nginx:

systemctl is-active nginx
systemctl is-enabled nginx

/app/nginx1.3/sbin/nginx -t

Keepalived:

systemctl is-active keepalived
systemctl is-enabled keepalived

keepalived -t

VIP:

ip addr show ens33

Nginx 监听:

ss -lntp | grep nginx

Keepalived 日志:

journalctl -u keepalived -n 50 --no-pager

Nginx 日志:

journalctl -u nginx -n 50 --no-pager

二十六、新环境需要修改哪些地方

keepalived.conf

主要修改:

router_id
interface
virtual_router_id
priority
unicast_src_ip
unicast_peer
auth_pass
VIP

http-app.conf

主要修改:

VIP
业务端口
后端 IP
后端端口
upstream 名称
日志文件名

nginx.conf

如果目录结构一致:

通常基本不用改

nginx.service

如果:

/app/nginx1.3

路径一致,基本可以直接复用。

check_nginx.sh

如果服务名仍然是:

nginx

基本也可以直接复用。


二十七、以后建议保存的模板

nginx-keepalived-ha-template/
│
├── nginx/
│   ├── nginx.conf
│   ├── nginx.service
│   └── http.d/
│       └── http-app.conf
│
└── keepalived/
    ├── keepalived-master.conf
    ├── keepalived-backup.conf
    └── check_nginx.sh

以后部署新环境就是:

复制模板
↓
修改两台 Nginx IP
↓
修改 VIP
↓
修改网卡
↓
修改后端 IP
↓
修改业务端口
↓
nginx -t
↓
keepalived -t
↓
启动
↓
验证 VIP
↓
验证业务
↓
验证漂移

二十八、最终标准部署流程

以后直接照这个顺序:

1. 规划 Nginx01 IP、Nginx02 IP、VIP、网卡、后端 IP、端口

2. 安装 Nginx

3. 配置 nginx.conf

4. 配置 http-app.conf

5. 创建 nginx.service

6. systemctl daemon-reload

7. systemctl enable nginx

8. systemctl start nginx

9. nginx -t

10. 安装 Keepalived

11. 配置主节点 keepalived.conf

12. 配置备节点 keepalived.conf

13. 创建 check_nginx.sh

14. chmod 755 check_nginx.sh

15. 开放业务端口

16. 开放 VRRP Protocol 112

17. keepalived -t

18. systemctl enable keepalived

19. systemctl start keepalived

20. 确认只有一台持有 VIP

21. 检查后端服务

22. 测试 VIP 业务端口

23. 测试 VIP 漂移

24. 恢复原节点验证 nopreempt

25. 测试 Nginx 自动恢复

26. 最终确认 nginx / keepalived 均 enabled + active

二十九、这次两套环境最值得记住的经验

  1. Nginx 配置文件修改以后必须执行

    nginx -t
    systemctl reload nginx
  2. Nginx 必须加入 systemd

    否则健康检查脚本无法可靠执行 systemctl restart nginx
  3. nginx -t 成功不代表代理逻辑正确

    proxy_pass 指错 upstream 仍可能语法正常
  4. MASTER 和 BACKUP 不能同时持有 VIP

  5. VRRP 放行的是 IP Protocol 112

  6. 用了 nopreempt 就不要再配 preempt_delay

  7. 健康检查脚本只负责检查和恢复 Nginx,不负责重启 Keepalived


三十、一句话总结

通过两台 Nginx 提供代理服务,Keepalived 使用 VRRP 维护统一 VIP,并周期性调用健康检查脚本检测 Nginx 状态;当当前代理节点或 Nginx 服务异常且无法恢复时,VIP 自动漂移到备用节点,Nginx 再通过 upstream 将请求转发到后端数据库、服务或中间件集群,从而实现代理入口和后端访问链路的高可用。

更多推荐