前言

本文将从零开始搭建一套生产环境最经典、最稳定、最简单的高可用 Web 集群架构。全程不涉及任何复杂组件(无 Jenkins、无 Harbor、无 Redis、无 MySQL),只保留核心的 Keepalived+Nginx+Tomcat+Docker 技术栈,实现无单点故障、自动故障转移、负载均衡的企业级 Web 服务。

一、项目概述

1.1 架构设计理念

本项目采用分层设计冗余设计原则,确保架构的每一层都没有单点故障:

  • 接入层:Keepalived 实现 Nginx 双机热备,VIP 自动漂移
  • 负载均衡层:Nginx 实现请求分发和健康检查
  • 应用层:多节点 Tomcat 集群,横向扩容方便

1.2 最终架构图

用户
  |
  v
VIP: 192.168.176.200 (Keepalived自动漂移)
  |
  ┌─────────────┬─────────────┐
  | Nginx主节点 | Nginx备节点 |  双机热备,无单点故障
  | (192.168.176.137) | (192.168.176.141) |
  └─────────────┴─────────────┘
  |
  v
┌─────────┬─────────┬─────────┬─────────┐
│ Tomcat1 │ Tomcat2 │ Tomcat3 │ Tomcat4 │  4节点应用集群
│ (137:8081) │ (137:8082) │ (141:8081) │ (141:8082) │
└─────────┴─────────┴─────────┴─────────┘

1.3 架构优势

  • 绝对简单:只有 3 个核心组件,没有任何多余依赖
  • 完全高可用:Nginx 和 Tomcat 都没有单点故障
  • 性能强劲:4 个 Tomcat 节点轻松支撑 2000 + 并发
  • 维护方便:所有组件都是 Docker 容器,一行命令启动
  • 成本最低:只需要 2 台服务器即可实现完整高可用
  • 快速部署:全程 30 分钟即可完成搭建

二、服务器规划

服务器 IP 角色 部署组件 操作系统 配置要求
192.168.176.137 主节点 Docker、Nginx 容器、Tomcat1+2 容器、Keepalived 主 CentOS 7.9 2 核 4G
192.168.176.141 备节点 Docker、Nginx 容器、Tomcat3+4 容器、Keepalived 备 CentOS 7.9 2 核 4G

三、第一步:全节点环境准备

3.1 关闭防火墙和 SELinux

在两台服务器上都执行

# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 关闭SELinux
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

yum install -y yum-utils

3.2 安装 Docker

在两台服务器上都执行

# 添加阿里云Docker-ce官方源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装Docker
yum install -y docker-ce 


# 启动Docker并设置开机自启
systemctl enable --now docker

# 配置镜像加速
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://jiju9kaj2.mirror.aliyuncs.com",
    "https://docker.uj.edu.cn",
    "https://dockerproxy.com"],
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
}
EOF

# 重启Docker
systemctl daemon-reload
systemctl restart docker

# 验证安装
docker --version
 

四、第二步:部署 Tomcat 应用集群

4.1 在主节点 (192.168.176.137) 部署 Tomcat1 和 Tomcat2

# 启动Tomcat1
docker run -d \
  --name tomcat1 \
  --restart always \
  -p 8081:8080 \
  tomcat:9.0.87-jdk17-corretto

# 启动Tomcat2
docker run -d \
  --name tomcat2 \
  --restart always \
  -p 8082:8080 \
  tomcat:9.0.87-jdk17-corretto

# 验证容器状态
docker ps
# 预期输出:两个Tomcat容器都显示Up状态

4.2 在备节点 (192.168.176.141) 部署 Tomcat3 和 Tomcat4

# 启动Tomcat3
docker run -d \
  --name tomcat3 \
  --restart always \
  -p 8081:8080 \
  tomcat:9.0.87-jdk17-corretto

# 启动Tomcat4
docker run -d \
  --name tomcat4 \
  --restart always \
  -p 8082:8080 \
  tomcat:9.0.87-jdk17-corretto

# 验证容器状态
docker ps

4.3 验证 Tomcat 服务

# 在主节点执行
curl http://localhost:8081
curl http://localhost:8082

# 在备节点执行
curl http://localhost:8081
curl http://localhost:8082

注意这个 404 页面正是 Tomcat 正常运行的标志,不是错误

