Tomcat 容器化部署
Containerization and Deployment
概述
容器化技术为Tomcat应用部署提供了标准化、可移植和可扩展的解决方案。本文详细介绍Tomcat的Docker容器化、Kubernetes部署、Docker Compose编排和容器优化技术。
1. Docker基础容器化
1.1 基础Dockerfile
# Dockerfile - 基础Tomcat容器
FROM openjdk:11-jre-slim
# 设置环境变量
ENV CATALINA_HOME /opt/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
ENV JAVA_OPTS="-Xms512m -Xmx2048m -XX:+UseG1GC"
# 创建tomcat用户
RUN groupadd -r tomcat && \
useradd -r -g tomcat tomcat
# 下载和安装Tomcat
RUN apt-get update && \
apt-get install -y wget && \
wget -O /tmp/tomcat.tar.gz \
https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.65/bin/apache-tomcat-9.0.65.tar.gz && \
tar -xzf /tmp/tomcat.tar.gz -C /opt && \
mv /opt/apache-tomcat-9.0.65 $CATALINA_HOME && \
rm /tmp/tomcat.tar.gz && \
rm -rf $CATALINA_HOME/webapps/examples && \
rm -rf $CATALINA_HOME/webapps/docs && \
chown -R tomcat:tomcat $CATALINA_HOME && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 复制配置文件
COPY server.xml $CATALINA_HOME/conf/
COPY context.xml $CATALINA_HOME/conf/
COPY web.xml $CATALINA_HOME/conf/
# 复制应用
COPY app.war $CATALINA_HOME/webapps/
# 设置权限
RUN chmod +x $CATALINA_HOME/bin/*.sh
# 切换用户
USER tomcat
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# 启动命令
CMD ["catalina.sh", "run"]
1.2 优化的Dockerfile
# 多阶段构建Dockerfile
FROM maven:3.8-openjdk-11-slim AS builder
# 构建应用
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# 运行时镜像
FROM tomcat:9.0-jre11-openjdk-slim
# 环境变量
ENV CATALINA_OPTS="-Xms512m -Xmx2048m -XX:+UseG1GC -XX:+UseStringDeduplication"
ENV JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"
# 删除默认应用
RUN rm -rf $CATALINA_HOME/webapps/ROOT && \
rm -rf $CATALINA_HOME/webapps/examples && \
rm -rf $CATALINA_HOME/webapps/docs && \
rm -rf $CATALINA_HOME/webapps/host-manager
# 复制构建的应用
COPY --from=builder /app/target/myapp.war $CATALINA_HOME/webapps/ROOT.war
# 复制配置文件
COPY config/server.xml $CATALINA_HOME/conf/
COPY config/context.xml $CATALINA_HOME/conf/
COPY config/logging.properties $CATALINA_HOME/conf/
# 健康检查脚本
COPY health-check.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/health-check.sh
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD /usr/local/bin/health-check.sh
EXPOSE 8080
1.3 健康检查脚本
#!/bin/bash
# health-check.sh
HEALTH_URL="http://localhost:8080/health"
TIMEOUT=10
# 检查HTTP响应
response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $TIMEOUT "$HEALTH_URL")
if [ "$response" = "200" ]; then
echo "Health check passed"
exit 0
else
echo "Health check failed: HTTP $response"
exit 1
fi
2. Docker Compose编排
2.1 基础编排配置
# docker-compose.yml
version: '3.8'
services:
tomcat:
build: .
ports:
- "8080:8080"
environment:
- JAVA_OPTS=-Xms512m -Xmx1024m
- CATALINA_OPTS=-Dspring.profiles.active=docker
volumes:
- ./logs:/opt/tomcat/logs
- ./webapps:/opt/tomcat/webapps
depends_on:
- mysql
- redis
networks:
- app-network
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- app-network
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
ports:
- "6379:6379"
networks:
- app-network
restart: unless-stopped
volumes:
mysql-data:
redis-data:
networks:
app-network:
driver: bridge
2.2 高可用编排配置
# docker-compose-ha.yml
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- tomcat1
- tomcat2
- tomcat3
networks:
- frontend
- backend
restart: unless-stopped
tomcat1:
build: .
environment:
- JAVA_OPTS=-Xms1g -Xmx2g -XX:+UseG1GC
- JVM_ROUTE=tomcat1
- REDIS_HOST=redis
volumes:
- ./logs/tomcat1:/opt/tomcat/logs
networks:
- backend
restart: unless-stopped
tomcat2:
build: .
environment:
- JAVA_OPTS=-Xms1g -Xmx2g -XX:+UseG1GC
- JVM_ROUTE=tomcat2
- REDIS_HOST=redis
volumes:
- ./logs/tomcat2:/opt/tomcat/logs
networks:
- backend
restart: unless-stopped
tomcat3:
build: .
environment:
- JAVA_OPTS=-Xms1g -Xmx2g -XX:+UseG1GC
- JVM_ROUTE=tomcat3
- REDIS_HOST=redis
volumes:
- ./logs/tomcat3:/opt/tomcat/logs
networks:
- backend
restart: unless-stopped
mysql-master:
image: mysql:8.0
command: --server-id=1 --log-bin=mysql-bin --binlog-format=ROW
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_REPLICATION_USER: repl
MYSQL_REPLICATION_PASSWORD: replpass
volumes:
- mysql-master-data:/var/lib/mysql
networks:
- backend
restart: unless-stopped
mysql-slave:
image: mysql:8.0
command: --server-id=2
environment:
MYSQL_ROOT_PASSWORD: rootpass
volumes:
- mysql-slave-data:/var/lib/mysql
depends_on:
- mysql-master
networks:
- backend
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 1gb
volumes:
- redis-data:/data
networks:
- backend
restart: unless-stopped
volumes:
mysql-master-data:
mysql-slave-data:
redis-data:
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
3. Kubernetes部署
3.1 基础Deployment
# tomcat-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-app
labels:
app: tomcat
spec:
replicas: 3
selector:
matchLabels:
app: tomcat
template:
metadata:
labels:
app: tomcat
spec:
containers:
- name: tomcat
image: myregistry/tomcat-app:latest
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xms1g -Xmx2g -XX:+UseG1GC"
- name: CATALINA_OPTS
value: "-Dspring.profiles.active=k8s"
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: db.host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db.password
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: app-config
mountPath: /opt/tomcat/conf/app.properties
subPath: app.properties
- name: logs
mountPath: /opt/tomcat/logs
volumes:
- name: app-config
configMap:
name: app-config
- name: logs
emptyDir: {}
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
selector:
app: tomcat
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
db.host: mysql-service
db.port: "3306"
redis.host: redis-service
app.properties: |
server.port=8080
logging.level.root=INFO
spring.datasource.url=jdbc:mysql://${DB_HOST}:3306/appdb
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
db.password: YXBwcGFzcw== # base64 encoded
3.2 Ingress配置
# tomcat-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tomcat-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/use-regex: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tomcat-service
port:
number: 80
3.3 HorizontalPodAutoscaler
# tomcat-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: tomcat-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: tomcat-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
4. 容器优化配置
4.1 JVM容器优化
# JVM容器优化配置
FROM openjdk:11-jre-slim
# 容器感知JVM参数
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
-XX:+UseStringDeduplication \
-XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps \
-Xloggc:/opt/tomcat/logs/gc.log \
-XX:+UseGCLogFileRotation \
-XX:NumberOfGCLogFiles=5 \
-XX:GCLogFileSize=10M"
# 容器优化的启动脚本
COPY start-tomcat.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/start-tomcat.sh
CMD ["/usr/local/bin/start-tomcat.sh"]
4.2 启动脚本优化
#!/bin/bash
# start-tomcat.sh
set -e
# 获取容器资源限制
MEMORY_LIMIT=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
CPU_LIMIT=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
echo "Container Memory Limit: $MEMORY_LIMIT bytes"
echo "Container CPU Limit: $CPU_LIMIT"
# 动态计算JVM参数
if [ "$MEMORY_LIMIT" != "9223372036854775807" ]; then
# 容器有内存限制
MEMORY_MB=$((MEMORY_LIMIT / 1024 / 1024))
HEAP_SIZE=$((MEMORY_MB * 70 / 100))
export JAVA_OPTS="$JAVA_OPTS -Xms${HEAP_SIZE}m -Xmx${HEAP_SIZE}m"
fi
# 设置垃圾回收器
if [ "$MEMORY_MB" -gt 4096 ]; then
export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
else
export JAVA_OPTS="$JAVA_OPTS -XX:+UseParallelGC"
fi
# 等待依赖服务
echo "Waiting for dependencies..."
wait-for-it.sh $DB_HOST:3306 -t 60
wait-for-it.sh $REDIS_HOST:6379 -t 30
# 启动Tomcat
echo "Starting Tomcat with JAVA_OPTS: $JAVA_OPTS"
exec catalina.sh run
5. 容器监控和日志
5.1 监控配置
# monitoring.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat-jmx
labels:
app: tomcat
spec:
ports:
- port: 9999
name: jmx
selector:
app: tomcat
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: tomcat-monitor
spec:
selector:
matchLabels:
app: tomcat
endpoints:
- port: http
interval: 30s
path: /metrics
5.2 日志收集配置
# fluent-bit-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
data:
fluent-bit.conf: |
[SERVICE]
Flush 1
Log_Level info
Daemon off
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/containers/tomcat-*.log
Parser docker
Tag tomcat.*
Refresh_Interval 5
[OUTPUT]
Name elasticsearch
Match tomcat.*
Host elasticsearch
Port 9200
Index tomcat-logs
parsers.conf: |
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep On
6. 部署脚本和工具
6.1 Docker部署脚本
#!/bin/bash
# deploy-docker.sh
set -e
IMAGE_NAME="myregistry/tomcat-app"
VERSION=${1:-latest}
ENVIRONMENT=${2:-production}
echo "Deploying Tomcat application..."
echo "Image: $IMAGE_NAME:$VERSION"
echo "Environment: $ENVIRONMENT"
# 构建镜像
echo "Building Docker image..."
docker build -t $IMAGE_NAME:$VERSION .
# 推送到仓库
echo "Pushing to registry..."
docker push $IMAGE_NAME:$VERSION
# 部署
case $ENVIRONMENT in
"development")
echo "Deploying to development..."
docker-compose -f docker-compose.dev.yml up -d
;;
"staging")
echo "Deploying to staging..."
docker-compose -f docker-compose.staging.yml up -d
;;
"production")
echo "Deploying to production..."
docker-compose -f docker-compose.prod.yml up -d
;;
*)
echo "Unknown environment: $ENVIRONMENT"
exit 1
;;
esac
# 健康检查
echo "Waiting for application to start..."
sleep 30
if curl -f http://localhost:8080/health; then
echo "Deployment successful!"
else
echo "Deployment failed - health check failed"
exit 1
fi
6.2 Kubernetes部署脚本
#!/bin/bash
# deploy-k8s.sh
set -e
NAMESPACE=${1:-default}
IMAGE_TAG=${2:-latest}
ENVIRONMENT=${3:-production}
echo "Deploying to Kubernetes..."
echo "Namespace: $NAMESPACE"
echo "Image Tag: $IMAGE_TAG"
# 创建命名空间
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
# 更新镜像标签
sed -i "s//$IMAGE_TAG/g" k8s/tomcat-deployment.yaml
# 应用配置
kubectl apply -f k8s/ -n $NAMESPACE
# 等待部署完成
echo "Waiting for deployment to complete..."
kubectl rollout status deployment/tomcat-app -n $NAMESPACE --timeout=300s
# 检查Pod状态
kubectl get pods -n $NAMESPACE -l app=tomcat
# 获取服务地址
SERVICE_IP=$(kubectl get service tomcat-service -n $NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Service available at: http://$SERVICE_IP"
echo "Deployment completed successfully!"
小结
通过本文学习,你应该掌握:
- Tomcat Docker容器化的最佳实践
- 多阶段构建和镜像优化技术
- Docker Compose服务编排配置
- Kubernetes部署和服务管理
- 容器自动扩缩容配置
- JVM容器优化和资源管理
- 容器监控和日志收集
- 自动化部署脚本编写
下一篇文章将介绍Tomcat高级配置技巧。