三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Linux服务器部署多Tomcat实例:端口配置、资源隔离与生产环境实践

Linux服务器部署多Tomcat实例:端口配置、资源隔离与生产环境实践

1. 从单兵作战到多线部署:为什么需要一台服务器跑多个Tomcat?

刚接触服务器部署的新手,或者是从开发转向运维的朋友,常常会有一个疑问:我买一台云服务器,难道不是装一个Tomcat,然后把所有项目都扔进去就行了吗?理论上,一个Tomcat确实可以部署多个Web应用,通过不同的上下文路径(Context Path)来区分,比如http://服务器IP:8080/app1http://服务器IP:8080/app2。这在早期或者小型内部系统中很常见。

但为什么我们还要大费周章地在一台机器上部署多个Tomcat实例呢?这背后其实是生产环境对隔离性、稳定性和灵活性的硬性要求。想象一下,你有一个对外提供服务的电商主站(Tomcat A),还有一个内部使用的后台管理系统(Tomcat B),以及一个定时跑批处理任务的Java应用(Tomcat C)。如果它们都挤在同一个Tomcat容器里,会是什么局面?

首先,最直接的风险是“一损俱损”。Tomcat A的应用因为一个内存泄漏或者死循环,把整个JVM搞崩了,那么B和C也会跟着一起挂掉,所有服务瞬间中断。其次,资源无法隔离。A应用是个“内存大户”,疯狂吃资源,B和C这两个“小应用”就可能因为资源不足而响应缓慢甚至崩溃。再者,升级和维护变得异常棘手。你想给后台管理系统B升级一下Spring框架版本,但这个新版本可能和主站A依赖的旧版本冲突,在同一个Tomcat里根本无法共存。最后,日志、配置、启停管理都会混杂在一起,排查问题如同大海捞针。

所以,在生产环境中,为不同的应用、甚至同一应用的不同环境(如测试、预发布)部署独立的Tomcat实例,是更专业和稳妥的做法。它实现了应用级别的资源与故障隔离,让每个应用都有自己的“独立套房”,互不干扰。升级、重启、调优都可以针对单个实例进行,不影响其他服务。这就像在一栋大楼里给每个租户分配独立的房间和水电表,而不是让大家共用一个大厅。

理解了“为什么”,我们再来看看“怎么做”。核心思路很简单:复制多份Tomcat的安装目录,然后为每一份配置不同的运行参数,主要是端口号和环境变量,让它们能和平共处。听起来容易,但实操中会遇到各种“坑”,比如端口冲突、环境变量覆盖、启动脚本修改、以及如何优雅地管理这些实例。接下来,我就结合自己多次在Linux服务器(以CentOS 7为例)上实操的经验,手把手带你走一遍最稳妥的部署流程,并分享那些文档里不会写的细节和避坑指南。

2. 战前准备:理清思路与备齐弹药

在开始动手之前,我们必须把整个部署的蓝图和需要的“弹药”准备好。盲目操作只会导致混乱和反复重来。

2.1 核心部署逻辑拆解

部署多个Tomcat的核心,在于解决两个根本冲突:网络端口冲突系统资源标识冲突

  1. 网络端口冲突:Tomcat默认使用8080端口(HTTP)、8005端口(SHUTDOWN)、8009端口(AJP)等。如果两个Tomcat实例使用相同的端口,后启动的那个一定会报错“Address already in use”。所以,我们必须为每个实例分配一套唯一的端口号。
  2. 系统资源标识冲突:这主要指CATALINA_HOMECATALINA_BASE环境变量。CATALINA_HOME指向Tomcat的安装目录(包含公共的lib、bin等)。CATALINA_BASE指向一个实例的工作目录(包含该实例私有的conf、logs、webapps、work、temp等)。传统单实例部署时,两者通常指向同一个目录。但在多实例部署中,我们通过设置不同的CATALINA_BASE,让多个实例共享一份CATALINA_HOME(即Tomcat二进制文件),但拥有各自独立的配置、日志和应用部署目录。这是官方推荐的做法,便于统一升级Tomcat版本。

