资讯详情

资讯详情

Tomcat版本升级全流程:从选型配置到回滚避坑指南

Tomcat 版本升级站在外面看就是“下载新包、解压、启动”三件事可真正动手做一次你就会明白这套流程里的坑全藏在细节里。前阵子我负责的一组线上服务要响应安全整改把老实例从 9.0.30 往 9.0.x 最新维护版迁同时给新项目搭了 Tomcat 10.1 的环境。这两条线走下来从版本选型、配置迁移、应用兼容到回滚预案算是把完整路径都摸了一遍。这篇就按我的实际操作顺序讲适合手里管着几台 Linux 服务器、又不想在凌晨被报警电话叫醒的运维和开发同学参考。1. 为什么我建议认真对待 Tomcat 版本升级1.1 安全漏洞是最直接的原因Tomcat 是互联网上暴露面最大的 Java 容器之一攻击者扫描器对它的敏感路径、特殊后缀、AJP 端口这类特征非常敏感。只要某个漏洞进了公共漏洞库扫描工具大概率会在几天内更新对应检测规则如果你还停在老版本上基本等于裸奔。前几年闹得比较大的 AJP 通道默认配置问题、请求走私类问题受影响的主要就是迟迟不升级的老实例。有人觉得“我内网部署外网打不进来”但内网横向移动同样会利用容器漏洞尤其在后渗透阶段攻击者拿到一台跳板机之后第一件事就是扫内网里的 Web 中间件版本。与其等到被扫出来不如把升级当成常规运维动作而不是安全整改通知下来才手忙脚乱。另外Tomcat 每个大版本都有维护期维护期内主要是修 bug 和安全补丁维护期结束后基本不会再发补丁。也就是说只要你拖得够久最终会面对“知道有漏洞但官方已经不管了”的尴尬局面。早升早安心这句话在中间件这儿是成立的。1.2 版本选型的几个判断标准不是越新越好选哪个版本取决于你现有应用的依赖基线。Tomcat 主要版本的差异我用一张表说清楚版本对应的 Servlet / Jakarta 标准最低 JDK 要求API 命名空间适合场景8.5.xServlet 3.1 / Java EE 7JDK 7后期要求 JDK 8javax.servlet非常老的遗留系统能不动就不动9.0.xServlet 4.0 / Java EE 8JDK 8javax.servlet存量传统项目最稳的选择Spring 5 及更早的框架10.1.xServlet 6.0 / Jakarta EE 10JDK 11jakarta.servlet新项目、Spring Boot 3 / Java 17 环境11.0.xServlet 6.1 / Jakarta EE 11JDK 17jakarta.servlet想激进跟随最新生态、团队有能力跟上游买单的场景我这里的建议很直接老项目升级优先落在 9.0.x 这条线因为它还停留在 javax.servlet 命名空间老代码不需要动包名新项目从第一天就用 10.1.x因为新一代 Java 生态默认都是 jakarta 了没必要再用老容器给自己挖坑。1.3 先看清应用兼容性再动手动手前最重要的一件事是搞清楚你的应用到底是“什么时代的产物”。方法很简单把 war 包解出来看 WEB-INF/lib 里有哪些 jar再进源码里搜索 import javax.servlet 还是 import jakarta.servlet。如果应用里有大把 javax.servlet、javax.servlet.http 的引用说明它是按 Java EE 8 及以前的标准写的直接扔进 Tomcat 10.1 会起不来或者页面 500。如果项目用的是 Spring 5 及以下版本那对应的是 javax 生态Spring 6 开始强制 jakarta。还要看有没有依赖容器提供的类库比如 JSTL这个在 Tomcat 10 里连坐标都变了。这一步决定了你整个升级方案的方向是只升小版本比如 9.0.30 → 9.0.x还是要跨大版本9.0 → 10.1后者意味着代码改动工作量不是一个运维动作能解决的。我的一句话经验小版本升级是运维活大版本升级是研发活别把两者混在一起做。2. 升级前准备备份和回滚方案才是这次操作的重点2.1 盘点现状先搞清楚线上到底跑着什么很多服务器上的 Tomcat 是前人装完就跑的文档早就不知道丢哪了。升级前必须把现状摸清楚我一般会执行这几条命令# 找到实际运行路径和启动用户 ps -ef | grep tomcat # 在安装目录内查看当前版本 /opt/tomcat/bin/version.sh # 查看 JDK 版本 java -version # 如果由 systemd 管理看服务定义 systemctl cat tomcat 2/dev/null建议把每一台实例的信息记成一张表升级的时候对着表格操作不会漏检查项命令或方法要记录的信息安装路径ps -ef 的结果CATALINA_HOME、CATALINA_BASE启动用户ps -o user -p 进程号用户名及目录属主监听端口ss -lntp8080 / 8005 / 8443 等JDK 版本java -version大版本号和厂商JVM 参数ps -ef 的 java 段-Xmx、-Xms、GC 参数这里多说一句如果你们用的是单实例标准安装CATALINA_BASE 通常等于 CATALINA_HOME如果搞过多实例部署升级只动二进制目录实例配置留在各自的 base 目录里这种情况升级风险小很多因为这相当于把公共代码和业务配置彻底分开了。2.2 备份清单这些文件一个都不能漏备份是整个升级里最不能省的一步。我的习惯是直接在原 Tomcat 目录同级做一份带日期的完整拷贝用 cp -a 保留属主和权限TS$(date %Y%m%d) cp -a /opt/tomcat /opt/tomcat.backup.$TS如果磁盘不允许整目录拷至少下面这几类东西必须单独备份conf 整个目录重点看 server.xml、context.xml、web.xml、tomcat-users.xml、catalina.properties。webapps 下自己部署的业务应用包括 war 包和已经解压出来的目录。注意 Tomcat 自带的管理端和示例应用不用管。lib 目录下额外添加的第三方 jar最常见的是数据库驱动、JSTL 实现、自定义类库。bin 目录下的 setenv.sh这个文件里通常写着 JAVA_HOME、JAVA_OPTS是最容易被忽略但又最重要的文件。conf/Catalina/localhost 下的单应用 Context 配置文件老实例迁移时容易漏。所有 JVM 参数和启动方式记录包括 systemd unit 文件或者自启动脚本。压缩一份放到别的机器或者备份目录里更稳妥毕竟服务器本身也可能出问题tar czf /backup/tomcat-conf-$TS.tar.gz -C /opt/tomcat conf webapps2.3 回滚方案新旧并存比覆盖升级安全得多我见过有人在原目录上直接删掉旧版本再解压新版本回滚时只能靠“再重新下载一遍”现场十几分钟起不来手忙脚乱。正确做法是新旧两套目录同时存在服务通过切换目录来切换版本。比较推荐的管理方式是这样/opt/tomcat/ ├── apache-tomcat-9.0.30/ # 旧版本原封不动 ├── apache-tomcat-9.0.x/ # 新版本刚解压的 └── current - apache-tomcat-9.0.x # 软链指向当前运行版本systemd 或者启动脚本统一指向 current 这个软链升级切换就变成了“改软链 重启服务”回滚就是“把软链指回旧目录 重启服务”整个切换过程可控可解释。需要提醒的是回滚方案要考虑数据兼容。如果新版本跑了一段时间应用往数据库写入了新格式的数据或者会话里存了新的序列化对象切回旧版本时可能报错。所以回滚只适合“刚切完发现有问题马上退回”的场景时间窗口控制在十分钟以内跑了一天再回滚就要先评估数据层面。3. 实操全流程从下载新版本到切换验证3.1 下载安装包并校验完整性从 Tomcat 官网下载页选对应大版本的 tar.gz 包不要在不明渠道下包这是供应链安全的基本底线。下载完一定要校验校验和官方会提供对应的 .sha512 文件cd /data/soft wget https://tomcat.apache.org 下载页面对应版本的 tar.gz 和 .sha512 sha512sum apache-tomcat-9.0.x.tar.gz cat apache-tomcat-9.0.x.tar.gz.sha512把两个结果对比一下一致再继续。这一步很多人嫌麻烦跳过但中间件二进制一旦被替换所有后面部署的应用全部暴露校验和多花两分钟相比成本低得可以忽略。另外留意一点下载 tar.gz 而不是 zipLinux 下 tar.gz 保留权限属性更干净解压后 bin 目录下的脚本本身就是可执行的省去 chmod 的麻烦。3.2 部署新版本并迁移既有配置解压到新目录路径里带上版本号方便后续识别和管理tar xzf apache-tomcat-9.0.x.tar.gz -C /opt/tomcat mv /opt/tomcat/apache-tomcat-9.0.x /opt/tomcat/apache-tomcat-9.0.x先把新版本自带的示例和管理端删掉生产环境留着这些目录纯粹是给扫描器送分rm -rf /opt/tomcat/apache-tomcat-9.0.x/webapps/docs rm -rf /opt/tomcat/apache-tomcat-9.0.x/webapps/examples rm -rf /opt/tomcat/apache-tomcat-9.0.x/webapps/host-manager rm -rf /opt/tomcat/apache-tomcat-9.0.x/webapps/manager rm -rf /opt/tomcat/apache-tomcat-9.0.x/webapps/ROOT然后迁移配置。我最常用的方法是先对比新旧默认配置差异再决定怎么合并diff /opt/tomcat/apache-tomcat-9.0.30/conf/server.xml \ /opt/tomcat/apache-tomcat-9.0.x/conf/server.xml如果你们对 server.xml 的改动不多比如只改了端口、连接池和 Host 名直接把老配置文件覆盖过去问题不大。但如果你改过很多默认值一定要多看一轮新版本可能对默认参数有调整直接覆盖反而把新版本的改进给淹没了。context.xml、web.xml 同理。lib 目录的额外 jar 直接复制cp /opt/tomcat/apache-tomcat-9.0.30/lib/mysql-connector-java.jar \ /opt/tomcat/apache-tomcat-9.0.x/lib/setenv.sh 务必一起迁同时检查里面的参数在新 JDK 下是否还合法export JAVA_HOME/usr/local/jdk-17 export CATALINA_HOME/opt/tomcat/apache-tomcat-9.0.x export CATALINA_BASE$CATALINA_HOME export JAVA_OPTS-Xms2g -Xmx2g -XX:UseG1GC -Dfile.encodingUTF-8迁移完成后做一次配置校验这个命令很多老运维都在用/opt/tomcat/apache-tomcat-9.0.x/bin/catalina.sh configtest看到类似 “INFO: Server configuration file [ ... ] is valid” 的输出说明 server.xml 至少没有语法级问题。最后处理属主和权限这一步不做启动阶段大概率遇到权限拒绝chown -R tomcat:tomcat /opt/tomcat/apache-tomcat-9.0.x chmod x /opt/tomcat/apache-tomcat-9.0.x/bin/*.sh3.3 启动、验证与流量切换策略新目录就绪后先别急着重启生产用前台模式启动一次最靠谱日志直接打在终端上任何启动异常都能一眼看到su - tomcat -c /opt/tomcat/apache-tomcat-9.0.x/bin/catalina.sh run确认正常后再切换到后台启动su - tomcat -c /opt/tomcat/apache-tomcat-9.0.x/bin/startup.sh启动后盯住日志tail -f /opt/tomcat/apache-tomcat-9.0.x/logs/catalina.out看到Server startup in [xxxx] milliseconds这行说明容器层面已经起来了。接下来按顺序做冒烟测试curl 应用首页确认 HTTP 状态码是 200 而不是 404、502。跑一遍真正的登录接口确认能拿到 JSESSIONID。执行一个会查数据库的操作确认数据源没问题。如果有文件上传、导出功能也顺手测一下这类功能最容易受临时目录权限影响。流量切换分两种情况。如果是多节点架构背后有负载均衡新实例可以先以一个低权重加入上游让它承接少量流量观察几分钟没有异常报错再逐步切完。这样即使有问题也只是影响个别请求不会整体炸掉。如果就是单机单实例没有负载均衡那只能走停旧启新窗口要提前和业务确认。停旧服务前最好在旧实例上把最后一份访问日志备份走方便事后对比。4. 升级过程中最容易踩的坑4.1 javax 和 jakarta 命名空间变更Tomcat 10 最大的分水岭Tomcat 10 开始Servlet API 的包名从 javax.servlet 变成了 jakarta.servlet这是整个升级里影响面最大的一个变化。老应用编译出来的 class 里引用的还是 javax.servlet.http.HttpServlet放进 Tomcat 10 后类加载器按 jakarta.servlet.http.HttpServlet 去找类必然找不到最典型的表现就是应用部署后直接失败日志里刷 NoClassDefFoundError。面对这个问题的处理路径有三条老应用不强迁继续留在 Tomcat 9.0.x这是成本最低也最稳妥的。用官方提供的 Jakarta EE 迁移工具对 war 包做一次批量转换工具会把字节码里的 javax.servlet 替换成 jakarta.servlet适合那些还拿得到源码但懒得逐个改文件的中间态项目。走源码改造把 import 全部换成 jakarta 前缀并升级 Spring、JSTL 等配套依赖这是彻底解法但工时就大了。我的判断标准很简单如果应用还在正常迭代、有开发资源投入那就走源码改造一次到位如果只是个无人维护的老系统容器不动才是对生产负责。别为了“统一版本”这种目标把一个好好的系统改成四不像。4.2 JSTL 标签库类找不到JSP 项目升级后很常见的一个报错是The absolute uri [http://java.sun.com/jsp/jstl/core] cannot be resolved出现这个问题的原因分两种。一种是 Tomcat 9 之前默认带了 JSTL 实现升级到某个版本后发现没带另一种是 Tomcat 10 之后 JSTL 的包名和坐标全变了老 JSP 页面里引用的 taglib uri 没有对应的 jar 去实现。老项目在 9.0.x 下解决直接把 jstl 实现 jar 放进 Tomcat 的 lib 目录或者放到应用自己的 WEB-INF/lib 下常见的是 jstl.jar 配套 taglibs-standard-impl.jar。新项目在 10.1 下解决依赖坐标要换成 jakarta 版本dependency groupIdorg.glassfish.web/groupId artifactIdjakarta.servlet.jsp.jstl/artifactId version3.0.1/version /dependency这个坑的隐蔽之处在于它不是启动阶段就报错而是请求第一次访问 JSP 页面时才发现编译不了。也就是说光看应用起来了没用必须真的去点几个 JSP 页面。4.3 JDK 版本和内存参数不匹配Tomcat 9.0.x 本身要求 JDK 8 以上Tomcat 10.1 要求 JDK 11 以上Tomcat 11 要求 JDK 17 以上。如果你把新版本装在一台老 JDK 8 的机器上启动时会直接看到UnsupportedClassVersionError: org/apache/catalina/startup/Bootstrap这行报错的意思就是 Tomcat 的字节码比当前 JDK 新跑不起来。解决办法是先把 JDK 升上去并把 setenv.sh 里的 JAVA_HOME 指向新 JDK。还有一类问题出在旧 setenv.sh 的参数上。JDK 8 时代不少模板喜欢写 -XX:PermSize、-XX:MaxPermSize到了新 JDK 里这两个参数已经不存在了JVM 启动直接报 Unrecognized VM option 退出。老目录里 jvm 参数还能用不代表新 JDK 也能用升级前把参数逐项过一遍。我个人的原则是一次只升级一个变量。要么升 Tomcat 不换 JDK要么升 JDK 不换 Tomcat两个一起动出了问题你连排查方向都没法确定。4.4 权限、systemd 和端口冲突这类“低端”问题这些错误本身不高深但生产环境里反而最常见而且一出就是事故级。端口冲突的报错长这样java.net.BindException: Address already in use原因多半是旧实例没停干净或者新实例被 systemd 在旧路径上又拉起来了。用ss -lntp | grep 8080看一眼是哪个进程占的端口别猜直接看。权限问题通常表现为日志里出现 Permission denied或者启动脚本根本没权限写 PID 文件。解决方式就是前面说的 chown -R tomcat:tomcat别忘了新目录的父目录也要让 tomcat 用户可进入。systemd 是最容易被坑的你改了路径但 unit 文件里的 ExecStart 还指向旧目录重启一百次也是在跑老版本。这类问题我整理成了一张速查表报错或现象可能原因处理办法Address already in use旧实例未停止或端口被占用ss -lntp 查端口归属先停旧进程UnsupportedClassVersionErrorJDK 版本低于 Tomcat 要求安装匹配 JDK修改 JAVA_HOMENoClassDefFoundError: jakarta/...应用还是 javax 命名空间不跨大版本或先做应用迁移taglib uri 无法解析JSTL 实现缺失按 4.2 补对应 jarsystemctl start 后还在旧版本unit 文件未更新修改 ExecStart 后执行 systemctl daemon-reload排查启动类问题有个好习惯用 tomcat 用户在前台跑catalina.sh run所有日志直接打在当前终端比你反复 tail catalina.out 高效得多。5. 升级后的检查清单与个人经验5.1 升级后一周内的检查清单升级完成不代表这件事结束了。新的容器跑了业务链路要观察至少一个完整周期我习惯按这个清单过一遍catalina.out 里没有反复出现的 ERROR 或 SEVERE异常堆栈不能只是“偶尔一条”要逐条看过。核心链路逐个验证登录、查询、写操作、导出、定时任务该点的页面都点一遍。看监控指标连接数、线程数、堆内存、GC 耗时和升级前对比一两周新版本如果有性能回退这几项会提前报警。磁盘空间新旧两套目录并存时占用是双倍的确认稳定运行一周后再把旧目录清理掉但保留一份 tar 包。更新资产记录把 CMDB 里这台机器的 Tomcat 版本、目录路径、JVM 参数等信息同步改掉避免下次升级时又拿错文档。这套清单看起来琐碎但真正帮你兜底的就是这些琐碎检查。我见过太多“升级完了就以为全好了”的例子结果是两天后用户反馈某个报表打不开一查才发现 JSP 编译错误当时没覆盖到。5.2 几点个人体会前后做过多次 Tomcat 升级之后我最大的体会是中间件升级这件事七分靠准备三分靠操作。真正执行只花半小时但前面的版本评估、备份、回滚方案设计决定了你是从容切换还是救火现场。另外一个经验是目录管理的价值。把版本号写进目录名再用软链做 current 指向这套做法让升级和回滚都变成了“改一个软链”的动作团队里的同事接手时也能秒懂不用翻历史文档猜路径。最后再分享一个小习惯就算这次升级完了也要保持对小版本更新的敏感度。Tomcat 的维护版基本每月都有安全公告邮件值得订阅每次看到更新先在测试环境跑一轮再上生产。中间件这东西平时不觉得它重要一旦出问题就是整条业务线被拖下水日常维护多一点耐心夜里就能少接几个电话。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →