1. 项目概述与核心价值
在中小型企业的IT基础设施演进过程中,一个非常典型的场景是:随着业务发展,从最初单一应用的单机部署,逐渐演变为需要在同一台服务器上运行多个独立的Java Web应用。这些应用可能分属不同部门,或者是一个大项目下的不同微服务模块。直接的想法可能是为每个应用购买一台新服务器,但这无疑会带来巨大的硬件成本和运维复杂度。更经济、更高效的做法,就是在一台物理机或虚拟机上,通过配置多个Tomcat实例,并利用Nginx进行统一的流量分发和管理,实现“一个IP,多个端口,多个项目”的并行部署。
这听起来像是基础操作,但实际操作中,从端口规划、Tomcat实例隔离、到Nginx配置的精准转发,每一步都有不少细节需要打磨。比如,如何避免多个Tomcat实例的环境变量冲突?如何优雅地管理各自的日志而不至于混乱?Nginx的upstream配置怎样写才能兼顾健康检查和负载均衡(哪怕目前是单机)?这些细节处理不好,轻则应用启动失败,端口占用,重则服务不稳定,排查问题如同大海捞针。
我经历过多次从零搭建这类环境,也处理过不少因为配置不当导致的线上问题。今天,我就以一个实战者的角度,抛开那些教科书式的理论,把从服务器准备、到多个Tomcat实例的独立部署与配置、再到Nginx反向代理的完整链条,连同我踩过的坑和总结的最佳实践,一次性讲清楚。无论你是运维工程师、后端开发者,还是需要自己搭建测试环境的技术负责人,这篇教程都能给你提供一份可直接“抄作业”的详细方案。
2. 环境规划与核心思路拆解
在动手之前,清晰的规划是成功的一半。盲目操作只会导致后续的配置混乱和难以维护。
2.1 部署架构设计
我们的目标架构非常明确:一台Linux服务器(以CentOS 7.x为例),一个对外的IP地址(例如192.168.1.100)。在这台服务器上,我们将部署两个独立的Tomcat实例,分别运行项目A和项目B。Nginx作为唯一的对外入口,监听标准的HTTP 80端口。用户访问不同的域名(或路径),由Nginx根据规则,将请求转发到对应Tomcat实例的监听端口上。
具体规划如下:
- 服务器IP:
192.168.1.100 - Nginx:监听
80端口。 - Tomcat实例1 (项目A):安装目录
/usr/local/tomcat-8081,监听8081端口,应用部署在webapps/ROOT或自定义目录。 - Tomcat实例2 (项目B):安装目录
/usr/local/tomcat-8082,监听8082端口,应用部署在webapps/ROOT或自定义目录。
注意:这里的关键是“实例隔离”。不是简单复制
webapps目录,而是复制整个Tomcat安装目录,并修改其核心配置文件,确保每个实例的端口、日志、临时文件等完全独立,互不干扰。
2.2 工具与软件版本选择
- 操作系统:CentOS 7.9 Minimal。选择稳定版,减少未知兼容性问题。
- JDK:OpenJDK 1.8。这是Tomcat 8/9的黄金搭档,社区支持最好。建议使用
yum安装,方便管理。 - Tomcat:Apache Tomcat 9.0.x。选择长期支持版本,避免使用过于前沿或已停止维护的版本。我们将从官网下载二进制包(
tar.gz)进行安装,这样灵活性最高。 - Nginx:Nginx 1.20.x。同样选择稳定版。我们将通过官方Yum仓库安装,便于后续升级和管理。
为什么选择源码/二进制包安装Tomcat,而用Yum安装Nginx?因为Tomcat需要多实例,每个实例的配置(尤其是端口)需要定制化修改,二进制包解压即用,方便复制和修改。而Nginx作为反向代理,配置通常集中在nginx.conf及其包含的conf.d/*.conf文件中,用Yum安装服务管理更规范(systemctl)。
3. 基础环境与Tomcat多实例部署
有了规划,我们开始一步步实施。首先准备基础环境,然后部署两个Tomcat实例。
3.1 服务器基础环境准备
以root用户或具有sudo权限的用户登录服务器。
更新系统并安装必要工具:
yum update -y yum install -y wget vim net-tools安装JDK:
yum install -y java-1.8.0-openjdk-devel安装完成后,验证版本:
java -version # 输出应类似:openjdk version "1.8.0_412"检查
JAVA_HOME,通常Yum安装的OpenJDK路径是/usr/lib/jvm/java-1.8.0-openjdk。如果需要,可以将其添加到环境变量,但对于Tomcat来说,只要java命令可用即可。
3.2 部署第一个Tomcat实例(端口8081)
我们将以第一个实例为模板,详细说明步骤,第二个实例则快速带过。
下载并解压Tomcat:
cd /usr/local/src wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85.tar.gz tar -zxvf apache-tomcat-9.0.85.tar.gz -C /usr/local/创建第一个实例目录并复制:
cd /usr/local mv apache-tomcat-9.0.85 tomcat-8081 # 重命名以端口区分配置第一个实例(关键步骤): 进入实例目录,我们需要修改
conf/server.xml来改变其默认的8080端口,避免冲突。cd /usr/local/tomcat-8081/conf vim server.xml找到以下三个连接器(Connector)配置,并修改端口:
- HTTP/1.1 Connector:这是主要的应用访问端口,将
port="8080"改为port="8081"。<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> - AJP Connector:如果不用AJP协议(通常与Nginx配合用HTTP即可),可以注释掉或将其端口
port="8009"改为18009之类的非冲突端口。 - Shutdown Port:这是Tomcat的关闭监听端口,将
port="8005"改为port="8006"。这一点非常重要!如果多个实例使用相同的关闭端口,你将无法正常关闭任何一个实例。<Server port="8006" shutdown="SHUTDOWN">
- HTTP/1.1 Connector:这是主要的应用访问端口,将
(可选)优化配置:
- 日志分割:默认
catalina.out会无限增长。建议使用logrotate服务来管理,或者修改bin/catalina.sh脚本,使用cronolog等工具按日期分割。一个简单的logrotate配置示例(/etc/logrotate.d/tomcat-8081):/usr/local/tomcat-8081/logs/catalina.out { daily rotate 30 copytruncate missingok compress delaycompress notifempty create 644 tomcat tomcat } - 内存调整:根据应用需要,修改
bin/catalina.sh,在文件开头添加:export JAVA_OPTS="-server -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m"
- 日志分割:默认
创建专用系统用户并授权(安全最佳实践):
groupadd tomcat useradd -s /bin/false -g tomcat -d /usr/local/tomcat-8081 tomcat chown -R tomcat:tomcat /usr/local/tomcat-8081 chmod +x /usr/local/tomcat-8081/bin/*.sh创建Systemd服务文件(推荐): 使用
systemctl管理服务比直接运行脚本更规范。创建文件/etc/systemd/system/tomcat-8081.service:[Unit] Description=Apache Tomcat 9 Instance for Port 8081 After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment="JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk" Environment="CATALINA_PID=/usr/local/tomcat-8081/temp/tomcat.pid" Environment="CATALINA_HOME=/usr/local/tomcat-8081" Environment="CATALINA_BASE=/usr/local/tomcat-8081" ExecStart=/usr/local/tomcat-8081/bin/startup.sh ExecStop=/usr/local/tomcat-8081/bin/shutdown.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target实操心得:
Type=forking和CATALINA_PID的配置是关键。Tomcat的启动脚本是forking模式的,systemd需要通过PID文件来跟踪主进程。确保temp目录存在且Tomcat用户有写权限,否则服务会启动失败且systemctl status看不到有用信息。启动并测试第一个实例:
systemctl daemon-reload systemctl start tomcat-8081 systemctl enable tomcat-8081 systemctl status tomcat-8081使用
curl或在浏览器访问http://服务器IP:8081,应该能看到Tomcat的默认主页。
3.3 快速部署第二个Tomcat实例(端口8082)
有了第一个实例作为模板,第二个实例的部署就快很多。
复制实例目录:
cd /usr/local cp -rp tomcat-8081 tomcat-8082 chown -R tomcat:tomcat /usr/local/tomcat-8082 # 重新授权修改第二个实例的配置:
cd /usr/local/tomcat-8082/conf vim server.xml修改端口:
- 将HTTP连接器端口从
8081改为8082。 - 将Shutdown端口从
8006改为8007(或其他未占用的端口)。 - AJP端口也相应修改,如从
18009改为18010。
- 将HTTP连接器端口从
修改第二个实例的Systemd服务文件: 复制并修改服务文件
/etc/systemd/system/tomcat-8082.service,主要修改Description、CATALINA_PID、CATALINA_HOME、CATALINA_BASE、ExecStart、ExecStop这些路径和端口相关的变量,指向tomcat-8082目录。启动并测试第二个实例:
systemctl daemon-reload systemctl start tomcat-8082 systemctl enable tomcat-8082 curl http://localhost:8082
至此,两个Tomcat实例已经在8081和8082端口独立运行。你可以将你的项目A的WAR包放到/usr/local/tomcat-8081/webapps/ROOT目录下(删除原有ROOT),将项目B的WAR包放到/usr/local/tomcat-8082/webapps/ROOT下,然后重启对应的Tomcat服务即可。
4. Nginx安装与反向代理配置
现在,两个后端应用已经就绪,我们需要在它们前面架设Nginx作为统一入口。
4.1 安装与启动Nginx
配置Nginx官方Yum仓库:
cat > /etc/yum.repos.d/nginx.repo << 'EOF' [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true EOF安装并启动Nginx:
yum install -y nginx systemctl start nginx systemctl enable nginx firewall-cmd --permanent --add-service=http # 开放80端口防火墙 firewall-cmd --reload访问
http://服务器IP,应能看到Nginx欢迎页。
4.2 配置反向代理(基于域名或路径)
Nginx的核心配置位于/etc/nginx/nginx.conf。最佳实践是不要在主配置文件中直接修改,而是在/etc/nginx/conf.d/目录下为每个站点创建独立的.conf文件,这样清晰且易于管理。
场景一:基于不同域名的代理(推荐)假设我们有两个域名:app-a.yourdomain.com指向项目A,app-b.yourdomain.com指向项目B。
创建项目A的配置文件:
vim /etc/nginx/conf.d/app-a.conf写入以下内容:
server { listen 80; server_name app-a.yourdomain.com; # 你的域名A # 访问日志和错误日志单独存放,便于排查 access_log /var/log/nginx/app-a.access.log main; error_log /var/log/nginx/app-a.error.log warn; location / { proxy_pass http://localhost:8081; # 转发到Tomcat实例A 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 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering off; # 对于需要流式响应或大文件上传的应用,建议关闭缓冲 } # 可选:静态资源缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ { proxy_pass http://localhost:8081; expires 30d; add_header Cache-Control "public, immutable"; } }创建项目B的配置文件:
vim /etc/nginx/conf.d/app-b.conf内容类似,只需修改
server_name和proxy_pass的端口:server { listen 80; server_name app-b.yourdomain.com; # 你的域名B access_log /var/log/nginx/app-b.access.log main; error_log /var/log/nginx/app-b.error.log warn; location / { proxy_pass http://localhost:8082; ... # 其他proxy_set_header等配置同上 } }
场景二:基于同一域名不同路径的代理如果只有一个域名,可以通过路径来区分。例如,yourdomain.com/app-a/和yourdomain.com/app-b/。
配置文件/etc/nginx/conf.d/yourdomain.conf:
server { listen 80; server_name yourdomain.com; location /app-a/ { proxy_pass http://localhost:8081/; # 注意结尾的斜杠! proxy_set_header Host $host; ... # 其他配置 # 重写请求路径,去掉前缀(如果应用上下文不是/app-a) # rewrite ^/app-a/(.*)$ /$1 break; } location /app-b/ { proxy_pass http://localhost:8082/; # 注意结尾的斜杠! proxy_set_header Host $host; ... # 其他配置 } # 根路径可以指向一个默认应用或静态页面 location = / { root /usr/share/nginx/html; index index.html; } }关键细节:
proxy_pass指令中,如果后端地址以斜杠结尾,Nginx会将匹配到的location路径部分从请求URI中剥离后再转发。例如,访问/app-a/api/user,如果proxy_pass是http://localhost:8081/,则转发给Tomcat的请求是/api/user。如果不加斜杠,则转发为/app-a/api/user。这需要与你的应用部署上下文(Context Path)相匹配,否则会出现404。
- 测试配置并重载Nginx:
nginx -t # 测试配置文件语法,确保无误 systemctl reload nginx # 平滑重载配置,不影响已有连接
现在,当你访问http://app-a.yourdomain.com时,请求会被Nginx转发到本机的8081端口,即项目A;访问http://app-b.yourdomain.com则转发到8082端口的项目B。对外只需要暴露80端口,实现了端口的统一和应用的隔离。
5. 高级配置、优化与问题排查
基础部署完成后,为了提升稳定性和可维护性,还需要进行一些优化配置。
5.1 使用Upstream模块进行负载均衡与健康检查
即使目前每个后端只有一个Tomcat实例,使用upstream模块也是一个好习惯,它为未来扩展(如增加实例)和配置健康检查提供了便利。
修改Nginx配置,在http块内(通常在/etc/nginx/nginx.conf中)定义upstream:
http { ... # 定义Tomcat服务器组 upstream backend_app_a { server localhost:8081 max_fails=3 fail_timeout=30s; # 未来可以在这里添加更多server,实现负载均衡 # server 192.168.1.101:8081 weight=2; } upstream backend_app_b { server localhost:8082 max_fails=3 fail_timeout=30s; } ... }然后修改各自的server块中的location:
location / { proxy_pass http://backend_app_a; # 引用upstream名称 ... }max_fails和fail_timeout参数定义了在30秒内,如果连接到该后端服务器失败3次,Nginx会将其标记为不可用30秒,实现了基本的被动健康检查。
5.2 Tomcat应用部署与上下文路径管理
如何将你的WAR包部署到Tomcat?有几种方式:
- 直接复制到
webapps目录:将your-app.war复制到/usr/local/tomcat-8081/webapps/下,Tomcat启动时会自动解压。访问路径为http://域名:端口/your-app。如果想用根路径访问,可以将WAR包重命名为ROOT.war,或者删除webapps/ROOT目录后,将你的WAR包解压并重命名为ROOT。 - 修改
server.xml中的Context:在<Host>标签内添加<Context path="" docBase="/path/to/your/exploded/war" />,并设置path=""来指定根上下文。这种方式更灵活,但需要手动管理应用文件。 - 使用
CATALINA_BASE/conf/Catalina/localhost/目录:创建一个XML文件(如yourapp.xml),内容为<Context docBase="/path/to/your/war" />。这种方式支持热部署,是推荐的方式。
避坑指南:如果你的应用是Spring Boot打包的包含内嵌容器的可执行JAR(
spring-boot-starter-web),它不能直接部署到外置Tomcat。你需要将打包方式改为war,并排除内嵌的Tomcat依赖。这是一个常见的部署错误。
5.3 常见问题与排查技巧实录
即使按照教程操作,也可能会遇到问题。这里记录几个我踩过的坑和排查思路。
问题1:Nginx报错502 Bad Gateway这是最常见的问题,意味着Nginx无法连接到后端Tomcat。
- 排查步骤:
- 检查Tomcat是否运行:
systemctl status tomcat-8081,查看状态和日志(journalctl -u tomcat-8081)。 - 检查端口监听:
netstat -tlnp | grep 8081,确认Tomcat进程是否在监听8081端口。 - 检查防火墙/SELinux:CentOS 7防火墙可能阻止了Nginx(或外部)访问8081端口。用
firewall-cmd --list-all查看,或临时关闭防火墙测试systemctl stop firewalld。SELinux也可能阻止,可尝试setenforce 0临时关闭测试。 - 检查Nginx配置:确认
proxy_pass地址和端口是否正确。用curl -v http://localhost:8081直接在服务器上测试后端是否可达。 - 查看Nginx错误日志:
tail -f /var/log/nginx/error.log,通常会有更具体的连接失败原因。
- 检查Tomcat是否运行:
问题2:应用静态资源(CSS, JS, 图片)加载404浏览器能访问页面,但样式全无,控制台报资源404。
- 原因与解决:
- 路径问题:页面中资源链接使用的是绝对路径或错误的相对路径。确保你的前端资源路径正确,或者使用
<base href="/">标签。 - Nginx配置未处理静态资源:如果静态资源由Tomcat提供,确保Nginx的
proxy_pass配置正确,且没有因为location匹配规则导致静态资源请求被错误处理。可以像前面示例一样,为静态资源单独配置一个带缓存的location块。 - 应用上下文路径不匹配:如果你的应用部署在非根上下文(如
/myapp),但前端资源请求路径没有包含这个上下文,就会404。需要检查应用打包配置和Nginx的proxy_pass规则(是否使用了结尾斜杠)。
- 路径问题:页面中资源链接使用的是绝对路径或错误的相对路径。确保你的前端资源路径正确,或者使用
问题3:Tomcat启动失败,提示端口已占用
- 排查:使用
netstat -tlnp | grep <端口号>查找是哪个进程占用了你配置的端口(8081, 8082, 8005, 8006等)。可能是另一个Tomcat实例,也可能是其他应用。修改冲突的端口号。
问题4:Session丢失或混乱(特别是在基于路径代理时)
- 原因:如果应用使用Cookie存储Session ID,而Cookie的Path属性默认是当前路径。当通过
/app-a/访问时,Cookie的Path可能是/app-a/,导致切换到/app-b/时Session不共享(这通常是期望行为)。但如果同一个应用通过不同路径访问,就会导致Session丢失。 - 解决:在Nginx的
proxy_cookie_path指令中重写Cookie的Path。例如,如果应用在/app-a下设置了Path为/app-a的Cookie,你想让它对整个域名有效,可以设置:proxy_cookie_path /app-a /;。这需要谨慎评估安全性。
问题5:上传大文件失败或超时
- 解决:需要调整Nginx和Tomcat两端的超时和大小限制。
- Nginx:在
location块中增加:client_max_body_size 100M; # 允许上传的最大body大小 proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; - Tomcat:在
conf/server.xml的对应<Connector>中增加:connectionTimeout="20000" maxPostSize="104857600" # 100MB disableUploadTimeout="false" uploadTimeout="300000" # 5分钟
- Nginx:在
日志排查黄金命令:
- 实时查看Nginx访问日志:
tail -f /var/log/nginx/app-a.access.log - 实时查看Nginx错误日志:
tail -f /var/log/nginx/error.log - 实时查看Tomcat应用日志:
tail -f /usr/local/tomcat-8081/logs/catalina.out或应用自身的日志文件(如localhost.log,yourapp.log)。 - 查看系统服务状态:
systemctl status nginx和systemctl status tomcat-8081,journalctl -u tomcat-8081 --since "10 minutes ago"。
6. 安全加固与维护建议
部署完成后,安全加固是必不可少的一步。
Tomcat安全:
- 删除默认管理页面:生产环境应删除
webapps目录下的docs,examples,host-manager,manager应用,减少攻击面。 - 使用强密码:如果必须使用Manager应用,务必修改
conf/tomcat-users.xml中的密码,并限制访问IP。 - 更新版本:定期关注Tomcat安全公告,及时更新到稳定版本。
- 删除默认管理页面:生产环境应删除
Nginx安全:
- 隐藏版本号:在
nginx.conf的http块中添加server_tokens off;。 - 限制请求方法:在不需要的
location中限制HTTP方法,如if ($request_method !~ ^(GET|POST|HEAD)$) { return 405; }。 - 配置HTTPS:使用Let‘s Encrypt等免费证书为域名配置HTTPS,这是现代网站的标配。这涉及到在Nginx中配置SSL证书和将HTTP重定向到HTTPS。
- 隐藏版本号:在
系统层面:
- 防火墙:只开放必要的端口(80, 443, SSH)。关闭Tomcat的管理端口(如8005)对公网的访问。
- 非root用户运行:我们已经使用
tomcat用户运行Tomcat,确保Nginx也以非root用户(通常是nginx)运行。 - 定期备份:备份Nginx配置(
/etc/nginx)、Tomcat配置(各实例的conf目录)和应用数据。
监控与维护:
- 日志轮转:如前所述,配置
logrotate防止日志撑满磁盘。 - 进程监控:可以使用
supervisor或systemd的自带监控功能,确保服务异常退出后能自动重启。 - 性能监控:关注服务器的CPU、内存、磁盘IO和网络流量。可以使用
top,htop,iftop,iotop等命令,或搭建更专业的监控系统如Prometheus+Grafana。
- 日志轮转:如前所述,配置
整个部署流程从规划到安全加固,虽然步骤繁多,但每一步都有其必要性。遵循“规划-部署-配置-优化-安全”这个流程,可以帮你搭建一个结构清晰、易于维护、稳定可靠的多项目部署环境。记住,配置文件是最好的文档,每次修改前做好备份,修改后充分测试。