基于此,我们的部署方案有两种:

  • 方案A(目录完全复制):直接复制完整的Tomcat安装目录,如tomcat-01,tomcat-02。每个目录都是独立的,互不干扰。你需要分别修改每个目录下conf/server.xml中的端口号。这种方式简单粗暴,适合实例不多、且对Tomcat版本可能有差异化需求的场景。
  • 方案B(共享Home,分离Base):只安装一份Tomcat作为CATALINA_HOME。然后为每个实例创建独立的CATALINA_BASE目录,里面包含conf,logs,webapps,work,temp等子目录,并从CATALINA_HOMEconf目录复制初始配置文件过来修改。这种方式更优雅,资源利用更高效,是运维的标准做法。

为了更直观地理解,我将以方案A为例进行演示,因为它更易于初学者理解和操作,且能更清晰地展现每个实例的独立性。掌握了方案A,过渡到方案B会非常容易。

2.2 环境与工具清单

假设我们有一台全新的CentOS 7服务器,我们需要部署两个Tomcat实例,分别运行两个不同的Java Web项目。

  • 服务器:CentOS 7.x 最小化安装。
  • 必备软件
    • JDK:必须提前安装好。Tomcat运行在JVM之上。建议使用JDK 8或JDK 11这些长期支持版本。可以通过java -version命令验证。
    • Tomcat:我们将使用Tomcat 9.0.x版本。去Apache官网下载Core版本的tar.gz压缩包,例如apache-tomcat-9.0.xx.tar.gz
  • 工具:通过SSH连接服务器进行操作。确保你拥有root权限或可以通过sudo执行特权命令。

在开始前,请确保JDK已正确安装且环境变量(JAVA_HOME)已配置。你可以通过以下命令检查:

java -version echo $JAVA_HOME

如果JAVA_HOME没有输出或指向错误,需要先配置。一个常见的配置方法是编辑/etc/profile文件,在末尾添加:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.xxx.x86_64 # 请替换为你的实际JDK路径 export PATH=$JAVA_HOME/bin:$PATH

然后执行source /etc/profile使配置生效。注意,这里配置的是系统环境变量,它对所有用户和所有Tomcat实例都生效,是Tomcat能找到Java运行时的基础。

3. 实战部署:一步步构建两个独立的Tomcat王国

现在,让我们进入实战环节。我会以部署两个实例为例,演示从安装、配置到启动的全过程。

3.1 第一步:基础Tomcat的安装与解压

首先,我们登录服务器,在一个合适的目录下操作,比如/opt

  1. 上传与解压

    cd /opt # 假设你已经通过FTP或SCP将 apache-tomcat-9.0.xx.tar.gz 上传到了 /opt 目录 tar -zxvf apache-tomcat-9.0.xx.tar.gz # 解压后得到一个目录,例如 apache-tomcat-9.0.xx

    为了便于区分,我们先将其重命名为一个“模板”目录或第一个实例的目录。

    mv apache-tomcat-9.0.xx tomcat-template # 或者 mv apache-tomcat-9.0.xx tomcat-8080
  2. 创建实例目录:现在我们基于这个“模板”复制出两个实例目录。

    cp -r tomcat-template tomcat-8080 cp -r tomcat-template tomcat-8081

    这样,我们就有了tomcat-8080tomcat-8081两个完全独立的Tomcat目录。用端口号命名目录是一个好习惯,一目了然。

3.2 第二步:攻克核心难关——端口配置

这是最关键的一步。我们需要修改每个实例conf/server.xml文件中的三个关键端口,确保它们互不冲突。

端口号规划表:

实例名称HTTP/1.1 连接器端口 (默认8080)关闭端口 (默认8005)AJP/1.3 连接器端口 (默认8009)
tomcat-80808080(保持不变)8005(保持不变)8009(保持不变)
tomcat-8081808180068010

注意:关闭端口(Shutdown Port)是Tomcat监听关闭命令的端口,每个实例必须唯一,否则无法独立关闭。AJP端口通常用于与Web服务器(如Apache HTTPD)集成,如果你不用,可以注释掉整个<Connector protocol="AJP/1.3" ...>节点,但修改端口是更规范的做法。

