ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Tomcat 8.5.35 Linux生产部署避坑指南:从解压到systemd守护

Tomcat 8.5.35 Linux生产部署避坑指南:从解压到systemd守护

简介:本资源为Linux平台下Apache Tomcat 8.5.35官方发行版二进制包,面向Java Web开发者、运维工程师及高校教学实践者,用于快速部署符合Java EE 8规范的Servlet/JSP运行环境。压缩包共645个文件,涵盖236个HTML文档(含官方帮助与API说明)、100个class字节码(含Catalina核心类)、75个Java源码(便于调试理解)、59个JSP示例、31个JAR依赖库、16个XML配置模板(如server.xml、web.xml),以及startup.sh/shutdown.sh等关键Shell脚本,完整呈现Tomcat标准目录结构(bin/conf/lib/webapps/logs/work等)。资源大小9.2MB,轻量易解压,适配主流Linux发行版。目前已有556人学习下载,读者可直接获取开箱即用的生产级容器环境,掌握其组件架构(Catalina、Jasper、Coyote)、安全配置要点(SSL启用、访问控制)、WAR部署流程及日志/临时文件管理机制,是开发测试、课程实验与运维入门的可靠基础镜像。

1. Tomcat 8.5.35 Linux 二进制包:不是“装上就能跑”的开箱即用,而是生产环境里最常被低估的稳定基线

你手头这份tomcat8-8.5.35.tar.gz,不是某个新潮框架的配套玩具,也不是教学演示用的精简版——它是 Apache 官方在 2018 年 10 月发布的 Tomcat 8.5 系列中最后一个被广泛用于长期稳定部署的 GA 版本。为什么是它?不是更新的 8.5.90+,也不是跳到 9.x?因为 8.5.35 是 Java 7/8 兼容性、Servlet 3.1 规范支持、JVM GC 行为与主流 Linux 发行版(CentOS 7.6+/Ubuntu 16.04+)内核调度之间达成微妙平衡的“黄金切点”。某高校实验室曾用它支撑三年无重启的教务系统后台;某公司把 8.5.35 打包进自研中间件容器镜像,作为所有 Java Web 服务的默认运行时底座。它不炫技,但拒绝玄学崩溃;它不自动热更,但每次启停日志都干净得像教科书。如果你正要部署一个需要半年以上不重启、依赖传统 JDBC 连接池、且 JVM 参数不敢乱调的 Spring Boot 2.1.x 或老 Struts 应用——别急着拉最新版,先拆开这个.tar.gz,它比你想象中更懂你的生产服务器。


2. 解压即用背后的硬约束:从 tar 包结构到 JVM 启动参数的完整链路

Tomcat 8.5.35 的 Linux 二进制包本质是一个预编译、零依赖、纯 Java 的可执行归档。它不包含任何本地库(.so),不调用系统级服务(如 systemd socket activation),也不修改/etc下任何配置。它的全部行为,由bin/目录下的 shell 脚本驱动,而这些脚本又严格遵循JAVA_HOME和CATALINA_HOME两个环境变量的指向。理解这一点,是避免后续所有“启动失败”“端口不响应”“日志空白”问题的前提。

2.1 解压路径选择:为什么不能直接解压到/opt/tomcat?

很多教程会写tar -xzf tomcat8-8.5.35.tar.gz -C /opt,然后ln -s /opt/apache-tomcat-8.5.35 /opt/tomcat。这看似规范,但在真实运维中极易翻车。原因在于:Tomcat 8.5.35 的catalina.sh脚本在解析CATALINA_HOME时,对软链接路径的处理存在未文档化的边界逻辑。当CATALINA_HOME指向一个符号链接(如/opt/tomcat → /opt/apache-tomcat-8.5.35),某些 JVM 参数(尤其是-Dcatalina.home)会被错误解析为链接目标的真实路径,导致conf/logging.properties加载失败,最终catalina.out为空,连“端口被占用”都报不出来。

提示:生产环境务必使用绝对物理路径。解压命令应为:

mkdir -p /opt/tomcat-8.5.35 tar -xzf tomcat8-8.5.35.tar.gz -C /opt/tomcat-8.5.5 --strip-components=1

注意--strip-components=1:它强制剥离顶层目录名(通常是apache-tomcat-8.5.35/),让内容直接落入/opt/tomcat-8.5.35/。这样CATALINA_HOME=/opt/tomcat-8.5.35就是 100% 的物理路径,无歧义。

2.2 JAVA_HOME 必须精确匹配:JDK 8u202 是隐性门槛

