Appearance
Tomcat 部署
Nginx 处理静态文件和反向代理在行,但它不会执行业务代码。Java Web 应用打包成 WAR 文件后,需要一个能加载 Java 应用、处理 Servlet 逻辑、执行 Java 代码的环境——Tomcat 就是干这个的。
典型链路是 Nginx 在前面接请求、转发给 Tomcat、Tomcat 跑 Java 应用:
一、核心对象
部署 Java Web 应用,要分清几个对象,它们各管一摊:
| 对象 | 做什么 | 运维关注什么 |
|---|---|---|
| JDK/JRE | Java 运行环境 | 版本、路径、JVM 内存参数 |
| Tomcat | Servlet 容器 | 端口、线程、日志、部署目录 |
| WAR | Java Web 应用包 | 发布包路径、解压目录、回滚版本 |
| Connector | Tomcat 接收 HTTP 请求的入口 | HTTP 端口、线程池、超时 |
| Context | 应用的访问路径 | /、/app 这类路径映射 |
部署可以拆成两层看:机器上先有稳定的 Java 和 Tomcat 基础环境,再把业务 WAR 放进去。两层混在一起,发布和回滚会互相牵连——WAR 没问题但 Java 升级了导致起不来,分不清是哪一层的锅。
二、安装 JDK
bash
# RHEL / Rocky / CentOS
yum install -y java-17-openjdk java-17-openjdk-devel
java -version
# Debian / Ubuntu
apt update
apt install -y openjdk-17-jdk
java -version多版本 Java 并存时确认实际使用的是哪个:
bash
readlink -f "$(which java)" # 看 java 命令最终指向的路径Tomcat 和 JDK 版本要匹配。Tomcat 10 使用了 Jakarta EE 命名空间(jakarta.servlet),如果应用还停留在 javax.servlet,直接迁到 Tomcat 10 会启动失败——这类应用通常停在 Tomcat 9。
三、安装 Tomcat
创建运行用户,用软链接管理版本:
bash
useradd -r -s /sbin/nologin tomcat
tar -xf apache-tomcat-9.0.x.tar.gz -C /opt/
ln -s /opt/apache-tomcat-9.0.x /opt/tomcat # 软链接便于后续升级切换
chown -R tomcat:tomcat /opt/apache-tomcat-9.0.x软链接的好处是升级时换链接指向就行,配置和脚本里写死的 /opt/tomcat 路径不用动。
目录结构:
| 目录 | 用途 |
|---|---|
bin/ | 启停脚本(startup.sh、shutdown.sh、catalina.sh) |
conf/ | 配置文件(server.xml、web.xml 等) |
logs/ | Tomcat 自身日志和应用控制台输出 |
webapps/ | 默认的应用部署目录 |
work/ | JSP 编译产生的临时文件 |
temp/ | 临时文件 |
lib/ | Tomcat 和应用共享的 jar 库 |
业务应用包和 Tomcat 程序目录分开管理更清晰——WAR 放在独立的数据目录,通过 Context 指向。直接把所有 WAR 堆在 webapps 里能跑,但多版本回滚、权限控制和清理都比较乱。
四、Connector 配置
/opt/tomcat/conf/server.xml 是核心配置文件。HTTP Connector 示例:
xml
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="200"
maxConnections="8192"
acceptCount="100" />这几个数字不是同一层东西,容易混:
| 字段 | 做什么 |
|---|---|
port | Tomcat 监听端口 |
protocol | HTTP 协议处理实现 |
connectionTimeout | 连接建立后等待请求的超时(毫秒) |
maxThreads | 最大工作线程数——同时处理多少个请求 |
maxConnections | 允许同时保持的最大连接数(含处理中和排队的) |
acceptCount | 连接数达到 maxConnections 后,OS 层面还能排多少连接 |
redirectPort | 需要 HTTPS 跳转时指向的端口 |
maxConnections 控制"能进多少连接",maxThreads 控制"有多少线程在真正处理请求",acceptCount 是"满了之后还能在门外排多少人"。三个一起决定 Tomcat 能扛多少并发。
如果前面有 Nginx,Tomcat 通常只监听本机,不直接对外暴露:
xml
<Connector port="8080"
address="127.0.0.1"
protocol="HTTP/1.1"
... />不绑 127.0.0.1 会怎样:Tomcat 8080 端口直接对外,能绕过 Nginx 的限流、日志、TLS,等于后门。
五、systemd 管理
直接用 startup.sh 启动的 Tomcat,重启后不会自动拉起。生产环境要用 systemd 托管:
ini
# /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-17-openjdk"
Environment="CATALINA_HOME=/opt/tomcat"
Environment="CATALINA_BASE=/opt/tomcat"
Environment="CATALINA_PID=/run/tomcat.pid"
Environment="CATALINA_OPTS=-Xms512m -Xmx512m"
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target启动并验证:
bash
systemctl daemon-reload
systemctl enable --now tomcat
systemctl status tomcat --no-pager
ss -lntp | grep ':8080'
curl -I http://127.0.0.1:8080/JAVA_HOME 路径在不同发行版差异很大。unit 写好后先看 systemctl status 和 journalctl 里的 Java 路径错误,比直接翻 catalina.out 更快定位到启动失败原因。
CATALINA_HOME 和 CATALINA_BASE 的区别:
| 变量 | 指向什么 | 典型内容 |
|---|---|---|
CATALINA_HOME | Tomcat 程序目录 | bin/、lib/ 等静态程序文件 |
CATALINA_BASE | 实例运行目录 | conf/、logs/、webapps/、temp/ |
单实例部署两者指向同一个目录最省事。多实例部署时,多个实例共享同一个 CATALINA_HOME(同一份 Tomcat 程序),各自用独立的 CATALINA_BASE(端口、日志、应用包不互相混)。
六、部署 WAR
最简单的部署是把 WAR 丢进 webapps:
bash
cp app.war /opt/tomcat/webapps/app.war
chown tomcat:tomcat /opt/tomcat/webapps/app.warTomcat 默认会自动解压并部署。访问路径由 WAR 文件名决定:
| WAR 文件 | 访问路径 |
|---|---|
app.war | /app |
ROOT.war | /(根路径) |
生产环境更推荐分离部署——WAR 放在独立的数据目录,通过 Context 文件指向:
bash
mkdir -p /data/apps/app/releases/20260522-001
cp app.war /data/apps/app/releases/20260522-001/app.war
ln -sfn /data/apps/app/releases/20260522-001/app.war /data/apps/app/current.warContext 配置(/opt/tomcat/conf/Catalina/localhost/app.xml):
xml
<Context docBase="/data/apps/app/current.war"
reloadable="false" />reloadable="false" 在生产环境是必要的——设为 true 时 Tomcat 会持续监控类文件变化,增加额外开销。发布应该交给明确的部署流程,不是靠 Tomcat 自动检测。
分离部署的好处是回滚快:当前版本有问题,把 current.war 软链接指向上一个 release 目录、重启 Tomcat 就回去了,WAR 文件本身没动。
七、日志
| 文件 | 内容 |
|---|---|
logs/catalina.out | 控制台输出,很多应用日志也落在这里 |
logs/catalina.YYYY-MM-DD.log | Tomcat 自身的 Catalina 引擎日志 |
logs/localhost.YYYY-MM-DD.log | 应用上下文相关的日志 |
logs/localhost_access_log.*.txt | Tomcat 的访问日志 |
查看:
bash
tail -f /opt/tomcat/logs/catalina.out
tail -f /opt/tomcat/logs/localhost_access_log.*.txt
journalctl -u tomcat -n 100 --no-pagercatalina.out 很容易变得很大——它不自动切割。如果应用已经用 logback/log4j 输出了独立日志文件,控制台输出要控制好,避免 catalina.out 长期不切割占满磁盘。
八、Nginx 代理
Tomcat 装好后,前面还是要有 Nginx 接 80 端口、转发到 8080:
nginx
upstream tomcat_app {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name app.example.local;
location / {
proxy_pass http://tomcat_app;
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;
}
}如果应用部署在 /app 路径下,Nginx 代理的路径要和 Context 对上:
nginx
location /app/ {
proxy_pass http://tomcat_app/app/;
}路径错位时,Tomcat 可能返回 404,或者页面能打开但 CSS/JS 全 404——因为静态资源的路径拼出来的 URL 不符合实际路由。排查时看 Nginx access log 里的 URI、Tomcat access log 里的请求路径、以及浏览器开发者工具的 Network 面板,三条线对一下。
九、部署问题
| 现象 | 常见原因 | 排查入口 |
|---|---|---|
| Tomcat 启动失败 | Java 路径错、端口被占用、XML 配置语法错 | journalctl -u tomcat、catalina.out |
| WAR 不解压 | 权限不足、磁盘满、WAR 包损坏 | logs/、webapps/ 目录权限 |
| 访问 404 | Context 路径不对、Nginx 代理路径不匹配 | Nginx access log + Tomcat access log |
| 访问 502 | Nginx 连不上 Tomcat | ss -lntp 确认 8080 在监听 |
| 启动很慢 | 应用初始化慢、数据库连接池初始化慢 | 应用日志、网络连通性 |
| 改配置不生效 | 没 reload/restart、改错了 CATALINA_BASE 目录 | systemctl status、启动参数确认 |
每次发布记录中保留:Java 版本、Tomcat 版本、WAR 包构建号、配置变更内容、JVM 启动参数和回滚包的路径。Java 应用出问题时,这些信息经常比"服务启动了还是没启动"更关键。