开始修改:

  1. 修改 tomcat-8081 的端口

    vi /opt/tomcat-8081/conf/server.xml

    使用vi编辑器(或你喜欢的nano),找到以下三处并进行修改:

    • 关闭端口:搜索Server port=, 大概在第22行左右。
      <Server port="8005" shutdown="SHUTDOWN"> <!-- 修改为 8006 -->
    • HTTP连接器端口:搜索Connector port="8080",大概在第69行左右。
      <Connector port="8080" protocol="HTTP/1.1" ... /> <!-- 修改为 8081 -->
    • AJP连接器端口:搜索Connector port="8009" protocol="AJP/1.3",大概在第116行左右。
      <Connector port="8009" protocol="AJP/1.3" ... /> <!-- 修改为 8010 -->

    修改完成后,保存退出(:wq)。

  2. 检查 tomcat-8080 的端口:理论上,第一个实例我们可以用默认端口,但为了保险起见,最好也打开server.xml确认一下,确保没有被其他程序占用。如果8080端口已被占用(比如已有其他Tomcat在运行),你也需要为tomcat-8080分配另一组端口。

一个重要的实操心得:在修改端口时,我强烈建议你一次性修改完一组端口,并记录在案。我曾经因为只改了HTTP端口忘了改关闭端口,导致第二个实例启动后,第一个实例的关闭命令失效,排查了半天。你可以写一个简单的脚本来批量修改,或者用sed命令,但对于新手,手动修改并仔细核对是最稳妥的。

3.3 第三步:启动脚本的“个性化”改造

默认情况下,Tomcat的启动脚本(bin/startup.sh)和关闭脚本(bin/shutdown.sh)会读取CATALINA_HOMECATALINA_BASE环境变量。在我们这种“目录完全复制”的方案中,这两个变量默认都会指向当前脚本所在的目录,这通常没问题。但是,当我们需要为每个实例配置特定的JVM参数(如内存大小、GC策略)时,就需要对脚本进行改造。

更常见的做法是,我们为每个实例创建独立的启动/停止脚本,或者通过修改bin/catalina.sh来传递参数。这里介绍一种简单有效的方法:修改每个实例的bin/setenv.sh文件(如果不存在就创建)

这个文件是Tomcat启动时自动加载的,用于设置该实例特有的环境变量。

  1. 为 tomcat-8080 创建 setenv.sh

    vi /opt/tomcat-8080/bin/setenv.sh

    添加内容,例如设置JVM堆内存:

    #!/bin/sh # 设置此Tomcat实例的JVM参数 export JAVA_OPTS="-server -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m" # 你也可以在这里设置其他实例特有的变量,比如日志路径前缀 export CATALINA_OUT="/opt/tomcat-8080/logs/catalina.out"

    保存退出,并赋予执行权限:

    chmod +x /opt/tomcat-8080/bin/setenv.sh
  2. 为 tomcat-8081 创建 setenv.sh: 同样操作,你可以设置不同的内存参数。

    vi /opt/tomcat-8081/bin/setenv.sh
    #!/bin/sh export JAVA_OPTS="-server -Xms256m -Xmx512m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m" export CATALINA_OUT="/opt/tomcat-8081/logs/catalina.out"
    chmod +x /opt/tomcat-8081/bin/setenv.sh

通过setenv.sh,我们实现了每个实例JVM参数的隔离配置。这是生产环境调优的必备步骤。

3.4 第四步:部署项目与首次启动

现在,我们可以将我们的Web项目(通常是WAR包)部署到对应的实例中。

  1. 部署项目:将你的project-a.war放到/opt/tomcat-8080/webapps/目录下。将project-b.war放到/opt/tomcat-8081/webapps/目录下。Tomcat启动时会自动解压WAR包。

  2. 启动实例

    # 启动第一个实例 /opt/tomcat-8080/bin/startup.sh # 启动第二个实例 /opt/tomcat-8081/bin/startup.sh
  3. 验证启动

    • 查看日志:这是最可靠的验证方式。
      tail -f /opt/tomcat-8080/logs/catalina.out tail -f /opt/tomcat-8081/logs/catalina.out
      在日志中寻找Server startup in [xxxx] milliseconds这样的信息,表示启动成功。
    • 检查进程
      ps -ef | grep tomcat
      你应该能看到两个Java进程,分别对应两个Tomcat实例,它们的启动命令路径是不同的。
    • 端口监听
      netstat -tlnp | grep java
      你应该能看到8080、8005、8009(第一个实例)和8081、8006、8010(第二个实例)端口处于LISTEN状态。
    • 浏览器访问:在本地浏览器输入http://你的服务器IP:8080http://你的服务器IP:8081,应该能看到Tomcat的默认主页或者你部署的应用。