这是官方 Tomcat Docker 镜像的标准设计

  • 官方镜像为了精简体积,默认没有部署任何应用
  • webapps目录是空的
  • 所有示例应用都放在webapps.dist目录下
  • 当访问根路径时,Tomcat 找不到应用,就会返回它自己的 404 错误页面
可以创建一个测试页面代码(index.jsp)
1. 在主节点 (192.168.176.137) 执行
# 创建测试页面
cat > index.jsp << 'EOF'
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <title>Docker Tomcat集群测试</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
        .container { border: 2px solid #525D76; padding: 30px; border-radius: 10px; display: inline-block; }
        h1 { color: #525D76; }
        .info { font-size: 20px; margin: 15px 0; }
        .highlight { color: #ff6600; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <h1>✅ Docker Tomcat集群测试成功!</h1>
        <div class="info">当前服务器IP:<span class="highlight"><%= request.getLocalAddr() %></span></div>
        <div class="info">当前服务器端口:<span class="highlight"><%= request.getLocalPort() %></span></div>
        <div class="info">当前时间:<span class="highlight"><%= new java.util.Date() %></span></div>
        <div class="info">请求次数:<span class="highlight"><%= session.getAttribute("count") == null ? 1 : (Integer)session.getAttribute("count") + 1 %></span></div>
        <% session.setAttribute("count", session.getAttribute("count") == null ? 1 : (Integer)session.getAttribute("count") + 1); %>
    </div>
</body>
</html>
EOF


2. 批量部署到所有 4 个 Tomcat 容器

# 在主节点(137)执行
docker exec tomcat1 mkdir -p /usr/local/tomcat/webapps/ROOT
docker exec tomcat2 mkdir -p /usr/local/tomcat/webapps/ROOT
docker cp index.jsp tomcat1:/usr/local/tomcat/webapps/ROOT/
docker cp index.jsp tomcat2:/usr/local/tomcat/webapps/ROOT/

# 在备节点(141)执行
docker exec tomcat3 mkdir -p /usr/local/tomcat/webapps/ROOT
docker exec tomcat4 mkdir -p /usr/local/tomcat/webapps/ROOT
docker cp index.jsp tomcat3:/usr/local/tomcat/webapps/ROOT/
docker cp index.jsp tomcat4:/usr/local/tomcat/webapps/ROOT/
单个节点验证
# 测试主节点tomcat1
curl http://192.168.176.137:8081

# 测试主节点tomcat2
curl http://192.168.176.137:8082

# 测试备节点tomcat3
curl http://192.168.176.141:8081

# 测试备节点tomcat4
curl http://192.168.176.141:8082

五、第三步:部署 Nginx 负载均衡

5.1 在主节点 (192.168.176.137) 配置 Nginx

# 创建Nginx配置目录
mkdir -p /data/nginx/conf

# 编写Nginx配置文件
cat > /data/nginx/conf/nginx.conf << EOF
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    use epoll;
    worker_connections 65535;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;

    # Tomcat集群配置
    upstream tomcat_cluster {
        server 127.0.0.1:8081 weight=1;  # 本地Tomcat1
        server 127.0.0.1:8082 weight=1;  # 本地Tomcat2
        server 192.168.176.141:8081 weight=1;  # 远程Tomcat3
        server 192.168.176.141:8082 weight=1;  # 远程Tomcat4
        
        # 健康检查:3秒检测一次,失败2次则标记为不可用
        keepalive 32;
    }

    server {
        listen 80;
        server_name _;

        location / {
            proxy_pass http://tomcat_cluster;
            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_http_version 1.1;
            proxy_set_header Connection "";
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;
        }
    }
}
EOF

# 启动Nginx容器(使用host网络模式,与Keepalived兼容)
docker run -d \
  --name nginx \
  --restart always \
  --net=host \
  -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
  nginx:1.25-alpine

# 验证Nginx服务
curl http://localhost
# 预期输出:Tomcat默认页面

5.2 在备节点 (192.168.176.141) 配置 Nginx

配置文件与主节点完全相同,直接复制即可:

# 创建配置目录
mkdir -p /data/nginx/conf

# 从主节点复制配置文件
scp root@192.168.176.137:/data/nginx/conf/nginx.conf /data/nginx/conf/

# 启动Nginx容器
docker run -d \
  --name nginx \
  --restart always \
  --net=host \
  -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
  nginx:1.25-alpine

# 验证Nginx服务
curl http://localhost

六、第四步:部署 Keepalived 实现 Nginx 高可用

重要说明:Keepalived 直接安装在宿主机上,不要使用 Docker 容器,稳定性更高。

6.1 在主节点 (192.168.176.137) 配置 Keepalived

# 安装Keepalived
yum install -y keepalived

# 备份原始配置文件
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak

# 编写主节点配置文件
cat > /etc/keepalived/keepalived.conf << EOF
! Configuration File for keepalived

global_defs {
   router_id NGINX_MASTER  # 主节点标识
}

# Nginx健康检查脚本
vrrp_script chk_nginx {
    script "/usr/bin/curl -s http://localhost > /dev/null"
    interval 2  # 每2秒检查一次
    weight -20  # 检查失败则优先级减20
}

vrrp_instance VI_1 {
    state MASTER        # 节点角色:主节点
    interface ens33     # 你的网卡名称,使用ip addr命令查看
    virtual_router_id 51  # 虚拟路由ID,主备节点必须相同
    priority 100        # 优先级,主节点高于备节点
    advert_int 1        # 心跳间隔1秒
    authentication {
        auth_type PASS
        auth_pass 1111  # 认证密码,主备节点必须相同
    }
    virtual_ipaddress {
        192.168.176.200/24  # 虚拟IP地址(VIP)
    }
    track_script {
        chk_nginx  # 关联Nginx健康检查
    }
}
EOF

# 启动Keepalived并设置开机自启
systemctl enable --now keepalived

# 验证VIP是否绑定成功
ip addr show ens33
# 预期输出:inet 192.168.176.200/24 scope global ens33

6.2 在备节点 (192.168.176.141) 配置 Keepalived

# 安装Keepalived
yum install -y keepalived

# 备份原始配置文件
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak

# 编写备节点配置文件
cat > /etc/keepalived/keepalived.conf << EOF
! Configuration File for keepalived

global_defs {
   router_id NGINX_BACKUP  # 备节点标识
}

# Nginx健康检查脚本
vrrp_script chk_nginx {
    script "/usr/bin/curl -s http://localhost > /dev/null"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state BACKUP        # 节点角色:备节点
    interface ens33     # 你的网卡名称
    virtual_router_id 51
    priority 90         # 优先级低于主节点
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.176.200/24
    }
    track_script {
        chk_nginx
    }
}
EOF

# 启动Keepalived并设置开机自启
systemctl enable --now keepalived

七、第五步:全链路高可用验证

7.1 验证 VIP 访问

在任意电脑上打开浏览器,访问:http://192.168.176.200

  • 应该看到 Tomcat 默认页面
  • 多次刷新页面,请求会被均匀分发到 2 个 Tomcat 节点

7.2 验证 Nginx 高可用(故障自动转移)

# 1. 在主节点(137)上停止Nginx服务
docker stop nginx

# 2. 在备节点(141)上查看VIP是否漂移
ip addr show ens33
# 预期输出:inet 192.168.176.200/24 scope global ens33

# 3. 再次访问VIP,仍然可以正常访问
curl http://192.168.176.200

# 4. 恢复主节点Nginx服务
docker start nginx

# 5. 查看VIP是否自动漂移回主节点
ip addr show ens33
# 预期输出:VIP重新绑定在主节点上

7.3 验证 Tomcat 高可用

# 1. 停止任意一个Tomcat容器
docker stop tomcat1

# 2. 多次访问VIP,仍然可以正常访问
# Nginx会自动将请求转发到其他健康的Tomcat节点

# 3. 恢复Tomcat服务
docker start tomcat1

八、常见问题排查

10.1 VIP 不漂移

  • 检查两台服务器的防火墙是否关闭
  • 检查 Keepalived 配置文件中的interface是否为正确的网卡名称
  • 检查主备节点的virtual_router_idauth_pass是否一致

10.2 Nginx 无法访问 Tomcat

  • 检查 Tomcat 容器是否正常运行
  • 检查 Nginx 配置文件中的 Tomcat 地址和端口是否正确
  • 检查两台服务器之间的网络是否连通

10.3 Docker 端口冲突

  • 使用netstat -tulpn命令查看端口占用情况
  • 修改 Tomcat 容器的端口映射
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