基于 Docker+Keepalived+Nginx+Tomcat 的高可用 Web 集群
·
前言
本文将从零开始搭建一套生产环境最经典、最稳定、最简单的高可用 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_id和auth_pass是否一致
10.2 Nginx 无法访问 Tomcat
- 检查 Tomcat 容器是否正常运行
- 检查 Nginx 配置文件中的 Tomcat 地址和端口是否正确
- 检查两台服务器之间的网络是否连通
10.3 Docker 端口冲突
- 使用
netstat -tulpn命令查看端口占用情况 - 修改 Tomcat 容器的端口映射
更多推荐


所有评论(0)