部署概述与手动部署
概述
部署目标
软件部署是将构建后的应用程序发布到目标环境并使其对外提供服务的过程。部署环节直接关系到系统的可用性、稳定性和交付效率。本文档旨在提供部署的总体概念介绍,并详细说明 Java、前端、Python、Node.js 四种技术栈的手动部署方式。
环境分类
标准部署流程通常包含以下环境,每个环境服务于不同的目的:
| 环境 | 用途 | 配置要求 | 部署频率 | 数据要求 |
|---|---|---|---|---|
| 开发环境 (Dev) | 开发人员自测、联调 | 最低配置,共享资源 | 频繁(每日多次) | 脱敏数据或少量测试数据 |
| 测试环境 (Test) | QA 功能测试、集成测试 | 中等配置,独立资源 | 按迭代节奏(每日) | 模拟真实业务场景的测试数据 |
| 预发布环境 (Staging) | 生产预演、性能验收、验收测试 | 与生产环境等配或接近 | 每次发版前 | 脱敏的生产数据副本 |
| 生产环境 (Prod) | 面向最终用户提供服务 | 高可用配置,多副本集群 | 按发布计划 | 真实用户数据 |
部署策略概览
不同环境和业务场景适合不同的部署策略:
- 全量部署:一次性替换所有实例,适用于低风险场景或开发/测试环境
- 滚动部署:分批替换实例,逐步完成升级,适用于无状态服务
- 蓝绿部署:同时维护两套完整环境,一次性切换流量,回滚迅速
- 灰度/金丝雀部署:小比例用户先行验证,逐步扩大范围,风险可控
Java 手动部署
构建流程
Java 项目通常使用 Maven 或 Gradle 进行构建,产出可执行的 JAR 包。
Maven 构建
# 清理并打包(跳过测试)
mvn clean package -DskipTests
# 构建并安装到本地仓库
mvn clean install -DskipTests
# 指定 profile 构建(如开发/生产环境)
mvn clean package -P prod -DskipTests构建产物通常位于 target/ 目录下,为 *.jar 文件。推荐使用 Spring Boot Maven Plugin 构建可执行 Fat JAR:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>Gradle 构建
# 清理并构建
./gradlew clean bootJar
# 指定环境
./gradlew clean bootJar -Dspring.profiles.active=prod后台运行
使用 nohup
# 后台启动,日志输出到文件
nohup java -jar app.jar \
--spring.profiles.active=prod \
> app.log 2>&1 &
# 记录 PID 以便后续管理
echo $! > app.pid
# 查看进程
ps aux | grep app.jar
# 停止进程
kill $(cat app.pid)使用 java 命令优化参数
# 生产环境推荐 JVM 参数
java -Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/app/heapdump.hprof \
-jar app.jar \
--spring.profiles.active=prodSystemd 服务管理
使用 systemd 可以将 Java 应用注册为系统服务,实现开机自启、自动重启和日志管理。
创建服务单元文件
在 /etc/systemd/system/app.service 中创建服务配置:
[Unit]
Description=Application Service
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/app
Environment="JAVA_HOME=/usr/lib/jvm/java-17"
Environment="SPRING_PROFILES_ACTIVE=prod"
ExecStart=/usr/lib/jvm/java-17/bin/java \
-Xms2g -Xmx2g \
-XX:+UseG1GC \
-jar /opt/app/app.jar
ExecStop=/bin/kill -SIGTERM $MAINPID
SuccessExitStatus=143
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target服务管理命令
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start app
# 设置开机自启
sudo systemctl enable app
# 查看状态
sudo systemctl status app
# 查看实时日志
sudo journalctl -u app -f
# 重启服务
sudo systemctl restart app
# 停止服务
sudo systemctl stop app日志管理
使用 logrotate 进行日志轮转
在 /etc/logrotate.d/app 中配置:
/opt/app/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
dateext
dateformat _%Y-%m-%d
}统一日志目录结构
/opt/app/
├── app.jar # 应用 JAR 包
├── config/ # 外部配置文件
│ └── application-prod.yml
├── logs/
│ ├── app.log # 应用主日志
│ ├── error.log # 错误日志
│ ├── gc.log # GC 日志
│ └── heapdump/ # 堆转储文件
└── bin/
├── start.sh # 启动脚本
├── stop.sh # 停止脚本
└── restart.sh # 重启脚本前端手动部署
构建流程
使用 npm 或 yarn 构建前端项目,输出静态资源文件。
Vue 项目构建
# 安装依赖
npm install
# 构建生产环境
npm run build
# 构建指定环境
npm run build -- --mode stagingReact 项目构建
# 安装依赖
npm install
# 构建生产环境
npm run build
# 指定环境变量
REACT_APP_API_BASE_URL=https://api.example.com npm run build构建产物通常位于 dist/ 或 build/ 目录下,包含 index.html、js/、css/、assets/ 等文件。
Nginx 配置静态文件服务
基本配置
server {
listen 80;
server_name example.com;
root /opt/frontend/dist;
index index.html;
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
gzip_min_length 1024;
gzip_vary on;
# 静态资源缓存(带有 hash 的文件)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# JS/CSS 文件缓存
location ~* \.(js|css)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# 图片缓存
location ~* \.(png|jpg|jpeg|gif|ico|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# SPA 路由重写(所有非文件请求返回 index.html)
location / {
try_files $uri $uri/ /index.html;
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}HTTPS 配置
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /opt/frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}CDN 部署
流程
- 构建前端项目,生成静态文件
- 将构建产物上传至 CDN 或对象存储(如阿里云 OSS、AWS S3)
- 在 CDN 控制台配置域名、回源策略、缓存规则
- 配置 CNAME 解析,将域名指向 CDN 加速域名
使用阿里云 OSS + CDN 示例
# 使用 ossutil 上传静态文件
ossutil cp -r ./dist/ oss://bucket-name/ --update
# 设置公共读权限
ossutil set-acl oss://bucket-name/ public-read --recursiveCDN 缓存配置建议
| 文件类型 | 缓存时间 | 策略 |
|---|---|---|
index.html | 不缓存或短缓存(5-10 分钟) | 必须回源验证,确保版本更新后用户能获取最新 HTML |
*.js, *.css(含 hash) | 长期缓存(1 年) | 使用内容 hash 文件名,不可变缓存 |
*.png, *.jpg, *.svg | 长期缓存(30 天) | 版本变更时更换文件名 |
favicon.ico | 长期缓存(30 天) | 版本变更时清理 CDN 缓存 |
Python 手动部署
虚拟环境
使用 venv 或 virtualenv 创建隔离的 Python 运行环境。
# 创建虚拟环境
python3 -m venv /opt/app/venv
# 激活虚拟环境
source /opt/app/venv/bin/activate
# 退出虚拟环境
deactivate依赖管理
生成 requirements.txt 并在部署时安装依赖。
# 导出依赖
pip freeze > requirements.txt
# 安装依赖(在生产环境)
pip install -r requirements.txt --no-cache-dir
# 使用国内镜像加速
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/建议使用 Pipenv 或 Poetry 进行更完善的依赖管理:
# Poetry 安装依赖
poetry install --no-dev
# 构建 wheel 包
poetry buildGunicorn + Nginx 部署
Gunicorn 配置
创建 gunicorn.conf.py:
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker" # 异步 ASGI 应用
# worker_class = "sync" # 同步 WSGI 应用
timeout = 120
graceful_timeout = 30
keepalive = 5
max_requests = 1000
max_requests_jitter = 50
accesslog = "/var/log/app/gunicorn_access.log"
errorlog = "/var/log/app/gunicorn_error.log"
loglevel = "info"启动 Gunicorn
# 启动(前台)
gunicorn -c gunicorn.conf.py app:app
# 后台启动(使用 nohup)
nohup gunicorn -c gunicorn.conf.py app:app > /dev/null 2>&1 &
# 重启(平滑重启)
kill -HUP $(cat /var/run/gunicorn.pid)Nginx 反向代理配置
upstream app_backend {
server 127.0.0.1:8000;
# 多实例时添加更多 upstream
# server 127.0.0.1:8001;
keepalive 64;
}
server {
listen 80;
server_name api.example.com;
# 请求体大小限制(根据业务调整)
client_max_body_size 50m;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时配置
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
# 静态文件由 Nginx 直接处理
location /static/ {
alias /opt/app/static/;
expires 30d;
}
location /media/ {
alias /opt/app/media/;
expires 7d;
}
}uWSGI + Nginx 部署
uWSGI 配置
创建 uwsgi.ini:
[uwsgi]
project = app
base = /opt/app
chdir = %(base)
module = %(project).wsgi:application
master = true
processes = 4
threads = 2
buffer-size = 32768
socket = /tmp/app.sock
chmod-socket = 666
vacuum = true
die-on-term = true
need-app = true
logto = /var/log/app/uwsgi.log
log-reopen = trueNginx + uWSGI 配置
upstream app_uwsgi {
server unix:///tmp/app.sock;
}
server {
listen 80;
server_name api.example.com;
location / {
include uwsgi_params;
uwsgi_pass app_uwsgi;
uwsgi_read_timeout 60s;
uwsgi_send_timeout 60s;
}
location /static/ {
alias /opt/app/static/;
}
}Node.js 手动部署
PM2 进程管理
PM2 是 Node.js 最常用的进程管理工具,支持进程守护、负载均衡、日志管理和开机自启。
安装 PM2
npm install -g pm2启动应用
# 启动应用
pm2 start app.js --name my-app
# 启动时指定环境变量
pm2 start app.js --name my-app --env production
# 使用配置文件启动(推荐)
pm2 start ecosystem.config.js配置文件 ecosystem.config.js
module.exports = {
apps: [{
name: "my-app",
script: "app.js",
instances: "max", // 利用所有 CPU 核心
exec_mode: "cluster", // 集群模式,实现负载均衡
env: {
NODE_ENV: "development",
PORT: 3000
},
env_production: {
NODE_ENV: "production",
PORT: 8080
},
// 日志配置
error_file: "/var/log/app/error.log",
out_file: "/var/log/app/out.log",
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
merge_logs: true,
// 自动重启
max_restarts: 10,
restart_delay: 5000,
// 内存监控
max_memory_restart: "500M",
// 源文件监控
watch: false
}]
};常用 PM2 命令
# 查看所有应用状态
pm2 list
# 查看实时日志
pm2 logs my-app
# 查看最近 100 行日志
pm2 logs my-app --lines 100
# 监控资源使用
pm2 monit
# 重启应用
pm2 restart my-app
# 重新加载(零停机重启集群模式)
pm2 reload my-app
# 停止应用
pm2 stop my-app
# 删除应用
pm2 delete my-app
# 查看应用详情
pm2 show my-app环境变量管理
# 方式一:启动时传入
NODE_ENV=production DATABASE_URL=mysql://user:pass@host/db pm2 start app.js
# 方式二:使用配置文件(推荐)
pm2 start ecosystem.config.js --env production
# 方式三:使用 .env 文件
# 安装 dotenv:npm install dotenv
# 在代码中:require("dotenv").config()日志管理
PM2 内置日志管理功能,可配合 logrotate 进行日志轮转。
# 安装 pm2-logrotate 插件
pm2 install pm2-logrotate
# 配置日志轮转
pm2 set pm2-logrotate:max_size 100M # 单个日志文件最大 100MB
pm2 set pm2-logrotate:retain 30 # 保留最近 30 个文件
pm2 set pm2-logrotate:compress true # 启用压缩
pm2 set pm2-logrotate:interval daily # 按天轮转
pm2 set pm2-logrotate:rotateInterval "0 0 * * *" # 每天 0 点执行轮转开机自启
# 生成开机自启脚本
pm2 startup
# 保存当前进程列表
pm2 save
# 查看已保存的进程列表
pm2 resurrect执行 pm2 startup 后,PM2 会提示执行类似以下命令来安装自启脚本:
# 示例(具体命令以 PM2 提示为准)
sudo pm2 startup systemd -u appuser --hp /home/appuserNginx 反向代理 Node.js
upstream node_backend {
# PM2 集群模式下,多个实例监听同一端口
server 127.0.0.1:8080;
keepalive 64;
}
server {
listen 80;
server_name api.example.com;
client_max_body_size 50m;
location / {
proxy_pass http://node_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /socket.io/ {
proxy_pass http://node_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # WebSocket 长连接超时
}
}各方式对比
适用场景与优缺点
| 技术栈 | 部署方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Java | nohup + JAR | 小型应用、临时部署、开发测试环境 | 简单直接,无需额外工具 | 缺乏进程守护,Shell 退出后可能丢失控制 |
| Java | systemd | 生产环境单机部署 | 开机自启、自动重启、日志统一管理 | 配置稍复杂,多实例管理不便 |
| Java | Docker/K8s | 微服务、容器化部署 | 环境隔离、弹性伸缩、编排能力强 | 学习成本高,资源开销略大 |
| 前端 | Nginx 静态文件 | 传统 Web 应用、SPA | 性能高,配置灵活,成熟稳定 | 需要手动管理文件更新 |
| 前端 | CDN 部署 | 高并发、全球化分发 | 全球加速,抗流量冲击,减轻源站压力 | 缓存刷新有延迟,调试复杂 |
| 前端 | 对象存储 + CDN | 静态站点、Jamstack 架构 | 无需服务器,按量付费,弹性伸缩 | 动态渲染需求受限 |
| Python | Gunicorn + Nginx | Django/Flask 等 WSGI 应用 | 成熟方案,性能良好 | 需要单独管理静态文件和反向代理 |
| Python | uWSGI + Nginx | 高并发 Python 应用 | 性能优于 Gunicorn,配置灵活 | 配置项复杂,社区活跃度下降 |
| Python | Gunicorn + Uvicorn | FastAPI 等 ASGI 异步应用 | 支持异步,性能高,生态现代 | 相比同步方案排查问题难度更高 |
| Node.js | PM2 | 通用 Node.js 生产部署 | 进程守护、零停机重载、日志管理 | 单机方案,多机需要额外负载均衡 |
| Node.js | PM2 + Nginx | 需要反向代理的场景 | 结合静态资源处理、SSL 终止 | 多一层代理,偶有配置复杂度 |
| Node.js | Docker + PM2 | 容器化 Node.js 应用 | 环境一致性,便于 CI/CD 集成 | 镜像体积较大,基础镜像选择需谨慎 |
部署方式维度对比
| 维度 | nohup / 前台启动 | systemd | PM2 | Gunicorn/uWSGI | Nginx 静态 |
|---|---|---|---|---|---|
| 进程守护 | 不支持 | 支持 | 支持 | 需自行配置 | N/A |
| 自动重启 | 不支持 | 支持(Restart=always) | 支持(max_restarts) | 需上层工具配合 | N/A |
| 开机自启 | 不支持 | 支持 | 支持(pm2 startup) | 需 systemd 配合 | 需 systemd 配合 |
| 日志管理 | 重定向 | journalctl | 内置 + logrotate | 内置 | access_log / error_log |
| 零停机重载 | 不支持 | 不支持 | 支持(reload) | 支持(-HUP) | nginx -s reload |
| 集群模式 | 需自实现 | 需自实现 | 原生支持 | 多进程模式 | N/A |
| 学习成本 | 低 | 中 | 低 | 中 | 低 |
| 适用语言 | 通用 | 通用 | Node.js 专用 | Python 专用 | 前端静态文件 |
选型建议
- Java 生产环境:优先使用 systemd 管理单机部署,多实例或微服务场景推荐 Docker/K8s
- 前端生产环境:高并发场景选择 CDN 部署,内网应用可选择 Nginx 静态文件服务
- Python 生产环境:小型应用用 Gunicorn + Nginx,高并发场景用 uWSGI,异步框架用 Uvicorn
- Node.js 生产环境:PM2 是事实标准,配合 Nginx 处理反向代理和静态资源,多机部署可结合 Docker