前言
作为Java开发者,掌握Spring Boot应用的打包与部署是必备技能。本文将全面系统地介绍Spring Boot应用的打包与部署方式,从基础到高级,涵盖各种场景和需求。
一、Spring Boot打包基础
1.1 打包格式对比
Spring Boot支持多种打包格式,以下是主要格式的对比:
打包格式 | 文件扩展名 | 特点 | 适用场景 |
---|---|---|---|
JAR | .jar | 内嵌容器,可直接运行 | 微服务、云原生应用 |
WAR | .war | 需要外部容器部署 | 传统企业应用,需部署到Tomcat等容器 |
ZIP | .zip | 包含启动脚本和依赖 | 需要脚本控制的部署 |
TAR.GZ | .tar.gz | 压缩格式,节省空间 | Linux环境部署 |
1.2 打包配置
在pom.xml
中配置打包方式:
<packaging>jar</packaging> <!-- 或 war -->
Spring Boot的Maven插件配置:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.0</version>
<configuration>
<executable>true</executable> <!-- 设置为可执行 -->
</configuration>
</plugin>
</plugins>
</build>
1.3 打包命令
# 普通打包
mvn package
# 跳过测试打包
mvn package -DskipTests
# 重新打包(清理后)
mvn clean package
打包后会在target
目录下生成相应的文件,如myapp-0.0.1-SNAPSHOT.jar
。
二、JAR包部署详解
2.1 可执行JAR原理
Spring Boot的可执行JAR采用特殊结构:
myapp.jar
├── META-INF
│ └── MANIFEST.MF # 包含Main-Class和Start-Class
├── BOOT-INF
│ ├── classes # 应用类文件
│ └── lib # 依赖库
└── org
└── springframework
└── boot
└── loader # Spring Boot类加载器
2.2 运行JAR的多种方式
基本运行
java -jar myapp.jar
指定配置文件
java -jar myapp.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
自定义JVM参数
java -Xms256m -Xmx1024m -jar myapp.jar
后台运行(Linux)
nohup java -jar myapp.jar > app.log 2>&1 &
服务化运行(Systemd)
创建服务文件/etc/systemd/system/myapp.service
:
[Unit]
Description=My Spring Boot Application
After=syslog.target
[Service]
User=appuser
ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
然后启用服务:
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
三、WAR包部署详解
3.1 转换为WAR包
修改pom.xml
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope> <!-- 表示由容器提供 -->
</dependency>
</dependencies>
修改启动类
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3.2 部署到外部Tomcat
- 打包:
mvn clean package
- 将生成的WAR文件复制到Tomcat的
webapps
目录 - 启动Tomcat:
${TOMCAT_HOME}/bin/startup.sh
3.3 传统部署与云原生部署对比
特性 | 传统WAR部署 | 云原生JAR部署 |
---|---|---|
容器依赖 | 需要外部容器 | 内嵌容器 |
部署方式 | 文件复制 | 直接运行 |
多实例 | 复杂 | 简单 |
启动速度 | 较慢 | 较快 |
资源占用 | 较高 | 较低 |
适用场景 | 传统企业应用 | 微服务、云环境 |
四、高级打包技巧
4.1 分类依赖打包
将依赖库分离,加快更新部署速度:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
打包后会生成两个文件:
myapp.jar
- 仅包含应用代码myapp-exec.jar
- 完整可执行JAR
4.2 自定义MANIFEST.MF
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Implementation-Vendor>My Company</Implementation-Vendor>
<Built-By>${user.name}</Built-By>
</manifestEntries>
</configuration>
</plugin>
4.3 多环境打包
使用Profile
<profiles>
<profile>
<id>dev</id>
<properties>
<activatedProperties>dev</activatedProperties>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<activatedProperties>prod</activatedProperties>
</properties>
</profile>
</profiles>
指定Profile打包
mvn package -Pprod
多环境配置文件
resources/
├── application.yml
├── application-dev.yml
├── application-prod.yml
└── application-test.yml
五、Docker化部署
5.1 基础Dockerfile
# 使用OpenJDK官方镜像
FROM openjdk:11-jre-slim
# 维护者信息
LABEL maintainer="developer@company.com"
# 设置工作目录
WORKDIR /app
# 复制JAR文件到容器
COPY target/myapp.jar myapp.jar
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java", "-jar", "myapp.jar"]
构建并运行:
docker build -t myapp .
docker run -p 8080:8080 -d myapp
5.2 多阶段构建(优化镜像大小)
# 第一阶段:构建
FROM maven:3.6.3-jdk-11 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src /app/src
RUN mvn package -DskipTests
# 第二阶段:运行
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/target/myapp.jar myapp.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "myapp.jar"]
5.3 最佳实践Dockerfile
FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM eclipse-temurin:17-jre-jammy
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=builder ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=builder ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=builder ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.MyApplication"]
5.4 Docker Compose部署
docker-compose.yml
示例:
# 指定 Docker Compose 文件的版本,这里使用的是 3.8 版本,不同版本可能有不同的特性和语法支持
version: '3.8'
# services 部分定义了要部署的服务列表
services:
# 定义名为 app 的服务
app:
# 指定服务使用的镜像,这里使用名为 myapp:latest 的镜像。
# 如果本地不存在该镜像,Docker Compose 会尝试拉取。
image: myapp:latest
# 配置构建镜像的相关信息,. 表示在当前目录下查找 Dockerfile 来构建镜像。
# 如果同时指定了 image 和 build,优先使用 build 构建镜像。
build: .
# 端口映射配置,将容器的 8080 端口映射到宿主机的 8080 端口,
# 这样外部可以通过宿主机的 8080 端口访问容器内的服务。
ports:
- "8080:8080"
# 环境变量配置,设置名为 SPRING_PROFILES_ACTIVE 的环境变量,值为 prod,
# 通常用于指定 Spring 应用的运行环境。
environment:
- SPRING_PROFILES_ACTIVE=prod
# 卷挂载配置,将宿主机当前目录下的 logs 目录挂载到容器内的 /app/logs 目录,
# 实现数据持久化和共享,方便查看容器内生成的日志。
volumes:
- ./logs:/app/logs
# 健康检查配置,用于定期检查服务是否正常运行
healthcheck:
# 执行的检查命令,使用 curl 命令检查 http://localhost:8080/actuator/health 地址是否可访问,
# -f 选项表示如果请求失败不显示错误信息。
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
# 检查的时间间隔,每隔 30 秒执行一次健康检查。
interval: 30s
# 单次检查的超时时间,如果 10 秒内未收到响应则认为检查失败。
timeout: 10s
# 最大重试次数,如果连续 3 次检查失败,则认为服务不健康。
retries: 3
# 定义名为 redis 的服务
redis:
# 指定服务使用的镜像,这里使用 redis:alpine 镜像,alpine 是一个轻量级的 Linux 发行版。
image: redis:alpine
# 端口映射配置,将容器的 6379 端口映射到宿主机的 6379 端口,
# 以便外部可以通过宿主机的 6379 端口访问 Redis 服务。
ports:
- "6379:6379"
# 卷挂载配置,将名为 redis_data 的卷挂载到容器内的 /data 目录,
# 用于持久化存储 Redis 的数据。
volumes:
- redis_data:/data
# volumes 部分定义了要使用的卷
volumes:
# 定义名为 redis_data 的卷,Docker 会自动管理这个卷的创建和删除。
redis_data:
六、云原生部署
6.1 Kubernetes部署
基础Deployment
# 定义 Kubernetes API 版本,apps/v1 表明使用应用相关的 API 组版本
apiVersion: apps/v1
# 定义资源类型,这里是 Deployment 资源,用于管理应用的副本数量和滚动更新等
kind: Deployment
# 元数据部分,包含资源的名称和标签等信息
metadata:
# Deployment 的名称,用于在 Kubernetes 集群中唯一标识该 Deployment
name: myapp-deployment
# 标签,用于对资源进行分类和选择,这里定义了一个名为 app 的标签,值为 myapp
labels:
app: myapp
# 规范部分,定义了 Deployment 的具体配置
spec:
# 指定应用的副本数量,这里设置为 3 个,意味着 Kubernetes 会确保始终有 3 个 Pod 运行该应用
replicas: 3
# 选择器,用于指定 Deployment 管理哪些 Pod,这里通过匹配标签 app=myapp 来选择 Pod
selector:
matchLabels:
app: myapp
# Pod 模板,定义了 Deployment 创建的 Pod 的配置
template:
# Pod 的元数据,包含标签等信息
metadata:
# 标签,用于对 Pod 进行分类和选择,这里的标签要与 selector 中的匹配标签一致
labels:
app: myapp
# Pod 的规范部分,定义了 Pod 中容器的配置
spec:
# 容器列表,这里只定义了一个容器
containers:
# 容器的名称,用于在 Pod 中唯一标识该容器
- name: myapp
# 容器使用的镜像,这里指定了镜像的仓库地址和版本号
image: myregistry/myapp:1.0.0
# 镜像拉取策略,Always 表示每次创建或重启 Pod 时都会尝试从镜像仓库拉取最新的镜像
imagePullPolicy: Always
# 容器暴露的端口,这里指定容器内部监听的端口为 8080
ports:
- containerPort: 8080
# 环境变量列表,用于向容器内传递配置信息
env:
# 环境变量的名称
- name: SPRING_PROFILES_ACTIVE
# 环境变量的值,这里指定 Spring 应用使用生产环境配置
value: "prod"
# 资源请求和限制配置
resources:
# 资源请求,告诉 Kubernetes 为该容器分配的最小资源量
requests:
# CPU 请求,500m 表示 0.5 个 CPU 核心
cpu: "500m"
# 内存请求,512Mi 表示 512 兆字节的内存
memory: "512Mi"
# 资源限制,限制容器使用的最大资源量
limits:
# CPU 限制,1 表示 1 个 CPU 核心
cpu: "1"
# 内存限制,1Gi 表示 1 吉字节的内存
memory: "1Gi"
# 存活探针配置,用于检测容器是否正常运行
livenessProbe:
# 使用 HTTP GET 请求进行检测
httpGet:
# 请求的路径,这里通过访问 /actuator/health 端点来检测应用的健康状态
path: /actuator/health
# 请求的端口,与容器暴露的端口一致
port: 8080
# 初始延迟时间,容器启动后等待 30 秒再开始进行存活检测
initialDelaySeconds: 30
# 检测周期,每隔 10 秒进行一次存活检测
periodSeconds: 10
# 就绪探针配置,用于检测容器是否准备好接收请求
readinessProbe:
# 使用 HTTP GET 请求进行检测
httpGet:
# 请求的路径,这里通过访问 /actuator/health 端点来检测应用的健康状态
path: /actuator/health
# 请求的端口,与容器暴露的端口一致
port: 8080
# 初始延迟时间,容器启动后等待 20 秒再开始进行就绪检测
initialDelaySeconds: 20
# 检测周期,每隔 5 秒进行一次就绪检测
periodSeconds: 5
Service配置
# 定义 Kubernetes API 版本,v1 是核心 API 组的版本
apiVersion: v1
# 定义资源类型,这里是 Service 资源,用于为一组 Pod 提供统一的网络访问入口
kind: Service
# 元数据部分,包含资源的名称等信息
metadata:
# Service 的名称,用于在 Kubernetes 集群中唯一标识该 Service
name: myapp-service
# 规范部分,定义了 Service 的具体配置
spec:
# 选择器,用于指定 Service 要代理的 Pod。这里通过匹配标签 app=myapp 来选择 Pod,
# 意味着这个 Service 会将请求转发到带有 app=myapp 标签的 Pod 上
selector:
app: myapp
# 端口配置,定义了 Service 如何接收和转发流量
ports:
# 端口配置项,这里定义了一个端口规则
- protocol: TCP
# Service 对外暴露的端口,外部客户端可以通过这个端口访问 Service
port: 80
# 目标端口,Service 接收到的流量会被转发到后端 Pod 的这个端口上
targetPort: 8080
# Service 的类型,LoadBalancer 表示使用云提供商的负载均衡器来将流量分发到后端 Pod。
# 当创建这种类型的 Service 时,云提供商会自动创建一个外部负载均衡器,并将其 IP 地址分配给该 Service
type: LoadBalancer
6.2 Helm Chart部署
目录结构:
myapp-chart/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── configmap.yaml
└── charts/
示例values.yaml
:
replicaCount: 3
image:
repository: myregistry/myapp
tag: 1.0.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
limits:
cpu: 1
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
七、性能优化与监控
7.1 JVM调优参数
常用JVM参数表:
参数 | 说明 | 示例 |
---|---|---|
-Xms | 初始堆大小 | -Xms512m |
-Xmx | 最大堆大小 | -Xmx1024m |
-XX:MetaspaceSize | 元空间初始大小 | -XX:MetaspaceSize=128m |
-XX:MaxMetaspaceSize | 元空间最大大小 | -XX:MaxMetaspaceSize=256m |
-XX:+UseG1GC | 使用G1垃圾收集器 | -XX:+UseG1GC |
-XX:MaxGCPauseMillis | 最大GC停顿时间目标 | -XX:MaxGCPauseMillis=200 |
-XX:ParallelGCThreads | 并行GC线程数 | -XX:ParallelGCThreads=4 |
-XX:ConcGCThreads | 并发GC线程数 | -XX:ConcGCThreads=2 |
7.2 Spring Boot Actuator监控
配置application.yml
:
management:
endpoint:
health:
show-details: always
metrics:
enabled: true
prometheus:
enabled: true
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
7.3 性能监控指标
关键监控指标表:
指标 | 路径 | 说明 |
---|---|---|
JVM内存 | /actuator/metrics/jvm.memory.used | JVM内存使用情况 |
HTTP请求 | /actuator/metrics/http.server.requests | HTTP请求统计 |
系统CPU | /actuator/metrics/system.cpu.usage | 系统CPU使用率 |
线程信息 | /actuator/metrics/jvm.threads.live | 活动线程数 |
垃圾回收 | /actuator/metrics/jvm.gc.pause | GC暂停时间 |
八、安全部署实践
8.1 安全加固措施
安全措施对照表:
措施 | 实现方式 | 说明 |
---|---|---|
禁用敏感端点 | management.endpoints.web.exposure.exclude=env,beans | 限制暴露的端点 |
启用HTTPS | server.ssl.enabled=true | 加密通信 |
认证保护 | spring.security.user.name/password | 基本认证 |
内容安全策略 | 添加安全头 | 防止XSS等攻击 |
最小权限原则 | 使用非root用户运行 | 降低风险 |
8.2 使用非root用户运行
Dockerfile示例:
FROM openjdk:11-jre-slim
RUN addgroup --system appuser && adduser --system --no-create-home --ingroup appuser appuser
WORKDIR /app
COPY --chown=appuser:appuser target/myapp.jar myapp.jar
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "myapp.jar"]
8.3 密钥管理
使用环境变量或密钥管理服务:
# application.yml
spring:
datasource:
password: ${DB_PASSWORD}
Kubernetes中使用Secret:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQxMjM=
然后在Deployment中引用:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
九、持续集成与持续部署(CI/CD)
9.1 GitHub Actions示例
.github/workflows/build-deploy.yml
:
name: Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Build with Maven
run: mvn -B package --file pom.xml -DskipTests
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push Docker image
run: |
docker build -t ${{ secrets.DOCKER_HUB_USERNAME }}/myapp:${{ github.sha }} .
docker push ${{ secrets.DOCKER_HUB_USERNAME }}/myapp:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Install kubectl
uses: azure/setup-kubectl@v1
- name: Deploy to Kubernetes
run: |
echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig.yaml
export KUBECONFIG=kubeconfig.yaml
kubectl set image deployment/myapp myapp=${{ secrets.DOCKER_HUB_USERNAME }}/myapp:${{ github.sha }}
9.2 Jenkins Pipeline示例
Jenkinsfile
:
pipeline {
agent any
environment {
DOCKER_IMAGE = 'myregistry/myapp'
KUBECONFIG = credentials('kubeconfig')
}
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '**/target/surefire-reports/*.xml'
}
}
}
stage('Build Docker Image') {
steps {
script {
docker.build("${DOCKER_IMAGE}:${env.BUILD_ID}")
}
}
}
stage('Push Docker Image') {
steps {
script {
docker.withRegistry('https://myregistry.com', 'dockerhub') {
docker.image("${DOCKER_IMAGE}:${env.BUILD_ID}").push()
}
}
}
}
stage('Deploy to Kubernetes') {
steps {
sh """
kubectl apply -f k8s/deployment.yaml
kubectl set image deployment/myapp myapp=${DOCKER_IMAGE}:${env.BUILD_ID}
"""
}
}
}
post {
success {
slackSend channel: '#deployments',
color: 'good',
message: "Deployment succeeded: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
}
failure {
slackSend channel: '#deployments',
color: 'danger',
message: "Deployment failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
}
}
}
十、故障排查与日志管理
10.1 常见问题排查表
问题现象 | 可能原因 | 解决方案 |
---|---|---|
应用启动失败 | 端口冲突 | 检查端口使用netstat -tulnp ,修改server.port |
内存溢出 | 内存不足或内存泄漏 | 增加JVM内存,分析内存dump |
响应缓慢 | 数据库查询慢或GC频繁 | 优化SQL,调整JVM参数 |
连接池耗尽 | 连接泄漏或配置不当 | 检查连接关闭,调整连接池大小 |
健康检查失败 | 依赖服务不可用 | 检查依赖服务,设置合理的超时 |
10.2 日志配置最佳实践
application.yml
示例:
logging:
level:
root: INFO
org.springframework.web: DEBUG
com.myapp: DEBUG
file:
name: logs/app.log
max-history: 30
max-size: 100MB
pattern:
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"
10.3 日志收集架构
推荐使用ELK(Elasticsearch+Logstash+Kibana)或EFK(Elasticsearch+Fluentd+Kibana)栈:
- 应用输出结构化日志(JSON格式)
- Filebeat或Fluentd收集日志
- 发送到Logstash进行过滤和处理
- 存储到Elasticsearch
- 通过Kibana可视化
Docker Compose日志配置示例:
services:
app:
image: myapp
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
总结
本文全面介绍了Spring Boot应用的打包与部署,从基础的JAR/WAR打包到高级的云原生部署,涵盖了各种场景和最佳实践。关键点总结:
- 打包选择:根据场景选择JAR(云原生)或WAR(传统部署)
- 部署方式:从简单命令行到容器化、Kubernetes集群部署
- 性能优化:合理配置JVM参数和Spring Boot特性
- 安全实践:最小权限原则、密钥管理、安全加固
- 自动化:通过CI/CD实现高效可靠的部署流程
- 可观测性:完善的日志和监控是生产环境必备
转发就算了,毕竟这么好笑的东西不能独享。
想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。