服务器部署
方法论: 部署不是玄学,理解架构原理后,所有问题都能迎刃而解。
📖 本节目标
学完本节,你将理解:
- ✅ 云服务器和安全组配置
- ✅ Nginx反向代理的原理和配置
- ✅ PM2进程管理器的使用
- ✅ HTTPS证书配置
- ✅ 域名解析和DNS配置
预计用时: 40分钟
1. 部署架构全景图
1.1 从用户访问到服务器响应
用户浏览器 www.yoursite.com
│
▼
域名解析(DNS) → 将域名转换为IP地址
│
▼
云服务器安全组 → 防火墙,检查是否允许访问
│
▼
Nginx反向代理 → 前台接待员,路由请求
│
├─→ 静态文件 → 直接返回HTML/CSS/JS
│
└─→ 后端API → PM2管理的Node.js/Python应用
│
└─→ 数据库 → 读取/写入数据
1.2 核心概念类比
| 组件 | 生活类比 | 作用 |
|---|---|---|
| 云服务器 | 商场大楼 | 24小时营业的电脑 |
| 安全组/防火墙 | 门禁系统 | 控制谁能进来 |
| 域名 | 商场地址 | www.yoursite.com |
| DNS | 导航地图 | 域名→IP地址 |
| Nginx | 前台接待 | 分流访客到不同部门 |
| PM2 | 部门经理 | 管理员工(进程),自动重启 |
| 应用进程 | 员工 | 处理具体业务 |
2. 云服务器和安全组
2.1 什么是云服务器?
云服务器 = 远程的24小时开机电脑
| 你的笔记本 | 云服务器 |
|---|---|
| 在你家里 | 在机房里 |
| 你关机就停止 | 24小时不停机 |
| 只有你能访问 | 全世界都能访问(如果配置正确) |
| 断网就用不了 | 多条网络线路,高可用 |
2.2 安全组配置 ⚠️ 新手必看!
最常见的部署失败原因: 忘记配置安全组!
安全组 = 云服务器的门禁系统
云安全组
用户 ─────────────> │端口80? 允许│ ────> Nginx
│端口443? 允许│
│端口3000? 拒绝│ ✗ 你的应用访问不了!
2.3 必须开放的端口
| 端口 | 协议 | 用途 | 是否必需 |
|---|---|---|---|
| 22 | SSH | 远程登录服务器 | ✅ 必需 |
| 80 | HTTP | 网站访问(HTTP) | ✅ 必需 |
| 443 | HTTPS | 网站访问(HTTPS) | ✅ 强烈推荐 |
| 3000/8000 | 自定义 | 应用直接访问 | ❌ 不推荐暴露 |
2.4 配置安全组(各云平台)
阿里云:
- 登录控制台
- 云服务器ECS → 网络与安全 → 安全组
- 配置规则 → 添加入方向规则
- 添加端口: 22, 80, 443
腾讯云:
- 登录控制台
- 云服务器 → 安全组 → 修改规则
- 入站规则 → 添加规则
- 添加端口: 22, 80, 443
AWS:
- EC2 → Security Groups
- Edit inbound rules
- Add Rule → 添加端口
验证配置:
# 在本地测试端口是否开放
telnet your-server-ip 80
telnet your-server-ip 443
# 或使用curl
curl http://your-server-ip
3. Nginx反向代理
3.1 为什么需要Nginx?
场景: 你有一个前端(React)和一个后端(Node.js)
不用Nginx的问题:
前端: http://your-ip:3000 ← 用户要记端口
后端: http://your-ip:8000 ← 两个地址,跨域问题
用Nginx后:
用户访问: https://www.yoursite.com
Nginx自动分流:
- www.yoursite.com/ → 前端静态文件
- www.yoursite.com/api/ → 后端API
一个域名,无跨域! ✅
3.2 Nginx的三大作用
| 作用 | 说明 | 类比 |
|---|---|---|
| 反向代理 | 隐藏真实服务器,统一入口 | 前台接待,分流访客 |
| 负载均衡 | 分发请求到多个服务器 | 开多个窗口,排队均匀 |
| 静态资源服务 | 高效提供HTML/CSS/JS | 自助服务区,不用麻烦后厨 |
3.3 安装Nginx
Ubuntu/Debian:
sudo apt update
sudo apt install nginx
# 启动Nginx
sudo systemctl start nginx
# 开机自启
sudo systemctl enable nginx
# 检查状态
sudo systemctl status nginx
CentOS/RHEL:
sudo yum install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
验证安装:
打开浏览器,访问 http://your-server-ip
看到“Welcome to nginx!“说明成功!
3.4 Nginx配置文件
主配置文件位置:
# Ubuntu/Debian
/etc/nginx/nginx.conf
/etc/nginx/sites-available/default
# CentOS/RHEL
/etc/nginx/nginx.conf
/etc/nginx/conf.d/default.conf
基础配置示例:
# /etc/nginx/sites-available/mysite
server {
listen 80; # 监听80端口
server_name www.yoursite.com; # 你的域名
# 前端静态文件
location / {
root /var/www/mysite/dist; # 前端build后的文件夹
index index.html;
try_files $uri $uri/ /index.html; # 解决SPA刷新404
}
# 后端API代理
location /api/ {
proxy_pass http://localhost:3000; # 转发到后端
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
配置说明:
| 指令 | 作用 |
|---|---|
listen 80 | 监听HTTP请求(80端口) |
server_name | 域名或IP |
location / | 处理根路径请求 |
root | 静态文件目录 |
try_files | SPA应用防404 |
proxy_pass | 反向代理到后端 |
3.5 启用配置
# 创建软链接(Ubuntu/Debian)
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
# 测试配置是否正确
sudo nginx -t
# 重载配置
sudo nginx -s reload
# 或重启Nginx
sudo systemctl restart nginx
3.6 解决SPA刷新404问题
问题: React/Vue应用刷新页面后404
原因: Nginx找不到物理文件/about.html
解决: 使用try_files
location / {
root /var/www/mysite/dist;
index index.html;
try_files $uri $uri/ /index.html; # 关键配置!
}
原理: 找不到文件时,返回index.html,让前端路由处理
4. PM2进程管理
4.1 为什么需要PM2?
问题: 直接运行node app.js
node app.js
# 程序崩溃了 → 应用停止! ✗
# 关闭终端 → 应用停止! ✗
# 服务器重启 → 应用不会自动启动! ✗
PM2解决方案:
- ✅ 自动崩溃重启
- ✅ 后台运行(关闭终端也不停)
- ✅ 服务器重启后自动启动
- ✅ 集群模式(多核CPU利用)
- ✅ 日志管理
- ✅ 零停机部署
4.2 安装PM2
# 全局安装PM2
sudo npm install -g pm2
# 验证安装
pm2 --version
4.3 PM2基础命令
启动应用:
# 启动应用
pm2 start app.js --name my-app
# 启动Python应用
pm2 start app.py --interpreter python3
# 集群模式(利用所有CPU核心)
pm2 start app.js -i max
管理应用:
# 查看所有应用
pm2 list
pm2 status
# 查看详细信息
pm2 show my-app
# 查看日志
pm2 logs my-app
# 实时监控
pm2 monit
重启和停止:
# 重启应用
pm2 restart my-app
# 重载应用(零停机)
pm2 reload my-app
# 停止应用
pm2 stop my-app
# 删除应用
pm2 delete my-app
# 重启所有应用
pm2 restart all
4.4 开机自启配置 ⚠️ 重要!
必须执行的两个命令:
# 1. 生成启动脚本
pm2 startup
# 会输出类似这样的命令,复制执行:
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u username --hp /home/username
# 2. 保存当前应用列表
pm2 save
为什么两个都要?
pm2 startup: 让系统启动时启动PM2pm2 save: 告诉PM2启动时要运行哪些应用
验证:
# 重启服务器
sudo reboot
# 重新登录后检查
pm2 list
# 你的应用应该自动运行了!
4.5 使用ecosystem.config.js
创建配置文件:
pm2 init
编辑ecosystem.config.js:
module.exports = {
apps: [
{
name: 'my-app', // 应用名称
script: './server.js', // 启动文件
instances: 2, // 进程数量(或'max')
exec_mode: 'cluster', // 集群模式
env: { // 环境变量
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8000
},
error_file: './logs/err.log', // 错误日志
out_file: './logs/out.log', // 输出日志
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
autorestart: true, // 自动重启
watch: false, // 文件变化重启(开发用)
max_memory_restart: '1G' // 内存超限重启
}
]
};
使用配置文件:
# 启动
pm2 start ecosystem.config.js
# 生产环境启动
pm2 start ecosystem.config.js --env production
# 更新配置后重启
pm2 restart ecosystem.config.js
5. HTTPS和SSL证书
5.1 为什么需要HTTPS?
HTTP的问题:
- ❌ 数据明文传输,容易被窃听
- ❌ 浏览器显示“不安全“
- ❌ 小程序/PWA必须HTTPS
- ❌ SEO排名更低
HTTPS的好处:
- ✅ 数据加密传输
- ✅ 浏览器显示安全锁
- ✅ 提升用户信任
- ✅ SEO更好
5.2 使用Let’s Encrypt免费证书
Let’s Encrypt = 免费、自动化、开放的证书颁发机构
安装Certbot:
# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx
# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx
自动配置Nginx:
# Certbot会自动修改Nginx配置
sudo certbot --nginx -d www.yoursite.com -d yoursite.com
# 按提示输入邮箱,同意协议
# 选择是否重定向HTTP到HTTPS(推荐选Yes)
Certbot做了什么:
- 验证你拥有这个域名
- 下载SSL证书
- 自动修改Nginx配置
- 设置HTTPS监听
- 设置HTTP→HTTPS重定向
配置自动续期:
# Let's Encrypt证书90天过期,需要自动续期
# 测试续期
sudo certbot renew --dry-run
# 设置自动续期(已自动配置,无需手动)
# Certbot会创建systemd定时器或cron任务
验证HTTPS:
打开浏览器,访问 https://www.yoursite.com
看到🔒安全锁,说明成功!
5.3 手动配置HTTPS(高级)
如果使用自己的证书:
server {
listen 443 ssl http2;
server_name www.yoursite.com;
# SSL证书配置
ssl_certificate /etc/nginx/ssl/cert.pem; # 证书
ssl_certificate_key /etc/nginx/ssl/key.pem; # 私钥
# SSL优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 其他配置...
location / {
root /var/www/mysite;
index index.html;
}
}
# HTTP自动跳转HTTPS
server {
listen 80;
server_name www.yoursite.com;
return 301 https://$server_name$request_uri;
}
6. 域名和DNS配置
6.1 域名解析原理
用户输入: www.yoursite.com
│
▼
DNS服务器: "这个域名对应IP是 123.45.67.89"
│
▼
浏览器访问: http://123.45.67.89
6.2 DNS记录类型
| 记录类型 | 作用 | 示例 |
|---|---|---|
| A记录 | 域名→IPv4地址 | www.yoursite.com → 123.45.67.89 |
| AAAA记录 | 域名→IPv6地址 | www.yoursite.com → 2001:db8::1 |
| CNAME记录 | 域名→域名(别名) | blog.yoursite.com → www.yoursite.com |
| MX记录 | 邮件服务器 | 邮箱相关 |
| TXT记录 | 文本信息 | 域名验证 |
6.3 配置DNS解析
常见域名服务商:
- 阿里云(万网)
- 腾讯云DNSPod
- Cloudflare
- GoDaddy
- Namecheap
配置步骤 (以阿里云为例):
- 登录域名控制台
- 找到你的域名 → 解析
- 添加记录
示例配置:
| 记录类型 | 主机记录 | 记录值 | TTL |
|---|---|---|---|
| A | www | 123.45.67.89 | 600 |
| A | @ | 123.45.67.89 | 600 |
| CNAME | blog | www.yoursite.com | 600 |
记录说明:
www: 访问www.yoursite.com@: 访问yoursite.com(根域名)*: 泛解析(所有子域名)
TTL: 缓存时间(秒)
- 600 = 10分钟
- 3600 = 1小时
6.4 验证DNS解析
# 查询域名的A记录
nslookup www.yoursite.com
# 或使用dig(更详细)
dig www.yoursite.com
# 查看解析到的IP
ping www.yoursite.com
DNS生效时间: 通常5分钟到24小时
7. 完整部署流程
7.1 前端打包详解
7.1.1 为什么要打包?
打包 = 把开发代码变成浏览器能高效运行的代码
| 开发环境代码 | 生产环境代码(打包后) |
|---|---|
| 多个JS文件 | 合并成几个文件 |
| 有注释、有空格 | 压缩、混淆 |
| 完整的错误提示 | 最小化体积 |
| React JSX语法 | 转换成纯JS |
| ES6+新语法 | 转换成ES5兼容代码 |
| 图片原始大小 | 压缩优化 |
打包前后对比:
开发环境:
src/
├── App.jsx (5KB, JSX语法)
├── Header.jsx (3KB)
├── Footer.jsx (2KB)
├── utils.js (4KB)
└── ...50个文件
打包后:
dist/
├── index.html (2KB)
├── main.abc123.js (120KB, 压缩混淆)
└── main.abc123.css (15KB, 压缩)
体积减少60%+, 文件数量减少90%+
加载速度提升3-5倍! ⚡
7.1.2 打包前的准备
检查package.json的scripts:
{
"scripts": {
"dev": "vite", // 开发服务器
"build": "vite build", // 生产打包
"preview": "vite preview" // 预览打包结果
}
}
常见框架的打包命令:
| 框架 | 打包命令 | 输出目录 |
|---|---|---|
| Create React App | npm run build | build/ |
| Vite (React/Vue) | npm run build | dist/ |
| Next.js | npm run build | .next/ |
| Vue CLI | npm run build | dist/ |
7.1.3 环境变量配置
为什么需要环境变量?
开发环境和生产环境的API地址不同:
开发: http://localhost:3000/api
生产: https://api.yoursite.com/api
创建环境变量文件:
# .env.development (开发环境)
VITE_API_URL=http://localhost:3000
VITE_APP_NAME=MyApp Dev
# .env.production (生产环境)
VITE_API_URL=https://api.yoursite.com
VITE_APP_NAME=MyApp
在代码中使用:
// Vite项目
const apiUrl = import.meta.env.VITE_API_URL;
// Create React App项目
const apiUrl = process.env.REACT_APP_API_URL;
⚠️ 重要:
- 环境变量必须以
VITE_或REACT_APP_开头才会暴露给前端 - 不要在前端环境变量中存储密钥!
7.1.4 执行打包
标准打包流程:
# 1. 安装依赖(如果还没装)
npm install
# 2. 执行打包
npm run build
# 打包过程输出示例:
# vite v4.3.0 building for production...
# ✓ 123 modules transformed.
# dist/index.html 2.15 kB
# dist/assets/index-abc123.js 142.35 kB │ gzip: 45.23 kB
# dist/assets/index-abc123.css 15.67 kB │ gzip: 4.12 kB
# ✓ built in 3.45s
# 3. 检查打包结果
ls dist/
# index.html assets/ favicon.ico
打包成功的标志:
- ✅ 没有报错
- ✅ 生成了
dist/或build/文件夹 - ✅ 文件夹里有
index.html - ✅ 有打包后的 JS 和 CSS 文件
7.1.5 本地预览打包结果
为什么要预览?
打包后的代码可能和开发环境表现不同,需要本地测试!
# Vite项目
npm run preview
# ➜ Local: http://localhost:4173/
# 或使用http-server
npx http-server dist -p 8080
# 或使用serve
npx serve -s dist -p 8080
打开浏览器访问,检查:
- ✅ 页面能正常显示
- ✅ 路由跳转正常(SPA应用)
- ✅ API请求正常
- ✅ 打开控制台,没有错误
7.1.6 常见打包错误
错误1: 内存溢出
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
解决:
# 增加Node.js内存限制
NODE_OPTIONS=--max_old_space_size=4096 npm run build
错误2: 环境变量未定义
ReferenceError: process is not defined
原因: 直接使用了 process.env 而不是框架提供的方式
解决:
// ❌ 错误
const apiUrl = process.env.API_URL;
// ✅ 正确(Vite)
const apiUrl = import.meta.env.VITE_API_URL;
// ✅ 正确(CRA)
const apiUrl = process.env.REACT_APP_API_URL;
错误3: 路径引用错误
Failed to load resource: net::ERR_FILE_NOT_FOUND
/assets/logo.png 404
原因: 静态资源路径不对
解决:
// vite.config.js
export default {
base: '/', // 或 '/your-subpath/'
}
// 确保图片放在public/文件夹
public/
└── logo.png
// 代码中引用
<img src="/logo.png" />
错误4: 打包体积过大
(!) Some chunks are larger than 500kb after minification
解决 - 代码分割:
// 使用动态导入
const MyComponent = React.lazy(() => import('./MyComponent'));
// 路由懒加载
const About = lazy(() => import('./pages/About'));
7.1.7 打包优化建议
1. 分析打包体积:
# Vite项目
npm run build -- --report
# 或使用rollup-plugin-visualizer
npm install --save-dev rollup-plugin-visualizer
2. 代码分割:
- ✅ 路由懒加载
- ✅ 组件懒加载
- ✅ 第三方库分离
3. 压缩优化:
- ✅ 图片压缩(TinyPNG)
- ✅ 启用Gzip压缩(Nginx配置)
- ✅ 移除console.log
4. CDN加速:
// vite.config.js
export default {
build: {
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
}
}
7.2 前端部署
# 1. 本地构建
npm run build
# 生成dist/文件夹
# 2. 上传到服务器
scp -r dist/* user@your-server:/var/www/mysite/
# 或使用rsync
rsync -avz dist/ user@your-server:/var/www/mysite/
# 3. 设置权限
sudo chown -R www-data:www-data /var/www/mysite
sudo chmod -R 755 /var/www/mysite
# 4. 配置Nginx
sudo vim /etc/nginx/sites-available/mysite
# 5. 测试并重载
sudo nginx -t
sudo nginx -s reload
7.3 后端部署
# 1. 上传代码到服务器
git clone https://github.com/yourname/yourproject.git
cd yourproject
# 2. 安装依赖
npm install --production
# 或 pip install -r requirements.txt
# 3. 配置环境变量
cp .env.example .env
vim .env
# 4. 用PM2启动
pm2 start server.js --name my-api
# 5. 配置开机自启
pm2 startup
pm2 save
# 6. 查看日志
pm2 logs my-api
7.4 完整Nginx配置
# /etc/nginx/sites-available/mysite
server {
listen 80;
server_name www.yoursite.com yoursite.com;
# 重定向到HTTPS
return 301 https://www.yoursite.com$request_uri;
}
server {
listen 443 ssl http2;
server_name www.yoursite.com;
# SSL证书(Certbot自动配置)
ssl_certificate /etc/letsencrypt/live/www.yoursite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.yoursite.com/privkey.pem;
# 前端静态文件
location / {
root /var/www/mysite/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# 后端API
location /api/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
root /var/www/mysite/dist;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
8. 常见问题排查
Q1: 访问域名显示502 Bad Gateway
原因: Nginx无法连接到后端
排查步骤:
# 1. 后端应用是否运行?
pm2 list
# 2. 端口是否正确?
netstat -tuln | grep 3000
# 3. 防火墙是否阻止?
sudo ufw status
# 4. 查看Nginx错误日志
sudo tail -f /var/log/nginx/error.log
# 5. 查看应用日志
pm2 logs my-app
Q2: 域名无法访问
排查步骤:
# 1. DNS是否解析正确?
nslookup www.yoursite.com
# 2. 服务器IP是否正确?
ping www.yoursite.com
# 3. 云安全组是否开放80/443?
# 登录云控制台检查
# 4. Nginx是否运行?
sudo systemctl status nginx
# 5. Nginx配置是否正确?
sudo nginx -t
Q3: HTTPS证书错误
排查:
# 1. 证书是否过期?
sudo certbot certificates
# 2. 手动续期
sudo certbot renew
# 3. 强制重新获取
sudo certbot --nginx -d www.yoursite.com --force-renew
Q4: PM2应用重启后停止
原因: 没有配置开机自启
# 重新配置
pm2 startup
# 复制输出的命令执行
pm2 save
# 验证
sudo reboot
# 重启后检查
pm2 list
9. 部署检查清单
部署前
- ✅ 代码在本地测试通过
- ✅ 环境变量配置正确
- ✅ package.json依赖完整
- ✅ .gitignore配置正确
服务器配置
- ✅ 云安全组开放80/443/22端口
- ✅ Node.js/Python环境已安装
- ✅ npm/pip镜像源已配置
- ✅ Nginx已安装并运行
应用部署
- ✅ 代码已上传到服务器
- ✅ 依赖已安装(npm install/pip install)
- ✅ PM2应用已启动
- ✅ PM2开机自启已配置(startup+save)
- ✅ 应用日志无错误
Nginx配置
- ✅ 静态文件路径正确
- ✅ 反向代理配置正确
- ✅ SPA应用try_files配置
- ✅ nginx -t测试通过
- ✅ 配置已重载
HTTPS配置
- ✅ SSL证书已配置
- ✅ HTTP自动跳转HTTPS
- ✅ 浏览器显示安全锁
DNS配置
- ✅ A记录已添加
- ✅ DNS解析生效
- ✅ 域名可以访问
10. 总结
核心要点
-
部署三件套:
- Nginx: 前台接待(反向代理)
- PM2: 部门经理(进程管理)
- 云安全组: 门禁系统(防火墙)
-
HTTPS是必需的:
- 使用Let’s Encrypt免费证书
- Certbot自动配置
- 自动续期
-
DNS解析:
- A记录指向服务器IP
- 等待5分钟-24小时生效
-
PM2必须配置开机自启:
pm2 startuppm2 save- 两个都要!
典型错误
| 错误 | 原因 | 解决 |
|---|---|---|
| 502 Bad Gateway | 后端没运行 | 检查PM2 |
| 403 Forbidden | 权限不对 | chown+chmod |
| 无法访问 | 安全组没开 | 开放端口 |
| 证书错误 | 证书过期 | certbot renew |
| 重启后应用停止 | 没保存PM2 | pm2 save |
下一步
恭喜!你已经掌握了完整的部署流程。
继续深入:
- 学习Docker容器化部署
- 学习CI/CD自动化部署
- 学习Kubernetes集群管理
- 学习监控和日志分析
记住: 部署出问题是正常的,重要的是学会看日志排查!