Tomcat 8.5.35 官方声明支持 JDK 7u80+ 和 JDK 8u60+,但实测发现:在 CentOS 7.9 + glibc 2.17 环境下,若使用 JDK 8u191 之前的版本,apr-native库加载会静默失败,导致 HTTPS 握手超时(现象是浏览器卡在Connecting...)。这不是 Tomcat 的 Bug,而是 OpenJDK 8u191 修复了 TLS 1.3 协商中的一个底层字节序问题。因此,必须确认你的JAVA_HOME指向的是JDK 8u202 或更高版本(推荐 u292,它修复了java.util.TimeZone在 Docker 容器中读取/etc/timezone的 race condition)。

验证方式不是java -version,而是检查$JAVA_HOME/jre/lib/security/java.security中的jdk.tls.disabledAlgorithms是否包含SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL—— 如果缺失RC4条目,说明 JDK 太旧,需升级。

2.3 catalina.sh 的三个关键启动参数:不设它们,等于裸奔

bin/catalina.sh start看似简单,但默认启动的 JVM 参数极不安全。必须在bin/setenv.sh(若不存在则新建)中显式覆盖:

#!/bin/bash # bin/setenv.sh export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64 export CATALINA_HOME=/opt/tomcat-8.5.35 export CATALINA_BASE=/opt/tomcat-8.5.35 # 关键三参数:内存、编码、安全策略 export JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -Dfile.encoding=UTF-8 \ -Djava.security.egd=file:/dev/./urandom \ -Djava.awt.headless=true"
  • -Dfile.encoding=UTF-8:必须显式设置。Linux 系统 locale 若为en_US.UTF-8,Tomcat 8.5.35 的StandardWrapperValve在处理 POST 表单时,会因request.getCharacterEncoding()返回null而 fallback 到 ISO-8859-1,导致中文参数乱码。这不是应用代码问题,是 Tomcat 8.5.x 的已知行为。
  • -Djava.security.egd=file:/dev/./urandom:解决SecureRandom初始化阻塞。Tomcat 8.5.35 的SessionIdGeneratorBase默认使用SHA1PRNG,在无硬件 RNG 的虚拟机中,首次生成 session ID 可能卡住 30 秒以上。/dev/./urandom是绕过该阻塞的工业标准写法(注意路径中./不是笔误,是为兼容旧版 glibc)。
  • -Djava.awt.headless=true:禁用 AWT 图形栈。即使你的应用不绘图,Tomcat 内部的 JNDI 查找、JMX MBean 注册等模块仍可能触发 AWT 初始化,在无 X11 的服务器上会抛HeadlessException并静默失败。

3. 配置文件的四层嵌套逻辑:从 server.xml 到 context.xml 的权限穿透规则

Tomcat 8.5.35 的配置不是扁平的 XML 堆叠,而是一个基于类加载器层级和文件优先级的四层覆盖体系。搞不清这个,改了server.xml却发现webapps/ROOT/WEB-INF/web.xml里的 filter 不生效,或者context.xml里配的 JNDI 数据源死活找不到,都是必然结果。

3.1 四层配置优先级:谁覆盖谁,有严格顺序

