feat: 添加 Jenkins Docker 部署配置
- 添加 Jenkinsfile 和 Jenkinsfile.docker 支持 Docker 构建 - 添加 Docker Compose 生产环境配置 - 添加后端 Dockerfile - 添加 Docker 部署脚本 - 添加部署文档 (JENKINS_DEPLOY.md, DOCKER_DEPLOY.md, DOCKER_ISOLATION.md) - 更新 nginx 配置支持生产环境部署
This commit is contained in:
parent
25a90eae02
commit
be1dab20e9
268
DOCKER_DEPLOY.md
Normal file
268
DOCKER_DEPLOY.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# Docker 部署说明
|
||||||
|
|
||||||
|
本文档说明如何在已有 Docker nginx 运行的服务器上部署本项目,避免端口和资源冲突。
|
||||||
|
|
||||||
|
## 部署架构
|
||||||
|
|
||||||
|
- **前端 Nginx**: 运行在 `8080` 端口(避免与现有 nginx 的 80 端口冲突)
|
||||||
|
- **后端服务**: 运行在 `3001` 端口
|
||||||
|
- **部署路径**: `/opt/nginx/html/ai/current`
|
||||||
|
- **容器名称**:
|
||||||
|
- `ai-learning-nginx` (前端)
|
||||||
|
- `ai-learning-backend` (后端)
|
||||||
|
|
||||||
|
## 避免冲突的设计
|
||||||
|
|
||||||
|
### 1. 端口隔离
|
||||||
|
- 现有 nginx: `80:80`, `443:443`
|
||||||
|
- 本项目 nginx: `8080:80`, `8443:443`
|
||||||
|
- 后端: `3001:3001`
|
||||||
|
|
||||||
|
### 2. 路径隔离
|
||||||
|
- 使用独立的部署路径: `/opt/nginx/html/ai/current`
|
||||||
|
- 独立的日志目录: `/opt/nginx/html/ai/current/docker/logs`
|
||||||
|
- 独立的配置文件: `/opt/nginx/html/ai/current/docker/`
|
||||||
|
|
||||||
|
### 3. 容器名称隔离
|
||||||
|
- 使用唯一的容器名称,避免与现有容器冲突
|
||||||
|
|
||||||
|
### 4. Docker 网络隔离
|
||||||
|
- 使用独立的 Docker 网络: `ai-learning-network`
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
部署后的目录结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/nginx/html/ai/current/
|
||||||
|
├── index.html # 前端入口文件
|
||||||
|
├── assets/ # 前端静态资源
|
||||||
|
├── backend/ # 后端文件
|
||||||
|
│ ├── dist/ # 后端构建产物
|
||||||
|
│ ├── prisma/ # 数据库文件
|
||||||
|
│ ├── Dockerfile # 后端 Dockerfile
|
||||||
|
│ └── package.json
|
||||||
|
└── docker/ # Docker 配置
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── nginx.conf.docker
|
||||||
|
└── logs/ # Nginx 日志
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署步骤
|
||||||
|
|
||||||
|
### 1. 通过 Jenkins 自动部署
|
||||||
|
|
||||||
|
Jenkins 流水线会自动:
|
||||||
|
1. 构建前端和后端
|
||||||
|
2. 打包部署文件
|
||||||
|
3. 传输到远程服务器
|
||||||
|
4. 解压并部署
|
||||||
|
5. 启动 Docker 容器
|
||||||
|
|
||||||
|
### 2. 手动部署
|
||||||
|
|
||||||
|
如果需要手动部署:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 构建项目
|
||||||
|
npm install
|
||||||
|
npm run install:all
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 2. 创建部署目录
|
||||||
|
ssh root@180.76.180.105 "mkdir -p /opt/nginx/html/ai/current"
|
||||||
|
|
||||||
|
# 3. 传输文件
|
||||||
|
scp -r frontend/dist/* root@180.76.180.105:/opt/nginx/html/ai/current/
|
||||||
|
scp -r backend/dist backend/prisma backend/package.json backend/Dockerfile root@180.76.180.105:/opt/nginx/html/ai/current/backend/
|
||||||
|
scp nginx/docker-compose.production.yml root@180.76.180.105:/opt/nginx/html/ai/current/docker/docker-compose.yml
|
||||||
|
scp nginx/nginx.conf.docker root@180.76.180.105:/opt/nginx/html/ai/current/docker/nginx.conf.docker
|
||||||
|
|
||||||
|
# 4. 在服务器上启动
|
||||||
|
ssh root@180.76.180.105 << 'ENDSSH'
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
docker-compose up -d --build
|
||||||
|
ENDSSH
|
||||||
|
```
|
||||||
|
|
||||||
|
## 访问地址
|
||||||
|
|
||||||
|
- **前端**: http://180.76.180.105:8080
|
||||||
|
- **后端 API**: http://180.76.180.105:3001
|
||||||
|
- **健康检查**: http://180.76.180.105:8080/health
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
### 查看容器状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看所有服务日志
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 查看 nginx 日志
|
||||||
|
docker-compose logs -f nginx
|
||||||
|
|
||||||
|
# 查看后端日志
|
||||||
|
docker-compose logs -f backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重启服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 停止服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重新构建并启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 1. 端口冲突
|
||||||
|
|
||||||
|
如果 8080 端口被占用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查端口占用
|
||||||
|
netstat -tulpn | grep 8080
|
||||||
|
# 或
|
||||||
|
lsof -i :8080
|
||||||
|
|
||||||
|
# 修改 docker-compose.yml 中的端口映射
|
||||||
|
# 例如改为 8081:80
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 容器无法启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看容器日志
|
||||||
|
docker-compose logs
|
||||||
|
|
||||||
|
# 检查容器状态
|
||||||
|
docker ps -a | grep ai-learning
|
||||||
|
|
||||||
|
# 检查 Docker 网络
|
||||||
|
docker network ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 前端无法访问
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 nginx 容器是否运行
|
||||||
|
docker ps | grep ai-learning-nginx
|
||||||
|
|
||||||
|
# 检查 nginx 配置
|
||||||
|
docker exec ai-learning-nginx nginx -t
|
||||||
|
|
||||||
|
# 查看 nginx 日志
|
||||||
|
docker-compose logs nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 后端 API 无法访问
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查后端容器是否运行
|
||||||
|
docker ps | grep ai-learning-backend
|
||||||
|
|
||||||
|
# 检查后端日志
|
||||||
|
docker-compose logs backend
|
||||||
|
|
||||||
|
# 测试后端连接
|
||||||
|
curl http://localhost:3001/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 数据库问题
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查数据库文件权限
|
||||||
|
ls -la /opt/nginx/html/ai/current/backend/prisma/
|
||||||
|
|
||||||
|
# 进入后端容器执行迁移
|
||||||
|
docker exec -it ai-learning-backend sh
|
||||||
|
npx prisma migrate deploy
|
||||||
|
npx prisma generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与现有 nginx 集成(可选)
|
||||||
|
|
||||||
|
如果希望通过现有 nginx(80 端口)访问本项目,可以在现有 nginx 配置中添加反向代理:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# 添加到现有 nginx 配置
|
||||||
|
location /ai/ {
|
||||||
|
proxy_pass http://localhost:8080/;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ai/api/ {
|
||||||
|
proxy_pass http://localhost:3001/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后访问:`http://180.76.180.105/ai/`
|
||||||
|
|
||||||
|
## 更新部署
|
||||||
|
|
||||||
|
每次代码更新后,Jenkins 会自动:
|
||||||
|
1. 构建新版本
|
||||||
|
2. 备份旧版本到 `/opt/nginx/html/ai/backup-YYYYMMDD-HHMMSS`
|
||||||
|
3. 部署新版本
|
||||||
|
4. 重启 Docker 容器
|
||||||
|
|
||||||
|
## 回滚
|
||||||
|
|
||||||
|
如果需要回滚到之前的版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@180.76.180.105 << 'ENDSSH'
|
||||||
|
cd /opt/nginx/html/ai
|
||||||
|
|
||||||
|
# 查看备份
|
||||||
|
ls -la backup-*
|
||||||
|
|
||||||
|
# 停止当前容器
|
||||||
|
cd current/docker
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 恢复备份
|
||||||
|
cd /opt/nginx/html/ai
|
||||||
|
mv current current-failed
|
||||||
|
mv backup-YYYYMMDD-HHMMSS current
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
cd current/docker
|
||||||
|
docker-compose up -d
|
||||||
|
ENDSSH
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全建议
|
||||||
|
|
||||||
|
1. **不要使用 root 用户**: 建议创建专用部署用户
|
||||||
|
2. **限制端口访问**: 使用防火墙限制 8080 端口的访问来源
|
||||||
|
3. **定期备份**: 自动备份数据库和配置文件
|
||||||
|
4. **监控日志**: 设置日志监控和告警
|
||||||
|
5. **更新镜像**: 定期更新 nginx:alpine 和 node:18-alpine 镜像
|
||||||
295
DOCKER_ISOLATION.md
Normal file
295
DOCKER_ISOLATION.md
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
# Docker 隔离架构说明
|
||||||
|
|
||||||
|
## 隔离层次概览
|
||||||
|
|
||||||
|
当前项目实现了**完全隔离**的架构,从构建到运行都在 Docker 容器中,与宿主机完全隔离。
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 宿主机 (Host) │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Jenkins 服务器 │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Docker 容器: node:18 (构建阶段) │ │ │
|
||||||
|
│ │ │ - 安装依赖 │ │ │
|
||||||
|
│ │ │ - 构建前端 │ │ │
|
||||||
|
│ │ │ - 构建后端 │ │ │
|
||||||
|
│ │ │ ✅ 与 Jenkins 服务器隔离 │ │ │
|
||||||
|
│ │ └──────────────────────────────────────────────┘ │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↓ │
|
||||||
|
│ 部署到远程服务器 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ 远程服务器 (180.76.180.105) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Docker 容器: nginx:alpine │ │ │
|
||||||
|
│ │ │ - 端口: 8080:80 │ │ │
|
||||||
|
│ │ │ - 网络: ai-learning-network │ │ │
|
||||||
|
│ │ │ ✅ 与宿主机和其他容器隔离 │ │ │
|
||||||
|
│ │ └──────────────────────────────────────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Docker 容器: backend (node:18-alpine) │ │ │
|
||||||
|
│ │ │ - 端口: 3001:3001 │ │ │
|
||||||
|
│ │ │ - 网络: ai-learning-network │ │ │
|
||||||
|
│ │ │ ✅ 与宿主机和其他容器隔离 │ │ │
|
||||||
|
│ │ └──────────────────────────────────────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ 宿主机文件系统 (仅数据持久化) │ │ │
|
||||||
|
│ │ │ - /opt/nginx/html/ai/current/ │ │ │
|
||||||
|
│ │ │ - 数据库文件 (volume 挂载) │ │ │
|
||||||
|
│ │ └──────────────────────────────────────────────┘ │ │
|
||||||
|
│ └──────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 隔离层次详解
|
||||||
|
|
||||||
|
### 1. 构建阶段隔离(Jenkins 服务器)
|
||||||
|
|
||||||
|
**位置**: Jenkins 服务器
|
||||||
|
**容器**: `node:18`
|
||||||
|
**隔离内容**:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
|
||||||
|
// 所有构建操作都在容器中执行
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**隔离效果**:
|
||||||
|
- ✅ **文件系统隔离**: 构建过程不影响 Jenkins 服务器文件系统
|
||||||
|
- ✅ **依赖隔离**: 不需要在 Jenkins 服务器安装 Node.js、npm
|
||||||
|
- ✅ **版本隔离**: 使用固定版本的 Node.js,不受宿主机影响
|
||||||
|
- ✅ **环境隔离**: 每次构建都是干净的环境,无历史残留
|
||||||
|
|
||||||
|
**好处**:
|
||||||
|
- Jenkins 服务器保持干净,不需要安装各种构建工具
|
||||||
|
- 多个项目可以使用不同版本的 Node.js,互不干扰
|
||||||
|
- 构建失败不会影响 Jenkins 服务器
|
||||||
|
|
||||||
|
### 2. 运行阶段隔离(生产服务器)
|
||||||
|
|
||||||
|
#### 2.1 Nginx 容器隔离
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ai-learning-nginx
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
networks:
|
||||||
|
- ai-learning-network
|
||||||
|
```
|
||||||
|
|
||||||
|
**隔离效果**:
|
||||||
|
- ✅ **进程隔离**: Nginx 进程在容器内运行,与宿主机进程隔离
|
||||||
|
- ✅ **网络隔离**: 使用独立的 Docker 网络 `ai-learning-network`
|
||||||
|
- ✅ **文件系统隔离**: 只能访问挂载的 volume
|
||||||
|
- ✅ **端口隔离**: 使用 8080 端口,不影响宿主机 80 端口
|
||||||
|
|
||||||
|
#### 2.2 Backend 容器隔离
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: /opt/nginx/html/ai/current/backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ai-learning-backend
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
networks:
|
||||||
|
- ai-learning-network
|
||||||
|
```
|
||||||
|
|
||||||
|
**隔离效果**:
|
||||||
|
- ✅ **运行时隔离**: Node.js 应用在容器内运行
|
||||||
|
- ✅ **依赖隔离**: 不需要在宿主机安装 Node.js
|
||||||
|
- ✅ **环境变量隔离**: 容器内独立的环境变量
|
||||||
|
- ✅ **网络隔离**: 只能通过 Docker 网络与其他容器通信
|
||||||
|
|
||||||
|
### 3. 网络隔离
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
ai-learning-network:
|
||||||
|
driver: bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
**隔离效果**:
|
||||||
|
- ✅ **独立网络**: 容器在独立的 Docker 网络中
|
||||||
|
- ✅ **服务发现**: 容器间通过服务名通信(如 `backend:3001`)
|
||||||
|
- ✅ **端口隔离**: 容器端口不直接暴露给宿主机(除非映射)
|
||||||
|
- ✅ **安全隔离**: 外部无法直接访问容器内部网络
|
||||||
|
|
||||||
|
## 完全隔离的好处
|
||||||
|
|
||||||
|
### 1. 安全性
|
||||||
|
|
||||||
|
```
|
||||||
|
宿主机 ← 完全隔离 → 应用容器
|
||||||
|
```
|
||||||
|
|
||||||
|
- ✅ **进程隔离**: 容器崩溃不影响宿主机
|
||||||
|
- ✅ **文件系统隔离**: 容器无法访问宿主机敏感文件
|
||||||
|
- ✅ **网络隔离**: 容器间通信受控
|
||||||
|
- ✅ **权限隔离**: 容器以非 root 用户运行(可配置)
|
||||||
|
|
||||||
|
### 2. 环境一致性
|
||||||
|
|
||||||
|
```
|
||||||
|
开发环境 = 构建环境 = 生产环境
|
||||||
|
```
|
||||||
|
|
||||||
|
- ✅ **版本一致**: 所有环境使用相同的 Node.js 版本
|
||||||
|
- ✅ **依赖一致**: 依赖版本锁定在 package.json
|
||||||
|
- ✅ **配置一致**: Docker 配置统一管理
|
||||||
|
|
||||||
|
### 3. 资源隔离
|
||||||
|
|
||||||
|
```
|
||||||
|
容器 A ← 资源限制 → 容器 B
|
||||||
|
```
|
||||||
|
|
||||||
|
- ✅ **CPU 限制**: 可以限制容器 CPU 使用率
|
||||||
|
- ✅ **内存限制**: 可以限制容器内存使用
|
||||||
|
- ✅ **磁盘限制**: 可以限制容器磁盘使用
|
||||||
|
|
||||||
|
### 4. 易于管理
|
||||||
|
|
||||||
|
- ✅ **独立更新**: 可以单独更新某个容器,不影响其他
|
||||||
|
- ✅ **快速回滚**: 可以快速回滚到之前的镜像版本
|
||||||
|
- ✅ **日志隔离**: 每个容器的日志独立管理
|
||||||
|
- ✅ **监控隔离**: 可以单独监控每个容器
|
||||||
|
|
||||||
|
## 数据持久化(唯一与宿主机共享的部分)
|
||||||
|
|
||||||
|
虽然容器是隔离的,但某些数据需要持久化:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
# 数据库文件(需要持久化)
|
||||||
|
- /opt/nginx/html/ai/current/backend/prisma:/app/prisma
|
||||||
|
|
||||||
|
# 前端静态文件(只读)
|
||||||
|
- /opt/nginx/html/ai/current:/usr/share/nginx/html:ro
|
||||||
|
|
||||||
|
# 日志文件(需要持久化)
|
||||||
|
- /opt/nginx/html/ai/current/docker/logs:/var/log/nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
- 数据库文件需要持久化,所以挂载到宿主机
|
||||||
|
- 前端文件是只读挂载,容器无法修改
|
||||||
|
- 日志文件挂载到宿主机,方便查看和管理
|
||||||
|
|
||||||
|
## 隔离验证
|
||||||
|
|
||||||
|
### 1. 检查容器隔离
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看运行中的容器
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# 查看容器网络
|
||||||
|
docker network inspect ai-learning-network
|
||||||
|
|
||||||
|
# 查看容器进程(在宿主机上)
|
||||||
|
ps aux | grep nginx # 看不到容器内的进程
|
||||||
|
|
||||||
|
# 进入容器查看
|
||||||
|
docker exec -it ai-learning-nginx sh
|
||||||
|
# 在容器内:无法访问宿主机其他文件(除了挂载的 volume)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 检查网络隔离
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在宿主机上无法直接访问容器内部网络
|
||||||
|
curl http://backend:3001 # ❌ 失败,backend 只在 Docker 网络内可访问
|
||||||
|
|
||||||
|
# 在容器内可以访问
|
||||||
|
docker exec -it ai-learning-nginx sh
|
||||||
|
curl http://backend:3001 # ✅ 成功,在同一 Docker 网络内
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 检查文件系统隔离
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 容器内的文件系统是独立的
|
||||||
|
docker exec -it ai-learning-backend ls /app
|
||||||
|
# 只能看到容器内的文件,看不到宿主机其他目录(除了挂载的 volume)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全建议
|
||||||
|
|
||||||
|
虽然已经实现了完全隔离,但还可以进一步加强:
|
||||||
|
|
||||||
|
### 1. 使用非 root 用户运行容器
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# 在 Dockerfile 中
|
||||||
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S nodejs -u 1001
|
||||||
|
USER nodejs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 限制资源使用
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '1'
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 256M
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 只读文件系统(除了必要目录)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp
|
||||||
|
- /var/cache/nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 网络策略
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
networks:
|
||||||
|
- ai-learning-network
|
||||||
|
# 不暴露端口到宿主机,只允许 nginx 访问
|
||||||
|
# ports: [] # 注释掉端口映射
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
**是的,当前架构实现了完全隔离**:
|
||||||
|
|
||||||
|
1. ✅ **构建阶段**: 在 Docker 容器中构建,与 Jenkins 服务器隔离
|
||||||
|
2. ✅ **运行阶段**: 应用在 Docker 容器中运行,与宿主机隔离
|
||||||
|
3. ✅ **网络隔离**: 容器在独立的 Docker 网络中
|
||||||
|
4. ✅ **文件系统隔离**: 容器只能访问挂载的 volume
|
||||||
|
5. ✅ **进程隔离**: 容器进程与宿主机进程隔离
|
||||||
|
|
||||||
|
**唯一与宿主机共享的**:
|
||||||
|
- 数据文件(数据库、日志)- 通过 volume 挂载
|
||||||
|
- 端口映射(8080, 3001)- 用于外部访问
|
||||||
|
|
||||||
|
这种架构提供了**最大的安全性和可移植性**,同时保持了必要的数据持久化。
|
||||||
264
JENKINS_DEPLOY.md
Normal file
264
JENKINS_DEPLOY.md
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
# Jenkins 部署配置说明
|
||||||
|
|
||||||
|
本文档说明如何通过 Jenkins 将项目部署到远程服务器 `180.76.180.105` 的 `/opt/nginx/html/ai` 目录。
|
||||||
|
|
||||||
|
## 前置要求
|
||||||
|
|
||||||
|
### 1. Jenkins 服务器要求
|
||||||
|
- Jenkins 已安装并运行
|
||||||
|
- 安装了必要的插件:
|
||||||
|
- Pipeline 插件
|
||||||
|
- SSH Pipeline Steps 插件(可选,用于更好的 SSH 支持)
|
||||||
|
- Node.js 和 npm 已安装(用于构建)
|
||||||
|
|
||||||
|
### 2. 远程服务器要求
|
||||||
|
- 服务器 IP: `180.76.180.105`
|
||||||
|
- 部署目录: `/opt/nginx/html/ai`
|
||||||
|
- 需要安装:
|
||||||
|
- Node.js (推荐 v18+)
|
||||||
|
- npm
|
||||||
|
- nginx
|
||||||
|
- PM2(用于管理后端进程,可选)
|
||||||
|
|
||||||
|
### 3. SSH 配置
|
||||||
|
确保 Jenkins 服务器可以通过 SSH 无密码登录到远程服务器:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 Jenkins 服务器上生成 SSH 密钥(如果还没有)
|
||||||
|
ssh-keygen -t rsa -b 4096
|
||||||
|
|
||||||
|
# 将公钥复制到远程服务器
|
||||||
|
ssh-copy-id root@180.76.180.105
|
||||||
|
|
||||||
|
# 测试连接
|
||||||
|
ssh root@180.76.180.105
|
||||||
|
```
|
||||||
|
|
||||||
|
或者在 Jenkins 中配置 SSH 凭据:
|
||||||
|
1. 进入 Jenkins → 系统管理 → 凭据管理
|
||||||
|
2. 添加 SSH 凭据
|
||||||
|
3. 在 Jenkinsfile 中使用 `SSH_CREDENTIALS` 环境变量
|
||||||
|
|
||||||
|
## Jenkins 配置步骤
|
||||||
|
|
||||||
|
### 1. 创建 Pipeline 任务
|
||||||
|
|
||||||
|
1. 在 Jenkins 中创建新任务
|
||||||
|
2. 选择 "流水线" (Pipeline) 类型
|
||||||
|
3. 在 "流水线" 配置中:
|
||||||
|
- **定义**: Pipeline script from SCM
|
||||||
|
- **SCM**: Git
|
||||||
|
- **Repository URL**: 你的 Git 仓库地址
|
||||||
|
- **脚本路径**: Jenkinsfile
|
||||||
|
|
||||||
|
### 2. 配置环境变量(可选)
|
||||||
|
|
||||||
|
如果需要修改默认配置,可以在 Jenkinsfile 中修改 `environment` 部分:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
environment {
|
||||||
|
DEPLOY_HOST = '180.76.180.105'
|
||||||
|
DEPLOY_USER = 'root' // 根据实际情况修改
|
||||||
|
DEPLOY_PATH = '/opt/nginx/html/ai'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置 SSH 凭据(推荐)
|
||||||
|
|
||||||
|
如果使用 SSH 凭据而不是密钥文件:
|
||||||
|
|
||||||
|
1. 在 Jenkins 中添加 SSH 凭据(ID: `deploy-ssh-key`)
|
||||||
|
2. 修改 Jenkinsfile 中的部署步骤,使用 `withCredentials`:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
withCredentials([sshUserPrivateKey(credentialsId: 'deploy-ssh-key', keyFileVariable: 'SSH_KEY')]) {
|
||||||
|
sh '''
|
||||||
|
ssh -i $SSH_KEY -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} ...
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署流程
|
||||||
|
|
||||||
|
Jenkins 流水线将执行以下步骤:
|
||||||
|
|
||||||
|
1. **Checkout**: 检出代码
|
||||||
|
2. **Install Dependencies**: 安装所有依赖
|
||||||
|
3. **Build Frontend**: 构建前端(生成 `frontend/dist`)
|
||||||
|
4. **Build Backend**: 构建后端(生成 `backend/dist`)
|
||||||
|
5. **Prepare Deployment Package**: 打包部署文件
|
||||||
|
6. **Deploy to Remote Server**:
|
||||||
|
- 传输文件到远程服务器
|
||||||
|
- 解压并部署到 `/opt/nginx/html/ai/current`
|
||||||
|
- 备份旧版本
|
||||||
|
7. **Restart Services**: 重启后端服务和 nginx
|
||||||
|
|
||||||
|
## 远程服务器配置
|
||||||
|
|
||||||
|
### 1. 创建部署目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/nginx/html/ai
|
||||||
|
sudo chown -R $USER:$USER /opt/nginx/html/ai
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置 Nginx
|
||||||
|
|
||||||
|
将 `nginx/nginx.conf` 复制到系统 nginx 配置目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo cp /opt/nginx/html/ai/current/nginx/nginx.conf /etc/nginx/sites-available/ai-learning-platform
|
||||||
|
sudo ln -s /etc/nginx/sites-available/ai-learning-platform /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 需要修改 nginx.conf 中的前端文件路径:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
root /opt/nginx/html/ai/current; # 修改这里
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置后端服务
|
||||||
|
|
||||||
|
#### 使用 PM2(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 PM2
|
||||||
|
npm install -g pm2
|
||||||
|
|
||||||
|
# 启动后端服务
|
||||||
|
cd /opt/nginx/html/ai/current/backend
|
||||||
|
pm2 start dist/index.js --name ai-learning-backend
|
||||||
|
pm2 save
|
||||||
|
pm2 startup # 设置开机自启
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 systemd
|
||||||
|
|
||||||
|
创建 systemd 服务文件 `/etc/systemd/system/ai-learning-backend.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=AI Learning Platform Backend
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/nginx/html/ai/current/backend
|
||||||
|
ExecStart=/usr/bin/node dist/index.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PORT=3001
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
然后启用服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable ai-learning-backend
|
||||||
|
sudo systemctl start ai-learning-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
## 手动部署(测试)
|
||||||
|
|
||||||
|
如果需要手动测试部署流程:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 构建项目
|
||||||
|
npm install
|
||||||
|
npm run install:all
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 2. 创建部署包
|
||||||
|
mkdir -p deploy-package
|
||||||
|
cp -r frontend/dist deploy-package/frontend-dist
|
||||||
|
cp -r backend/dist deploy-package/backend/dist
|
||||||
|
cp -r backend/prisma deploy-package/backend/prisma
|
||||||
|
cp backend/package.json deploy-package/backend/
|
||||||
|
cp nginx/nginx.conf deploy-package/nginx/
|
||||||
|
cp scripts/deploy.sh deploy-package/
|
||||||
|
tar -czf deploy-package.tar.gz deploy-package/
|
||||||
|
|
||||||
|
# 3. 传输到远程服务器
|
||||||
|
scp deploy-package.tar.gz root@180.76.180.105:/tmp/
|
||||||
|
|
||||||
|
# 4. 在远程服务器上执行
|
||||||
|
ssh root@180.76.180.105
|
||||||
|
cd /tmp
|
||||||
|
tar -xzf deploy-package.tar.gz
|
||||||
|
mkdir -p /opt/nginx/html/ai/current
|
||||||
|
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
|
||||||
|
cp -r deploy-package/backend /opt/nginx/html/ai/current/
|
||||||
|
bash deploy-package/deploy.sh /opt/nginx/html/ai/current
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 1. SSH 连接失败
|
||||||
|
|
||||||
|
- 检查网络连接
|
||||||
|
- 确认 SSH 密钥配置正确
|
||||||
|
- 检查防火墙设置
|
||||||
|
|
||||||
|
### 2. 构建失败
|
||||||
|
|
||||||
|
- 检查 Node.js 版本
|
||||||
|
- 确认所有依赖已安装
|
||||||
|
- 查看 Jenkins 构建日志
|
||||||
|
|
||||||
|
### 3. 部署后服务无法访问
|
||||||
|
|
||||||
|
- 检查 nginx 配置是否正确
|
||||||
|
- 确认后端服务是否运行:`pm2 list` 或 `systemctl status ai-learning-backend`
|
||||||
|
- 检查端口是否被占用:`netstat -tulpn | grep 3001`
|
||||||
|
- 查看日志:`pm2 logs ai-learning-backend` 或 `journalctl -u ai-learning-backend`
|
||||||
|
|
||||||
|
### 4. 数据库问题
|
||||||
|
|
||||||
|
- 确认数据库文件权限
|
||||||
|
- 检查 Prisma 迁移是否成功
|
||||||
|
- 查看数据库文件:`ls -la /opt/nginx/html/ai/current/backend/prisma/`
|
||||||
|
|
||||||
|
## 安全建议
|
||||||
|
|
||||||
|
1. **不要使用 root 用户**: 建议创建专用部署用户
|
||||||
|
2. **使用 SSH 密钥**: 避免使用密码认证
|
||||||
|
3. **限制 SSH 访问**: 使用防火墙限制 SSH 访问来源
|
||||||
|
4. **定期备份**: 自动备份数据库和配置文件
|
||||||
|
5. **监控日志**: 设置日志监控和告警
|
||||||
|
|
||||||
|
## 更新部署
|
||||||
|
|
||||||
|
每次代码更新后,只需在 Jenkins 中触发构建即可。Jenkins 会自动:
|
||||||
|
- 构建最新代码
|
||||||
|
- 部署到远程服务器
|
||||||
|
- 备份旧版本
|
||||||
|
- 重启服务
|
||||||
|
|
||||||
|
## 回滚
|
||||||
|
|
||||||
|
如果需要回滚到之前的版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@180.76.180.105
|
||||||
|
cd /opt/nginx/html/ai
|
||||||
|
# 查看备份目录
|
||||||
|
ls -la backup-*
|
||||||
|
# 恢复备份
|
||||||
|
mv current current-failed
|
||||||
|
mv backup-YYYYMMDD-HHMMSS current
|
||||||
|
# 重启服务
|
||||||
|
pm2 restart ai-learning-backend
|
||||||
|
nginx -s reload
|
||||||
|
```
|
||||||
334
JENKINS_DOCKER.md
Normal file
334
JENKINS_DOCKER.md
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
# Jenkins Docker 构建配置说明
|
||||||
|
|
||||||
|
本文档说明如何在 Jenkins 中使用 Docker 容器执行构建和部署。
|
||||||
|
|
||||||
|
## Jenkins Docker 支持
|
||||||
|
|
||||||
|
Jenkins 支持在 Docker 容器中执行构建步骤,这提供了以下优势:
|
||||||
|
|
||||||
|
1. **环境隔离**: 每次构建都在干净的容器环境中执行
|
||||||
|
2. **版本一致性**: 使用固定版本的 Node.js,避免环境差异
|
||||||
|
3. **易于维护**: 不需要在 Jenkins 服务器上安装 Node.js
|
||||||
|
4. **可移植性**: 构建环境与代码一起定义
|
||||||
|
|
||||||
|
## 前置要求
|
||||||
|
|
||||||
|
### 1. 安装 Jenkins Docker 插件
|
||||||
|
|
||||||
|
在 Jenkins 中安装以下插件:
|
||||||
|
- **Docker Pipeline** (推荐)
|
||||||
|
- **Docker** (可选,用于 Docker 命令支持)
|
||||||
|
|
||||||
|
安装步骤:
|
||||||
|
1. 进入 Jenkins → 系统管理 → 插件管理
|
||||||
|
2. 搜索 "Docker Pipeline"
|
||||||
|
3. 安装并重启 Jenkins
|
||||||
|
|
||||||
|
### 2. 配置 Docker
|
||||||
|
|
||||||
|
确保 Jenkins 服务器可以访问 Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 Docker 是否运行
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# 确保 Jenkins 用户有权限访问 Docker socket
|
||||||
|
# 方法1: 将 Jenkins 用户添加到 docker 组
|
||||||
|
sudo usermod -aG docker jenkins
|
||||||
|
|
||||||
|
# 方法2: 修改 Docker socket 权限(不推荐,安全风险)
|
||||||
|
sudo chmod 666 /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 测试 Docker 访问
|
||||||
|
|
||||||
|
在 Jenkins 中创建一个测试 Pipeline:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
stages {
|
||||||
|
stage('Test Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh 'node --version'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 两种 Jenkinsfile 配置
|
||||||
|
|
||||||
|
### 方式一:完全 Docker 化(推荐)
|
||||||
|
|
||||||
|
使用 `Jenkinsfile.docker`,所有构建步骤都在 Docker 容器中执行:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Build in Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
|
||||||
|
sh '''
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- 完全隔离的构建环境
|
||||||
|
- 不依赖 Jenkins 服务器的 Node.js 版本
|
||||||
|
- 每次构建都是干净的环境
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- 需要 Docker Pipeline 插件
|
||||||
|
- 首次构建需要下载 Docker 镜像
|
||||||
|
|
||||||
|
### 方式二:混合模式(当前 Jenkinsfile)
|
||||||
|
|
||||||
|
部分步骤在 Docker 中,部分在主机上:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Build in Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用 Docker 构建的 Jenkinsfile
|
||||||
|
|
||||||
|
项目提供了两个版本的 Jenkinsfile:
|
||||||
|
|
||||||
|
1. **Jenkinsfile** - 当前版本,支持 Docker 构建
|
||||||
|
2. **Jenkinsfile.docker** - 完全 Docker 化版本
|
||||||
|
|
||||||
|
### 切换到 Docker 版本
|
||||||
|
|
||||||
|
如果需要使用完全 Docker 化的版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 备份当前版本
|
||||||
|
cp Jenkinsfile Jenkinsfile.original
|
||||||
|
|
||||||
|
# 使用 Docker 版本
|
||||||
|
cp Jenkinsfile.docker Jenkinsfile
|
||||||
|
```
|
||||||
|
|
||||||
|
或者在 Jenkins 中直接指定文件路径:
|
||||||
|
- Pipeline script from SCM
|
||||||
|
- Script Path: `Jenkinsfile.docker`
|
||||||
|
|
||||||
|
## Docker 构建配置说明
|
||||||
|
|
||||||
|
### 1. Node.js 版本
|
||||||
|
|
||||||
|
在 Jenkinsfile 中指定 Node.js 版本:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
environment {
|
||||||
|
NODE_VERSION = '18' // 或 '20', '18-alpine' 等
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Docker Socket 挂载
|
||||||
|
|
||||||
|
如果需要构建 Docker 镜像,需要挂载 Docker socket:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
|
||||||
|
// 可以在这里执行 docker build 等命令
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Volume 挂载
|
||||||
|
|
||||||
|
如果需要持久化构建产物,可以挂载卷:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
nodeImage.inside('-v /workspace:/workspace') {
|
||||||
|
// 构建产物会保留在 /workspace
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. Docker 命令未找到
|
||||||
|
|
||||||
|
**错误**: `docker: command not found`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 确保安装了 Docker Pipeline 插件
|
||||||
|
- 检查 Jenkins 用户是否有 Docker 访问权限
|
||||||
|
|
||||||
|
### 2. 权限 denied
|
||||||
|
|
||||||
|
**错误**: `permission denied while trying to connect to the Docker daemon socket`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG docker jenkins
|
||||||
|
sudo systemctl restart jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 镜像拉取失败
|
||||||
|
|
||||||
|
**错误**: `Error pulling image`
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 检查网络连接
|
||||||
|
- 配置 Docker 镜像加速器
|
||||||
|
- 使用国内镜像源(如阿里云)
|
||||||
|
|
||||||
|
### 4. 构建产物丢失
|
||||||
|
|
||||||
|
**问题**: Docker 容器退出后,构建产物丢失
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- Jenkins 会自动将工作目录挂载到容器中
|
||||||
|
- 确保构建产物在 `$WORKSPACE` 目录下
|
||||||
|
- 使用 `docker.inside()` 会自动处理工作目录
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### 1. 使用 Docker 镜像缓存
|
||||||
|
|
||||||
|
Jenkins 会自动缓存 Docker 镜像,但可以手动拉取:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Pull Docker Image') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker.image('node:18').pull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用 Alpine 镜像
|
||||||
|
|
||||||
|
使用更小的 Alpine 镜像可以加快拉取速度:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
def nodeImage = docker.image("node:18-alpine")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 并行构建
|
||||||
|
|
||||||
|
可以在不同的 Docker 容器中并行构建前端和后端:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Parallel Build') {
|
||||||
|
parallel {
|
||||||
|
stage('Build Frontend') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker.image('node:18').inside() {
|
||||||
|
sh 'npm run build --workspace=frontend'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build Backend') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker.image('node:18').inside() {
|
||||||
|
sh 'cd backend && npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **固定版本**: 使用具体的 Node.js 版本标签(如 `node:18`),避免使用 `latest`
|
||||||
|
2. **缓存依赖**: 使用 npm cache 或 Docker layer caching
|
||||||
|
3. **清理资源**: 在 `post` 阶段清理 Docker 资源
|
||||||
|
4. **错误处理**: 添加错误处理和日志记录
|
||||||
|
5. **安全扫描**: 定期扫描 Docker 镜像的安全漏洞
|
||||||
|
|
||||||
|
## 示例:完整的 Docker 构建 Pipeline
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
NODE_VERSION = '18'
|
||||||
|
DOCKER_REGISTRY = 'your-registry.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def nodeImage = docker.image("node:${NODE_VERSION}")
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh '''
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker.image("node:${NODE_VERSION}").inside() {
|
||||||
|
sh 'npm test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build Docker Images') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker.build("myapp:${env.BUILD_NUMBER}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
sh 'docker system prune -f'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
使用 Docker 构建的优势:
|
||||||
|
- ✅ 环境一致性
|
||||||
|
- ✅ 易于维护
|
||||||
|
- ✅ 可移植性
|
||||||
|
- ✅ 隔离性
|
||||||
|
|
||||||
|
当前项目已支持 Docker 构建,只需确保 Jenkins 安装了 Docker Pipeline 插件即可使用。
|
||||||
196
Jenkinsfile
vendored
Normal file
196
Jenkinsfile
vendored
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
// 远程服务器配置
|
||||||
|
DEPLOY_HOST = '180.76.180.105'
|
||||||
|
DEPLOY_USER = 'root' // 根据实际情况修改用户名
|
||||||
|
DEPLOY_PATH = '/opt/nginx/html/ai'
|
||||||
|
// 如果需要 SSH 密钥,可以在 Jenkins 中配置 SSH credentials
|
||||||
|
// SSH_CREDENTIALS = credentials('deploy-ssh-key')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
echo '检出代码...'
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build in Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
// 在 Node.js Docker 容器中执行构建
|
||||||
|
// 需要 Jenkins 安装 Docker Pipeline 插件
|
||||||
|
def nodeImage = docker.image("node:18")
|
||||||
|
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
|
||||||
|
sh '''
|
||||||
|
echo "在 Docker 容器中构建..."
|
||||||
|
echo "Node 版本:"
|
||||||
|
node --version
|
||||||
|
echo "NPM 版本:"
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
echo "安装依赖..."
|
||||||
|
npm install
|
||||||
|
npm run install:all
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
echo "构建前端..."
|
||||||
|
npm run build --workspace=frontend
|
||||||
|
|
||||||
|
# 构建后端
|
||||||
|
echo "构建后端..."
|
||||||
|
cd backend
|
||||||
|
npm run build
|
||||||
|
npm run prisma:generate
|
||||||
|
cd ..
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Prepare Deployment Package') {
|
||||||
|
steps {
|
||||||
|
echo '准备部署包...'
|
||||||
|
sh '''
|
||||||
|
# 创建临时部署目录
|
||||||
|
mkdir -p deploy-package
|
||||||
|
|
||||||
|
# 复制前端构建产物
|
||||||
|
cp -r frontend/dist deploy-package/frontend-dist
|
||||||
|
|
||||||
|
# 复制后端构建产物和必要文件
|
||||||
|
mkdir -p deploy-package/backend
|
||||||
|
cp -r backend/dist deploy-package/backend/dist
|
||||||
|
cp -r backend/prisma deploy-package/backend/prisma
|
||||||
|
cp backend/package.json deploy-package/backend/
|
||||||
|
cp backend/package-lock.json deploy-package/backend/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# 复制 Docker 相关文件
|
||||||
|
mkdir -p deploy-package/docker
|
||||||
|
cp nginx/docker-compose.production.yml deploy-package/docker/docker-compose.yml
|
||||||
|
cp nginx/nginx.conf.docker deploy-package/docker/nginx.conf.docker
|
||||||
|
if [ -f backend/Dockerfile ]; then
|
||||||
|
cp backend/Dockerfile deploy-package/backend/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制部署脚本
|
||||||
|
cp scripts/deploy-docker.sh deploy-package/
|
||||||
|
chmod +x deploy-package/deploy-docker.sh
|
||||||
|
|
||||||
|
# 创建部署包
|
||||||
|
tar -czf deploy-package.tar.gz deploy-package/
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy to Remote Server') {
|
||||||
|
steps {
|
||||||
|
echo '部署到远程服务器...'
|
||||||
|
script {
|
||||||
|
// 使用 SSH 传输文件并执行部署
|
||||||
|
sh '''
|
||||||
|
# 传输部署包到远程服务器
|
||||||
|
scp -o StrictHostKeyChecking=no deploy-package.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
|
||||||
|
|
||||||
|
# 在远程服务器上执行部署
|
||||||
|
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
|
||||||
|
# 创建部署目录
|
||||||
|
mkdir -p /opt/nginx/html/ai
|
||||||
|
|
||||||
|
# 解压部署包
|
||||||
|
cd /tmp
|
||||||
|
tar -xzf deploy-package.tar.gz
|
||||||
|
|
||||||
|
# 备份旧版本(如果存在)
|
||||||
|
if [ -d /opt/nginx/html/ai/current ]; then
|
||||||
|
mv /opt/nginx/html/ai/current /opt/nginx/html/ai/backup-$(date +%Y%m%d-%H%M%S)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建新版本目录
|
||||||
|
mkdir -p /opt/nginx/html/ai/current
|
||||||
|
|
||||||
|
# 复制前端文件
|
||||||
|
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
|
||||||
|
|
||||||
|
# 复制后端文件
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/backend
|
||||||
|
cp -r deploy-package/backend/* /opt/nginx/html/ai/current/backend/
|
||||||
|
|
||||||
|
# 复制 Docker 配置文件
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/docker
|
||||||
|
cp deploy-package/docker/docker-compose.yml /opt/nginx/html/ai/current/docker/
|
||||||
|
cp deploy-package/docker/nginx.conf.docker /opt/nginx/html/ai/current/docker/
|
||||||
|
|
||||||
|
# 创建 nginx logs 目录
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/docker/logs
|
||||||
|
|
||||||
|
# 执行 Docker 部署脚本
|
||||||
|
if [ -f deploy-package/deploy-docker.sh ]; then
|
||||||
|
chmod +x deploy-package/deploy-docker.sh
|
||||||
|
bash deploy-package/deploy-docker.sh /opt/nginx/html/ai/current
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -rf /tmp/deploy-package /tmp/deploy-package.tar.gz
|
||||||
|
|
||||||
|
echo "部署完成!"
|
||||||
|
ENDSSH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Restart Services') {
|
||||||
|
steps {
|
||||||
|
echo '重启 Docker 服务...'
|
||||||
|
sh '''
|
||||||
|
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
|
||||||
|
# 使用 Docker Compose 重启服务
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
|
||||||
|
# 检查 docker-compose 命令(支持新版本的 docker compose)
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
else
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 停止旧容器
|
||||||
|
$COMPOSE_CMD down || true
|
||||||
|
|
||||||
|
# 重新构建并启动
|
||||||
|
$COMPOSE_CMD up -d --build
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
$COMPOSE_CMD ps
|
||||||
|
|
||||||
|
echo "Docker 服务已重启"
|
||||||
|
echo "前端访问: http://180.76.180.105:8080"
|
||||||
|
echo "后端 API: http://180.76.180.105:3001"
|
||||||
|
ENDSSH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
echo '部署成功!'
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
echo '部署失败!'
|
||||||
|
}
|
||||||
|
always {
|
||||||
|
// 清理本地临时文件
|
||||||
|
sh 'rm -rf deploy-package deploy-package.tar.gz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
Jenkinsfile.docker
Normal file
195
Jenkinsfile.docker
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
// 远程服务器配置
|
||||||
|
DEPLOY_HOST = '180.76.180.105'
|
||||||
|
DEPLOY_USER = 'root'
|
||||||
|
DEPLOY_PATH = '/opt/nginx/html/ai'
|
||||||
|
// Node.js 版本
|
||||||
|
NODE_VERSION = '18'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
echo '检出代码...'
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build in Docker') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
// 在 Node.js Docker 容器中执行构建
|
||||||
|
def nodeImage = docker.image("node:${NODE_VERSION}")
|
||||||
|
nodeImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') {
|
||||||
|
sh '''
|
||||||
|
echo "在 Docker 容器中构建..."
|
||||||
|
echo "Node 版本:"
|
||||||
|
node --version
|
||||||
|
echo "NPM 版本:"
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
echo "安装依赖..."
|
||||||
|
npm install
|
||||||
|
npm run install:all
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
echo "构建前端..."
|
||||||
|
npm run build --workspace=frontend
|
||||||
|
|
||||||
|
# 构建后端
|
||||||
|
echo "构建后端..."
|
||||||
|
cd backend
|
||||||
|
npm run build
|
||||||
|
npm run prisma:generate
|
||||||
|
cd ..
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Prepare Deployment Package') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
// 在 Docker 容器中打包
|
||||||
|
def nodeImage = docker.image("node:${NODE_VERSION}")
|
||||||
|
nodeImage.inside() {
|
||||||
|
sh '''
|
||||||
|
echo "准备部署包..."
|
||||||
|
|
||||||
|
# 创建临时部署目录
|
||||||
|
mkdir -p deploy-package
|
||||||
|
|
||||||
|
# 复制前端构建产物
|
||||||
|
cp -r frontend/dist deploy-package/frontend-dist
|
||||||
|
|
||||||
|
# 复制后端构建产物和必要文件
|
||||||
|
mkdir -p deploy-package/backend
|
||||||
|
cp -r backend/dist deploy-package/backend/dist
|
||||||
|
cp -r backend/prisma deploy-package/backend/prisma
|
||||||
|
cp backend/package.json deploy-package/backend/
|
||||||
|
cp backend/package-lock.json deploy-package/backend/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# 复制 Docker 相关文件
|
||||||
|
mkdir -p deploy-package/docker
|
||||||
|
cp nginx/docker-compose.production.yml deploy-package/docker/docker-compose.yml
|
||||||
|
cp nginx/nginx.conf.docker deploy-package/docker/nginx.conf.docker
|
||||||
|
if [ -f backend/Dockerfile ]; then
|
||||||
|
cp backend/Dockerfile deploy-package/backend/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 复制部署脚本
|
||||||
|
cp scripts/deploy-docker.sh deploy-package/
|
||||||
|
chmod +x deploy-package/deploy-docker.sh
|
||||||
|
|
||||||
|
# 创建部署包
|
||||||
|
tar -czf deploy-package.tar.gz deploy-package/
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy to Remote Server') {
|
||||||
|
steps {
|
||||||
|
echo '部署到远程服务器...'
|
||||||
|
script {
|
||||||
|
sh '''
|
||||||
|
# 传输部署包到远程服务器
|
||||||
|
scp -o StrictHostKeyChecking=no deploy-package.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
|
||||||
|
|
||||||
|
# 在远程服务器上执行部署
|
||||||
|
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
|
||||||
|
# 创建部署目录
|
||||||
|
mkdir -p /opt/nginx/html/ai
|
||||||
|
|
||||||
|
# 解压部署包
|
||||||
|
cd /tmp
|
||||||
|
tar -xzf deploy-package.tar.gz
|
||||||
|
|
||||||
|
# 备份旧版本(如果存在)
|
||||||
|
if [ -d /opt/nginx/html/ai/current ]; then
|
||||||
|
mv /opt/nginx/html/ai/current /opt/nginx/html/ai/backup-$(date +%Y%m%d-%H%M%S)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 创建新版本目录
|
||||||
|
mkdir -p /opt/nginx/html/ai/current
|
||||||
|
|
||||||
|
# 复制前端文件
|
||||||
|
cp -r deploy-package/frontend-dist/* /opt/nginx/html/ai/current/
|
||||||
|
|
||||||
|
# 复制后端文件
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/backend
|
||||||
|
cp -r deploy-package/backend/* /opt/nginx/html/ai/current/backend/
|
||||||
|
|
||||||
|
# 复制 Docker 配置文件
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/docker
|
||||||
|
cp deploy-package/docker/docker-compose.yml /opt/nginx/html/ai/current/docker/
|
||||||
|
cp deploy-package/docker/nginx.conf.docker /opt/nginx/html/ai/current/docker/
|
||||||
|
|
||||||
|
# 创建 nginx logs 目录
|
||||||
|
mkdir -p /opt/nginx/html/ai/current/docker/logs
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -rf /tmp/deploy-package /tmp/deploy-package.tar.gz
|
||||||
|
|
||||||
|
echo "部署完成!"
|
||||||
|
ENDSSH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Restart Docker Services') {
|
||||||
|
steps {
|
||||||
|
echo '重启 Docker 服务...'
|
||||||
|
sh '''
|
||||||
|
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} << 'ENDSSH'
|
||||||
|
# 使用 Docker Compose 重启服务
|
||||||
|
cd /opt/nginx/html/ai/current/docker
|
||||||
|
|
||||||
|
# 检查 docker-compose 命令(支持新版本的 docker compose)
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
else
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 停止旧容器
|
||||||
|
$COMPOSE_CMD down || true
|
||||||
|
|
||||||
|
# 重新构建并启动
|
||||||
|
$COMPOSE_CMD up -d --build
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
$COMPOSE_CMD ps
|
||||||
|
|
||||||
|
echo "Docker 服务已重启"
|
||||||
|
echo "前端访问: http://180.76.180.105:8080"
|
||||||
|
echo "后端 API: http://180.76.180.105:3001"
|
||||||
|
ENDSSH
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
echo '部署成功!'
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
echo '部署失败!'
|
||||||
|
}
|
||||||
|
always {
|
||||||
|
// 清理本地临时文件
|
||||||
|
sh 'rm -rf deploy-package deploy-package.tar.gz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
backend/Dockerfile
Normal file
22
backend/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 package 文件
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY prisma ./prisma/
|
||||||
|
|
||||||
|
# 安装依赖(包括生产依赖)
|
||||||
|
RUN npm ci --production
|
||||||
|
|
||||||
|
# 复制构建产物
|
||||||
|
COPY dist ./dist
|
||||||
|
|
||||||
|
# 生成 Prisma Client
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
42
nginx/docker-compose.production.yml
Normal file
42
nginx/docker-compose.production.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# 前端 Nginx 服务(使用 8080 端口避免与现有 nginx 冲突)
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ai-learning-nginx
|
||||||
|
ports:
|
||||||
|
- "8080:80" # 使用 8080 端口,避免与现有 nginx 冲突
|
||||||
|
- "8443:443" # HTTPS 端口(可选)
|
||||||
|
volumes:
|
||||||
|
- /opt/nginx/html/ai/current/docker/nginx.conf.docker:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- /opt/nginx/html/ai/current:/usr/share/nginx/html:ro
|
||||||
|
- /opt/nginx/html/ai/current/docker/logs:/var/log/nginx
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- ai-learning-network
|
||||||
|
|
||||||
|
# 后端服务
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: /opt/nginx/html/ai/current/backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: ai-learning-backend
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3001
|
||||||
|
- DATABASE_URL=file:./prisma/dev.db
|
||||||
|
volumes:
|
||||||
|
- /opt/nginx/html/ai/current/backend/prisma:/app/prisma
|
||||||
|
- /opt/nginx/html/ai/current/backend/dist:/app/dist:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- ai-learning-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ai-learning-network:
|
||||||
|
driver: bridge
|
||||||
@ -20,7 +20,7 @@ server {
|
|||||||
|
|
||||||
# 前端静态文件
|
# 前端静态文件
|
||||||
location / {
|
location / {
|
||||||
root /var/www/ai-learning-platform/frontend/dist;
|
root /opt/nginx/html/ai/current;
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
|||||||
59
nginx/nginx.conf.docker
Normal file
59
nginx/nginx.conf.docker
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Nginx 配置文件 - Docker 部署版本
|
||||||
|
# 用于 docker-compose 部署,使用 Docker 服务名进行服务发现
|
||||||
|
|
||||||
|
# 上游后端服务器(使用 Docker 服务名)
|
||||||
|
upstream backend {
|
||||||
|
server backend:3001; # 使用 Docker Compose 服务名
|
||||||
|
keepalive 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP 服务器配置
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _; # 匹配所有域名和 IP
|
||||||
|
|
||||||
|
# 客户端最大请求体大小
|
||||||
|
client_max_body_size 10M;
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
access_log /var/log/nginx/ai-learning-access.log;
|
||||||
|
error_log /var/log/nginx/ai-learning-error.log;
|
||||||
|
|
||||||
|
# 前端静态文件
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 代理
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
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;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
|
||||||
|
# 超时设置
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "healthy\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
128
nginx/nginx.conf.production
Normal file
128
nginx/nginx.conf.production
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Nginx 配置文件 - AI学习平台(生产环境)
|
||||||
|
# 部署路径: /opt/nginx/html/ai/current
|
||||||
|
|
||||||
|
# 上游后端服务器
|
||||||
|
upstream backend {
|
||||||
|
server localhost:3001;
|
||||||
|
keepalive 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP 服务器配置
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name 180.76.180.105; # 服务器 IP 或域名
|
||||||
|
|
||||||
|
# 客户端最大请求体大小
|
||||||
|
client_max_body_size 10M;
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
access_log /var/log/nginx/ai-learning-access.log;
|
||||||
|
error_log /var/log/nginx/ai-learning-error.log;
|
||||||
|
|
||||||
|
# 前端静态文件
|
||||||
|
location / {
|
||||||
|
root /opt/nginx/html/ai/current;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 代理
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
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;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
|
||||||
|
# 超时设置
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
location /health {
|
||||||
|
access_log off;
|
||||||
|
return 200 "healthy\n";
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS 服务器配置(可选,需要 SSL 证书)
|
||||||
|
# server {
|
||||||
|
# listen 443 ssl http2;
|
||||||
|
# server_name 180.76.180.105; # 服务器 IP 或域名
|
||||||
|
#
|
||||||
|
# # SSL 证书配置
|
||||||
|
# ssl_certificate /path/to/your/certificate.crt;
|
||||||
|
# ssl_certificate_key /path/to/your/private.key;
|
||||||
|
#
|
||||||
|
# # SSL 优化配置
|
||||||
|
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
# ssl_prefer_server_ciphers on;
|
||||||
|
# ssl_session_cache shared:SSL:10m;
|
||||||
|
# ssl_session_timeout 10m;
|
||||||
|
#
|
||||||
|
# # 客户端最大请求体大小
|
||||||
|
# client_max_body_size 10M;
|
||||||
|
#
|
||||||
|
# # 日志配置
|
||||||
|
# access_log /var/log/nginx/ai-learning-ssl-access.log;
|
||||||
|
# error_log /var/log/nginx/ai-learning-ssl-error.log;
|
||||||
|
#
|
||||||
|
# # 前端静态文件
|
||||||
|
# location / {
|
||||||
|
# root /opt/nginx/html/ai/current;
|
||||||
|
# index index.html;
|
||||||
|
# try_files $uri $uri/ /index.html;
|
||||||
|
#
|
||||||
|
# # 静态资源缓存
|
||||||
|
# location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
# expires 1y;
|
||||||
|
# add_header Cache-Control "public, immutable";
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # API 代理
|
||||||
|
# location /api {
|
||||||
|
# proxy_pass http://backend;
|
||||||
|
# proxy_http_version 1.1;
|
||||||
|
# proxy_set_header Upgrade $http_upgrade;
|
||||||
|
# proxy_set_header Connection 'upgrade';
|
||||||
|
# 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;
|
||||||
|
# proxy_cache_bypass $http_upgrade;
|
||||||
|
#
|
||||||
|
# # 超时设置
|
||||||
|
# proxy_connect_timeout 60s;
|
||||||
|
# proxy_send_timeout 60s;
|
||||||
|
# proxy_read_timeout 60s;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # 健康检查
|
||||||
|
# location /health {
|
||||||
|
# access_log off;
|
||||||
|
# return 200 "healthy\n";
|
||||||
|
# add_header Content-Type text/plain;
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # HTTP 重定向到 HTTPS
|
||||||
|
# server {
|
||||||
|
# listen 80;
|
||||||
|
# server_name 180.76.180.105; # 服务器 IP 或域名
|
||||||
|
# return 301 https://$server_name$request_uri;
|
||||||
|
# }
|
||||||
83
scripts/deploy-docker.sh
Executable file
83
scripts/deploy-docker.sh
Executable file
@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Docker 部署脚本 - 在远程服务器上执行
|
||||||
|
# 用法: ./deploy-docker.sh <部署目录>
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEPLOY_DIR=${1:-/opt/nginx/html/ai/current}
|
||||||
|
DOCKER_DIR="$DEPLOY_DIR/docker"
|
||||||
|
|
||||||
|
echo "开始 Docker 部署到: $DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 检查部署目录是否存在
|
||||||
|
if [ ! -d "$DEPLOY_DIR" ]; then
|
||||||
|
echo "错误: 部署目录不存在: $DEPLOY_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 Docker 是否安装
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "错误: Docker 未安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||||
|
echo "错误: docker-compose 未安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 docker-compose 命令(支持新版本的 docker compose)
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入 Docker 目录
|
||||||
|
if [ ! -d "$DOCKER_DIR" ]; then
|
||||||
|
echo "错误: Docker 配置目录不存在: $DOCKER_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$DOCKER_DIR" || exit 1
|
||||||
|
|
||||||
|
# 停止旧容器(如果存在)
|
||||||
|
echo "停止旧容器..."
|
||||||
|
$COMPOSE_CMD down || true
|
||||||
|
|
||||||
|
# 构建并启动服务
|
||||||
|
echo "构建并启动 Docker 服务..."
|
||||||
|
$COMPOSE_CMD up -d --build
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
echo "等待服务启动..."
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
echo "检查服务状态..."
|
||||||
|
$COMPOSE_CMD ps
|
||||||
|
|
||||||
|
# 检查容器健康状态
|
||||||
|
echo "检查容器健康状态..."
|
||||||
|
if docker ps | grep -q ai-learning-nginx; then
|
||||||
|
echo "✅ Nginx 容器运行中"
|
||||||
|
else
|
||||||
|
echo "❌ Nginx 容器未运行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker ps | grep -q ai-learning-backend; then
|
||||||
|
echo "✅ Backend 容器运行中"
|
||||||
|
else
|
||||||
|
echo "❌ Backend 容器未运行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Docker 部署完成!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "前端访问: http://180.76.180.105:8080"
|
||||||
|
echo "后端 API: http://180.76.180.105:3001"
|
||||||
|
echo "健康检查: http://180.76.180.105:8080/health"
|
||||||
|
echo "=========================================="
|
||||||
54
scripts/deploy.sh
Executable file
54
scripts/deploy.sh
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 部署脚本 - 在远程服务器上执行
|
||||||
|
# 用法: ./deploy.sh <部署目录>
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEPLOY_DIR=${1:-/opt/nginx/html/ai/current}
|
||||||
|
BACKEND_DIR="$DEPLOY_DIR/backend"
|
||||||
|
|
||||||
|
echo "开始部署到: $DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 检查部署目录是否存在
|
||||||
|
if [ ! -d "$DEPLOY_DIR" ]; then
|
||||||
|
echo "错误: 部署目录不存在: $DEPLOY_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入后端目录
|
||||||
|
cd "$BACKEND_DIR" || exit 1
|
||||||
|
|
||||||
|
# 安装后端依赖(仅生产依赖)
|
||||||
|
if [ -f "package.json" ]; then
|
||||||
|
echo "安装后端依赖..."
|
||||||
|
npm ci --production || npm install --production
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 初始化数据库(如果需要)
|
||||||
|
if [ -d "prisma" ]; then
|
||||||
|
echo "初始化数据库..."
|
||||||
|
# 检查数据库文件是否存在
|
||||||
|
if [ ! -f "prisma/dev.db" ]; then
|
||||||
|
echo "数据库不存在,执行迁移..."
|
||||||
|
npx prisma migrate deploy || npx prisma migrate dev
|
||||||
|
npx prisma generate
|
||||||
|
# 如果有 seed 文件,执行 seed
|
||||||
|
if [ -f "prisma/seed.ts" ]; then
|
||||||
|
npx tsx prisma/seed.ts || echo "Seed 执行失败或已存在数据"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "数据库已存在,执行迁移..."
|
||||||
|
npx prisma migrate deploy || npx prisma migrate dev
|
||||||
|
npx prisma generate
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置文件权限
|
||||||
|
echo "设置文件权限..."
|
||||||
|
chmod +x dist/index.js 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "部署脚本执行完成!"
|
||||||
|
echo "部署目录: $DEPLOY_DIR"
|
||||||
|
echo "前端文件: $DEPLOY_DIR"
|
||||||
|
echo "后端文件: $BACKEND_DIR"
|
||||||
Loading…
Reference in New Issue
Block a user