如果一切顺利,恭喜你,两个Tomcat实例已经成功运行在同一台服务器上了!

4. 进阶管理与深度避坑指南

把服务跑起来只是第一步,如何管理好它们才是体现运维水平的地方。下面分享一些进阶配置和那些容易踩的“坑”。

4.1 权限、用户与安全加固

默认的Tomcat安装存在一些安全隐患,尤其是在多实例环境下,需要逐一加固。

  1. 创建专用系统用户绝对不要使用root用户直接运行Tomcat!这会给系统带来巨大风险。我们应该为Tomcat服务创建专用的、权限受限的系统用户。

    # 创建一个名为‘tomcat’的系统用户,且不创建家目录,禁止登录shell useradd -r -m -d /opt/tomcat -s /bin/false tomcat

    然后,将我们所有的Tomcat实例目录的所有权赋予这个用户(假设实例目录都在/opt下):

    chown -R tomcat:tomcat /opt/tomcat-8080 chown -R tomcat:tomcat /opt/tomcat-8081

    以后启动Tomcat,应该切换到这个用户来执行,或者在启动脚本中通过su - tomcat -c来切换。

  2. 管理后台密码:每个实例的conf/tomcat-users.xml文件是独立的。你需要为每个实例单独配置管理员用户,并且务必使用强密码。同时,考虑是否真的需要Tomcat自带的管理后台(managerhost-manager应用),如果不需要,可以直接删除webapps目录下的managerhost-manager文件夹,这是最安全的方式。

  3. 关闭不必要的连接器和功能:在生产环境,如果不需要AJP协议(比如前面没有Apache HTTPD),建议在server.xml中注释掉AJP连接器。同样,检查conf/web.xml中是否启用了不必要的内置Servlet。

4.2 日志管理:让排查有迹可循

多实例的日志管理至关重要。默认日志都在各自实例的logs目录下,但我们可以做得更好。

  1. 日志切割:Tomcat默认的catalina.out不会自动切割,会无限增长。我们需要借助logrotate工具。创建配置文件/etc/logrotate.d/tomcat-8080

    /opt/tomcat-8080/logs/catalina.out { daily rotate 30 copytruncate missingok compress delaycompress notifempty create 644 tomcat tomcat }

    同样为tomcat-8081创建一份。这样日志会按天切割并压缩,保留30天。

  2. 日志级别与输出:可以在conf/logging.properties中调整每个实例的日志级别,将不必要的DEBUG信息关闭,减少日志量,聚焦错误(SEVERE)和警告(WARNING)。

4.3 服务化与开机自启

通过命令行startup.sh启动,在SSH断开后进程可能会终止。我们需要将Tomcat实例注册为系统服务,实现后台运行和开机自启。

这里以systemd为例(CentOS 7及以上):

  1. 为 tomcat-8080 创建服务单元文件

    vi /etc/systemd/system/tomcat-8080.service

    写入以下内容(注意根据你的实际路径修改):

    [Unit] Description=Apache Tomcat 9 Instance for Port 8080 After=network.target [Service] Type=forking User=tomcat Group=tomcat # 设置环境变量,非常重要! Environment="JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk" Environment="CATALINA_PID=/opt/tomcat-8080/temp/tomcat.pid" Environment="CATALINA_HOME=/opt/tomcat-8080" Environment="CATALINA_BASE=/opt/tomcat-8080" ExecStart=/opt/tomcat-8080/bin/startup.sh ExecStop=/opt/tomcat-8080/bin/shutdown.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target
  2. 为 tomcat-8081 创建服务单元文件:同理,创建/etc/systemd/system/tomcat-8081.service,修改其中的端口、PID文件路径和目录路径。

  3. 启用并启动服务

    systemctl daemon-reload systemctl enable tomcat-8080 systemctl enable tomcat-8081 systemctl start tomcat-8080 systemctl start tomcat-8081

    现在,你可以使用systemctl status tomcat-8080来查看服务状态,并且服务器重启后,这两个Tomcat实例也会自动启动。