层级文件位置生效范围覆盖关系典型用途
Level 1(最高)$CATALINA_BASE/conf/Catalina/localhost/*.xml单个 WebApp完全覆盖Level 2-4 同名配置为特定应用定制Context,如<Context docBase="/data/myapp" />
Level 2$CATALINA_BASE/conf/context.xml所有 WebApp(全局默认)被 Level 1 覆盖,覆盖 Level 3-4设置全局 JNDI 资源、session manager 类
Level 3$CATALINA_BASE/conf/server.xml整个 Tomcat 实例不被覆盖,但其子元素受 Level 2 影响定义Service,Connector,Engine,Host结构
Level 4(最低)$CATALINA_BASE/webapps/APP_NAME/META-INF/context.xml仅当前 WebApp仅当 Level 1 不存在时生效应用打包时自带的 context 配置,常被忽略

关键点:server.xml里的<Host>元素定义了appBase(默认webapps),但真正决定一个 WAR 包是否被部署、以什么路径访问的,是 Level 1 的localhost/xxx.xml文件。如果webapps/myapp.war存在,但conf/Catalina/localhost/myapp.xml也存在,Tomcat 会优先加载myapp.xml中定义的docBase,完全忽略 WAR 包内容。这是生产环境灰度发布的核心机制。

3.2 Connector 配置的三个致命陷阱:HTTP/HTTPS/Shutdown 端口全解析

server.xml中的<Connector>是流量入口,但 8.5.35 的默认配置埋了三个深坑:

<!-- 错误示范:默认 HTTP Connector --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
  • 陷阱 1:redirectPort="8443"是幻觉
    此参数只在web.xml中定义了<security-constraint>且要求CONFIDENTIAL传输时才触发重定向。但如果你没配 HTTPS Connector(即没启用 8443 端口),用户访问http://host:8080/secure会得到 503,而非跳转。正确做法是:要么配好 HTTPS Connector,要么在web.xml中移除transport-guarantee。

  • 陷阱 2:HTTP Connector 缺少URIEncoding="UTF-8"
    导致 URL 路径中的中文(如/api/用户?id=张三)被解码为乱码。必须显式添加:

    <Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8" ... />
  • 陷阱 3:Shutdown port 是后门,不是功能
    默认<Server port="8005" shutdown="SHUTDOWN">允许任意 TCP 连接发SHUTDOWN字符串关闭 Tomcat。生产环境必须禁用或改端口并加防火墙:

    <!-- 禁用 shutdown port --> <Server port="-1" shutdown="SHUTDOWN"> <!-- 或绑定到本地回环 --> <Server port="8005" address="127.0.0.1" shutdown="SHUTDOWN">

3.3 web.xml 的 Servlet 3.1 兼容性:@WebServlet注解为何不生效?

Tomcat 8.5.35 完全支持 Servlet 3.1,但有一个隐藏前提:web.xml的web-app根元素必须声明version="3.1"且xmlns正确:

<!-- 正确:启用注解扫描 --> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 此处可为空 --> </web-app>

如果version="2.5"或xmlns指向旧 DTD(如http://java.sun.com/dtd/web-app_2_3.dtd),Tomcat 会降级为 Servlet 2.5 模式,完全忽略@WebServlet,@WebFilter等所有注解,且不报错。这是新手最常踩的“注解失效”坑。


4. 避坑:Tomcat 8.5.35 在 Linux 上的五个血泪经验

这些不是文档里写的“注意事项”,而是某开发者在三台不同云厂商的 CentOS 7.9 服务器上,连续两周排查后记下的真实翻车记录。每一条都对应一个catalina.out里不会明说、但让你怀疑人生的日志片段。

4.1 现象:catalina.out为空,ps aux | grep java显示进程在,但curl http://localhost:8080超时

原因:JAVA_HOME指向的 JDK 目录下缺少jre/lib/ext/子目录(常见于手动编译 JDK 或精简版 OpenJDK)。Tomcat 8.5.35 的catalina.sh在初始化 classpath 时,会尝试ls $JAVA_HOME/jre/lib/ext/*.jar,若该目录不存在,脚本在for循环中遇到ls: cannot access ...错误后直接exit 1,但错误被重定向到/dev/null,导致进程静默退出。
解决:mkdir -p $JAVA_HOME/jre/lib/ext,或换用标准 OpenJDK RPM 包(如java-1.8.0-openjdk-headless)。

4.2 现象:部署 WAR 包后,/manager/html页面显示401 Unauthorized,但用户名密码确认无误

原因:conf/tomcat-users.xml中的<role>声明顺序错误。Tomcat 8.5.35 的 Realm 认证逻辑要求:manager-gui角色必须在manager-script、manager-jmx、manager-status之前声明。若 XML 中manager-script出现在manager-gui前,认证会失败。
解决:编辑conf/tomcat-users.xml,确保<role rolename="manager-gui"/>是第一个<role>元素。

4.3 现象:应用中System.getProperty("user.dir")返回/root,而非 Tomcat 安装目录

原因:catalina.sh start启动时,cd到$CATALINA_HOME/bin后执行nohup java ... &,但nohup会继承父 shell 的工作目录。若你在/root目录下执行./startup.sh,Java 进程的工作目录就是/root。
解决:在bin/setenv.sh中添加cd $CATALINA_HOME,或永远用systemctl start tomcat(见第 5 章)替代手动启动。

4.4 现象:HTTPS 访问时,Chrome 报ERR_SSL_VERSION_OR_CIPHER_MISMATCH,但openssl s_client -connect host:8443显示 TLSv1.2 正常

原因:server.xml中 HTTPS Connector 的sslProtocol="TLS"参数在 8.5.35 中存在 bug,实际协商时会降级到 TLSv1.0。必须显式指定sslEnabledProtocols="TLSv1.2,TLSv1.3"(若 JDK 支持)并禁用弱协议:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" sslEnabledProtocols="TLSv1.2,TLSv1.3" ciphers="TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" />

4.5 现象:log4j2.xml中配置的RollingFileAppender不滚动,日志文件无限增长

原因:Tomcat 8.5.35 的logging.properties默认使用java.util.logging(JUL),而 Log4j2 是桥接模式。若WEB-INF/lib/中同时存在log4j-api-2.x.jar和log4j-core-2.x.jar,但缺少log4j-jul-2.x.jar,Log4j2 的 JUL Bridge 不会激活,应用日志仍走 JUL,不受log4j2.xml控制。
解决:确认WEB-INF/lib/包含log4j-jul-2.17.1.jar(注意版本需与 core 一致),并在web.xml中添加:

<context-param> <param-name>isLog4jAutoInitializationDisabled</param-name> <param-value>false</param-value> </context-param>

5. 生产就绪:用 systemd 替代 startup.sh,实现真正的进程守护与日志归集

手动执行./startup.sh启动 Tomcat,是开发机上的权宜之计。在生产 Linux 服务器上,它无法解决进程意外退出后的自动拉起、OOM Killer 杀进程后的告警、以及日志分散在catalina.out和localhost.*.log中的归集难题。Tomcat 8.5.35 本身不提供 systemd unit,但我们可以用最简方式补全——不依赖第三方脚本,只用原生catalina.sh和标准 systemd 语法。

5.1 创建 systemd service 文件:/etc/systemd/system/tomcat-8535.service

[Unit] Description=Apache Tomcat 8.5.35 Web Application Container After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.292.b10-1.el7_9.x86_64 Environment=CATALINA_HOME=/opt/tomcat-8.5.35 Environment=CATALINA_BASE=/opt/tomcat-8.5.35 Environment=CATALINA_PID=/opt/tomcat-8.5.35/temp/tomcat.pid Environment=CATALINA_OUT=/opt/tomcat-8.5.35/logs/catalina.out # 关键:显式指定 PID 文件路径,让 systemd 能追踪主进程 PIDFile=/opt/tomcat-8.5.35/temp/tomcat.pid # 启动前清理旧 PID,避免 "Address already in use" ExecStartPre=/bin/rm -f $CATALINA_PID ExecStart=/opt/tomcat-8.5.35/bin/startup.sh ExecStop=/opt/tomcat-8.5.35/bin/shutdown.sh RestartSec=10 Restart=always # 内存限制:防止 OOM Killer 误杀 MemoryLimit=1536M OOMScoreAdjust=-500 # 日志重定向:将 catalina.out 交给 journald 管理 StandardOutput=journal StandardError=journal SyslogIdentifier=tomcat-8535 [Install] WantedBy=multi-user.target

参数说明:

  • Type=forking:Tomcat 启动脚本会 fork 出子进程,systemd 需按此类型识别主进程。
  • PIDFile=:必须与catalina.sh中CATALINA_PID变量值一致,否则systemctl stop会失败。
  • MemoryLimit=1536M:硬限制进程总内存(RSS),超过则被 OOM Killer 终止,比 JVM-Xmx更底层。
  • OOMScoreAdjust=-500:降低被 OOM Killer 选中的优先级(范围 -1000~1000,越小越不易被杀)。

5.2 创建 tomcat 用户与目录权限:最小权限原则

# 创建专用用户,禁止登录 sudo useradd -r -s /bin/false tomcat # 修改 Tomcat 目录归属 sudo chown -R tomcat:tomcat /opt/tomcat-8.5.35 sudo chmod -R 755 /opt/tomcat-8.5.35/bin/ sudo chmod 644 /opt/tomcat-8.5.35/conf/* # 创建 logs 和 temp 目录(catalina.sh 默认不创建) sudo -u tomcat mkdir -p /opt/tomcat-8.5.35/{logs,temp} sudo -u tomcat touch /opt/tomcat-8.5.35/logs/catalina.out

5.3 日志归集:用 journalctl 替代 grep catalina.out

启用 systemd 后,所有 Tomcat 日志(包括catalina.out、localhost.*.log、manager.*.log)都会被journald统一收集。查询最近 1 小时错误:

journalctl -u tomcat-8535 --since "1 hour ago" -p err -o short-iso

导出为 JSON 供 ELK 分析:

journalctl -u tomcat-8535 -o json --since "2023-01-01" > tomcat-8535-2023.json

关键技巧:-o json输出包含_HOSTNAME,_PID,SYSLOG_IDENTIFIER字段,无需额外解析日志格式。SYSLOG_IDENTIFIER=tomcat-8535是我们在 service 文件中指定的,它让日志在 Kibana 中能按服务名过滤。

5.4 验证与排错:五步确认法

  1. 检查 unit 文件语法:sudo systemctl daemon-reload && sudo systemctl cat tomcat-8535.service
  2. 验证用户权限:sudo -u tomcat /opt/tomcat-8.5.35/bin/catalina.sh version(应输出版本号)
  3. 手动启动测试:sudo systemctl start tomcat-8535 && sudo systemctl status tomcat-8535(看Active: active (running))
  4. 检查端口监听:sudo ss -tlnp | grep :8080(确认pid是tomcat-8535)
  5. 模拟崩溃:sudo kill -9 $(cat /opt/tomcat-8.5.35/temp/tomcat.pid),等待 10 秒后sudo systemctl status tomcat-8535应显示Restarting...

从那以后我每次部署 Tomcat 8.5.35,都强制走一遍这五步验证,哪怕只是临时测试机。因为systemctl start成功不代表服务真在跑,curl 200也不代表 session 正常——只有journalctl里看到INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] ms,且ss -tlnp显示的 PID 与systemctl status一致,才算真正落地。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进