Tomcat Web应用部署

Web Application Deployment

概述

Web应用部署是Tomcat使用中最核心的操作之一。本文将详细介绍各种部署方式、配置技巧以及部署过程中的常见问题和解决方案。

1. 部署方式概览

1.1 部署方式分类

Tomcat部署方式
├── 自动部署 (Auto Deployment)
│   ├── WAR文件部署
│   ├── 目录部署
│   └── 热部署
├── 手动部署 (Manual Deployment)
│   ├── Manager应用部署
│   ├── 配置文件部署
│   └── 命令行部署
└── 外部部署 (External Deployment)
    ├── IDE集成部署
    ├── CI/CD自动化部署
    └── 容器化部署

1.2 部署目录结构

$CATALINA_HOME/
├── webapps/              # 默认应用部署目录
│   ├── ROOT/            # 根应用
│   ├── manager/         # 管理应用
│   ├── myapp.war        # WAR文件
│   └── myapp/           # 解压后的应用目录
├── conf/
│   └── Catalina/
│       └── localhost/   # Context配置文件目录
│           └── myapp.xml
└── work/                # 编译和临时文件
    └── Catalina/
        └── localhost/
            └── myapp/

2. WAR文件部署

2.1 标准WAR结构

myapp.war
├── WEB-INF/
│   ├── web.xml          # 部署描述符
│   ├── classes/         # Java类文件
│   │   └── com/example/
│   ├── lib/             # JAR依赖
│   │   ├── spring-core.jar
│   │   └── mysql-connector.jar
│   └── tld/             # 标签库描述符
├── META-INF/
│   ├── MANIFEST.MF
│   └── context.xml      # Context配置
├── index.jsp            # JSP页面
├── css/                 # 静态资源
├── js/
└── images/

2.2 自动WAR部署

# 复制WAR文件到webapps目录
cp myapp.war $CATALINA_HOME/webapps/

# Tomcat会自动:
# 1. 检测到新的WAR文件
# 2. 解压WAR文件到同名目录
# 3. 部署应用
# 4. 应用可通过 http://localhost:8080/myapp 访问

2.3 部署配置

<!-- server.xml中的Host配置 -->
<Host name="localhost" 
      appBase="webapps"
      unpackWARs="true"          <!-- 自动解压WAR文件 -->
      autoDeploy="true"          <!-- 自动部署 -->
      deployOnStartup="true">    <!-- 启动时部署 -->

  <!-- 部署目录配置 -->
  <Context path="/myapp" 
           docBase="${catalina.home}/external-apps/myapp.war"
           reloadable="true"/>
</Host>

3. 目录部署

3.1 直接目录部署

# 创建应用目录结构
mkdir -p $CATALINA_HOME/webapps/myapp/WEB-INF/{classes,lib}

