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
部署操作:
- 选择WAR文件上传
- 指定Context路径
- 点击部署按钮
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 "=== 诊断完成 ==="
小结
通过本文学习,你应该掌握:
- 多种Tomcat应用部署方式
- WAR文件和目录部署的具体操作
- Context配置文件的使用方法
- Manager应用的部署管理功能
- 热部署的配置和实现
- 多环境部署的最佳实践
- 部署自动化的集成方法
- 部署监控和故障排除技巧
下一篇文章将详细介绍Tomcat服务器的高级配置。