4.4 那些年我踩过的“坑”与解决方案

  1. 端口冲突的幽灵:明明改了server.xml,启动时还是报“Address already in use”。除了检查其他Tomcat实例,还要用netstat -tlnp看看是不是有其他程序(比如旧的Java进程、Nginx、某个开发工具)占用了你规划的端口。一个习惯:在分配端口前,先用netstat命令扫一遍。

  2. 环境变量的“陷阱”:在systemd的service文件中,JAVA_HOME必须显式设置。即使你在/etc/profile中设置了,systemd服务在启动时也读取不到用户环境变量。这是最常见的启动失败原因之一,错误信息通常是“Neither the JAVA_HOME nor the JRE_HOME environment variable is defined”。务必在service文件的[Service]段用Environment=指令设置

  3. 权限不足导致启动失败:当你用tomcat用户启动时,务必确保该用户对Tomcat目录下的logs,temp,work,webapps等目录有写权限。启动失败时,首先去logs/catalina.out里看错误日志,权限问题通常会有“Permission denied”的提示。

  4. 关闭端口被误杀shutdown.sh脚本是通过向关闭端口发送SHUTDOWN命令来停止Tomcat的。如果这个端口配置错误或者被防火墙拦截,shutdown.sh就会失效,你只能通过kill -9来强制结束进程。确保每个实例的关闭端口唯一且可访问

  5. 内存溢出(OOM)连环计:在多实例环境下,物理内存是共享的。如果你给每个实例都配置了过大的-Xmx(最大堆内存),总和超过了物理内存,就会导致频繁的Swap交换,系统变得巨卡,甚至触发OOM Killer杀掉其他进程。务必根据服务器总内存和实例数量,合理分配每个实例的堆内存,要预留一部分给系统和其他进程。

5. 从多实例到容器化:另一种更现代的思考

在一台服务器上部署多个Tomcat实例,是传统虚拟主机或物理服务器时代的经典做法。但随着云原生和微服务架构的普及,Docker容器化成为了更主流、更优雅的解决方案。

你可以为每个Tomcat应用创建一个Docker镜像,然后在一台服务器上运行多个Docker容器。每个容器都有自己完全隔离的文件系统、网络命名空间和进程空间。端口映射、资源限制(CPU、内存)、环境变量配置都可以通过Docker命令或docker-compose.yml文件轻松管理。

例如,一个简单的docker-compose.yml可以定义两个Tomcat服务:

version: '3' services: app1: image: tomcat:9.0-jdk8 container_name: tomcat-app1 ports: - "8080:8080" volumes: - "./app1/webapps:/usr/local/tomcat/webapps" - "./app1/logs:/usr/local/tomcat/logs" environment: - JAVA_OPTS=-Xms512m -Xmx1024m app2: image: tomcat:9.0-jdk8 container_name: tomcat-app2 ports: - "8081:8080" volumes: - "./app2/webapps:/usr/local/tomcat/webapps" - "./app2/logs:/usr/local/tomcat/logs" environment: - JAVA_OPTS=-Xms256m -Xmx512m

一行命令docker-compose up -d就能启动两个完全隔离的Tomcat环境。这种方式在部署速度、环境一致性、资源调度和水平扩展上都有巨大优势。

所以,当你熟练掌握了手动部署多Tomcat实例后,不妨将学习Docker提上日程。它并不是要取代你刚学会的技能,而是为你提供了在更复杂场景下(比如混合部署、持续集成/持续部署)更强大的工具。理解手动部署的每一个细节,会让你在使用容器化工具时更加得心应手,因为容器化的本质,正是对这些底层隔离和配置机制的一种标准化和自动化封装。

← 返回列表