# 复制应用文件
cp -r /src/webapp/* $CATALINA_HOME/webapps/myapp/
cp /src/target/classes/* $CATALINA_HOME/webapps/myapp/WEB-INF/classes/
cp /src/lib/*.jar $CATALINA_HOME/webapps/myapp/WEB-INF/lib/

3.2 符号链接部署

# 创建符号链接(开发环境推荐)
ln -s /path/to/webapp/target $CATALINA_HOME/webapps/myapp

# 注意:需要在server.xml中配置allowLinking
<Context path="/myapp" 
         docBase="/path/to/webapp/target"
         allowLinking="true"
         reloadable="true"/>

4. Context配置部署

4.1 独立Context文件

<!-- conf/Catalina/localhost/myapp.xml -->
<Context docBase="/external/path/to/myapp"
         reloadable="true"
         crossContext="false">

  <!-- 数据源配置 -->
  <Resource name="jdbc/MyDB" 
            auth="Container"
            type="javax.sql.DataSource"
            maxTotal="20"
            maxIdle="10"
            driverClassName="com.mysql.cj.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/myapp"
            username="app_user" 
            password="app_pass"/>

  <!-- 环境变量 -->
  <Environment name="app.env" 
               value="production" 
               type="java.lang.String"/>

  <!-- 参数配置 -->
  <Parameter name="config.file" 
             value="/etc/myapp/config.properties"/>

  <!-- 监听资源变化 -->
  <WatchedResource>WEB-INF/web.xml</WatchedResource>
  <WatchedResource>/etc/myapp/config.properties</WatchedResource>
</Context>

4.2 应用内Context配置

<!-- myapp/META-INF/context.xml -->
<Context>
  <!-- 本地数据源 -->
  <Resource name="jdbc/LocalDB" 
            auth="Container"
            type="javax.sql.DataSource"
            factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
            maxActive="50"
            maxIdle="10"
            initialSize="5"
            validationQuery="SELECT 1"
            testOnBorrow="true"
            driverClassName="org.h2.Driver"
            url="jdbc:h2:mem:testdb"/>

  <!-- 资源链接 -->
  <ResourceLink name="jdbc/SharedDB"
                global="jdbc/GlobalDB"
                type="javax.sql.DataSource"/>
</Context>

5. Manager应用部署

5.1 Web界面部署

通过浏览器访问Manager应用进行部署:

http://localhost:8080/manager/html

部署操作:

  1. 选择WAR文件上传
  2. 指定Context路径
  3. 点击部署按钮

5.2 Manager API部署

# 部署WAR文件
curl -T myapp.war \
     -u admin:password \
     "http://localhost:8080/manager/text/deploy?path=/myapp&update=true"

# 从URL部署
curl -u admin:password \
     "http://localhost:8080/manager/text/deploy?path=/myapp&war=jar:file:/path/to/myapp.war!/"

# 部署本地目录
curl -u admin:password \
     "http://localhost:8080/manager/text/deploy?path=/myapp&war=file:/path/to/myapp"

# 重新部署
curl -u admin:password \
     "http://localhost:8080/manager/text/reload?path=/myapp"

# 取消部署
curl -u admin:password \
     "http://localhost:8080/manager/text/undeploy?path=/myapp"

5.3 Manager命令脚本

#!/bin/bash
# deploy-script.sh

TOMCAT_URL="http://localhost:8080"
MANAGER_USER="admin"
MANAGER_PASS="password"
WAR_FILE="$1"
APP_PATH="$2"

if [ -z "$WAR_FILE" ] || [ -z "$APP_PATH" ]; then
    echo "用法: $0 <war文件> <应用路径>"
    exit 1
fi

# 检查应用是否已存在
echo "检查应用状态..."
STATUS=$(curl -s -u $MANAGER_USER:$MANAGER_PASS \
         "$TOMCAT_URL/manager/text/list" | grep "^$APP_PATH:")

if [ -n "$STATUS" ]; then
    echo "应用已存在,执行重新部署..."
    curl -s -u $MANAGER_USER:$MANAGER_PASS \
         "$TOMCAT_URL/manager/text/undeploy?path=$APP_PATH"
fi

echo "部署新应用..."
curl -T "$WAR_FILE" \
     -u $MANAGER_USER:$MANAGER_PASS \
     "$TOMCAT_URL/manager/text/deploy?path=$APP_PATH"

echo "部署完成!"

6. 热部署配置

6.1 启用热部署

<!-- 在Context中启用热部署 -->
<Context reloadable="true">
  <!-- 监控的资源 -->
  <WatchedResource>WEB-INF/web.xml</WatchedResource>
  <WatchedResource>WEB-INF/classes</WatchedResource>
  <WatchedResource>META-INF/context.xml</WatchedResource>
</Context>

6.2 类级别重载

// 使用ClassLoader实现热重载
public class HotReloadServlet extends HttpServlet {

    private long lastModified = 0;
    private Class<?> dynamicClass;

    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
            throws ServletException, IOException {

        File classFile = new File("/path/to/DynamicClass.class");

        if (classFile.lastModified() > lastModified) {
            try {
                // 重新加载类
                URLClassLoader loader = new URLClassLoader(
                    new URL[]{classFile.getParentFile().toURI().toURL()});
                dynamicClass = loader.loadClass("DynamicClass");
                lastModified = classFile.lastModified();

            } catch (Exception e) {
                throw new ServletException("Failed to reload class", e);
            }
        }

        // 使用动态加载的类
        try {
            Object instance = dynamicClass.newInstance();
            // 调用方法...
        } catch (Exception e) {
            throw new ServletException("Failed to use dynamic class", e);
        }
    }
}

7. 多环境部署

7.1 环境配置管理

# 环境配置目录结构
config/
├── dev/
│   ├── context.xml
│   ├── logging.properties
│   └── app.properties
├── test/
│   ├── context.xml
│   ├── logging.properties
│   └── app.properties
└── prod/
    ├── context.xml
    ├── logging.properties
    └── app.properties

7.2 环境切换脚本

#!/bin/bash
# deploy-env.sh

ENVIRONMENT=$1
APP_NAME=$2
WAR_FILE=$3

if [ -z "$ENVIRONMENT" ] || [ -z "$APP_NAME" ] || [ -z "$WAR_FILE" ]; then
    echo "用法: $0 <环境> <应用名> <WAR文件>"
    echo "环境: dev|test|prod"
    exit 1
fi

CONFIG_DIR="config/$ENVIRONMENT"
TOMCAT_HOME="/opt/tomcat9"

# 复制环境特定配置
echo "配置$ENVIRONMENT环境..."
cp "$CONFIG_DIR/context.xml" \
   "$TOMCAT_HOME/conf/Catalina/localhost/$APP_NAME.xml"

cp "$CONFIG_DIR/logging.properties" \
   "$TOMCAT_HOME/conf/"

# 部署应用
echo "部署应用..."
cp "$WAR_FILE" "$TOMCAT_HOME/webapps/$APP_NAME.war"

# 设置环境变量
export APP_ENV=$ENVIRONMENT
echo "export APP_ENV=$ENVIRONMENT" >> "$TOMCAT_HOME/bin/setenv.sh"

echo "部署完成: $APP_NAME ($ENVIRONMENT)"

7.3 配置外部化

<!-- 外部配置Context -->
<Context>
  <!-- 通过JNDI配置外部化 -->
  <Environment name="config.dir" 
               value="${catalina.base}/conf/apps/${app.name}"
               type="java.lang.String"/>

  <Environment name="app.env" 
               value="${APP_ENV}" 
               type="java.lang.String"/>
</Context>
// 在应用中读取外部化配置
@WebServlet("/config")
public class ConfigServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
            throws ServletException, IOException {

        try {
            Context initCtx = new InitialContext();
            Context envCtx = (Context) initCtx.lookup("java:comp/env");

            String configDir = (String) envCtx.lookup("config.dir");
            String appEnv = (String) envCtx.lookup("app.env");

            // 加载环境特定配置
            Properties props = new Properties();
            props.load(new FileInputStream(
                configDir + "/" + appEnv + ".properties"));

            response.getWriter().println("Environment: " + appEnv);
            response.getWriter().println("Config: " + props.toString());

        } catch (Exception e) {
            throw new ServletException("Failed to load config", e);
        }
    }
}

8. 部署自动化

8.1 Maven集成部署

<!-- pom.xml中的Tomcat插件配置 -->
<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.2</version>
  <configuration>
    <url>http://localhost:8080/manager/text</url>
    <server>tomcat-server</server>
    <path>/myapp</path>
    <username>admin</username>
    <password>password</password>
  </configuration>
</plugin>
# Maven部署命令
mvn clean package tomcat7:deploy
mvn tomcat7:redeploy
mvn tomcat7:undeploy

8.2 Gradle集成部署

// build.gradle
plugins {
    id 'com.bmuschko.tomcat' version '2.5'
}

tomcat {
    httpProtocol = 'org.apache.coyote.http11.Http11Nio2Protocol'
    ajpProtocol  = 'org.apache.coyote.ajp.AjpNio2Protocol'
}

task deployToTomcat {
    doLast {
        def warFile = file("build/libs/${project.name}.war")
        def url = "http://localhost:8080/manager/text/deploy"
        def credentials = "admin:password"

        exec {
            commandLine 'curl', '-T', warFile.absolutePath,
                       '-u', credentials,
                       "${url}?path=/${project.name}&update=true"
        }
    }
}

8.3 CI/CD Pipeline

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - mvn clean package
  artifacts:
    paths:
      - target/*.war

deploy_dev:
  stage: deploy
  script:
    - curl -T target/myapp.war 
           -u $TOMCAT_USER:$TOMCAT_PASS 
           "$TOMCAT_URL/manager/text/deploy?path=/myapp&update=true"
  environment:
    name: development
    url: http://dev.example.com:8080/myapp
  only:
    - develop

deploy_prod:
  stage: deploy
  script:
    - curl -T target/myapp.war 
           -u $TOMCAT_USER:$TOMCAT_PASS 
           "$TOMCAT_URL/manager/text/deploy?path=/myapp&update=true"
  environment:
    name: production
    url: https://app.example.com/myapp
  only:
    - master
  when: manual

9. 部署监控和健康检查

9.1 部署状态检查

#!/bin/bash
# check-deployment.sh

APP_NAME=$1
TOMCAT_URL="http://localhost:8080"
MANAGER_USER="admin"
MANAGER_PASS="password"

# 检查应用状态
STATUS=$(curl -s -u $MANAGER_USER:$MANAGER_PASS \
         "$TOMCAT_URL/manager/text/list" | grep "^/$APP_NAME:")

if [ -n "$STATUS" ]; then
    echo "应用状态: $STATUS"

    # 解析状态
    IFS=':' read -ra PARTS <<< "$STATUS"
    APP_PATH=${PARTS[0]}
    APP_STATE=${PARTS[1]}
    SESSION_COUNT=${PARTS[2]}

    if [ "$APP_STATE" = "running" ]; then
        echo "✓ 应用运行正常"
        echo "  活跃会话数: $SESSION_COUNT"

        # 健康检查
        HEALTH_CHECK=$(curl -s -o /dev/null -w "%{http_code}" \
                      "$TOMCAT_URL$APP_PATH/health")

        if [ "$HEALTH_CHECK" = "200" ]; then
            echo "✓ 健康检查通过"
        else
            echo "✗ 健康检查失败 (HTTP $HEALTH_CHECK)"
        fi
    else
        echo "✗ 应用状态异常: $APP_STATE"
    fi
else
    echo "✗ 应用未部署"
fi

9.2 部署回滚机制

#!/bin/bash
# rollback-deployment.sh

APP_NAME=$1
BACKUP_DIR="/var/backups/tomcat-apps"
TOMCAT_HOME="/opt/tomcat9"

# 创建当前版本备份
echo "备份当前版本..."
if [ -f "$TOMCAT_HOME/webapps/$APP_NAME.war" ]; then
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    cp "$TOMCAT_HOME/webapps/$APP_NAME.war" \
       "$BACKUP_DIR/$APP_NAME-$TIMESTAMP.war"
fi

# 获取上一个版本
LAST_VERSION=$(ls -t "$BACKUP_DIR/$APP_NAME-"*.war | head -2 | tail -1)

if [ -n "$LAST_VERSION" ]; then
    echo "回滚到版本: $(basename $LAST_VERSION)"

    # 停止应用
    curl -u admin:password \
         "http://localhost:8080/manager/text/stop?path=/$APP_NAME"

    # 替换WAR文件
    cp "$LAST_VERSION" "$TOMCAT_HOME/webapps/$APP_NAME.war"

    # 启动应用
    curl -u admin:password \
         "http://localhost:8080/manager/text/start?path=/$APP_NAME"

    echo "回滚完成"
else
    echo "未找到可回滚的版本"
    exit 1
fi

10. 常见问题和解决方案

10.1 部署失败问题

# 问题1: 端口占用
netstat -tuln | grep 8080
lsof -i :8080

# 解决: 修改端口或停止占用进程
kill -9 $(lsof -t -i:8080)

# 问题2: 权限问题
ls -la $CATALINA_HOME/webapps/
chown -R tomcat:tomcat $CATALINA_HOME/webapps/

# 问题3: 内存不足
echo "检查JVM内存设置:"
ps aux | grep java | grep catalina

10.2 部署诊断工具

#!/bin/bash
# deployment-diagnostics.sh

echo "=== Tomcat部署诊断 ==="

# 检查Tomcat进程
echo "1. Tomcat进程状态:"
ps aux | grep java | grep catalina || echo "  Tomcat未运行"

# 检查端口
echo "2. 端口监听状态:"
netstat -tuln | grep -E ":(8080|8005|8009|8443)" || echo "  端口未监听"

# 检查webapps目录
echo "3. 应用部署状态:"
ls -la $CATALINA_HOME/webapps/

# 检查日志
echo "4. 最近的错误日志:"
tail -20 $CATALINA_HOME/logs/catalina.out | grep -i error

# 检查Context配置
echo "5. Context配置文件:"
ls -la $CATALINA_HOME/conf/Catalina/localhost/

echo "=== 诊断完成 ==="

小结

通过本文学习,你应该掌握:

  1. 多种Tomcat应用部署方式
  2. WAR文件和目录部署的具体操作
  3. Context配置文件的使用方法
  4. Manager应用的部署管理功能
  5. 热部署的配置和实现
  6. 多环境部署的最佳实践
  7. 部署自动化的集成方法
  8. 部署监控和故障排除技巧

下一篇文章将详细介绍Tomcat服务器的高级配置。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-08-29 15:40:15

results matching ""

    No results matching ""