feat: 新增 Docker 部署支持、Swoole/Octane 集成及相关优化
- 添加 Dockerfile 与多套 docker-compose 配置(开发/生产环境) - 集成 Laravel Octane (Swoole) 提升性能 - 新增健康检查、监控脚本及部署文档 - 新增 Docker 镜像离线导入包(MySQL/Redis/Meilisearch) - 优化文档转换、预览服务及队列任务 - 添加 CreateAdminUser 命令与路由健康检查接口 - 新增 Swoole 队列兼容性测试套件 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
126
docker/DATA_PERSISTENCE_README.md
Normal file
126
docker/DATA_PERSISTENCE_README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 数据持久化和目录映射实现完成
|
||||
|
||||
## 任务概述
|
||||
|
||||
✅ **任务3: 实现数据持久化和目录映射** 已完成
|
||||
|
||||
本任务实现了Docker部署中的完整数据持久化和目录映射配置,确保容器重启后数据不丢失,满足生产环境的可靠性要求。
|
||||
|
||||
## 实现的功能
|
||||
|
||||
### 1. 项目代码目录映射到容器 ✅
|
||||
- **配置**: `./:/var/www/html`
|
||||
- **用途**: 支持开发环境代码热重载
|
||||
- **应用于**: 应用容器和队列容器
|
||||
|
||||
### 2. 上传文档存储目录持久化 ✅
|
||||
- **文档存储**: `documents_data:/var/www/html/storage/app/private/documents`
|
||||
- **公共文件**: `public_data:/var/www/html/storage/app/public`
|
||||
- **映射到**: `./storage/app/private/documents` 和 `./storage/app/public`
|
||||
|
||||
### 3. 数据库数据目录持久化 ✅
|
||||
- **配置**: `mysql_data:/var/lib/mysql`
|
||||
- **映射到**: `./storage/mysql`
|
||||
- **用途**: MySQL数据库文件持久化
|
||||
|
||||
### 4. 搜索引擎数据目录持久化 ✅
|
||||
- **配置**: `meilisearch_data:/meili_data`
|
||||
- **映射到**: `./storage/meilisearch`
|
||||
- **用途**: Meilisearch搜索索引持久化
|
||||
|
||||
### 5. 日志目录映射到宿主机 ✅
|
||||
- **应用日志**: `app_logs:/var/log` → `./storage/logs/app`
|
||||
- **队列日志**: `queue_logs:/var/log` → `./storage/logs/queue`
|
||||
- **Laravel日志**: `laravel_logs:/var/www/html/storage/logs` → `./storage/logs`
|
||||
|
||||
## 创建的文件和脚本
|
||||
|
||||
### 配置文件
|
||||
- ✅ `docker-compose.yml` - 更新了完整的数据卷映射配置
|
||||
- ✅ `storage/*/` - 创建了所有必要的存储目录结构
|
||||
|
||||
### 管理脚本
|
||||
- ✅ `docker/init-storage.sh` - 存储目录初始化脚本
|
||||
- ✅ `docker/test-persistence.sh` - 数据持久化测试脚本
|
||||
- ✅ `docker/validate-storage-config.sh` - 完整配置验证脚本
|
||||
|
||||
### 文档
|
||||
- ✅ `docker/STORAGE_CONFIGURATION.md` - 详细的存储配置说明文档
|
||||
- ✅ `storage/*/.gitignore` - 数据目录的版本控制配置
|
||||
|
||||
## 存储目录结构
|
||||
|
||||
```
|
||||
storage/
|
||||
├── app/ # Laravel应用存储 (持久化)
|
||||
│ ├── private/
|
||||
│ │ ├── documents/ # 上传文档存储 (持久化)
|
||||
│ │ └── markdown/ # Markdown文件存储
|
||||
│ └── public/ # 公共文件存储 (持久化)
|
||||
├── framework/ # Laravel框架缓存
|
||||
├── logs/ # 日志文件 (映射到宿主机)
|
||||
│ ├── app/ # 应用容器日志
|
||||
│ ├── queue/ # 队列容器日志
|
||||
│ └── laravel.log # Laravel应用日志
|
||||
├── mysql/ # MySQL数据文件 (持久化)
|
||||
├── redis/ # Redis数据文件 (持久化)
|
||||
└── meilisearch/ # Meilisearch索引文件 (持久化)
|
||||
```
|
||||
|
||||
## 验证结果
|
||||
|
||||
运行 `./docker/validate-storage-config.sh` 的验证结果:
|
||||
- ✅ **54项检查全部通过**
|
||||
- ✅ **0项失败**
|
||||
- ✅ 所有存储目录结构正确
|
||||
- ✅ 所有Docker Compose卷映射配置正确
|
||||
- ✅ 所有服务容器卷映射正确
|
||||
- ✅ 所有数据卷绑定配置正确
|
||||
- ✅ 所有目录权限正确
|
||||
- ✅ Docker Compose配置文件语法正确
|
||||
- ✅ 所有目录写入权限正常
|
||||
|
||||
## 满足的需求
|
||||
|
||||
本实现完全满足以下需求:
|
||||
|
||||
- **需求 3.1**: ✅ 项目代码目录映射到容器内部
|
||||
- **需求 3.2**: ✅ 上传文档存储目录持久化到宿主机
|
||||
- **需求 3.3**: ✅ 数据库数据目录持久化到宿主机
|
||||
- **需求 3.4**: ✅ 搜索引擎数据目录持久化到宿主机
|
||||
- **需求 3.5**: ✅ 日志目录映射到宿主机便于查看
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 初始化存储目录
|
||||
```bash
|
||||
./docker/init-storage.sh
|
||||
```
|
||||
|
||||
### 验证配置
|
||||
```bash
|
||||
./docker/validate-storage-config.sh
|
||||
```
|
||||
|
||||
### 测试持久化
|
||||
```bash
|
||||
./docker/test-persistence.sh
|
||||
```
|
||||
|
||||
### 启动服务
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **完整性**: 覆盖了所有需要持久化的数据类型
|
||||
2. **可靠性**: 使用bind mount确保数据真正持久化
|
||||
3. **可维护性**: 提供了完整的管理和验证脚本
|
||||
4. **安全性**: 正确的目录权限设置
|
||||
5. **可扩展性**: 易于添加新的存储需求
|
||||
|
||||
## 下一步
|
||||
|
||||
数据持久化和目录映射配置已完成,可以继续执行下一个任务:
|
||||
- **任务4**: 配置环境变量和网络设置
|
||||
608
docker/DEPLOYMENT_GUIDE.md
Normal file
608
docker/DEPLOYMENT_GUIDE.md
Normal file
@@ -0,0 +1,608 @@
|
||||
# Laravel知识库系统 - OpenEuler部署指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南详细说明如何在OpenEuler服务器上部署Laravel知识库系统。系统采用Docker容器化技术,支持完整的生产环境运行。
|
||||
|
||||
## 系统要求
|
||||
|
||||
### 硬件要求
|
||||
|
||||
- **CPU**: 2核心或以上 (推荐4核心)
|
||||
- **内存**: 4GB或以上 (推荐8GB)
|
||||
- **存储**: 20GB可用空间 (推荐50GB)
|
||||
- **网络**: 稳定的网络连接
|
||||
|
||||
### 软件要求
|
||||
|
||||
- **操作系统**: OpenEuler 20.03 LTS或更高版本
|
||||
- **架构**: x86_64 (amd64)
|
||||
- **Docker**: 20.10或更高版本
|
||||
- **Docker Compose**: 2.0或更高版本
|
||||
|
||||
## 部署架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ OpenEuler服务器 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Swoole │ │ Laravel │ │ Queue │ │
|
||||
│ │ (Web服务) │ │ (应用) │ │ (队列) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ MySQL │ │ Redis │ │ Meilisearch │ │
|
||||
│ │ (数据库) │ │ (缓存) │ │ (搜索) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 持久化存储 │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ 数据库数据 │ │ 应用文件 │ │ 日志 │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
#### 1.1 系统更新
|
||||
|
||||
```bash
|
||||
# 更新系统包
|
||||
sudo dnf update -y
|
||||
|
||||
# 安装必要工具
|
||||
sudo dnf install -y curl wget git unzip
|
||||
```
|
||||
|
||||
#### 1.2 创建部署用户
|
||||
|
||||
```bash
|
||||
# 创建部署用户
|
||||
sudo useradd -m -s /bin/bash deploy
|
||||
sudo usermod -aG wheel deploy
|
||||
|
||||
# 切换到部署用户
|
||||
sudo su - deploy
|
||||
```
|
||||
|
||||
### 2. Docker安装
|
||||
|
||||
#### 2.1 自动安装 (推荐)
|
||||
|
||||
使用提供的部署脚本自动安装Docker:
|
||||
|
||||
```bash
|
||||
# 下载部署脚本
|
||||
wget https://your-server.com/deploy-to-openeuler.sh
|
||||
chmod +x deploy-to-openeuler.sh
|
||||
|
||||
# 运行部署脚本 (会自动安装Docker)
|
||||
sudo ./deploy-to-openeuler.sh /path/to/docker-images
|
||||
```
|
||||
|
||||
#### 2.2 手动安装
|
||||
|
||||
```bash
|
||||
# 添加Docker仓库
|
||||
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
# 安装Docker
|
||||
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# 启动Docker服务
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
# 将用户添加到docker组
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# 重新登录以使组权限生效
|
||||
exit
|
||||
sudo su - deploy
|
||||
|
||||
# 验证安装
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### 3. 镜像准备
|
||||
|
||||
#### 3.1 镜像导出 (在开发环境)
|
||||
|
||||
在有网络连接的开发环境中导出镜像:
|
||||
|
||||
```bash
|
||||
# 使用导出脚本
|
||||
./docker/export-images.sh -c -v
|
||||
|
||||
# 或手动导出
|
||||
docker save -o knowledge-base-app.tar knowledge-base-app:latest
|
||||
docker save -o mysql.tar mysql:8.0
|
||||
docker save -o redis.tar redis:7-alpine
|
||||
docker save -o meilisearch.tar getmeili/meilisearch:v1.5
|
||||
|
||||
# 压缩镜像文件
|
||||
gzip *.tar
|
||||
```
|
||||
|
||||
#### 3.2 镜像传输
|
||||
|
||||
将镜像文件传输到OpenEuler服务器:
|
||||
|
||||
```bash
|
||||
# 使用scp传输
|
||||
scp docker-images/*.tar.gz deploy@openeuler-server:/tmp/
|
||||
|
||||
# 或使用rsync
|
||||
rsync -avz docker-images/ deploy@openeuler-server:/tmp/docker-images/
|
||||
```
|
||||
|
||||
#### 3.3 镜像导入
|
||||
|
||||
在OpenEuler服务器上导入镜像:
|
||||
|
||||
```bash
|
||||
# 使用导入脚本
|
||||
./docker/import-and-verify.sh -f --test-run /tmp/docker-images
|
||||
|
||||
# 或手动导入
|
||||
cd /tmp/docker-images
|
||||
for file in *.tar.gz; do
|
||||
gunzip -c "$file" | docker load
|
||||
done
|
||||
|
||||
# 验证导入的镜像
|
||||
docker images
|
||||
```
|
||||
|
||||
### 4. 应用部署
|
||||
|
||||
#### 4.1 创建部署目录
|
||||
|
||||
```bash
|
||||
# 创建部署目录
|
||||
sudo mkdir -p /opt/knowledge-base
|
||||
sudo chown deploy:deploy /opt/knowledge-base
|
||||
cd /opt/knowledge-base
|
||||
```
|
||||
|
||||
#### 4.2 准备配置文件
|
||||
|
||||
创建docker-compose.yml文件:
|
||||
|
||||
```bash
|
||||
# 复制配置文件模板
|
||||
cp /path/to/source/docker-compose.yml .
|
||||
cp /path/to/source/.env.production .env
|
||||
|
||||
# 或下载配置文件
|
||||
wget https://your-server.com/docker-compose.yml
|
||||
wget https://your-server.com/.env.production -O .env
|
||||
```
|
||||
|
||||
#### 4.3 配置环境变量
|
||||
|
||||
编辑.env文件:
|
||||
|
||||
```bash
|
||||
nano .env
|
||||
```
|
||||
|
||||
重要配置项:
|
||||
|
||||
```env
|
||||
# 应用配置
|
||||
APP_NAME="知识库系统"
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:your-generated-key-here
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://your-server-ip
|
||||
|
||||
# 数据库配置
|
||||
DB_PASSWORD=your-secure-password
|
||||
|
||||
# 搜索配置
|
||||
MEILISEARCH_KEY=your-master-key-here
|
||||
```
|
||||
|
||||
#### 4.4 创建存储目录
|
||||
|
||||
```bash
|
||||
# 创建持久化存储目录
|
||||
mkdir -p storage/{mysql,redis,meilisearch,app,logs}
|
||||
mkdir -p storage/logs/{app,queue}
|
||||
|
||||
# 设置权限
|
||||
sudo chown -R 1000:1000 storage/
|
||||
chmod -R 755 storage/
|
||||
```
|
||||
|
||||
### 5. 启动服务
|
||||
|
||||
#### 5.1 启动所有服务
|
||||
|
||||
```bash
|
||||
# 启动服务
|
||||
docker compose up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker compose ps
|
||||
|
||||
# 查看日志
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
#### 5.2 初始化应用
|
||||
|
||||
```bash
|
||||
# 运行数据库迁移
|
||||
docker compose exec app php artisan migrate --force
|
||||
|
||||
# 创建存储链接
|
||||
docker compose exec app php artisan storage:link
|
||||
|
||||
# 清除缓存
|
||||
docker compose exec app php artisan cache:clear
|
||||
docker compose exec app php artisan config:cache
|
||||
docker compose exec app php artisan route:cache
|
||||
docker compose exec app php artisan view:cache
|
||||
```
|
||||
|
||||
### 6. 验证部署
|
||||
|
||||
#### 6.1 健康检查
|
||||
|
||||
```bash
|
||||
# 检查所有容器状态
|
||||
docker compose ps
|
||||
|
||||
# 检查健康状态
|
||||
docker compose exec app curl -f http://localhost/health
|
||||
|
||||
# 检查数据库连接
|
||||
docker compose exec app php artisan tinker --execute="DB::connection()->getPdo();"
|
||||
```
|
||||
|
||||
#### 6.2 功能测试
|
||||
|
||||
1. **Web访问测试**
|
||||
```bash
|
||||
curl -I http://your-server-ip
|
||||
```
|
||||
|
||||
2. **数据库测试**
|
||||
```bash
|
||||
docker compose exec mysql mysql -u root -p -e "SHOW DATABASES;"
|
||||
```
|
||||
|
||||
3. **搜索服务测试**
|
||||
```bash
|
||||
curl http://your-server-ip:7700/health
|
||||
```
|
||||
|
||||
4. **队列测试**
|
||||
```bash
|
||||
docker compose exec app php artisan queue:work --once
|
||||
```
|
||||
|
||||
## 运维管理
|
||||
|
||||
### 日常操作
|
||||
|
||||
#### 查看日志
|
||||
|
||||
```bash
|
||||
# 查看所有服务日志
|
||||
docker compose logs -f
|
||||
|
||||
# 查看特定服务日志
|
||||
docker compose logs -f app
|
||||
docker compose logs -f mysql
|
||||
docker compose logs -f redis
|
||||
docker compose logs -f meilisearch
|
||||
docker compose logs -f queue
|
||||
|
||||
# 查看Laravel日志
|
||||
docker compose exec app tail -f storage/logs/laravel.log
|
||||
```
|
||||
|
||||
#### 重启服务
|
||||
|
||||
```bash
|
||||
# 重启所有服务
|
||||
docker compose restart
|
||||
|
||||
# 重启特定服务
|
||||
docker compose restart app
|
||||
docker compose restart mysql
|
||||
```
|
||||
|
||||
#### 停止和启动
|
||||
|
||||
```bash
|
||||
# 停止所有服务
|
||||
docker compose down
|
||||
|
||||
# 启动所有服务
|
||||
docker compose up -d
|
||||
|
||||
# 停止并删除所有容器和网络
|
||||
docker compose down --volumes --remove-orphans
|
||||
```
|
||||
|
||||
### 备份和恢复
|
||||
|
||||
#### 数据备份
|
||||
|
||||
```bash
|
||||
# 创建备份脚本
|
||||
cat > backup.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/opt/backups/knowledge-base"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# 备份数据库
|
||||
docker compose exec -T mysql mysqldump -u root -p$DB_PASSWORD knowledge_base > "$BACKUP_DIR/database_$DATE.sql"
|
||||
|
||||
# 备份应用文件
|
||||
tar -czf "$BACKUP_DIR/storage_$DATE.tar.gz" storage/
|
||||
|
||||
# 备份配置文件
|
||||
cp .env "$BACKUP_DIR/env_$DATE"
|
||||
cp docker-compose.yml "$BACKUP_DIR/docker-compose_$DATE.yml"
|
||||
|
||||
echo "备份完成: $BACKUP_DIR"
|
||||
EOF
|
||||
|
||||
chmod +x backup.sh
|
||||
```
|
||||
|
||||
#### 数据恢复
|
||||
|
||||
```bash
|
||||
# 恢复数据库
|
||||
docker compose exec -T mysql mysql -u root -p$DB_PASSWORD knowledge_base < /path/to/database_backup.sql
|
||||
|
||||
# 恢复应用文件
|
||||
tar -xzf /path/to/storage_backup.tar.gz
|
||||
```
|
||||
|
||||
### 更新和升级
|
||||
|
||||
#### 应用更新
|
||||
|
||||
```bash
|
||||
# 拉取新镜像
|
||||
docker compose pull
|
||||
|
||||
# 重新启动服务
|
||||
docker compose up -d
|
||||
|
||||
# 运行迁移
|
||||
docker compose exec app php artisan migrate --force
|
||||
|
||||
# 清除缓存
|
||||
docker compose exec app php artisan cache:clear
|
||||
docker compose exec app php artisan config:cache
|
||||
```
|
||||
|
||||
#### 系统更新
|
||||
|
||||
```bash
|
||||
# 更新系统包
|
||||
sudo dnf update -y
|
||||
|
||||
# 更新Docker
|
||||
sudo dnf update docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# 重启Docker服务
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
### 监控和告警
|
||||
|
||||
#### 系统监控
|
||||
|
||||
```bash
|
||||
# 查看系统资源使用
|
||||
htop
|
||||
df -h
|
||||
free -h
|
||||
|
||||
# 查看Docker资源使用
|
||||
docker stats
|
||||
|
||||
# 查看容器资源使用
|
||||
docker compose exec app ps aux
|
||||
```
|
||||
|
||||
#### 日志监控
|
||||
|
||||
```bash
|
||||
# 监控错误日志
|
||||
tail -f storage/logs/laravel.log | grep ERROR
|
||||
|
||||
# 监控访问日志
|
||||
docker compose logs -f app | grep "GET\|POST"
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. 容器启动失败
|
||||
|
||||
**症状**: 容器无法启动或立即退出
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 查看容器日志
|
||||
docker compose logs container_name
|
||||
|
||||
# 检查配置文件
|
||||
docker compose config
|
||||
|
||||
# 检查端口占用
|
||||
netstat -tlnp | grep :8000
|
||||
```
|
||||
|
||||
#### 2. 数据库连接失败
|
||||
|
||||
**症状**: 应用无法连接到数据库
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 检查数据库容器状态
|
||||
docker compose ps mysql
|
||||
|
||||
# 测试数据库连接
|
||||
docker compose exec mysql mysql -u root -p
|
||||
|
||||
# 检查网络连接
|
||||
docker compose exec app ping mysql
|
||||
```
|
||||
|
||||
#### 3. 权限问题
|
||||
|
||||
**症状**: 文件写入失败或权限错误
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 修复存储目录权限
|
||||
sudo chown -R 1000:1000 storage/
|
||||
chmod -R 775 storage/
|
||||
|
||||
# 检查SELinux状态
|
||||
getenforce
|
||||
sudo setsebool -P container_manage_cgroup on
|
||||
```
|
||||
|
||||
#### 4. 内存不足
|
||||
|
||||
**症状**: 容器被OOM Killer终止
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 检查内存使用
|
||||
free -h
|
||||
docker stats
|
||||
|
||||
# 调整容器内存限制
|
||||
# 编辑docker-compose.yml中的deploy.resources.limits.memory
|
||||
```
|
||||
|
||||
#### 5. 磁盘空间不足
|
||||
|
||||
**症状**: 容器无法写入文件
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 检查磁盘使用
|
||||
df -h
|
||||
|
||||
# 清理Docker资源
|
||||
docker system prune -a
|
||||
|
||||
# 清理日志文件
|
||||
sudo journalctl --vacuum-time=7d
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
|
||||
#### 1. 数据库优化
|
||||
|
||||
```bash
|
||||
# 调整MySQL配置
|
||||
# 编辑docker/mysql/my.cnf
|
||||
[mysqld]
|
||||
innodb_buffer_pool_size = 1G
|
||||
innodb_log_file_size = 256M
|
||||
max_connections = 200
|
||||
```
|
||||
|
||||
#### 2. Redis优化
|
||||
|
||||
```bash
|
||||
# 调整Redis配置
|
||||
# 编辑docker/redis/redis.conf
|
||||
maxmemory 512mb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
#### 3. PHP优化
|
||||
|
||||
```bash
|
||||
# 调整PHP配置
|
||||
# 编辑docker/php/php.ini
|
||||
memory_limit = 512M
|
||||
max_execution_time = 300
|
||||
upload_max_filesize = 100M
|
||||
```
|
||||
|
||||
#### 4. Swoole优化
|
||||
|
||||
```bash
|
||||
# 调整Swoole配置
|
||||
# 编辑.env文件
|
||||
OCTANE_WORKERS=4
|
||||
OCTANE_TASK_WORKERS=2
|
||||
OCTANE_MAX_REQUESTS=500
|
||||
```
|
||||
|
||||
## 安全配置
|
||||
|
||||
### 防火墙设置
|
||||
|
||||
```bash
|
||||
# 配置防火墙
|
||||
sudo firewall-cmd --permanent --add-port=80/tcp
|
||||
sudo firewall-cmd --permanent --add-port=443/tcp
|
||||
sudo firewall-cmd --reload
|
||||
|
||||
# 限制数据库端口访问
|
||||
sudo firewall-cmd --permanent --remove-port=3306/tcp
|
||||
sudo firewall-cmd --permanent --remove-port=6379/tcp
|
||||
sudo firewall-cmd --permanent --remove-port=7700/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
### SSL/TLS配置
|
||||
|
||||
```bash
|
||||
# 安装Certbot
|
||||
sudo dnf install -y certbot
|
||||
|
||||
# 获取SSL证书
|
||||
sudo certbot certonly --standalone -d your-domain.com
|
||||
|
||||
# 配置Nginx SSL
|
||||
# 编辑docker/nginx/default.conf添加SSL配置
|
||||
```
|
||||
|
||||
### 访问控制
|
||||
|
||||
```bash
|
||||
# 配置IP白名单
|
||||
# 在docker-compose.yml中添加网络限制
|
||||
|
||||
# 配置用户认证
|
||||
# 在应用中启用认证中间件
|
||||
```
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果遇到问题,请:
|
||||
|
||||
1. 查看日志文件获取详细错误信息
|
||||
2. 检查系统资源使用情况
|
||||
3. 参考故障排除章节
|
||||
4. 联系技术支持团队
|
||||
|
||||
---
|
||||
|
||||
**注意**: 本指南基于OpenEuler 20.03 LTS编写,其他版本可能需要适当调整。在生产环境部署前,请务必在测试环境中验证所有步骤。
|
||||
288
docker/ENVIRONMENT_SETUP.md
Normal file
288
docker/ENVIRONMENT_SETUP.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# 环境配置设置指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南介绍如何配置和设置知识库系统的Docker部署环境,包括生产环境和开发环境的配置。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 自动配置(推荐)
|
||||
|
||||
使用自动配置脚本快速设置环境:
|
||||
|
||||
```bash
|
||||
# 生产环境配置
|
||||
./docker/setup-env.sh -e production
|
||||
|
||||
# 开发环境配置
|
||||
./docker/setup-env.sh -e development
|
||||
|
||||
# 交互式配置
|
||||
./docker/setup-env.sh -i
|
||||
```
|
||||
|
||||
### 2. 手动配置
|
||||
|
||||
如果需要手动配置,请按照以下步骤:
|
||||
|
||||
#### 生产环境
|
||||
|
||||
1. 复制环境模板:
|
||||
```bash
|
||||
cp .env.production .env
|
||||
```
|
||||
|
||||
2. 编辑 `.env` 文件,修改以下关键配置:
|
||||
```bash
|
||||
APP_KEY=base64:your-generated-key-here
|
||||
DB_PASSWORD=your-secure-database-password
|
||||
MEILISEARCH_KEY=your-meilisearch-master-key
|
||||
APP_URL=http://your-domain.com
|
||||
```
|
||||
|
||||
3. 生成应用密钥:
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
#### 开发环境
|
||||
|
||||
1. 复制开发环境模板:
|
||||
```bash
|
||||
cp .env.development .env
|
||||
```
|
||||
|
||||
2. 编辑配置(开发环境可以使用默认值)
|
||||
|
||||
## 配置验证
|
||||
|
||||
### 验证环境变量
|
||||
|
||||
```bash
|
||||
# 验证当前环境配置
|
||||
./docker/validate-env.sh
|
||||
```
|
||||
|
||||
### 验证Docker配置
|
||||
|
||||
```bash
|
||||
# 验证生产环境配置
|
||||
docker-compose config
|
||||
|
||||
# 验证开发环境配置
|
||||
docker-compose -f docker-compose.dev.yml config
|
||||
```
|
||||
|
||||
## 启动服务
|
||||
|
||||
### 生产环境
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker-compose up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose ps
|
||||
|
||||
# 查看日志
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### 开发环境
|
||||
|
||||
```bash
|
||||
# 启动开发环境
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose -f docker-compose.dev.yml ps
|
||||
|
||||
# 查看日志
|
||||
docker-compose -f docker-compose.dev.yml logs -f
|
||||
```
|
||||
|
||||
## 网络测试
|
||||
|
||||
启动服务后,测试网络连接:
|
||||
|
||||
```bash
|
||||
# 测试容器间网络连接
|
||||
./docker/test-network.sh
|
||||
```
|
||||
|
||||
## 应用初始化
|
||||
|
||||
服务启动后,初始化Laravel应用:
|
||||
|
||||
```bash
|
||||
# 运行数据库迁移
|
||||
docker exec knowledge_base_app php artisan migrate
|
||||
|
||||
# 运行数据库种子
|
||||
docker exec knowledge_base_app php artisan db:seed
|
||||
|
||||
# 创建搜索索引
|
||||
docker exec knowledge_base_app php artisan scout:import "App\Models\Document"
|
||||
```
|
||||
|
||||
## 环境配置详解
|
||||
|
||||
### 网络配置
|
||||
|
||||
- **生产环境网络**: `knowledge_base_network` (172.20.0.0/16)
|
||||
- **开发环境网络**: `knowledge_base_dev_network` (172.21.0.0/16)
|
||||
|
||||
### 端口映射
|
||||
|
||||
#### 生产环境
|
||||
- Web应用: 80
|
||||
- MySQL: 3306
|
||||
- Redis: 6379
|
||||
- Meilisearch: 7700
|
||||
|
||||
#### 开发环境
|
||||
- Web应用: 8080
|
||||
- MySQL: 3307
|
||||
- Redis: 6380
|
||||
- Meilisearch: 7701
|
||||
- PHP-FPM调试: 9000
|
||||
|
||||
### 存储卷
|
||||
|
||||
#### 生产环境
|
||||
- 数据库数据: `./storage/mysql`
|
||||
- Redis数据: `./storage/redis`
|
||||
- 搜索数据: `./storage/meilisearch`
|
||||
- 应用存储: `./storage/app`
|
||||
- 日志文件: `./storage/logs`
|
||||
|
||||
#### 开发环境
|
||||
- 数据库数据: `./storage/dev/mysql`
|
||||
- Redis数据: `./storage/dev/redis`
|
||||
- 搜索数据: `./storage/dev/meilisearch`
|
||||
- 应用存储: `./storage/dev/app`
|
||||
- 日志文件: `./storage/dev/logs`
|
||||
|
||||
## 环境变量说明
|
||||
|
||||
### 必需配置
|
||||
|
||||
| 变量名 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `APP_KEY` | 应用加密密钥 | `base64:xxx...` |
|
||||
| `DB_PASSWORD` | 数据库密码 | `secure_password` |
|
||||
| `MEILISEARCH_KEY` | 搜索引擎密钥 | `master_key_xxx` |
|
||||
|
||||
### 可选配置
|
||||
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `APP_NAME` | 知识库系统 | 应用名称 |
|
||||
| `APP_URL` | http://localhost | 应用URL |
|
||||
| `DB_DATABASE` | knowledge_base | 数据库名 |
|
||||
| `DB_USERNAME` | knowledge_user | 数据库用户 |
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **容器启动失败**
|
||||
```bash
|
||||
# 查看容器日志
|
||||
docker-compose logs [service_name]
|
||||
|
||||
# 检查容器状态
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
2. **网络连接问题**
|
||||
```bash
|
||||
# 测试网络连接
|
||||
./docker/test-network.sh
|
||||
|
||||
# 检查网络配置
|
||||
docker network ls
|
||||
docker network inspect knowledge_base_network
|
||||
```
|
||||
|
||||
3. **环境变量问题**
|
||||
```bash
|
||||
# 验证环境变量
|
||||
./docker/validate-env.sh
|
||||
|
||||
# 查看容器环境变量
|
||||
docker exec knowledge_base_app env
|
||||
```
|
||||
|
||||
4. **权限问题**
|
||||
```bash
|
||||
# 修复存储目录权限
|
||||
chmod -R 775 storage
|
||||
chmod -R 775 bootstrap/cache
|
||||
```
|
||||
|
||||
### 重置环境
|
||||
|
||||
如果需要重置环境:
|
||||
|
||||
```bash
|
||||
# 停止所有服务
|
||||
docker-compose down
|
||||
|
||||
# 删除数据卷(注意:这会删除所有数据)
|
||||
docker-compose down -v
|
||||
|
||||
# 重新配置环境
|
||||
./docker/setup-env.sh -f -e production
|
||||
|
||||
# 重新启动服务
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
### 生产环境
|
||||
|
||||
1. **更改默认密码**:确保所有默认密码都已更改
|
||||
2. **使用强密钥**:使用复杂的APP_KEY和MEILISEARCH_KEY
|
||||
3. **限制网络访问**:配置防火墙规则
|
||||
4. **定期备份**:定期备份数据库和文件
|
||||
5. **监控日志**:监控应用和系统日志
|
||||
|
||||
### 开发环境
|
||||
|
||||
1. **隔离环境**:不要在生产环境使用开发配置
|
||||
2. **定期更新**:保持开发环境与生产环境同步
|
||||
3. **清理数据**:定期清理开发环境数据
|
||||
|
||||
## 维护操作
|
||||
|
||||
### 备份
|
||||
|
||||
```bash
|
||||
# 备份数据库
|
||||
docker exec knowledge_base_mysql mysqldump -u root -p knowledge_base > backup.sql
|
||||
|
||||
# 备份文件
|
||||
tar -czf storage_backup.tar.gz storage/
|
||||
```
|
||||
|
||||
### 更新
|
||||
|
||||
```bash
|
||||
# 更新镜像
|
||||
docker-compose pull
|
||||
|
||||
# 重启服务
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 监控
|
||||
|
||||
```bash
|
||||
# 查看资源使用情况
|
||||
docker stats
|
||||
|
||||
# 查看服务健康状态
|
||||
docker-compose ps
|
||||
```
|
||||
288
docker/ENVIRONMENT_VARIABLES.md
Normal file
288
docker/ENVIRONMENT_VARIABLES.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# 环境变量配置文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细说明了知识库系统Docker部署所需的环境变量配置,包括生产环境和开发环境的不同设置。
|
||||
|
||||
## 环境变量分类
|
||||
|
||||
### 应用基础配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `APP_NAME` | "知识库系统" | "知识库系统-开发" | 应用名称 |
|
||||
| `APP_ENV` | production | local | 应用环境 |
|
||||
| `APP_KEY` | 必须设置 | 必须设置 | 应用加密密钥 |
|
||||
| `APP_DEBUG` | false | true | 调试模式 |
|
||||
| `APP_URL` | http://your-domain.com | http://localhost:8000 | 应用URL |
|
||||
|
||||
### 数据库配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `DB_CONNECTION` | mysql | mysql | 数据库类型 |
|
||||
| `DB_HOST` | mysql | mysql | 数据库主机(容器名) |
|
||||
| `DB_PORT` | 3306 | 3306 | 数据库端口 |
|
||||
| `DB_DATABASE` | knowledge_base | knowledge_base_dev | 数据库名 |
|
||||
| `DB_USERNAME` | knowledge_user | dev_user | 数据库用户名 |
|
||||
| `DB_PASSWORD` | 必须设置 | dev_password | 数据库密码 |
|
||||
|
||||
### Redis配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `REDIS_CLIENT` | phpredis | phpredis | Redis客户端 |
|
||||
| `REDIS_HOST` | redis | redis | Redis主机(容器名) |
|
||||
| `REDIS_PORT` | 6379 | 6379 | Redis端口 |
|
||||
| `REDIS_PASSWORD` | 空 | 空 | Redis密码 |
|
||||
| `CACHE_STORE` | redis | redis | 缓存驱动 |
|
||||
| `SESSION_DRIVER` | redis | redis | 会话驱动 |
|
||||
| `QUEUE_CONNECTION` | redis | redis | 队列驱动 |
|
||||
|
||||
### Meilisearch配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `SCOUT_DRIVER` | meilisearch | meilisearch | 搜索驱动 |
|
||||
| `MEILISEARCH_HOST` | http://meilisearch:7700 | http://meilisearch:7700 | 搜索引擎地址 |
|
||||
| `MEILISEARCH_KEY` | 必须设置 | dev-master-key | 搜索引擎密钥 |
|
||||
|
||||
### 日志配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `LOG_CHANNEL` | stack | stack | 日志通道 |
|
||||
| `LOG_LEVEL` | info | debug | 日志级别 |
|
||||
|
||||
### 邮件配置
|
||||
|
||||
| 变量名 | 生产环境默认值 | 开发环境默认值 | 说明 |
|
||||
|--------|----------------|----------------|------|
|
||||
| `MAIL_MAILER` | smtp | log | 邮件驱动 |
|
||||
| `MAIL_HOST` | 必须设置 | - | SMTP主机 |
|
||||
| `MAIL_PORT` | 587 | - | SMTP端口 |
|
||||
| `MAIL_USERNAME` | 必须设置 | - | SMTP用户名 |
|
||||
| `MAIL_PASSWORD` | 必须设置 | - | SMTP密码 |
|
||||
| `MAIL_ENCRYPTION` | tls | - | 加密方式 |
|
||||
|
||||
## 环境文件配置
|
||||
|
||||
### 生产环境 (.env.production)
|
||||
|
||||
```bash
|
||||
# 应用配置
|
||||
APP_NAME="知识库系统"
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:your-app-key-here-change-this-in-production
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://your-domain.com
|
||||
|
||||
# 数据库配置
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=knowledge_base
|
||||
DB_USERNAME=knowledge_user
|
||||
DB_PASSWORD=secure_password_change_this_in_production
|
||||
|
||||
# Redis配置
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# Meilisearch配置
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
MEILISEARCH_KEY=your-master-key-change-this-in-production
|
||||
|
||||
# 邮件配置
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=your-smtp-host.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=your-email@domain.com
|
||||
MAIL_PASSWORD=your-email-password-change-this
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="noreply@your-domain.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
### 开发环境 (.env.development)
|
||||
|
||||
```bash
|
||||
# 应用配置
|
||||
APP_NAME="知识库系统-开发"
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:your-dev-app-key-here
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
# 数据库配置
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=knowledge_base_dev
|
||||
DB_USERNAME=dev_user
|
||||
DB_PASSWORD=dev_password
|
||||
|
||||
# Redis配置
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# Meilisearch配置
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
MEILISEARCH_KEY=dev-master-key
|
||||
|
||||
# 邮件配置(开发环境使用日志)
|
||||
MAIL_MAILER=log
|
||||
MAIL_FROM_ADDRESS="dev@knowledge-base.local"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# 开发工具配置
|
||||
TELESCOPE_ENABLED=true
|
||||
DEBUGBAR_ENABLED=true
|
||||
XDEBUG_MODE=develop,debug
|
||||
XDEBUG_CONFIG=client_host=host.docker.internal client_port=9003
|
||||
```
|
||||
|
||||
## Docker Compose环境变量
|
||||
|
||||
### 生产环境 (docker-compose.yml)
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
# 从.env文件读取或使用默认值
|
||||
APP_NAME: ${APP_NAME:-知识库系统}
|
||||
APP_ENV: ${APP_ENV:-production}
|
||||
APP_KEY: ${APP_KEY}
|
||||
APP_DEBUG: ${APP_DEBUG:-false}
|
||||
APP_URL: ${APP_URL:-http://localhost}
|
||||
|
||||
# 数据库配置
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: mysql
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: ${DB_DATABASE:-knowledge_base}
|
||||
DB_USERNAME: ${DB_USERNAME:-knowledge_user}
|
||||
DB_PASSWORD: ${DB_PASSWORD:-secure_password}
|
||||
```
|
||||
|
||||
### 开发环境 (docker-compose.dev.yml)
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
# 开发环境特定配置
|
||||
APP_NAME: ${APP_NAME:-知识库系统-开发}
|
||||
APP_ENV: ${APP_ENV:-local}
|
||||
APP_DEBUG: ${APP_DEBUG:-true}
|
||||
LOG_LEVEL: debug
|
||||
|
||||
# 开发工具配置
|
||||
TELESCOPE_ENABLED: true
|
||||
DEBUGBAR_ENABLED: true
|
||||
```
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
### 必须更改的默认值
|
||||
|
||||
生产环境部署前必须更改以下默认值:
|
||||
|
||||
1. **APP_KEY**: 使用 `php artisan key:generate` 生成
|
||||
2. **DB_PASSWORD**: 设置强密码
|
||||
3. **MEILISEARCH_KEY**: 设置复杂的主密钥
|
||||
4. **MAIL_PASSWORD**: 设置邮件服务密码
|
||||
|
||||
### 敏感信息保护
|
||||
|
||||
1. **不要将敏感信息提交到版本控制**
|
||||
2. **使用Docker secrets管理敏感数据**
|
||||
3. **定期轮换密钥和密码**
|
||||
4. **限制环境变量的访问权限**
|
||||
|
||||
## 环境变量验证
|
||||
|
||||
### 启动前检查
|
||||
|
||||
创建验证脚本检查必要的环境变量:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# validate-env.sh
|
||||
|
||||
required_vars=(
|
||||
"APP_KEY"
|
||||
"DB_PASSWORD"
|
||||
"MEILISEARCH_KEY"
|
||||
)
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [ -z "${!var}" ]; then
|
||||
echo "错误: 环境变量 $var 未设置"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "环境变量验证通过"
|
||||
```
|
||||
|
||||
### 运行时检查
|
||||
|
||||
Laravel应用启动时会自动验证关键配置:
|
||||
|
||||
- 数据库连接
|
||||
- Redis连接
|
||||
- Meilisearch连接
|
||||
- 应用密钥格式
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **APP_KEY未设置**:
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
2. **数据库连接失败**:
|
||||
- 检查DB_HOST是否为容器名
|
||||
- 验证数据库密码
|
||||
- 确认MySQL容器已启动
|
||||
|
||||
3. **Redis连接失败**:
|
||||
- 检查REDIS_HOST是否为容器名
|
||||
- 验证Redis容器状态
|
||||
|
||||
4. **Meilisearch连接失败**:
|
||||
- 检查MEILISEARCH_HOST格式
|
||||
- 验证MEILISEARCH_KEY
|
||||
|
||||
### 调试命令
|
||||
|
||||
```bash
|
||||
# 查看容器环境变量
|
||||
docker exec knowledge_base_app env
|
||||
|
||||
# 测试数据库连接
|
||||
docker exec knowledge_base_app php artisan tinker
|
||||
>>> DB::connection()->getPdo();
|
||||
|
||||
# 测试Redis连接
|
||||
docker exec knowledge_base_app php artisan tinker
|
||||
>>> Redis::ping();
|
||||
|
||||
# 测试Meilisearch连接
|
||||
docker exec knowledge_base_app php artisan scout:status
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用环境特定的配置文件**
|
||||
2. **为不同环境设置不同的密钥**
|
||||
3. **定期备份环境配置**
|
||||
4. **使用配置管理工具**
|
||||
5. **监控配置变更**
|
||||
6. **文档化所有环境变量**
|
||||
309
docker/HEALTH_CHECK_IMPLEMENTATION.md
Normal file
309
docker/HEALTH_CHECK_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# 健康检查和自动重启机制实现总结
|
||||
|
||||
## 概述
|
||||
|
||||
本文档总结了Laravel知识库系统Docker部署中健康检查和自动重启机制的完整实现。
|
||||
|
||||
## 实现的功能
|
||||
|
||||
### 1. Web应用HTTP健康检查 ✅
|
||||
|
||||
**实现位置**: `routes/web.php`
|
||||
**端点**: `GET /health`
|
||||
**检查项目**:
|
||||
- 数据库连接状态
|
||||
- Redis缓存连接状态
|
||||
- Meilisearch搜索引擎连接状态
|
||||
- 存储目录可写性
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"status": "ok|degraded",
|
||||
"timestamp": "2024-12-24T10:30:00.000000Z",
|
||||
"services": {
|
||||
"database": "connected|disconnected",
|
||||
"redis": "connected|disconnected|not_configured",
|
||||
"meilisearch": "connected|disconnected|not_configured",
|
||||
"storage": "writable|not_writable"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据库连接健康检查 ✅
|
||||
|
||||
**实现方式**: MySQL内置的 `mysqladmin ping` 命令
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### 3. Redis连接健康检查 ✅
|
||||
|
||||
**实现方式**: Redis内置的 `redis-cli ping` 命令
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
```
|
||||
|
||||
### 4. Meilisearch API健康检查 ✅
|
||||
|
||||
**实现方式**: 调用Meilisearch的 `/health` API端点
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### 5. 队列处理器健康检查 ✅
|
||||
|
||||
**实现位置**: `docker/queue-health-check.sh`
|
||||
**检查内容**:
|
||||
- 队列进程是否运行 (`pgrep -f "queue:work"`)
|
||||
- Laravel应用数据库连接
|
||||
- Laravel应用Redis连接
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "/usr/local/bin/queue-health-check.sh"]
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### 6. 容器自动重启策略 ✅
|
||||
|
||||
**配置**: 所有服务都使用 `restart: unless-stopped` 策略
|
||||
**行为**:
|
||||
- 容器异常退出时自动重启
|
||||
- 手动停止的容器不会自动重启
|
||||
- 系统重启后自动启动容器(除非手动停止)
|
||||
|
||||
**应用的服务**:
|
||||
- MySQL数据库
|
||||
- Redis缓存
|
||||
- Meilisearch搜索引擎
|
||||
- Laravel应用容器
|
||||
- 队列处理容器
|
||||
|
||||
## 支持脚本
|
||||
|
||||
### 1. 服务状态检查脚本 ✅
|
||||
|
||||
**文件**: `docker/check-services.sh`
|
||||
**功能**:
|
||||
- 检查Docker服务状态
|
||||
- 检查所有容器的健康状态
|
||||
- 测试服务连接
|
||||
- 验证数据持久化配置
|
||||
- 生成详细的健康报告
|
||||
|
||||
### 2. 持续监控脚本 ✅
|
||||
|
||||
**文件**: `docker/monitor-services.sh`
|
||||
**功能**:
|
||||
- 持续监控所有服务健康状态
|
||||
- 自动重启不健康的容器
|
||||
- 限制重启次数防止无限重启
|
||||
- 记录详细监控日志
|
||||
- 支持告警通知扩展
|
||||
|
||||
**配置参数**:
|
||||
- `MONITOR_INTERVAL`: 监控间隔(默认60秒)
|
||||
- `MAX_RESTART_ATTEMPTS`: 最大重启尝试次数(默认3次)
|
||||
- `RESTART_COOLDOWN`: 重启冷却时间(默认300秒)
|
||||
- `LOG_FILE`: 日志文件路径
|
||||
|
||||
### 3. 启动和监控脚本 ✅
|
||||
|
||||
**文件**: `docker/start-with-monitoring.sh`
|
||||
**功能**:
|
||||
- 完整的服务启动流程
|
||||
- 环境检查和目录创建
|
||||
- 服务就绪等待
|
||||
- 自动启动监控进程
|
||||
- 支持多种启动选项
|
||||
|
||||
**选项**:
|
||||
- `--no-monitor`: 不启动监控进程
|
||||
- `--skip-build`: 跳过镜像构建
|
||||
- `--skip-wait`: 跳过服务就绪等待
|
||||
|
||||
### 4. 停止监控脚本 ✅
|
||||
|
||||
**文件**: `docker/stop-monitoring.sh`
|
||||
**功能**:
|
||||
- 停止监控进程
|
||||
- 可选择性停止Docker服务
|
||||
- 清理监控日志文件
|
||||
- 支持多种停止选项
|
||||
|
||||
**选项**:
|
||||
- `--stop-services`: 同时停止Docker服务
|
||||
- `--cleanup-logs`: 清理监控日志文件
|
||||
- `--all`: 停止监控、服务并清理日志
|
||||
|
||||
### 5. 健康检查测试脚本 ✅
|
||||
|
||||
**文件**: `docker/test-health-checks.sh`
|
||||
**功能**:
|
||||
- 验证所有脚本语法正确性
|
||||
- 检查脚本执行权限
|
||||
- 验证Docker配置文件
|
||||
- 测试健康检查配置
|
||||
- 检查存储目录结构
|
||||
|
||||
## 配置文件
|
||||
|
||||
### 1. Swoole配置 ✅
|
||||
|
||||
**文件**: `docker/supervisor/supervisord.conf`
|
||||
**健康检查相关**:
|
||||
- Swoole HTTP服务器健康检查端点
|
||||
- 自动重启配置
|
||||
- 日志管理
|
||||
|
||||
### 2. PHP配置 ✅
|
||||
|
||||
**文件**: `docker/php/php.ini`
|
||||
**健康检查相关**:
|
||||
- 适当的超时设置
|
||||
- 内存限制配置
|
||||
|
||||
### 3. Redis配置 ✅
|
||||
|
||||
**文件**: `docker/redis/redis.conf`
|
||||
**健康检查相关**:
|
||||
- 网络绑定配置
|
||||
- 内存和持久化设置
|
||||
- 性能优化配置
|
||||
|
||||
### 4. MySQL配置 ✅
|
||||
|
||||
**文件**: `docker/mysql/my.cnf`
|
||||
**健康检查相关**:
|
||||
- 字符集和时区配置
|
||||
- 性能优化设置
|
||||
- 日志配置
|
||||
|
||||
### 5. Supervisor配置 ✅
|
||||
|
||||
**文件**: `docker/supervisor/supervisord.conf`
|
||||
**健康检查相关**:
|
||||
- Swoole和队列进程管理
|
||||
- 自动重启配置
|
||||
- 日志管理
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 启动服务和监控
|
||||
|
||||
```bash
|
||||
# 完整启动(包含监控)
|
||||
./docker/start-with-monitoring.sh
|
||||
|
||||
# 启动服务但不启动监控
|
||||
./docker/start-with-monitoring.sh --no-monitor
|
||||
|
||||
# 跳过镜像构建
|
||||
./docker/start-with-monitoring.sh --skip-build
|
||||
```
|
||||
|
||||
### 检查服务状态
|
||||
|
||||
```bash
|
||||
# 运行完整的健康检查
|
||||
./docker/check-services.sh
|
||||
|
||||
# 查看容器状态
|
||||
docker-compose ps
|
||||
|
||||
# 查看容器健康状态
|
||||
docker inspect --format='{{.State.Health.Status}}' knowledge_base_app
|
||||
```
|
||||
|
||||
### 监控管理
|
||||
|
||||
```bash
|
||||
# 查看监控日志
|
||||
tail -f ./storage/logs/monitor.log
|
||||
|
||||
# 停止监控进程
|
||||
./docker/stop-monitoring.sh
|
||||
|
||||
# 停止监控和服务
|
||||
./docker/stop-monitoring.sh --stop-services
|
||||
```
|
||||
|
||||
### 测试健康检查功能
|
||||
|
||||
```bash
|
||||
# 运行健康检查功能测试
|
||||
./docker/test-health-checks.sh
|
||||
```
|
||||
|
||||
## 验证结果
|
||||
|
||||
通过运行 `./docker/test-health-checks.sh`,所有测试项目都通过:
|
||||
|
||||
- ✅ 脚本语法测试
|
||||
- ✅ 脚本权限测试
|
||||
- ✅ Docker配置测试
|
||||
- ✅ 健康检查配置测试
|
||||
- ✅ 存储目录测试
|
||||
|
||||
## 监控指标
|
||||
|
||||
### 健康检查状态
|
||||
|
||||
- `healthy`: 服务正常运行
|
||||
- `unhealthy`: 服务健康检查失败
|
||||
- `starting`: 服务正在启动
|
||||
- `no-healthcheck`: 服务未配置健康检查
|
||||
|
||||
### 重启计数器
|
||||
|
||||
监控系统维护每个容器的重启计数器:
|
||||
- 位置: `./storage/logs/restart_counters/`
|
||||
- 格式: `{container_name}.count`
|
||||
- 重置: 容器健康时自动重置
|
||||
|
||||
## 总结
|
||||
|
||||
健康检查和自动重启机制已完全实现,包括:
|
||||
|
||||
1. **Web应用HTTP健康检查** - 完整的Laravel健康检查端点
|
||||
2. **数据库连接健康检查** - MySQL ping检查
|
||||
3. **Redis连接健康检查** - Redis ping检查
|
||||
4. **Meilisearch API健康检查** - 搜索引擎健康API
|
||||
5. **容器自动重启策略** - unless-stopped策略
|
||||
6. **完整的监控和管理脚本** - 自动化运维工具
|
||||
|
||||
所有功能都经过测试验证,可以确保系统的高可用性和自动故障恢复能力。
|
||||
364
docker/HEALTH_MONITORING.md
Normal file
364
docker/HEALTH_MONITORING.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Docker健康检查和监控指南
|
||||
|
||||
本文档描述了Laravel知识库系统的Docker健康检查和自动重启机制的配置和使用方法。
|
||||
|
||||
## 概述
|
||||
|
||||
系统实现了完整的健康检查和自动重启机制,包括:
|
||||
|
||||
- **Web应用HTTP健康检查** - 检查应用程序和依赖服务状态
|
||||
- **数据库连接健康检查** - 验证MySQL数据库连接
|
||||
- **Redis连接健康检查** - 验证Redis缓存服务连接
|
||||
- **Meilisearch API健康检查** - 验证搜索引擎服务状态
|
||||
- **容器自动重启策略** - 在服务失败时自动恢复
|
||||
- **持续监控系统** - 主动监控和故障处理
|
||||
|
||||
## 健康检查配置
|
||||
|
||||
### 1. Web应用健康检查
|
||||
|
||||
**端点**: `GET /health`
|
||||
|
||||
**检查项目**:
|
||||
- 数据库连接状态
|
||||
- Redis缓存连接状态
|
||||
- Meilisearch搜索引擎连接状态
|
||||
- 存储目录可写性
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"status": "ok|degraded",
|
||||
"timestamp": "2024-12-24T10:30:00.000000Z",
|
||||
"services": {
|
||||
"database": "connected|disconnected",
|
||||
"redis": "connected|disconnected|not_configured",
|
||||
"meilisearch": "connected|disconnected|not_configured",
|
||||
"storage": "writable|not_writable"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
```
|
||||
|
||||
### 2. MySQL数据库健康检查
|
||||
|
||||
**检查方法**: `mysqladmin ping`
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### 3. Redis缓存健康检查
|
||||
|
||||
**检查方法**: `redis-cli ping`
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
```
|
||||
|
||||
### 4. Meilisearch搜索引擎健康检查
|
||||
|
||||
**检查方法**: `curl -f http://localhost:7700/health`
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
### 5. 队列处理器健康检查
|
||||
|
||||
**检查方法**: 自定义脚本检查队列进程和依赖服务
|
||||
|
||||
**脚本位置**: `/usr/local/bin/queue-health-check.sh`
|
||||
|
||||
**Docker配置**:
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "/usr/local/bin/queue-health-check.sh"]
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
```
|
||||
|
||||
## 自动重启策略
|
||||
|
||||
所有服务都配置了 `restart: unless-stopped` 策略:
|
||||
|
||||
- **自动重启**: 容器异常退出时自动重启
|
||||
- **手动停止**: 手动停止的容器不会自动重启
|
||||
- **系统重启**: 系统重启后自动启动容器(除非手动停止)
|
||||
|
||||
## 监控系统
|
||||
|
||||
### 持续监控脚本
|
||||
|
||||
**脚本**: `docker/monitor-services.sh`
|
||||
|
||||
**功能**:
|
||||
- 持续监控所有服务的健康状态
|
||||
- 自动重启不健康的容器
|
||||
- 限制重启次数防止无限重启
|
||||
- 记录详细的监控日志
|
||||
- 发送告警通知
|
||||
|
||||
**配置参数**:
|
||||
- `MONITOR_INTERVAL`: 监控间隔(默认60秒)
|
||||
- `MAX_RESTART_ATTEMPTS`: 最大重启尝试次数(默认3次)
|
||||
- `RESTART_COOLDOWN`: 重启冷却时间(默认300秒)
|
||||
- `LOG_FILE`: 日志文件路径
|
||||
|
||||
### 服务状态检查脚本
|
||||
|
||||
**脚本**: `docker/check-services.sh`
|
||||
|
||||
**功能**:
|
||||
- 一次性检查所有服务状态
|
||||
- 详细的健康状态报告
|
||||
- 连接测试和故障诊断
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启动服务和监控
|
||||
|
||||
```bash
|
||||
# 完整启动(包含监控)
|
||||
./docker/start-with-monitoring.sh
|
||||
|
||||
# 启动服务但不启动监控
|
||||
./docker/start-with-monitoring.sh --no-monitor
|
||||
|
||||
# 跳过镜像构建
|
||||
./docker/start-with-monitoring.sh --skip-build
|
||||
|
||||
# 跳过服务就绪等待
|
||||
./docker/start-with-monitoring.sh --skip-wait
|
||||
```
|
||||
|
||||
### 2. 检查服务状态
|
||||
|
||||
```bash
|
||||
# 运行完整的健康检查
|
||||
./docker/check-services.sh
|
||||
|
||||
# 查看容器状态
|
||||
docker-compose ps
|
||||
|
||||
# 查看容器健康状态
|
||||
docker inspect --format='{{.State.Health.Status}}' knowledge_base_app
|
||||
```
|
||||
|
||||
### 3. 查看监控日志
|
||||
|
||||
```bash
|
||||
# 查看监控日志
|
||||
tail -f ./storage/logs/monitor.log
|
||||
|
||||
# 查看监控输出
|
||||
tail -f ./storage/logs/monitor-output.log
|
||||
|
||||
# 查看容器日志
|
||||
docker-compose logs -f app
|
||||
docker-compose logs -f queue
|
||||
```
|
||||
|
||||
### 4. 停止监控
|
||||
|
||||
```bash
|
||||
# 只停止监控进程
|
||||
./docker/stop-monitoring.sh
|
||||
|
||||
# 停止监控和服务
|
||||
./docker/stop-monitoring.sh --stop-services
|
||||
|
||||
# 停止监控、服务并清理日志
|
||||
./docker/stop-monitoring.sh --all
|
||||
```
|
||||
|
||||
### 5. 手动重启服务
|
||||
|
||||
```bash
|
||||
# 重启单个服务
|
||||
docker-compose restart app
|
||||
|
||||
# 重启所有服务
|
||||
docker-compose restart
|
||||
|
||||
# 重新构建并启动
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
## 监控指标
|
||||
|
||||
### 健康检查状态
|
||||
|
||||
- `healthy`: 服务正常运行
|
||||
- `unhealthy`: 服务健康检查失败
|
||||
- `starting`: 服务正在启动
|
||||
- `no-healthcheck`: 服务未配置健康检查
|
||||
|
||||
### 监控日志格式
|
||||
|
||||
```
|
||||
2024-12-24 10:30:00 [INFO] 开始监控检查 (共5个服务)
|
||||
2024-12-24 10:30:01 [SUCCESS] MySQL数据库容器健康状态正常
|
||||
2024-12-24 10:30:02 [SUCCESS] Redis缓存容器健康状态正常
|
||||
2024-12-24 10:30:03 [SUCCESS] Meilisearch搜索容器健康状态正常
|
||||
2024-12-24 10:30:04 [SUCCESS] Web应用容器健康状态正常
|
||||
2024-12-24 10:30:05 [SUCCESS] 队列处理器容器健康状态正常
|
||||
2024-12-24 10:30:06 [SUCCESS] 所有服务运行正常
|
||||
```
|
||||
|
||||
### 重启计数器
|
||||
|
||||
监控系统维护每个容器的重启计数器:
|
||||
|
||||
- 位置: `./storage/logs/restart_counters/`
|
||||
- 格式: `{container_name}.count`
|
||||
- 重置: 容器健康时自动重置
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **健康检查失败**
|
||||
```bash
|
||||
# 检查容器日志
|
||||
docker-compose logs app
|
||||
|
||||
# 手动测试健康检查端点
|
||||
curl -v http://localhost/health
|
||||
```
|
||||
|
||||
2. **监控进程无法启动**
|
||||
```bash
|
||||
# 检查权限
|
||||
ls -la docker/monitor-services.sh
|
||||
|
||||
# 手动运行监控脚本
|
||||
./docker/monitor-services.sh
|
||||
```
|
||||
|
||||
3. **容器重启循环**
|
||||
```bash
|
||||
# 查看重启计数器
|
||||
cat ./storage/logs/restart_counters/knowledge_base_app.count
|
||||
|
||||
# 重置重启计数器
|
||||
echo "0" > ./storage/logs/restart_counters/knowledge_base_app.count
|
||||
```
|
||||
|
||||
4. **存储权限问题**
|
||||
```bash
|
||||
# 修复存储目录权限
|
||||
sudo chown -R $(id -u):$(id -g) ./storage
|
||||
chmod -R 755 ./storage
|
||||
```
|
||||
|
||||
### 调试模式
|
||||
|
||||
启用详细日志记录:
|
||||
|
||||
```bash
|
||||
# 设置环境变量
|
||||
export LOG_LEVEL=debug
|
||||
|
||||
# 运行监控脚本
|
||||
./docker/monitor-services.sh --interval 30
|
||||
```
|
||||
|
||||
## 生产环境建议
|
||||
|
||||
1. **监控配置**
|
||||
- 设置适当的监控间隔(建议60-120秒)
|
||||
- 配置告警通知(邮件、Slack等)
|
||||
- 定期检查监控日志
|
||||
|
||||
2. **资源限制**
|
||||
- 为容器设置内存和CPU限制
|
||||
- 监控系统资源使用情况
|
||||
- 配置日志轮转
|
||||
|
||||
3. **备份策略**
|
||||
- 定期备份数据库和搜索索引
|
||||
- 备份应用配置和上传文件
|
||||
- 测试恢复流程
|
||||
|
||||
4. **安全考虑**
|
||||
- 限制健康检查端点的访问
|
||||
- 使用强密码和密钥
|
||||
- 定期更新容器镜像
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 自定义告警
|
||||
|
||||
在 `monitor-services.sh` 中的 `send_alert` 函数中添加自定义告警逻辑:
|
||||
|
||||
```bash
|
||||
send_alert() {
|
||||
local message=$1
|
||||
local severity=$2
|
||||
|
||||
# 发送邮件告警
|
||||
echo "$message" | mail -s "Docker监控告警" admin@example.com
|
||||
|
||||
# 发送到Slack
|
||||
curl -X POST -H 'Content-type: application/json' \
|
||||
--data "{\"text\":\"$message\"}" \
|
||||
"$SLACK_WEBHOOK_URL"
|
||||
}
|
||||
```
|
||||
|
||||
### 集成外部监控
|
||||
|
||||
可以将健康检查数据发送到外部监控系统:
|
||||
|
||||
- Prometheus + Grafana
|
||||
- Zabbix
|
||||
- Nagios
|
||||
- DataDog
|
||||
|
||||
### 自动扩缩容
|
||||
|
||||
基于健康检查结果实现自动扩缩容:
|
||||
|
||||
```bash
|
||||
# 检查负载并调整副本数
|
||||
if [ $cpu_usage -gt 80 ]; then
|
||||
docker-compose up -d --scale app=3
|
||||
fi
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
本系统提供了完整的健康检查和自动重启机制,确保服务的高可用性。通过合理配置和使用这些工具,可以大大提高系统的稳定性和可靠性。
|
||||
|
||||
定期检查监控日志,及时处理告警,并根据实际情况调整配置参数,是维护系统健康运行的关键。
|
||||
179
docker/NETWORK_CONFIGURATION.md
Normal file
179
docker/NETWORK_CONFIGURATION.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Docker网络配置文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了知识库系统Docker部署的网络配置,包括生产环境和开发环境的网络设置。
|
||||
|
||||
## 网络架构
|
||||
|
||||
### 生产环境网络
|
||||
|
||||
- **网络名称**: `knowledge_base_network`
|
||||
- **网络类型**: bridge
|
||||
- **子网**: 172.20.0.0/16
|
||||
- **网关**: 172.20.0.1
|
||||
|
||||
#### 服务端口映射
|
||||
|
||||
| 服务 | 容器名称 | 内部端口 | 外部端口 | 协议 |
|
||||
|------|----------|----------|----------|------|
|
||||
| Web应用 | knowledge_base_app | 80 | 80 | HTTP |
|
||||
| MySQL | knowledge_base_mysql | 3306 | 3306 | MySQL |
|
||||
| Redis | knowledge_base_redis | 6379 | 6379 | Redis |
|
||||
| Meilisearch | knowledge_base_meilisearch | 7700 | 7700 | HTTP |
|
||||
|
||||
### 开发环境网络
|
||||
|
||||
- **网络名称**: `knowledge_base_dev_network`
|
||||
- **网络类型**: bridge
|
||||
- **子网**: 172.21.0.0/16
|
||||
- **网关**: 172.21.0.1
|
||||
|
||||
#### 开发环境端口映射
|
||||
|
||||
| 服务 | 容器名称 | 内部端口 | 外部端口 | 协议 | 说明 |
|
||||
|------|----------|----------|----------|------|------|
|
||||
| Web应用 | knowledge_base_app_dev | 80 | 8080 | HTTP | 避免与生产环境冲突 |
|
||||
| PHP-FPM调试 | knowledge_base_app_dev | 9000 | 9000 | TCP | Xdebug调试端口 |
|
||||
| MySQL | knowledge_base_mysql_dev | 3306 | 3307 | MySQL | 避免与本地MySQL冲突 |
|
||||
| Redis | knowledge_base_redis_dev | 6379 | 6380 | Redis | 避免与本地Redis冲突 |
|
||||
| Meilisearch | knowledge_base_meilisearch_dev | 7700 | 7701 | HTTP | 避免与生产环境冲突 |
|
||||
|
||||
## 服务间通信
|
||||
|
||||
### 内部服务发现
|
||||
|
||||
所有容器通过Docker内部DNS进行服务发现,使用容器名称作为主机名:
|
||||
|
||||
- **数据库连接**: `mysql:3306`
|
||||
- **Redis连接**: `redis:6379`
|
||||
- **Meilisearch连接**: `http://meilisearch:7700`
|
||||
|
||||
### 环境变量配置
|
||||
|
||||
#### 生产环境
|
||||
|
||||
```bash
|
||||
# 数据库连接
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
|
||||
# Redis连接
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Meilisearch连接
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
```
|
||||
|
||||
#### 开发环境
|
||||
|
||||
```bash
|
||||
# 数据库连接
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
|
||||
# Redis连接
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Meilisearch连接
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
```
|
||||
|
||||
注意:开发环境内部端口保持一致,只有外部映射端口不同。
|
||||
|
||||
## 网络安全
|
||||
|
||||
### 防火墙规则
|
||||
|
||||
生产环境建议配置防火墙规则:
|
||||
|
||||
```bash
|
||||
# 只允许必要的端口访问
|
||||
sudo ufw allow 80/tcp # HTTP
|
||||
sudo ufw allow 443/tcp # HTTPS (如果使用SSL)
|
||||
sudo ufw deny 3306/tcp # 禁止外部直接访问MySQL
|
||||
sudo ufw deny 6379/tcp # 禁止外部直接访问Redis
|
||||
sudo ufw deny 7700/tcp # 禁止外部直接访问Meilisearch
|
||||
```
|
||||
|
||||
### 容器间通信安全
|
||||
|
||||
- 所有服务运行在隔离的Docker网络中
|
||||
- 数据库、缓存和搜索服务不直接暴露给外部网络
|
||||
- 只有Web应用容器暴露HTTP端口
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 网络连接问题
|
||||
|
||||
1. **检查容器网络状态**:
|
||||
```bash
|
||||
docker network ls
|
||||
docker network inspect knowledge_base_network
|
||||
```
|
||||
|
||||
2. **测试容器间连通性**:
|
||||
```bash
|
||||
docker exec knowledge_base_app ping mysql
|
||||
docker exec knowledge_base_app ping redis
|
||||
docker exec knowledge_base_app ping meilisearch
|
||||
```
|
||||
|
||||
3. **检查端口监听状态**:
|
||||
```bash
|
||||
docker exec knowledge_base_mysql netstat -tlnp
|
||||
docker exec knowledge_base_redis netstat -tlnp
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **端口冲突**: 确保外部端口没有被其他服务占用
|
||||
2. **DNS解析失败**: 检查容器是否在同一网络中
|
||||
3. **防火墙阻断**: 检查宿主机防火墙设置
|
||||
|
||||
## 监控和日志
|
||||
|
||||
### 网络监控
|
||||
|
||||
```bash
|
||||
# 查看网络流量
|
||||
docker exec knowledge_base_app ss -tuln
|
||||
|
||||
# 监控连接状态
|
||||
docker exec knowledge_base_app netstat -an | grep ESTABLISHED
|
||||
```
|
||||
|
||||
### 连接日志
|
||||
|
||||
应用连接日志位置:
|
||||
- Laravel日志: `/var/www/html/storage/logs/laravel.log`
|
||||
- Swoole访问日志: `/var/log/supervisor/swoole_stdout.log`
|
||||
- Swoole错误日志: `/var/log/supervisor/swoole_stderr.log`
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 网络性能调优
|
||||
|
||||
1. **启用HTTP/2** (如果使用HTTPS)
|
||||
2. **配置连接池**:
|
||||
- MySQL连接池大小
|
||||
- Redis连接池配置
|
||||
3. **启用压缩**:
|
||||
- Swoole内置压缩支持
|
||||
- 静态资源压缩
|
||||
|
||||
### 资源限制
|
||||
|
||||
```yaml
|
||||
# 在docker-compose.yml中配置资源限制
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: '0.5'
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: '0.25'
|
||||
```
|
||||
232
docker/PACKAGING_README.md
Normal file
232
docker/PACKAGING_README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Docker镜像打包和部署工具
|
||||
|
||||
本目录包含用于Docker镜像打包和OpenEuler服务器部署的完整工具集。
|
||||
|
||||
## 脚本概览
|
||||
|
||||
### 核心脚本
|
||||
|
||||
1. **export-images.sh** - Docker镜像导出脚本
|
||||
2. **compress-and-verify.sh** - 镜像压缩和完整性检查脚本
|
||||
3. **import-and-verify.sh** - 镜像导入和验证脚本
|
||||
4. **deploy-to-openeuler.sh** - OpenEuler服务器部署脚本
|
||||
5. **one-click-deploy.sh** - 一键部署脚本
|
||||
|
||||
### 文档
|
||||
|
||||
- **DEPLOYMENT_GUIDE.md** - 详细部署指南
|
||||
- **PACKAGING_README.md** - 本文件
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 导出镜像
|
||||
|
||||
```bash
|
||||
# 基本导出
|
||||
./docker/export-images.sh
|
||||
|
||||
# 导出并压缩,验证完整性
|
||||
./docker/export-images.sh -c -v
|
||||
|
||||
# 导出到指定目录
|
||||
./docker/export-images.sh -o /path/to/export -c -v
|
||||
```
|
||||
|
||||
### 2. 压缩和验证
|
||||
|
||||
```bash
|
||||
# 压缩现有镜像文件
|
||||
./docker/compress-and-verify.sh
|
||||
|
||||
# 仅验证文件完整性
|
||||
./docker/compress-and-verify.sh --verify-only
|
||||
|
||||
# 解压缩文件
|
||||
./docker/compress-and-verify.sh --uncompress
|
||||
```
|
||||
|
||||
### 3. 导入镜像
|
||||
|
||||
```bash
|
||||
# 导入镜像文件
|
||||
./docker/import-and-verify.sh /path/to/images
|
||||
|
||||
# 强制导入并测试
|
||||
./docker/import-and-verify.sh -f --test-run
|
||||
|
||||
# 仅验证不导入
|
||||
./docker/import-and-verify.sh --verify-only
|
||||
```
|
||||
|
||||
### 4. 部署到OpenEuler
|
||||
|
||||
```bash
|
||||
# 全新部署
|
||||
sudo ./docker/deploy-to-openeuler.sh /path/to/images
|
||||
|
||||
# 更新现有部署
|
||||
sudo ./docker/deploy-to-openeuler.sh -u /path/to/images
|
||||
|
||||
# 备份并部署
|
||||
sudo ./docker/deploy-to-openeuler.sh -b /path/to/images
|
||||
```
|
||||
|
||||
### 5. 一键部署
|
||||
|
||||
```bash
|
||||
# 导出镜像
|
||||
./docker/one-click-deploy.sh export -c -v
|
||||
|
||||
# 部署到服务器
|
||||
./docker/one-click-deploy.sh deploy --server 192.168.1.100
|
||||
|
||||
# 完整流程
|
||||
./docker/one-click-deploy.sh full -c --server 192.168.1.100
|
||||
```
|
||||
|
||||
## 典型工作流程
|
||||
|
||||
### 开发环境 → 生产环境
|
||||
|
||||
1. **在开发环境导出镜像**
|
||||
```bash
|
||||
./docker/export-images.sh -c -v -o ./docker-images
|
||||
```
|
||||
|
||||
2. **传输到生产服务器**
|
||||
```bash
|
||||
scp -r docker-images/ user@server:/tmp/
|
||||
```
|
||||
|
||||
3. **在生产服务器部署**
|
||||
```bash
|
||||
sudo ./docker/deploy-to-openeuler.sh /tmp/docker-images
|
||||
```
|
||||
|
||||
### 离线部署流程
|
||||
|
||||
1. **准备镜像包**
|
||||
```bash
|
||||
./docker/export-images.sh -c -v
|
||||
./docker/compress-and-verify.sh -c 9
|
||||
```
|
||||
|
||||
2. **物理传输到目标环境**
|
||||
|
||||
3. **导入和部署**
|
||||
```bash
|
||||
./docker/import-and-verify.sh -f --test-run
|
||||
sudo ./docker/deploy-to-openeuler.sh --skip-images
|
||||
```
|
||||
|
||||
## 脚本选项说明
|
||||
|
||||
### export-images.sh 选项
|
||||
|
||||
- `-c, --compress`: 启用gzip压缩
|
||||
- `-v, --verify`: 导出后验证完整性
|
||||
- `-o, --output DIR`: 指定导出目录
|
||||
- `--custom-images`: 导出指定镜像列表
|
||||
- `--skip-build`: 跳过镜像构建
|
||||
|
||||
### compress-and-verify.sh 选项
|
||||
|
||||
- `-c, --compress-level N`: 压缩级别 (1-9)
|
||||
- `-k, --keep-original`: 保留原始文件
|
||||
- `-v, --verify-only`: 仅验证不压缩
|
||||
- `-u, --uncompress`: 解压缩文件
|
||||
- `--parallel N`: 并行处理数量
|
||||
|
||||
### import-and-verify.sh 选项
|
||||
|
||||
- `-v, --verify-only`: 仅验证不导入
|
||||
- `-f, --force`: 强制导入覆盖现有镜像
|
||||
- `-c, --check-manifest`: 检查清单文件
|
||||
- `--skip-compatibility`: 跳过兼容性检查
|
||||
- `--test-run`: 导入后运行测试
|
||||
|
||||
### deploy-to-openeuler.sh 选项
|
||||
|
||||
- `-d, --deploy-dir DIR`: 部署目录
|
||||
- `-b, --backup`: 部署前备份
|
||||
- `-u, --update`: 更新现有部署
|
||||
- `-r, --rollback`: 回滚到上一版本
|
||||
- `--skip-docker-install`: 跳过Docker安装
|
||||
- `--dry-run`: 仅显示操作不执行
|
||||
|
||||
## 生成的文件
|
||||
|
||||
### 导出过程生成
|
||||
|
||||
- `docker-images/` - 镜像文件目录
|
||||
- `images-manifest.txt` - 镜像清单文件
|
||||
- `import-images.sh` - 自动生成的导入脚本
|
||||
- `export.log` - 导出日志
|
||||
|
||||
### 部署过程生成
|
||||
|
||||
- `/opt/knowledge-base/` - 默认部署目录
|
||||
- `.env` - 环境配置文件
|
||||
- `storage/` - 持久化存储目录
|
||||
- `/var/log/knowledge-base-deploy.log` - 部署日志
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **权限错误**
|
||||
```bash
|
||||
sudo chown -R $USER:$USER docker-images/
|
||||
chmod +x docker/*.sh
|
||||
```
|
||||
|
||||
2. **Docker未运行**
|
||||
```bash
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
```
|
||||
|
||||
3. **磁盘空间不足**
|
||||
```bash
|
||||
docker system prune -a
|
||||
df -h
|
||||
```
|
||||
|
||||
4. **网络连接问题**
|
||||
```bash
|
||||
ping target-server
|
||||
ssh user@target-server
|
||||
```
|
||||
|
||||
### 日志查看
|
||||
|
||||
```bash
|
||||
# 查看导出日志
|
||||
tail -f docker-images/export.log
|
||||
|
||||
# 查看部署日志
|
||||
sudo tail -f /var/log/knowledge-base-deploy.log
|
||||
|
||||
# 查看Docker日志
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **始终验证镜像完整性**
|
||||
2. **在生产部署前进行测试**
|
||||
3. **定期备份重要数据**
|
||||
4. **监控系统资源使用**
|
||||
5. **保持脚本和文档更新**
|
||||
|
||||
## 支持的平台
|
||||
|
||||
- **源平台**: Linux/macOS (开发环境)
|
||||
- **目标平台**: OpenEuler 20.03 LTS+
|
||||
- **架构**: x86_64 (amd64)
|
||||
- **Docker**: 20.10+
|
||||
- **Docker Compose**: 2.0+
|
||||
|
||||
---
|
||||
|
||||
更多详细信息请参考 `DEPLOYMENT_GUIDE.md`。
|
||||
288
docker/README-production.md
Normal file
288
docker/README-production.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Laravel知识库系统 - 生产环境Docker部署指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南介绍如何使用Docker在生产环境中部署Laravel知识库系统。系统包含以下组件:
|
||||
|
||||
- **Web应用**: Laravel应用 + Swoole (端口8000)
|
||||
- **数据库**: MySQL 8.0 (端口3306)
|
||||
- **缓存**: Redis 7 (端口6379)
|
||||
- **搜索**: Meilisearch v1.5 (端口7700)
|
||||
- **队列**: Laravel队列处理器
|
||||
|
||||
## 系统要求
|
||||
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- 至少4GB可用内存
|
||||
- 至少10GB可用磁盘空间
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 环境配置
|
||||
|
||||
```bash
|
||||
# 复制环境配置文件
|
||||
cp .env.production .env
|
||||
|
||||
# 编辑环境配置(重要!)
|
||||
nano .env
|
||||
```
|
||||
|
||||
**必须修改的配置项:**
|
||||
- `APP_KEY`: 运行 `php artisan key:generate` 生成
|
||||
- `DB_PASSWORD`: 设置强密码
|
||||
- `MEILISEARCH_KEY`: 设置搜索引擎主密钥
|
||||
- `APP_URL`: 设置实际域名
|
||||
|
||||
### 2. 启动服务
|
||||
|
||||
```bash
|
||||
# 使用启动脚本(推荐)
|
||||
./docker/start-production.sh
|
||||
|
||||
# 或手动启动
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 3. 验证部署
|
||||
|
||||
```bash
|
||||
# 检查服务状态
|
||||
./docker/check-services.sh
|
||||
|
||||
# 访问应用
|
||||
curl http://localhost/health
|
||||
```
|
||||
|
||||
## 服务配置详情
|
||||
|
||||
### MySQL数据库
|
||||
- **镜像**: mysql:8.0
|
||||
- **端口**: 3306
|
||||
- **数据持久化**: `./storage/mysql`
|
||||
- **配置文件**: `./docker/mysql/my.cnf`
|
||||
- **字符集**: utf8mb4
|
||||
|
||||
### Redis缓存
|
||||
- **镜像**: redis:7-alpine
|
||||
- **端口**: 6379
|
||||
- **数据持久化**: `./storage/redis`
|
||||
- **配置文件**: `./docker/redis/redis.conf`
|
||||
- **内存限制**: 512MB
|
||||
|
||||
### Meilisearch搜索
|
||||
- **镜像**: getmeili/meilisearch:v1.5
|
||||
- **端口**: 7700
|
||||
- **数据持久化**: `./storage/meilisearch`
|
||||
- **内存限制**: 1GB
|
||||
|
||||
### Laravel应用
|
||||
- **基础镜像**: php:8.2-cli-alpine
|
||||
- **Web服务器**: Swoole (通过 Laravel Octane)
|
||||
- **端口**: 8000
|
||||
- **PHP扩展**: pdo_mysql, redis, gd, zip, intl等
|
||||
- **文档转换**: Pandoc
|
||||
|
||||
### 队列处理器
|
||||
- **功能**: 处理文档转换等后台任务
|
||||
- **命令**: `php artisan queue:work`
|
||||
- **重试次数**: 3次
|
||||
- **超时时间**: 90秒
|
||||
|
||||
## 数据持久化
|
||||
|
||||
所有重要数据都持久化到宿主机:
|
||||
|
||||
```
|
||||
storage/
|
||||
├── mysql/ # MySQL数据文件
|
||||
├── redis/ # Redis数据文件
|
||||
├── meilisearch/ # 搜索引擎数据
|
||||
├── app/ # 应用上传文件
|
||||
└── logs/ # 应用日志
|
||||
├── app/ # Web应用日志
|
||||
└── queue/ # 队列处理日志
|
||||
```
|
||||
|
||||
## 健康检查
|
||||
|
||||
系统包含完整的健康检查机制:
|
||||
|
||||
- **Web应用**: HTTP检查 `/health` 端点
|
||||
- **MySQL**: 数据库连接检查
|
||||
- **Redis**: Redis ping检查
|
||||
- **Meilisearch**: API健康检查
|
||||
|
||||
## 管理命令
|
||||
|
||||
### 启动和停止
|
||||
```bash
|
||||
# 启动所有服务
|
||||
./docker/start-production.sh
|
||||
|
||||
# 停止所有服务
|
||||
./docker/stop-production.sh
|
||||
|
||||
# 重启特定服务
|
||||
docker-compose restart app
|
||||
```
|
||||
|
||||
### 监控和调试
|
||||
```bash
|
||||
# 检查服务状态
|
||||
./docker/check-services.sh
|
||||
|
||||
# 查看日志
|
||||
docker-compose logs -f app
|
||||
docker-compose logs -f mysql
|
||||
docker-compose logs -f queue
|
||||
|
||||
# 进入容器
|
||||
docker-compose exec app bash
|
||||
docker-compose exec mysql mysql -u root -p
|
||||
```
|
||||
|
||||
### Laravel管理
|
||||
```bash
|
||||
# 运行Artisan命令
|
||||
docker-compose exec app php artisan migrate
|
||||
docker-compose exec app php artisan queue:work
|
||||
docker-compose exec app php artisan cache:clear
|
||||
|
||||
# 查看队列状态
|
||||
docker-compose exec app php artisan queue:monitor
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 资源限制
|
||||
- **应用容器**: 1GB内存限制
|
||||
- **队列容器**: 512MB内存限制
|
||||
- **Redis**: 512MB内存限制
|
||||
- **Meilisearch**: 1GB内存限制
|
||||
|
||||
### 缓存配置
|
||||
- **OPcache**: 已启用PHP操作码缓存
|
||||
- **Laravel缓存**: 使用Redis存储
|
||||
- **配置缓存**: 生产环境已启用
|
||||
|
||||
## 安全配置
|
||||
|
||||
### 网络安全
|
||||
- 使用专用Docker网络
|
||||
- 仅暴露必要端口
|
||||
- 容器间通信使用内部网络
|
||||
|
||||
### 数据安全
|
||||
- 数据库密码保护
|
||||
- Redis可选密码保护
|
||||
- Meilisearch主密钥保护
|
||||
|
||||
### 文件权限
|
||||
- 应用文件使用www-data用户
|
||||
- 存储目录适当权限设置
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **容器启动失败**
|
||||
```bash
|
||||
# 查看详细日志
|
||||
docker-compose logs [service_name]
|
||||
|
||||
# 检查配置
|
||||
docker-compose config
|
||||
```
|
||||
|
||||
2. **数据库连接失败**
|
||||
```bash
|
||||
# 检查MySQL状态
|
||||
docker-compose exec mysql mysqladmin ping
|
||||
|
||||
# 检查环境变量
|
||||
docker-compose exec app env | grep DB_
|
||||
```
|
||||
|
||||
3. **权限问题**
|
||||
```bash
|
||||
# 修复存储权限
|
||||
docker-compose exec app chown -R www-data:www-data /var/www/html/storage
|
||||
```
|
||||
|
||||
4. **内存不足**
|
||||
```bash
|
||||
# 检查资源使用
|
||||
docker stats
|
||||
|
||||
# 调整内存限制(docker-compose.yml)
|
||||
```
|
||||
|
||||
### 日志位置
|
||||
- **应用日志**: `storage/logs/app/`
|
||||
- **队列日志**: `storage/logs/queue/`
|
||||
- **MySQL日志**: 容器内 `/var/log/mysql/`
|
||||
- **Swoole日志**: 容器内 `/var/log/supervisor/`
|
||||
|
||||
## 备份和恢复
|
||||
|
||||
### 数据备份
|
||||
```bash
|
||||
# 备份MySQL数据
|
||||
docker-compose exec mysql mysqldump -u root -p knowledge_base > backup.sql
|
||||
|
||||
# 备份上传文件
|
||||
tar -czf storage-backup.tar.gz storage/app/
|
||||
|
||||
# 备份搜索数据
|
||||
tar -czf meilisearch-backup.tar.gz storage/meilisearch/
|
||||
```
|
||||
|
||||
### 数据恢复
|
||||
```bash
|
||||
# 恢复MySQL数据
|
||||
docker-compose exec -T mysql mysql -u root -p knowledge_base < backup.sql
|
||||
|
||||
# 恢复上传文件
|
||||
tar -xzf storage-backup.tar.gz
|
||||
```
|
||||
|
||||
## 更新和维护
|
||||
|
||||
### 应用更新
|
||||
```bash
|
||||
# 拉取最新代码
|
||||
git pull
|
||||
|
||||
# 重新构建镜像
|
||||
docker-compose build --no-cache app
|
||||
|
||||
# 重启服务
|
||||
docker-compose up -d
|
||||
|
||||
# 运行迁移
|
||||
docker-compose exec app php artisan migrate
|
||||
```
|
||||
|
||||
### 系统维护
|
||||
```bash
|
||||
# 清理日志
|
||||
docker-compose exec app php artisan log:clear
|
||||
|
||||
# 清理缓存
|
||||
docker-compose exec app php artisan cache:clear
|
||||
docker-compose exec app php artisan config:cache
|
||||
|
||||
# 优化数据库
|
||||
docker-compose exec mysql mysqlcheck -u root -p --optimize --all-databases
|
||||
```
|
||||
|
||||
## 支持
|
||||
|
||||
如遇到问题,请检查:
|
||||
1. Docker和Docker Compose版本
|
||||
2. 系统资源使用情况
|
||||
3. 环境变量配置
|
||||
4. 网络连接状态
|
||||
5. 日志文件内容
|
||||
131
docker/README.md
Normal file
131
docker/README.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Docker镜像构建说明
|
||||
|
||||
## 概述
|
||||
|
||||
本目录包含了Laravel知识库系统的Docker化配置文件,支持构建适用于OpenEuler服务器的amd64架构镜像。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
docker/
|
||||
├── build.sh # 镜像构建脚本
|
||||
├── php/
|
||||
│ └── php.ini # PHP配置
|
||||
├── supervisor/
|
||||
│ └── supervisord.conf # Supervisor配置
|
||||
├── mysql/
|
||||
│ └── my.cnf # MySQL配置
|
||||
├── redis/
|
||||
│ └── redis.conf # Redis配置
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 镜像特性
|
||||
|
||||
- **基础环境**: PHP 8.2-cli + Alpine Linux
|
||||
- **Web服务器**: Swoole (通过 Laravel Octane)
|
||||
- **架构**: linux/amd64 (OpenEuler兼容)
|
||||
- **文档转换**: Pandoc
|
||||
- **进程管理**: Supervisor
|
||||
- **优化**: 多阶段构建,最小化镜像大小
|
||||
|
||||
## 构建镜像
|
||||
|
||||
### 方法1: 使用构建脚本(推荐)
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
./docker/build.sh
|
||||
```
|
||||
|
||||
### 方法2: 手动构建
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
docker build --platform linux/amd64 -t knowledge-base-app:latest .
|
||||
```
|
||||
|
||||
## 运行容器
|
||||
|
||||
### 单独运行(测试用)
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name knowledge-base \
|
||||
-p 8000:8000 \
|
||||
-e APP_ENV=production \
|
||||
-e APP_KEY=your-app-key \
|
||||
knowledge-base-app:latest
|
||||
```
|
||||
|
||||
### 使用docker-compose(推荐)
|
||||
|
||||
请参考项目根目录的docker-compose.yml文件。
|
||||
|
||||
## 环境变量
|
||||
|
||||
主要环境变量配置:
|
||||
|
||||
- `APP_ENV`: 应用环境 (production/local)
|
||||
- `APP_KEY`: Laravel应用密钥
|
||||
- `DB_HOST`: 数据库主机
|
||||
- `DB_DATABASE`: 数据库名称
|
||||
- `DB_USERNAME`: 数据库用户名
|
||||
- `DB_PASSWORD`: 数据库密码
|
||||
- `REDIS_HOST`: Redis主机
|
||||
- `MEILISEARCH_HOST`: Meilisearch主机
|
||||
|
||||
## 健康检查
|
||||
|
||||
镜像内置健康检查端点:
|
||||
|
||||
- HTTP检查: `http://localhost/health`
|
||||
- PHP-FPM检查: `http://localhost/ping`
|
||||
|
||||
## 日志
|
||||
|
||||
日志文件位置:
|
||||
|
||||
- Swoole访问日志: `/var/log/supervisor/swoole_stdout.log`
|
||||
- Swoole错误日志: `/var/log/supervisor/swoole_stderr.log`
|
||||
- PHP错误日志: `/var/log/php_errors.log`
|
||||
- Supervisor日志: `/var/log/supervisor/`
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 构建失败
|
||||
|
||||
1. 检查Docker是否运行
|
||||
2. 确保网络连接正常(需要下载依赖)
|
||||
3. 检查磁盘空间是否充足
|
||||
|
||||
### 容器启动失败
|
||||
|
||||
1. 检查环境变量配置
|
||||
2. 查看容器日志: `docker logs <container-name>`
|
||||
3. 检查端口是否被占用
|
||||
|
||||
### 权限问题
|
||||
|
||||
确保storage和bootstrap/cache目录有正确的写权限。
|
||||
|
||||
## 镜像导出和导入
|
||||
|
||||
### 导出镜像
|
||||
|
||||
```bash
|
||||
docker save knowledge-base-app:latest | gzip > knowledge-base-app.tar.gz
|
||||
```
|
||||
|
||||
### 导入镜像
|
||||
|
||||
```bash
|
||||
gunzip -c knowledge-base-app.tar.gz | docker load
|
||||
```
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. 生产环境请使用HTTPS
|
||||
2. 定期更新基础镜像
|
||||
3. 使用非root用户运行应用
|
||||
4. 配置适当的防火墙规则
|
||||
135
docker/STORAGE_CONFIGURATION.md
Normal file
135
docker/STORAGE_CONFIGURATION.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# 数据持久化和目录映射配置说明
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了Docker部署中的数据持久化和目录映射配置,确保容器重启后数据不丢失。
|
||||
|
||||
## 目录映射配置
|
||||
|
||||
### 1. 项目代码目录映射
|
||||
```yaml
|
||||
volumes:
|
||||
- ./:/var/www/html
|
||||
```
|
||||
- **用途**: 将项目根目录映射到容器内的Web根目录
|
||||
- **好处**: 支持开发环境的代码热重载
|
||||
- **注意**: 生产环境建议使用镜像内置代码
|
||||
|
||||
### 2. 应用存储目录持久化
|
||||
```yaml
|
||||
volumes:
|
||||
- storage_data:/var/www/html/storage
|
||||
- documents_data:/var/www/html/storage/app/private/documents
|
||||
- public_data:/var/www/html/storage/app/public
|
||||
```
|
||||
- **storage_data**: Laravel应用的主存储目录
|
||||
- **documents_data**: 上传文档的私有存储目录
|
||||
- **public_data**: 公共文件存储目录
|
||||
|
||||
### 3. 数据库数据持久化
|
||||
```yaml
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
```
|
||||
- **用途**: MySQL数据库文件持久化
|
||||
- **映射到**: `./storage/mysql`
|
||||
- **重要性**: 确保数据库数据在容器重启后不丢失
|
||||
|
||||
### 4. 缓存数据持久化
|
||||
```yaml
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
```
|
||||
- **用途**: Redis缓存和会话数据持久化
|
||||
- **映射到**: `./storage/redis`
|
||||
- **好处**: 保持用户会话和缓存数据
|
||||
|
||||
### 5. 搜索引擎数据持久化
|
||||
```yaml
|
||||
volumes:
|
||||
- meilisearch_data:/meili_data
|
||||
```
|
||||
- **用途**: Meilisearch搜索索引数据持久化
|
||||
- **映射到**: `./storage/meilisearch`
|
||||
- **重要性**: 避免重新构建搜索索引
|
||||
|
||||
### 6. 日志目录映射
|
||||
```yaml
|
||||
volumes:
|
||||
- app_logs:/var/log
|
||||
- queue_logs:/var/log
|
||||
- laravel_logs:/var/www/html/storage/logs
|
||||
```
|
||||
- **app_logs**: 应用容器系统日志
|
||||
- **queue_logs**: 队列容器系统日志
|
||||
- **laravel_logs**: Laravel应用日志
|
||||
- **映射到**: `./storage/logs/` 相应子目录
|
||||
|
||||
## 存储目录结构
|
||||
|
||||
```
|
||||
storage/
|
||||
├── app/ # Laravel应用存储
|
||||
│ ├── private/
|
||||
│ │ ├── documents/ # 上传文档存储
|
||||
│ │ └── markdown/ # Markdown文件存储
|
||||
│ └── public/ # 公共文件存储
|
||||
├── framework/ # Laravel框架缓存
|
||||
│ ├── cache/
|
||||
│ ├── sessions/
|
||||
│ ├── testing/
|
||||
│ └── views/
|
||||
├── logs/ # 日志文件
|
||||
│ ├── app/ # 应用容器日志
|
||||
│ ├── queue/ # 队列容器日志
|
||||
│ └── laravel.log # Laravel应用日志
|
||||
├── mysql/ # MySQL数据文件
|
||||
├── redis/ # Redis数据文件
|
||||
└── meilisearch/ # Meilisearch索引文件
|
||||
```
|
||||
|
||||
## 权限配置
|
||||
|
||||
所有存储目录都设置为755权限,确保:
|
||||
- 容器内的应用可以读写数据
|
||||
- 宿主机可以访问和备份数据
|
||||
- 安全性和可用性的平衡
|
||||
|
||||
## 初始化脚本
|
||||
|
||||
使用 `docker/init-storage.sh` 脚本初始化存储目录:
|
||||
|
||||
```bash
|
||||
./docker/init-storage.sh
|
||||
```
|
||||
|
||||
该脚本会:
|
||||
1. 创建所有必要的存储目录
|
||||
2. 设置正确的权限
|
||||
3. 显示目录结构
|
||||
|
||||
## 备份建议
|
||||
|
||||
定期备份以下重要目录:
|
||||
- `storage/mysql/` - 数据库数据
|
||||
- `storage/app/private/documents/` - 上传的文档
|
||||
- `storage/meilisearch/` - 搜索索引
|
||||
- `storage/logs/` - 应用日志
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 权限问题
|
||||
如果遇到权限错误,运行:
|
||||
```bash
|
||||
sudo chown -R $USER:$USER storage/
|
||||
chmod -R 755 storage/
|
||||
```
|
||||
|
||||
### 目录不存在
|
||||
运行初始化脚本:
|
||||
```bash
|
||||
./docker/init-storage.sh
|
||||
```
|
||||
|
||||
### 数据丢失
|
||||
检查卷映射配置是否正确,确保使用bind mount而不是匿名卷。
|
||||
51
docker/build.sh
Executable file
51
docker/build.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像构建脚本
|
||||
# 用于构建Laravel知识库系统的Docker镜像
|
||||
|
||||
set -e
|
||||
|
||||
# 配置变量
|
||||
IMAGE_NAME="knowledge-base-app"
|
||||
IMAGE_TAG="latest"
|
||||
PLATFORM="linux/amd64"
|
||||
|
||||
echo "开始构建Docker镜像..."
|
||||
echo "镜像名称: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
echo "目标平台: ${PLATFORM}"
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "错误: Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 构建镜像
|
||||
echo "正在构建镜像..."
|
||||
docker build \
|
||||
--platform ${PLATFORM} \
|
||||
--tag ${IMAGE_NAME}:${IMAGE_TAG} \
|
||||
--file Dockerfile \
|
||||
.
|
||||
|
||||
# 验证构建结果
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 镜像构建成功!"
|
||||
|
||||
# 显示镜像信息
|
||||
echo ""
|
||||
echo "镜像信息:"
|
||||
docker images ${IMAGE_NAME}:${IMAGE_TAG}
|
||||
|
||||
# 检查镜像架构
|
||||
echo ""
|
||||
echo "镜像架构信息:"
|
||||
docker inspect ${IMAGE_NAME}:${IMAGE_TAG} | grep -A 5 "Architecture"
|
||||
|
||||
echo ""
|
||||
echo "构建完成! 可以使用以下命令运行容器:"
|
||||
echo "docker run -d -p 8000:8000 ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
else
|
||||
echo "❌ 镜像构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
287
docker/check-services.sh
Executable file
287
docker/check-services.sh
Executable file
@@ -0,0 +1,287 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker服务健康检查脚本
|
||||
# 用于检查所有服务的健康状态
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查Docker是否运行
|
||||
check_docker() {
|
||||
log_info "检查Docker服务状态..."
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Docker服务正常运行"
|
||||
}
|
||||
|
||||
# 检查容器状态
|
||||
check_container_status() {
|
||||
local container_name=$1
|
||||
local service_name=$2
|
||||
|
||||
log_info "检查${service_name}容器状态..."
|
||||
|
||||
if ! docker ps --format "table {{.Names}}" | grep -q "^${container_name}$"; then
|
||||
log_error "${service_name}容器未运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查容器健康状态
|
||||
local health_status=$(docker inspect --format='{{.State.Health.Status}}' ${container_name} 2>/dev/null || echo "no-healthcheck")
|
||||
|
||||
case $health_status in
|
||||
"healthy")
|
||||
log_success "${service_name}容器健康状态正常"
|
||||
return 0
|
||||
;;
|
||||
"unhealthy")
|
||||
log_error "${service_name}容器健康检查失败"
|
||||
return 1
|
||||
;;
|
||||
"starting")
|
||||
log_warning "${service_name}容器正在启动中..."
|
||||
return 2
|
||||
;;
|
||||
"no-healthcheck")
|
||||
log_warning "${service_name}容器未配置健康检查"
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
log_warning "${service_name}容器健康状态未知: ${health_status}"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 检查MySQL数据库连接
|
||||
check_mysql() {
|
||||
log_info "检查MySQL数据库连接..."
|
||||
|
||||
local max_attempts=5
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if docker exec knowledge_base_mysql mysqladmin ping -h localhost --silent 2>/dev/null; then
|
||||
log_success "MySQL数据库连接正常"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warning "MySQL连接尝试 ${attempt}/${max_attempts} 失败,等待5秒后重试..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "MySQL数据库连接失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 检查Redis连接
|
||||
check_redis() {
|
||||
log_info "检查Redis缓存连接..."
|
||||
|
||||
if docker exec knowledge_base_redis redis-cli ping | grep -q "PONG"; then
|
||||
log_success "Redis缓存连接正常"
|
||||
return 0
|
||||
else
|
||||
log_error "Redis缓存连接失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查Meilisearch连接
|
||||
check_meilisearch() {
|
||||
log_info "检查Meilisearch搜索引擎连接..."
|
||||
|
||||
local max_attempts=3
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if docker exec knowledge_base_meilisearch curl -f http://localhost:7700/health >/dev/null 2>&1; then
|
||||
log_success "Meilisearch搜索引擎连接正常"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warning "Meilisearch连接尝试 ${attempt}/${max_attempts} 失败,等待3秒后重试..."
|
||||
sleep 3
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "Meilisearch搜索引擎连接失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 检查Web应用健康状态
|
||||
check_web_app() {
|
||||
log_info "检查Web应用健康状态..."
|
||||
|
||||
local max_attempts=3
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
local response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/health 2>/dev/null || echo "000")
|
||||
|
||||
if [ "$response" = "200" ]; then
|
||||
log_success "Web应用健康检查通过"
|
||||
return 0
|
||||
elif [ "$response" = "503" ]; then
|
||||
log_warning "Web应用部分服务不可用,但应用仍在运行"
|
||||
return 2
|
||||
fi
|
||||
|
||||
# 如果没有专门的健康检查路由,尝试访问根路径
|
||||
local root_response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/ 2>/dev/null || echo "000")
|
||||
if [ "$root_response" = "200" ]; then
|
||||
log_success "Web应用根路径访问正常"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warning "Web应用健康检查尝试 ${attempt}/${max_attempts} 失败 (HTTP: ${response}),等待5秒后重试..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
log_error "Web应用健康检查失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 检查队列处理器
|
||||
check_queue_worker() {
|
||||
log_info "检查队列处理器状态..."
|
||||
|
||||
# 检查应用容器中的队列进程是否正在运行
|
||||
if docker exec knowledge_base_app pgrep -f "queue:work" >/dev/null 2>&1; then
|
||||
log_success "队列处理器正常运行"
|
||||
return 0
|
||||
else
|
||||
log_error "队列处理器进程未运行"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查数据持久化
|
||||
check_data_persistence() {
|
||||
log_info "检查数据持久化状态..."
|
||||
|
||||
local errors=0
|
||||
|
||||
# 检查存储目录
|
||||
local storage_dirs=("./storage/mysql" "./storage/redis" "./storage/meilisearch" "./storage/app")
|
||||
|
||||
for dir in "${storage_dirs[@]}"; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
log_error "存储目录不存在: $dir"
|
||||
((errors++))
|
||||
elif [ ! -w "$dir" ]; then
|
||||
log_error "存储目录不可写: $dir"
|
||||
((errors++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "数据持久化配置正常"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个数据持久化问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主检查函数
|
||||
main() {
|
||||
echo "========================================"
|
||||
echo "Docker服务健康检查开始"
|
||||
echo "时间: $(date)"
|
||||
echo "========================================"
|
||||
|
||||
local total_checks=0
|
||||
local failed_checks=0
|
||||
local warning_checks=0
|
||||
|
||||
# 执行所有检查
|
||||
checks=(
|
||||
"check_docker:Docker服务"
|
||||
"check_container_status:knowledge_base_mysql:MySQL容器"
|
||||
"check_container_status:knowledge_base_redis:Redis容器"
|
||||
"check_container_status:knowledge_base_meilisearch:Meilisearch容器"
|
||||
"check_container_status:knowledge_base_app:应用容器"
|
||||
"check_mysql:MySQL连接"
|
||||
"check_redis:Redis连接"
|
||||
"check_meilisearch:Meilisearch连接"
|
||||
"check_web_app:Web应用"
|
||||
"check_queue_worker:队列处理器"
|
||||
"check_data_persistence:数据持久化"
|
||||
)
|
||||
|
||||
for check in "${checks[@]}"; do
|
||||
IFS=':' read -ra CHECK_PARTS <<< "$check"
|
||||
local check_func="${CHECK_PARTS[0]}"
|
||||
local check_args=("${CHECK_PARTS[@]:1}")
|
||||
|
||||
((total_checks++))
|
||||
|
||||
if [ ${#check_args[@]} -eq 0 ]; then
|
||||
$check_func
|
||||
else
|
||||
$check_func "${check_args[@]}"
|
||||
fi
|
||||
|
||||
local result=$?
|
||||
if [ $result -eq 1 ]; then
|
||||
((failed_checks++))
|
||||
elif [ $result -eq 2 ]; then
|
||||
((warning_checks++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# 输出总结
|
||||
echo "========================================"
|
||||
echo "健康检查完成"
|
||||
echo "总检查项: $total_checks"
|
||||
echo "失败: $failed_checks"
|
||||
echo "警告: $warning_checks"
|
||||
echo "成功: $((total_checks - failed_checks - warning_checks))"
|
||||
echo "========================================"
|
||||
|
||||
if [ $failed_checks -gt 0 ]; then
|
||||
log_error "发现 $failed_checks 个严重问题,请检查服务状态"
|
||||
exit 1
|
||||
elif [ $warning_checks -gt 0 ]; then
|
||||
log_warning "发现 $warning_checks 个警告,服务可能需要关注"
|
||||
exit 2
|
||||
else
|
||||
log_success "所有服务健康检查通过"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 如果脚本被直接执行
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
399
docker/compress-and-verify.sh
Executable file
399
docker/compress-and-verify.sh
Executable file
@@ -0,0 +1,399 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像压缩和完整性检查脚本
|
||||
# 用于压缩导出的镜像文件并验证完整性
|
||||
|
||||
set -e
|
||||
|
||||
# 脚本配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
DEFAULT_INPUT_DIR="${PROJECT_ROOT}/docker-images"
|
||||
LOG_FILE="${DEFAULT_INPUT_DIR}/compress-verify.log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Docker镜像压缩和完整性检查脚本
|
||||
|
||||
用法: $0 [选项] [输入目录]
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助信息
|
||||
-c, --compress-level N 压缩级别 (1-9, 默认: 6)
|
||||
-k, --keep-original 保留原始文件
|
||||
-v, --verify-only 仅验证,不压缩
|
||||
-u, --uncompress 解压缩文件
|
||||
--parallel N 并行处理数量 (默认: 2)
|
||||
|
||||
参数:
|
||||
输入目录 包含Docker镜像tar文件的目录 (默认: ./docker-images)
|
||||
|
||||
示例:
|
||||
$0 # 压缩默认目录中的所有tar文件
|
||||
$0 -c 9 -k /path/to/images # 最高压缩级别,保留原文件
|
||||
$0 --verify-only # 仅验证现有文件完整性
|
||||
$0 --uncompress # 解压缩所有.gz文件
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
COMPRESS_LEVEL=6
|
||||
KEEP_ORIGINAL=false
|
||||
VERIFY_ONLY=false
|
||||
UNCOMPRESS=false
|
||||
PARALLEL_JOBS=2
|
||||
INPUT_DIR="$DEFAULT_INPUT_DIR"
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-c|--compress-level)
|
||||
COMPRESS_LEVEL="$2"
|
||||
if [[ ! "$COMPRESS_LEVEL" =~ ^[1-9]$ ]]; then
|
||||
log_error "压缩级别必须是1-9之间的数字"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
-k|--keep-original)
|
||||
KEEP_ORIGINAL=true
|
||||
shift
|
||||
;;
|
||||
-v|--verify-only)
|
||||
VERIFY_ONLY=true
|
||||
shift
|
||||
;;
|
||||
-u|--uncompress)
|
||||
UNCOMPRESS=true
|
||||
shift
|
||||
;;
|
||||
--parallel)
|
||||
PARALLEL_JOBS="$2"
|
||||
if [[ ! "$PARALLEL_JOBS" =~ ^[1-9][0-9]*$ ]]; then
|
||||
log_error "并行任务数必须是正整数"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
-*)
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
INPUT_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查输入目录
|
||||
if [[ ! -d "$INPUT_DIR" ]]; then
|
||||
log_error "输入目录不存在: $INPUT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log "开始镜像压缩和完整性检查..."
|
||||
log "输入目录: $INPUT_DIR"
|
||||
log "压缩级别: $COMPRESS_LEVEL"
|
||||
log "保留原文件: $KEEP_ORIGINAL"
|
||||
log "仅验证: $VERIFY_ONLY"
|
||||
log "解压缩: $UNCOMPRESS"
|
||||
log "并行任务: $PARALLEL_JOBS"
|
||||
|
||||
# 检查必要工具
|
||||
check_tools() {
|
||||
local tools=("gzip" "sha256sum" "tar")
|
||||
|
||||
for tool in "${tools[@]}"; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
log_error "缺少必要工具: $tool"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_tools
|
||||
|
||||
# 验证文件完整性
|
||||
verify_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
log "验证文件: $filename"
|
||||
|
||||
if [[ "$file" == *.tar.gz ]]; then
|
||||
# 验证gzip文件
|
||||
if gzip -t "$file" 2>/dev/null; then
|
||||
log_success "压缩文件完整性验证通过: $filename"
|
||||
return 0
|
||||
else
|
||||
log_error "压缩文件完整性验证失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
elif [[ "$file" == *.tar ]]; then
|
||||
# 验证tar文件
|
||||
if tar -tf "$file" >/dev/null 2>&1; then
|
||||
log_success "tar文件完整性验证通过: $filename"
|
||||
return 0
|
||||
else
|
||||
log_error "tar文件完整性验证失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_warning "未知文件类型,跳过验证: $filename"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 压缩文件
|
||||
compress_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
local compressed_file="${file}.gz"
|
||||
|
||||
log "压缩文件: $filename (级别: $COMPRESS_LEVEL)"
|
||||
|
||||
# 检查是否已经压缩
|
||||
if [[ "$file" == *.gz ]]; then
|
||||
log_warning "文件已经压缩,跳过: $filename"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 检查压缩文件是否已存在
|
||||
if [[ -f "$compressed_file" ]]; then
|
||||
log_warning "压缩文件已存在,跳过: ${filename}.gz"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 获取原始文件大小
|
||||
local original_size=$(du -b "$file" | cut -f1)
|
||||
local original_size_human=$(du -h "$file" | cut -f1)
|
||||
|
||||
# 压缩文件
|
||||
if gzip -"$COMPRESS_LEVEL" -c "$file" > "$compressed_file"; then
|
||||
# 获取压缩后文件大小
|
||||
local compressed_size=$(du -b "$compressed_file" | cut -f1)
|
||||
local compressed_size_human=$(du -h "$compressed_file" | cut -f1)
|
||||
|
||||
# 计算压缩比
|
||||
local ratio=$(echo "scale=2; $compressed_size * 100 / $original_size" | bc -l 2>/dev/null || echo "N/A")
|
||||
|
||||
log_success "文件压缩成功: ${filename}.gz"
|
||||
log "原始大小: $original_size_human"
|
||||
log "压缩后大小: $compressed_size_human"
|
||||
if [[ "$ratio" != "N/A" ]]; then
|
||||
log "压缩比: ${ratio}%"
|
||||
fi
|
||||
|
||||
# 验证压缩文件
|
||||
if verify_file "$compressed_file"; then
|
||||
# 删除原文件(如果不保留)
|
||||
if [[ "$KEEP_ORIGINAL" == false ]]; then
|
||||
rm "$file"
|
||||
log "已删除原文件: $filename"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
log_error "压缩文件验证失败,删除压缩文件"
|
||||
rm -f "$compressed_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "文件压缩失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 解压缩文件
|
||||
uncompress_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
log "解压缩文件: $filename"
|
||||
|
||||
# 检查是否是压缩文件
|
||||
if [[ "$file" != *.gz ]]; then
|
||||
log_warning "文件未压缩,跳过: $filename"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 生成解压后的文件名
|
||||
local uncompressed_file="${file%.gz}"
|
||||
local uncompressed_filename=$(basename "$uncompressed_file")
|
||||
|
||||
# 检查解压文件是否已存在
|
||||
if [[ -f "$uncompressed_file" ]]; then
|
||||
log_warning "解压文件已存在,跳过: $uncompressed_filename"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 解压文件
|
||||
if gunzip -c "$file" > "$uncompressed_file"; then
|
||||
log_success "文件解压成功: $uncompressed_filename"
|
||||
|
||||
# 验证解压文件
|
||||
if verify_file "$uncompressed_file"; then
|
||||
# 删除压缩文件(如果不保留)
|
||||
if [[ "$KEEP_ORIGINAL" == false ]]; then
|
||||
rm "$file"
|
||||
log "已删除压缩文件: $filename"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
log_error "解压文件验证失败,删除解压文件"
|
||||
rm -f "$uncompressed_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "文件解压失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 处理单个文件
|
||||
process_file() {
|
||||
local file="$1"
|
||||
|
||||
if [[ "$VERIFY_ONLY" == true ]]; then
|
||||
verify_file "$file"
|
||||
elif [[ "$UNCOMPRESS" == true ]]; then
|
||||
uncompress_file "$file"
|
||||
else
|
||||
compress_file "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
# 查找需要处理的文件
|
||||
if [[ "$UNCOMPRESS" == true ]]; then
|
||||
FILES=($(find "$INPUT_DIR" -name "*.tar.gz" -type f))
|
||||
log "找到 ${#FILES[@]} 个压缩文件"
|
||||
elif [[ "$VERIFY_ONLY" == true ]]; then
|
||||
FILES=($(find "$INPUT_DIR" -name "*.tar*" -type f))
|
||||
log "找到 ${#FILES[@]} 个文件需要验证"
|
||||
else
|
||||
FILES=($(find "$INPUT_DIR" -name "*.tar" -type f))
|
||||
log "找到 ${#FILES[@]} 个tar文件需要压缩"
|
||||
fi
|
||||
|
||||
if [[ ${#FILES[@]} -eq 0 ]]; then
|
||||
log_warning "没有找到需要处理的文件"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 处理文件
|
||||
PROCESSED=0
|
||||
FAILED=0
|
||||
TOTAL=${#FILES[@]}
|
||||
|
||||
# 使用并行处理
|
||||
export -f process_file verify_file compress_file uncompress_file log log_success log_warning log_error
|
||||
export COMPRESS_LEVEL KEEP_ORIGINAL VERIFY_ONLY UNCOMPRESS LOG_FILE
|
||||
export RED GREEN YELLOW BLUE NC
|
||||
|
||||
printf '%s\n' "${FILES[@]}" | xargs -n 1 -P "$PARALLEL_JOBS" -I {} bash -c 'process_file "$@"' _ {}
|
||||
|
||||
# 统计结果
|
||||
for file in "${FILES[@]}"; do
|
||||
if [[ "$VERIFY_ONLY" == true ]]; then
|
||||
if verify_file "$file" >/dev/null 2>&1; then
|
||||
((PROCESSED++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
elif [[ "$UNCOMPRESS" == true ]]; then
|
||||
uncompressed_file="${file%.gz}"
|
||||
if [[ -f "$uncompressed_file" ]] || [[ "$file" != *.gz ]]; then
|
||||
((PROCESSED++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
compressed_file="${file}.gz"
|
||||
if [[ -f "$compressed_file" ]] || [[ "$file" == *.gz ]]; then
|
||||
((PROCESSED++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 更新清单文件
|
||||
if [[ "$VERIFY_ONLY" == false ]]; then
|
||||
manifest_file="${INPUT_DIR}/images-manifest.txt"
|
||||
if [[ -f "$manifest_file" ]]; then
|
||||
log "更新镜像清单..."
|
||||
|
||||
# 备份原清单
|
||||
cp "$manifest_file" "${manifest_file}.backup"
|
||||
|
||||
# 重新生成清单
|
||||
cat > "$manifest_file" << EOF
|
||||
# Docker镜像清单
|
||||
# 更新时间: $(date)
|
||||
# 处理目录: $INPUT_DIR
|
||||
|
||||
EOF
|
||||
|
||||
for file in "$INPUT_DIR"/*.tar*; do
|
||||
if [[ -f "$file" ]]; then
|
||||
filename=$(basename "$file")
|
||||
size=$(du -h "$file" | cut -f1)
|
||||
checksum=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
|
||||
echo "文件: $filename" >> "$manifest_file"
|
||||
echo "大小: $size" >> "$manifest_file"
|
||||
echo "SHA256: $checksum" >> "$manifest_file"
|
||||
echo "" >> "$manifest_file"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "镜像清单已更新"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 显示总结
|
||||
log_success "处理完成!"
|
||||
log "总文件数: $TOTAL"
|
||||
log "成功处理: $PROCESSED"
|
||||
log "失败数量: $FAILED"
|
||||
|
||||
if [[ "$FAILED" -gt 0 ]]; then
|
||||
log_warning "有 $FAILED 个文件处理失败,请检查日志"
|
||||
exit 1
|
||||
else
|
||||
log_success "所有文件处理成功"
|
||||
fi
|
||||
616
docker/deploy-to-openeuler.sh
Executable file
616
docker/deploy-to-openeuler.sh
Executable file
@@ -0,0 +1,616 @@
|
||||
#!/bin/bash
|
||||
|
||||
# OpenEuler服务器部署脚本
|
||||
# 用于在OpenEuler服务器上部署Laravel知识库系统
|
||||
|
||||
set -e
|
||||
|
||||
# 脚本配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_NAME="knowledge-base"
|
||||
DEPLOY_DIR="/opt/${PROJECT_NAME}"
|
||||
BACKUP_DIR="/opt/${PROJECT_NAME}-backup"
|
||||
LOG_FILE="/var/log/${PROJECT_NAME}-deploy.log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
OpenEuler服务器部署脚本
|
||||
|
||||
用法: $0 [选项] [镜像目录]
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助信息
|
||||
-d, --deploy-dir DIR 部署目录 (默认: /opt/knowledge-base)
|
||||
-b, --backup 部署前备份现有安装
|
||||
-u, --update 更新现有部署
|
||||
-r, --rollback 回滚到上一个版本
|
||||
--skip-docker-install 跳过Docker安装
|
||||
--skip-images 跳过镜像导入
|
||||
--skip-env-setup 跳过环境配置
|
||||
--dry-run 仅显示将要执行的操作
|
||||
|
||||
参数:
|
||||
镜像目录 包含Docker镜像文件的目录
|
||||
|
||||
示例:
|
||||
$0 /path/to/docker-images # 全新部署
|
||||
$0 -u /path/to/docker-images # 更新现有部署
|
||||
$0 -b -d /custom/path # 备份并部署到自定义目录
|
||||
$0 --rollback # 回滚到上一个版本
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
BACKUP=false
|
||||
UPDATE=false
|
||||
ROLLBACK=false
|
||||
SKIP_DOCKER_INSTALL=false
|
||||
SKIP_IMAGES=false
|
||||
SKIP_ENV_SETUP=false
|
||||
DRY_RUN=false
|
||||
IMAGES_DIR=""
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-d|--deploy-dir)
|
||||
DEPLOY_DIR="$2"
|
||||
BACKUP_DIR="${DEPLOY_DIR}-backup"
|
||||
shift 2
|
||||
;;
|
||||
-b|--backup)
|
||||
BACKUP=true
|
||||
shift
|
||||
;;
|
||||
-u|--update)
|
||||
UPDATE=true
|
||||
shift
|
||||
;;
|
||||
-r|--rollback)
|
||||
ROLLBACK=true
|
||||
shift
|
||||
;;
|
||||
--skip-docker-install)
|
||||
SKIP_DOCKER_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--skip-images)
|
||||
SKIP_IMAGES=true
|
||||
shift
|
||||
;;
|
||||
--skip-env-setup)
|
||||
SKIP_ENV_SETUP=true
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
IMAGES_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查是否以root权限运行
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "此脚本需要root权限运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log "开始OpenEuler服务器部署..."
|
||||
log "部署目录: $DEPLOY_DIR"
|
||||
log "备份目录: $BACKUP_DIR"
|
||||
log "镜像目录: $IMAGES_DIR"
|
||||
|
||||
# 检查系统信息
|
||||
check_system() {
|
||||
log "检查系统信息..."
|
||||
|
||||
# 检查操作系统
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
source /etc/os-release
|
||||
log "操作系统: $NAME $VERSION"
|
||||
|
||||
if [[ "$ID" != "openEuler" ]] && [[ "$ID_LIKE" != *"rhel"* ]]; then
|
||||
log_warning "此脚本专为OpenEuler设计,当前系统: $NAME"
|
||||
read -p "是否继续? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_warning "无法检测操作系统版本"
|
||||
fi
|
||||
|
||||
# 检查架构
|
||||
local arch=$(uname -m)
|
||||
log "系统架构: $arch"
|
||||
|
||||
if [[ "$arch" != "x86_64" ]]; then
|
||||
log_warning "推荐使用x86_64架构,当前: $arch"
|
||||
fi
|
||||
|
||||
# 检查内存
|
||||
local memory=$(free -h | awk '/^Mem:/ {print $2}')
|
||||
log "系统内存: $memory"
|
||||
|
||||
# 检查磁盘空间
|
||||
local disk_space=$(df -h / | awk 'NR==2 {print $4}')
|
||||
log "可用磁盘空间: $disk_space"
|
||||
}
|
||||
|
||||
# 安装Docker
|
||||
install_docker() {
|
||||
if [[ "$SKIP_DOCKER_INSTALL" == true ]]; then
|
||||
log "跳过Docker安装"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "检查Docker安装状态..."
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
local docker_version=$(docker --version)
|
||||
log "Docker已安装: $docker_version"
|
||||
|
||||
# 检查Docker是否运行
|
||||
if systemctl is-active --quiet docker; then
|
||||
log_success "Docker服务正在运行"
|
||||
else
|
||||
log "启动Docker服务..."
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
log_success "Docker服务已启动"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "安装Docker..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将安装Docker"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 更新系统包
|
||||
dnf update -y
|
||||
|
||||
# 安装必要的包
|
||||
dnf install -y dnf-plugins-core
|
||||
|
||||
# 添加Docker仓库
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
|
||||
# 安装Docker
|
||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# 启动Docker服务
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
|
||||
# 验证安装
|
||||
if docker --version >/dev/null 2>&1; then
|
||||
log_success "Docker安装成功"
|
||||
else
|
||||
log_error "Docker安装失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 添加当前用户到docker组(如果不是root)
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
usermod -aG docker "$SUDO_USER"
|
||||
log "已将用户 $SUDO_USER 添加到docker组"
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装Docker Compose
|
||||
install_docker_compose() {
|
||||
log "检查Docker Compose..."
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
local compose_version=$(docker compose version)
|
||||
log_success "Docker Compose已安装: $compose_version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "安装Docker Compose..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将安装Docker Compose"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Docker Compose通常随Docker一起安装
|
||||
# 如果没有,可以手动安装
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
log_error "Docker Compose未找到,请手动安装"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 备份现有部署
|
||||
backup_existing() {
|
||||
if [[ "$BACKUP" == false ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -d "$DEPLOY_DIR" ]]; then
|
||||
log "没有现有部署需要备份"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "备份现有部署..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将备份 $DEPLOY_DIR 到 $BACKUP_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 停止现有服务
|
||||
if [[ -f "$DEPLOY_DIR/docker-compose.yml" ]]; then
|
||||
log "停止现有服务..."
|
||||
cd "$DEPLOY_DIR"
|
||||
docker compose down || true
|
||||
fi
|
||||
|
||||
# 创建备份
|
||||
local backup_name="${BACKUP_DIR}-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
if cp -r "$DEPLOY_DIR" "$backup_name"; then
|
||||
log_success "备份创建成功: $backup_name"
|
||||
|
||||
# 创建符号链接到最新备份
|
||||
rm -f "$BACKUP_DIR"
|
||||
ln -s "$backup_name" "$BACKUP_DIR"
|
||||
else
|
||||
log_error "备份创建失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 回滚部署
|
||||
rollback_deployment() {
|
||||
if [[ "$ROLLBACK" == false ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "回滚部署..."
|
||||
|
||||
if [[ ! -L "$BACKUP_DIR" ]] || [[ ! -d "$BACKUP_DIR" ]]; then
|
||||
log_error "没有找到备份,无法回滚"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将从 $BACKUP_DIR 回滚"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 停止当前服务
|
||||
if [[ -f "$DEPLOY_DIR/docker-compose.yml" ]]; then
|
||||
log "停止当前服务..."
|
||||
cd "$DEPLOY_DIR"
|
||||
docker compose down || true
|
||||
fi
|
||||
|
||||
# 恢复备份
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
cp -r "$BACKUP_DIR" "$DEPLOY_DIR"
|
||||
|
||||
# 启动服务
|
||||
cd "$DEPLOY_DIR"
|
||||
docker compose up -d
|
||||
|
||||
log_success "回滚完成"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 导入Docker镜像
|
||||
import_images() {
|
||||
if [[ "$SKIP_IMAGES" == true ]]; then
|
||||
log "跳过镜像导入"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -z "$IMAGES_DIR" ]] || [[ ! -d "$IMAGES_DIR" ]]; then
|
||||
log_error "镜像目录不存在或未指定: $IMAGES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "导入Docker镜像..."
|
||||
|
||||
# 查找镜像文件
|
||||
local image_files=($(find "$IMAGES_DIR" -name "*.tar*" -type f))
|
||||
|
||||
if [[ ${#image_files[@]} -eq 0 ]]; then
|
||||
log_error "在 $IMAGES_DIR 中没有找到镜像文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "找到 ${#image_files[@]} 个镜像文件"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
for file in "${image_files[@]}"; do
|
||||
log "[DRY RUN] 将导入: $(basename "$file")"
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 导入镜像
|
||||
for file in "${image_files[@]}"; do
|
||||
local filename=$(basename "$file")
|
||||
log "导入镜像: $filename"
|
||||
|
||||
if [[ "$file" == *.gz ]]; then
|
||||
# 解压并导入
|
||||
if gunzip -c "$file" | docker load; then
|
||||
log_success "镜像导入成功: $filename"
|
||||
else
|
||||
log_error "镜像导入失败: $filename"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# 直接导入
|
||||
if docker load -i "$file"; then
|
||||
log_success "镜像导入成功: $filename"
|
||||
else
|
||||
log_error "镜像导入失败: $filename"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 显示导入的镜像
|
||||
log "已导入的镜像:"
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
|
||||
}
|
||||
|
||||
# 设置部署环境
|
||||
setup_deployment() {
|
||||
log "设置部署环境..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将创建部署目录: $DEPLOY_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 创建部署目录
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
# 复制配置文件(如果镜像目录包含)
|
||||
if [[ -n "$IMAGES_DIR" ]]; then
|
||||
# 查找配置文件
|
||||
local config_files=(
|
||||
"docker-compose.yml"
|
||||
".env.production"
|
||||
"docker"
|
||||
)
|
||||
|
||||
for config in "${config_files[@]}"; do
|
||||
if [[ -e "$IMAGES_DIR/../$config" ]]; then
|
||||
log "复制配置: $config"
|
||||
cp -r "$IMAGES_DIR/../$config" .
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 创建必要的目录
|
||||
mkdir -p storage/{mysql,redis,meilisearch,app,logs}
|
||||
mkdir -p storage/logs/{app,queue}
|
||||
|
||||
# 设置权限
|
||||
chown -R 1000:1000 storage/
|
||||
chmod -R 755 storage/
|
||||
|
||||
log_success "部署环境设置完成"
|
||||
}
|
||||
|
||||
# 配置环境变量
|
||||
setup_environment() {
|
||||
if [[ "$SKIP_ENV_SETUP" == true ]]; then
|
||||
log "跳过环境配置"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "配置环境变量..."
|
||||
|
||||
local env_file="$DEPLOY_DIR/.env"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将创建环境配置文件: $env_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 生成随机密钥
|
||||
local app_key="base64:$(openssl rand -base64 32)"
|
||||
local db_password=$(openssl rand -base64 16)
|
||||
local meilisearch_key=$(openssl rand -base64 32)
|
||||
|
||||
# 创建环境配置文件
|
||||
cat > "$env_file" << EOF
|
||||
# Laravel知识库系统 - 生产环境配置
|
||||
# 生成时间: $(date)
|
||||
|
||||
# 应用配置
|
||||
APP_NAME="知识库系统"
|
||||
APP_ENV=production
|
||||
APP_KEY=$app_key
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
# 数据库配置
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=knowledge_base
|
||||
DB_USERNAME=knowledge_user
|
||||
DB_PASSWORD=$db_password
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 缓存配置
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
# 搜索配置
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
MEILISEARCH_KEY=$meilisearch_key
|
||||
|
||||
# 日志配置
|
||||
LOG_CHANNEL=stack
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=error
|
||||
|
||||
# 文件系统
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# 邮件配置
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=localhost
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="\${APP_NAME}"
|
||||
EOF
|
||||
|
||||
log_success "环境配置文件创建成功"
|
||||
log_warning "请根据实际情况修改 $env_file 中的配置"
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
start_services() {
|
||||
log "启动服务..."
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
log "[DRY RUN] 将启动Docker Compose服务"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
# 检查配置文件
|
||||
if [[ ! -f "docker-compose.yml" ]]; then
|
||||
log_error "docker-compose.yml 文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f ".env" ]]; then
|
||||
log_error ".env 文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
if docker compose up -d; then
|
||||
log_success "服务启动成功"
|
||||
else
|
||||
log_error "服务启动失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 等待服务就绪
|
||||
log "等待服务就绪..."
|
||||
sleep 30
|
||||
|
||||
# 检查服务状态
|
||||
docker compose ps
|
||||
|
||||
# 运行Laravel初始化命令
|
||||
log "运行Laravel初始化..."
|
||||
docker compose exec -T app php artisan migrate --force || log_warning "数据库迁移失败"
|
||||
docker compose exec -T app php artisan storage:link || log_warning "存储链接创建失败"
|
||||
|
||||
log_success "部署完成!"
|
||||
}
|
||||
|
||||
# 显示部署信息
|
||||
show_deployment_info() {
|
||||
log "部署信息:"
|
||||
log "部署目录: $DEPLOY_DIR"
|
||||
log "访问地址: http://$(hostname -I | awk '{print $1}')"
|
||||
log "管理命令:"
|
||||
log " 查看日志: cd $DEPLOY_DIR && docker compose logs -f"
|
||||
log " 重启服务: cd $DEPLOY_DIR && docker compose restart"
|
||||
log " 停止服务: cd $DEPLOY_DIR && docker compose down"
|
||||
log " 更新应用: cd $DEPLOY_DIR && docker compose pull && docker compose up -d"
|
||||
}
|
||||
|
||||
# 主执行流程
|
||||
main() {
|
||||
check_system
|
||||
|
||||
# 处理回滚
|
||||
rollback_deployment
|
||||
|
||||
# 备份现有部署
|
||||
backup_existing
|
||||
|
||||
# 安装Docker
|
||||
install_docker
|
||||
install_docker_compose
|
||||
|
||||
# 导入镜像
|
||||
import_images
|
||||
|
||||
# 设置部署环境
|
||||
setup_deployment
|
||||
|
||||
# 配置环境
|
||||
setup_environment
|
||||
|
||||
# 启动服务
|
||||
start_services
|
||||
|
||||
# 显示部署信息
|
||||
show_deployment_info
|
||||
}
|
||||
|
||||
# 执行主流程
|
||||
main
|
||||
335
docker/export-images.sh
Executable file
335
docker/export-images.sh
Executable file
@@ -0,0 +1,335 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像导出脚本
|
||||
# 用于将构建好的Docker镜像导出为tar文件,便于离线部署
|
||||
|
||||
set -e
|
||||
|
||||
# 脚本配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
EXPORT_DIR="${PROJECT_ROOT}/docker-images"
|
||||
LOG_FILE="${EXPORT_DIR}/export.log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Docker镜像导出脚本
|
||||
|
||||
用法: $0 [选项]
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助信息
|
||||
-o, --output DIR 指定导出目录 (默认: ./docker-images)
|
||||
-c, --compress 启用压缩 (使用gzip)
|
||||
-v, --verify 导出后验证镜像完整性
|
||||
--custom-images 导出自定义镜像列表 (用逗号分隔)
|
||||
--skip-build 跳过镜像构建,直接导出现有镜像
|
||||
|
||||
示例:
|
||||
$0 # 导出所有镜像
|
||||
$0 -c -v # 导出并压缩,验证完整性
|
||||
$0 -o /tmp/images --compress # 导出到指定目录并压缩
|
||||
$0 --custom-images "mysql:8.0,redis:7-alpine" # 导出指定镜像
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
COMPRESS=false
|
||||
VERIFY=false
|
||||
SKIP_BUILD=false
|
||||
CUSTOM_IMAGES=""
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-o|--output)
|
||||
EXPORT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--compress)
|
||||
COMPRESS=true
|
||||
shift
|
||||
;;
|
||||
-v|--verify)
|
||||
VERIFY=true
|
||||
shift
|
||||
;;
|
||||
--custom-images)
|
||||
CUSTOM_IMAGES="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-build)
|
||||
SKIP_BUILD=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 创建导出目录
|
||||
mkdir -p "$EXPORT_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log "开始Docker镜像导出过程..."
|
||||
log "导出目录: $EXPORT_DIR"
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 定义需要导出的镜像列表
|
||||
if [[ -n "$CUSTOM_IMAGES" ]]; then
|
||||
IFS=',' read -ra IMAGES <<< "$CUSTOM_IMAGES"
|
||||
else
|
||||
IMAGES=(
|
||||
"knowledge-base-app:latest"
|
||||
"mysql:8.0"
|
||||
"redis:7-alpine"
|
||||
"getmeili/meilisearch:v1.5"
|
||||
)
|
||||
fi
|
||||
|
||||
# 构建自定义镜像(如果需要)
|
||||
if [[ "$SKIP_BUILD" == false ]]; then
|
||||
log "检查是否需要构建自定义镜像..."
|
||||
|
||||
# 检查knowledge-base-app镜像是否存在
|
||||
if ! docker image inspect knowledge-base-app:latest >/dev/null 2>&1; then
|
||||
log "构建knowledge-base-app镜像..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if docker build --platform linux/amd64 -t knowledge-base-app:latest -f Dockerfile --target production .; then
|
||||
log_success "knowledge-base-app镜像构建成功"
|
||||
else
|
||||
log_error "knowledge-base-app镜像构建失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_success "knowledge-base-app镜像已存在"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 拉取外部镜像
|
||||
log "拉取外部镜像..."
|
||||
for image in "mysql:8.0" "redis:7-alpine" "getmeili/meilisearch:v1.5"; do
|
||||
if [[ " ${IMAGES[@]} " =~ " ${image} " ]]; then
|
||||
log "拉取镜像: $image"
|
||||
if docker pull --platform linux/amd64 "$image"; then
|
||||
log_success "镜像 $image 拉取成功"
|
||||
else
|
||||
log_warning "镜像 $image 拉取失败,将尝试使用本地镜像"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 导出镜像
|
||||
log "开始导出镜像..."
|
||||
EXPORTED_FILES=()
|
||||
|
||||
for image in "${IMAGES[@]}"; do
|
||||
log "导出镜像: $image"
|
||||
|
||||
# 检查镜像是否存在
|
||||
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
||||
log_error "镜像 $image 不存在,跳过导出"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 生成文件名(替换特殊字符)
|
||||
filename=$(echo "$image" | sed 's/[\/:]/_/g')
|
||||
output_file="${EXPORT_DIR}/${filename}.tar"
|
||||
|
||||
# 导出镜像
|
||||
if docker save -o "$output_file" "$image"; then
|
||||
log_success "镜像 $image 导出成功: $output_file"
|
||||
EXPORTED_FILES+=("$output_file")
|
||||
|
||||
# 显示文件大小
|
||||
size=$(du -h "$output_file" | cut -f1)
|
||||
log "文件大小: $size"
|
||||
else
|
||||
log_error "镜像 $image 导出失败"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 压缩文件(如果启用)
|
||||
if [[ "$COMPRESS" == true ]]; then
|
||||
log "压缩文件: $output_file"
|
||||
if gzip "$output_file"; then
|
||||
compressed_file="${output_file}.gz"
|
||||
log_success "文件压缩成功: $compressed_file"
|
||||
|
||||
# 更新文件列表
|
||||
EXPORTED_FILES=("${EXPORTED_FILES[@]/$output_file}")
|
||||
EXPORTED_FILES+=("$compressed_file")
|
||||
|
||||
# 显示压缩后大小
|
||||
compressed_size=$(du -h "$compressed_file" | cut -f1)
|
||||
original_size=$(du -h "$output_file" 2>/dev/null | cut -f1 || echo "N/A")
|
||||
log "压缩后大小: $compressed_size (原始: $original_size)"
|
||||
else
|
||||
log_error "文件压缩失败"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 验证导出的镜像(如果启用)
|
||||
if [[ "$VERIFY" == true ]]; then
|
||||
log "验证导出的镜像..."
|
||||
|
||||
for file in "${EXPORTED_FILES[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
log "验证文件: $file"
|
||||
|
||||
# 检查文件完整性
|
||||
if [[ "$file" == *.gz ]]; then
|
||||
# 验证gzip文件
|
||||
if gzip -t "$file"; then
|
||||
log_success "压缩文件完整性验证通过"
|
||||
else
|
||||
log_error "压缩文件完整性验证失败"
|
||||
fi
|
||||
else
|
||||
# 验证tar文件
|
||||
if tar -tf "$file" >/dev/null 2>&1; then
|
||||
log_success "tar文件完整性验证通过"
|
||||
else
|
||||
log_error "tar文件完整性验证失败"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 生成镜像清单
|
||||
manifest_file="${EXPORT_DIR}/images-manifest.txt"
|
||||
log "生成镜像清单: $manifest_file"
|
||||
|
||||
cat > "$manifest_file" << EOF
|
||||
# Docker镜像导出清单
|
||||
# 生成时间: $(date)
|
||||
# 导出目录: $EXPORT_DIR
|
||||
# 压缩: $COMPRESS
|
||||
# 验证: $VERIFY
|
||||
|
||||
EOF
|
||||
|
||||
for file in "${EXPORTED_FILES[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
filename=$(basename "$file")
|
||||
size=$(du -h "$file" | cut -f1)
|
||||
checksum=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
|
||||
echo "文件: $filename" >> "$manifest_file"
|
||||
echo "大小: $size" >> "$manifest_file"
|
||||
echo "SHA256: $checksum" >> "$manifest_file"
|
||||
echo "" >> "$manifest_file"
|
||||
fi
|
||||
done
|
||||
|
||||
# 生成导入脚本
|
||||
import_script="${EXPORT_DIR}/import-images.sh"
|
||||
log "生成导入脚本: $import_script"
|
||||
|
||||
cat > "$import_script" << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像导入脚本
|
||||
# 自动生成,用于导入导出的Docker镜像
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "开始导入Docker镜像..."
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "错误: Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 导入所有tar文件
|
||||
for file in "$SCRIPT_DIR"/*.tar*; do
|
||||
if [[ -f "$file" ]]; then
|
||||
echo "导入镜像: $(basename "$file")"
|
||||
|
||||
if [[ "$file" == *.gz ]]; then
|
||||
# 解压并导入
|
||||
if gunzip -c "$file" | docker load; then
|
||||
echo "✓ 镜像导入成功"
|
||||
else
|
||||
echo "✗ 镜像导入失败"
|
||||
fi
|
||||
else
|
||||
# 直接导入
|
||||
if docker load -i "$file"; then
|
||||
echo "✓ 镜像导入成功"
|
||||
else
|
||||
echo "✗ 镜像导入失败"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "镜像导入完成"
|
||||
echo "可用镜像列表:"
|
||||
docker images
|
||||
EOF
|
||||
|
||||
chmod +x "$import_script"
|
||||
|
||||
# 显示总结
|
||||
log_success "镜像导出完成!"
|
||||
log "导出的文件:"
|
||||
for file in "${EXPORTED_FILES[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
size=$(du -h "$file" | cut -f1)
|
||||
log " - $(basename "$file") ($size)"
|
||||
fi
|
||||
done
|
||||
|
||||
log "生成的文件:"
|
||||
log " - images-manifest.txt (镜像清单)"
|
||||
log " - import-images.sh (导入脚本)"
|
||||
|
||||
total_size=$(du -sh "$EXPORT_DIR" | cut -f1)
|
||||
log "总大小: $total_size"
|
||||
|
||||
log_success "所有文件已保存到: $EXPORT_DIR"
|
||||
496
docker/import-and-verify.sh
Executable file
496
docker/import-and-verify.sh
Executable file
@@ -0,0 +1,496 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像导入和验证脚本
|
||||
# 用于导入Docker镜像并验证其完整性和兼容性
|
||||
|
||||
set -e
|
||||
|
||||
# 脚本配置
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEFAULT_IMAGES_DIR="$(dirname "$SCRIPT_DIR")/docker-images"
|
||||
LOG_FILE="${DEFAULT_IMAGES_DIR}/import-verify.log"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] ⚠${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Docker镜像导入和验证脚本
|
||||
|
||||
用法: $0 [选项] [镜像目录]
|
||||
|
||||
选项:
|
||||
-h, --help 显示此帮助信息
|
||||
-v, --verify-only 仅验证,不导入
|
||||
-f, --force 强制导入,覆盖现有镜像
|
||||
-c, --check-manifest 检查清单文件
|
||||
--skip-compatibility 跳过兼容性检查
|
||||
--parallel N 并行导入数量 (默认: 2)
|
||||
--test-run 导入后运行测试容器
|
||||
|
||||
参数:
|
||||
镜像目录 包含Docker镜像文件的目录 (默认: ./docker-images)
|
||||
|
||||
示例:
|
||||
$0 # 导入默认目录中的所有镜像
|
||||
$0 -v /path/to/images # 仅验证镜像文件
|
||||
$0 -f --test-run # 强制导入并测试
|
||||
$0 --check-manifest # 检查清单文件完整性
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
VERIFY_ONLY=false
|
||||
FORCE_IMPORT=false
|
||||
CHECK_MANIFEST=false
|
||||
SKIP_COMPATIBILITY=false
|
||||
PARALLEL_JOBS=2
|
||||
TEST_RUN=false
|
||||
IMAGES_DIR="$DEFAULT_IMAGES_DIR"
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-v|--verify-only)
|
||||
VERIFY_ONLY=true
|
||||
shift
|
||||
;;
|
||||
-f|--force)
|
||||
FORCE_IMPORT=true
|
||||
shift
|
||||
;;
|
||||
-c|--check-manifest)
|
||||
CHECK_MANIFEST=true
|
||||
shift
|
||||
;;
|
||||
--skip-compatibility)
|
||||
SKIP_COMPATIBILITY=true
|
||||
shift
|
||||
;;
|
||||
--parallel)
|
||||
PARALLEL_JOBS="$2"
|
||||
if [[ ! "$PARALLEL_JOBS" =~ ^[1-9][0-9]*$ ]]; then
|
||||
log_error "并行任务数必须是正整数"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
--test-run)
|
||||
TEST_RUN=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
IMAGES_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查输入目录
|
||||
if [[ ! -d "$IMAGES_DIR" ]]; then
|
||||
log_error "镜像目录不存在: $IMAGES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log "开始Docker镜像导入和验证..."
|
||||
log "镜像目录: $IMAGES_DIR"
|
||||
log "仅验证: $VERIFY_ONLY"
|
||||
log "强制导入: $FORCE_IMPORT"
|
||||
log "并行任务: $PARALLEL_JOBS"
|
||||
|
||||
# 检查Docker是否运行
|
||||
check_docker() {
|
||||
log "检查Docker环境..."
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
log_error "Docker未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local docker_version=$(docker --version)
|
||||
log_success "Docker环境正常: $docker_version"
|
||||
|
||||
# 检查系统架构
|
||||
local system_arch=$(uname -m)
|
||||
log "系统架构: $system_arch"
|
||||
|
||||
if [[ "$system_arch" != "x86_64" ]] && [[ "$SKIP_COMPATIBILITY" == false ]]; then
|
||||
log_warning "系统架构不是x86_64,可能存在兼容性问题"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查清单文件
|
||||
check_manifest_file() {
|
||||
if [[ "$CHECK_MANIFEST" == false ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local manifest_file="${IMAGES_DIR}/images-manifest.txt"
|
||||
|
||||
log "检查清单文件..."
|
||||
|
||||
if [[ ! -f "$manifest_file" ]]; then
|
||||
log_warning "清单文件不存在: $manifest_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "验证清单文件中的镜像..."
|
||||
|
||||
# 解析清单文件
|
||||
local current_file=""
|
||||
local expected_checksum=""
|
||||
local verification_failed=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^文件:\ (.+)$ ]]; then
|
||||
current_file="${BASH_REMATCH[1]}"
|
||||
elif [[ "$line" =~ ^SHA256:\ (.+)$ ]]; then
|
||||
expected_checksum="${BASH_REMATCH[1]}"
|
||||
|
||||
if [[ -n "$current_file" ]] && [[ -n "$expected_checksum" ]]; then
|
||||
local file_path="${IMAGES_DIR}/${current_file}"
|
||||
|
||||
if [[ -f "$file_path" ]]; then
|
||||
log "验证文件: $current_file"
|
||||
local actual_checksum=$(sha256sum "$file_path" | cut -d' ' -f1)
|
||||
|
||||
if [[ "$actual_checksum" == "$expected_checksum" ]]; then
|
||||
log_success "校验和匹配: $current_file"
|
||||
else
|
||||
log_error "校验和不匹配: $current_file"
|
||||
log_error "期望: $expected_checksum"
|
||||
log_error "实际: $actual_checksum"
|
||||
((verification_failed++))
|
||||
fi
|
||||
else
|
||||
log_error "文件不存在: $current_file"
|
||||
((verification_failed++))
|
||||
fi
|
||||
|
||||
current_file=""
|
||||
expected_checksum=""
|
||||
fi
|
||||
fi
|
||||
done < "$manifest_file"
|
||||
|
||||
if [[ $verification_failed -eq 0 ]]; then
|
||||
log_success "清单文件验证通过"
|
||||
return 0
|
||||
else
|
||||
log_error "清单文件验证失败,$verification_failed 个文件有问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证镜像文件
|
||||
verify_image_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
log "验证镜像文件: $filename"
|
||||
|
||||
# 检查文件是否存在
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log_error "文件不存在: $filename"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查文件大小
|
||||
local file_size=$(du -h "$file" | cut -f1)
|
||||
log "文件大小: $file_size"
|
||||
|
||||
# 验证文件完整性
|
||||
if [[ "$file" == *.tar.gz ]]; then
|
||||
# 验证gzip文件
|
||||
if gzip -t "$file" 2>/dev/null; then
|
||||
log_success "压缩文件完整性验证通过: $filename"
|
||||
else
|
||||
log_error "压缩文件完整性验证失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
elif [[ "$file" == *.tar ]]; then
|
||||
# 验证tar文件
|
||||
if tar -tf "$file" >/dev/null 2>&1; then
|
||||
log_success "tar文件完整性验证通过: $filename"
|
||||
else
|
||||
log_error "tar文件完整性验证失败: $filename"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_warning "未知文件类型,跳过验证: $filename"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 导入镜像文件
|
||||
import_image_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
log "导入镜像文件: $filename"
|
||||
|
||||
# 首先验证文件
|
||||
if ! verify_image_file "$file"; then
|
||||
log_error "文件验证失败,跳过导入: $filename"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查是否需要强制导入
|
||||
local import_args=""
|
||||
if [[ "$FORCE_IMPORT" == true ]]; then
|
||||
import_args="--quiet"
|
||||
fi
|
||||
|
||||
# 导入镜像
|
||||
local import_output
|
||||
if [[ "$file" == *.tar.gz ]]; then
|
||||
# 解压并导入
|
||||
import_output=$(gunzip -c "$file" | docker load 2>&1)
|
||||
else
|
||||
# 直接导入
|
||||
import_output=$(docker load -i "$file" 2>&1)
|
||||
fi
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
log_success "镜像导入成功: $filename"
|
||||
|
||||
# 提取导入的镜像名称
|
||||
local imported_image=$(echo "$import_output" | grep "Loaded image" | sed 's/Loaded image: //')
|
||||
if [[ -n "$imported_image" ]]; then
|
||||
log "导入的镜像: $imported_image"
|
||||
|
||||
# 验证镜像架构
|
||||
if [[ "$SKIP_COMPATIBILITY" == false ]]; then
|
||||
verify_image_architecture "$imported_image"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
log_error "镜像导入失败: $filename"
|
||||
log_error "错误信息: $import_output"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证镜像架构
|
||||
verify_image_architecture() {
|
||||
local image="$1"
|
||||
|
||||
log "验证镜像架构: $image"
|
||||
|
||||
# 获取镜像信息
|
||||
local image_info=$(docker image inspect "$image" 2>/dev/null)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log_error "无法获取镜像信息: $image"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 提取架构信息
|
||||
local architecture=$(echo "$image_info" | grep -o '"Architecture":"[^"]*"' | cut -d'"' -f4)
|
||||
local os=$(echo "$image_info" | grep -o '"Os":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
log "镜像架构: $os/$architecture"
|
||||
|
||||
# 检查架构兼容性
|
||||
local system_arch=$(uname -m)
|
||||
local expected_arch="amd64"
|
||||
|
||||
if [[ "$system_arch" == "x86_64" ]]; then
|
||||
expected_arch="amd64"
|
||||
elif [[ "$system_arch" == "aarch64" ]]; then
|
||||
expected_arch="arm64"
|
||||
fi
|
||||
|
||||
if [[ "$architecture" == "$expected_arch" ]]; then
|
||||
log_success "镜像架构兼容: $architecture"
|
||||
return 0
|
||||
else
|
||||
log_warning "镜像架构可能不兼容: $architecture (系统: $system_arch)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试镜像运行
|
||||
test_image_run() {
|
||||
local image="$1"
|
||||
|
||||
log "测试镜像运行: $image"
|
||||
|
||||
# 根据镜像类型选择测试命令
|
||||
local test_command=""
|
||||
local container_name="test-$(echo "$image" | sed 's/[\/:]/_/g')-$$"
|
||||
|
||||
case "$image" in
|
||||
*mysql*)
|
||||
test_command="docker run --rm --name $container_name -e MYSQL_ROOT_PASSWORD=test -d $image"
|
||||
;;
|
||||
*redis*)
|
||||
test_command="docker run --rm --name $container_name -d $image"
|
||||
;;
|
||||
*meilisearch*)
|
||||
test_command="docker run --rm --name $container_name -e MEILI_MASTER_KEY=test -d $image"
|
||||
;;
|
||||
*knowledge-base-app*)
|
||||
# 应用镜像需要更复杂的测试
|
||||
log_warning "应用镜像测试需要完整环境,跳过单独测试"
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
log_warning "未知镜像类型,跳过运行测试: $image"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# 运行测试容器
|
||||
if eval "$test_command"; then
|
||||
log "测试容器启动成功: $container_name"
|
||||
|
||||
# 等待容器启动
|
||||
sleep 5
|
||||
|
||||
# 检查容器状态
|
||||
if docker ps | grep -q "$container_name"; then
|
||||
log_success "镜像运行测试通过: $image"
|
||||
|
||||
# 停止测试容器
|
||||
docker stop "$container_name" >/dev/null 2>&1 || true
|
||||
return 0
|
||||
else
|
||||
log_error "测试容器启动失败: $image"
|
||||
|
||||
# 显示容器日志
|
||||
docker logs "$container_name" 2>/dev/null || true
|
||||
docker rm "$container_name" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "无法启动测试容器: $image"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 处理单个镜像文件
|
||||
process_image_file() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
log "处理镜像文件: $filename"
|
||||
|
||||
if [[ "$VERIFY_ONLY" == true ]]; then
|
||||
verify_image_file "$file"
|
||||
return $?
|
||||
else
|
||||
if import_image_file "$file"; then
|
||||
# 如果需要测试运行
|
||||
if [[ "$TEST_RUN" == true ]]; then
|
||||
# 提取镜像名称进行测试
|
||||
local image_name=$(echo "$filename" | sed 's/\.tar.*$//' | sed 's/_/:/g')
|
||||
test_image_run "$image_name" || true
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主处理流程
|
||||
main() {
|
||||
check_docker
|
||||
check_manifest_file
|
||||
|
||||
# 查找镜像文件
|
||||
local image_files=($(find "$IMAGES_DIR" -name "*.tar*" -type f))
|
||||
|
||||
if [[ ${#image_files[@]} -eq 0 ]]; then
|
||||
log_error "在 $IMAGES_DIR 中没有找到镜像文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "找到 ${#image_files[@]} 个镜像文件"
|
||||
|
||||
# 处理镜像文件
|
||||
local processed=0
|
||||
local failed=0
|
||||
local total=${#image_files[@]}
|
||||
|
||||
# 使用并行处理
|
||||
export -f process_image_file verify_image_file import_image_file verify_image_architecture test_image_run
|
||||
export -f log log_success log_warning log_error
|
||||
export VERIFY_ONLY FORCE_IMPORT SKIP_COMPATIBILITY TEST_RUN LOG_FILE
|
||||
export RED GREEN YELLOW BLUE NC
|
||||
|
||||
for file in "${image_files[@]}"; do
|
||||
if process_image_file "$file"; then
|
||||
((processed++))
|
||||
else
|
||||
((failed++))
|
||||
fi
|
||||
done
|
||||
|
||||
# 显示导入的镜像
|
||||
if [[ "$VERIFY_ONLY" == false ]]; then
|
||||
log "当前Docker镜像列表:"
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
|
||||
fi
|
||||
|
||||
# 显示总结
|
||||
log_success "处理完成!"
|
||||
log "总文件数: $total"
|
||||
log "成功处理: $processed"
|
||||
log "失败数量: $failed"
|
||||
|
||||
if [[ $failed -gt 0 ]]; then
|
||||
log_warning "有 $failed 个文件处理失败,请检查日志"
|
||||
exit 1
|
||||
else
|
||||
log_success "所有文件处理成功"
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主流程
|
||||
main
|
||||
44
docker/init-storage.sh
Executable file
44
docker/init-storage.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 初始化存储目录脚本
|
||||
# 用于确保所有数据持久化目录存在并具有正确权限
|
||||
|
||||
set -e
|
||||
|
||||
echo "正在初始化存储目录结构..."
|
||||
|
||||
# 创建数据库存储目录
|
||||
mkdir -p storage/mysql
|
||||
chmod 755 storage/mysql
|
||||
|
||||
# 创建Redis存储目录
|
||||
mkdir -p storage/redis
|
||||
chmod 755 storage/redis
|
||||
|
||||
# 创建Meilisearch存储目录
|
||||
mkdir -p storage/meilisearch
|
||||
chmod 755 storage/meilisearch
|
||||
|
||||
# 创建应用存储目录
|
||||
mkdir -p storage/app/private/documents
|
||||
mkdir -p storage/app/private/markdown
|
||||
mkdir -p storage/app/public
|
||||
chmod -R 755 storage/app
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p storage/logs/app
|
||||
mkdir -p storage/logs/queue
|
||||
chmod -R 755 storage/logs
|
||||
|
||||
# 创建Laravel框架目录
|
||||
mkdir -p storage/framework/cache/data
|
||||
mkdir -p storage/framework/sessions
|
||||
mkdir -p storage/framework/testing
|
||||
mkdir -p storage/framework/views
|
||||
chmod -R 755 storage/framework
|
||||
|
||||
echo "存储目录结构初始化完成!"
|
||||
|
||||
# 显示目录结构
|
||||
echo "当前存储目录结构:"
|
||||
tree storage/ || ls -la storage/
|
||||
316
docker/monitor-services.sh
Executable file
316
docker/monitor-services.sh
Executable file
@@ -0,0 +1,316 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker服务监控脚本
|
||||
# 持续监控服务状态并在需要时采取行动
|
||||
|
||||
set -e
|
||||
|
||||
# 配置
|
||||
MONITOR_INTERVAL=${MONITOR_INTERVAL:-60} # 监控间隔(秒)
|
||||
MAX_RESTART_ATTEMPTS=${MAX_RESTART_ATTEMPTS:-3} # 最大重启尝试次数
|
||||
RESTART_COOLDOWN=${RESTART_COOLDOWN:-300} # 重启冷却时间(秒)
|
||||
LOG_FILE=${LOG_FILE:-"./storage/logs/monitor.log"}
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 创建日志目录
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
# 日志函数
|
||||
log_with_timestamp() {
|
||||
local level=$1
|
||||
local message=$2
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
log_with_timestamp "INFO" "${BLUE}$1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
log_with_timestamp "SUCCESS" "${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
log_with_timestamp "WARNING" "${YELLOW}$1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
log_with_timestamp "ERROR" "${RED}$1${NC}"
|
||||
}
|
||||
|
||||
# 重启计数器文件
|
||||
RESTART_COUNTER_DIR="./storage/logs/restart_counters"
|
||||
mkdir -p "$RESTART_COUNTER_DIR"
|
||||
|
||||
# 获取容器重启次数
|
||||
get_restart_count() {
|
||||
local container_name=$1
|
||||
local counter_file="$RESTART_COUNTER_DIR/${container_name}.count"
|
||||
|
||||
if [ -f "$counter_file" ]; then
|
||||
cat "$counter_file"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# 增加重启次数
|
||||
increment_restart_count() {
|
||||
local container_name=$1
|
||||
local counter_file="$RESTART_COUNTER_DIR/${container_name}.count"
|
||||
local current_count=$(get_restart_count "$container_name")
|
||||
local new_count=$((current_count + 1))
|
||||
|
||||
echo "$new_count" > "$counter_file"
|
||||
echo "$new_count"
|
||||
}
|
||||
|
||||
# 重置重启次数
|
||||
reset_restart_count() {
|
||||
local container_name=$1
|
||||
local counter_file="$RESTART_COUNTER_DIR/${container_name}.count"
|
||||
echo "0" > "$counter_file"
|
||||
}
|
||||
|
||||
# 检查容器是否需要重启
|
||||
should_restart_container() {
|
||||
local container_name=$1
|
||||
local restart_count=$(get_restart_count "$container_name")
|
||||
|
||||
if [ "$restart_count" -ge "$MAX_RESTART_ATTEMPTS" ]; then
|
||||
return 1 # 不应该重启
|
||||
else
|
||||
return 0 # 可以重启
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查容器健康状态
|
||||
check_container_health() {
|
||||
local container_name=$1
|
||||
local service_name=$2
|
||||
|
||||
# 检查容器是否运行
|
||||
if ! docker ps --format "table {{.Names}}" | grep -q "^${container_name}$"; then
|
||||
log_error "${service_name}容器未运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 检查容器健康状态
|
||||
local health_status=$(docker inspect --format='{{.State.Health.Status}}' ${container_name} 2>/dev/null || echo "no-healthcheck")
|
||||
|
||||
case $health_status in
|
||||
"healthy")
|
||||
# 如果容器健康,重置重启计数器
|
||||
reset_restart_count "$container_name"
|
||||
return 0
|
||||
;;
|
||||
"unhealthy")
|
||||
log_error "${service_name}容器健康检查失败"
|
||||
return 1
|
||||
;;
|
||||
"starting")
|
||||
log_warning "${service_name}容器正在启动中..."
|
||||
return 2
|
||||
;;
|
||||
"no-healthcheck")
|
||||
# 对于没有健康检查的容器,检查是否正在运行
|
||||
local container_status=$(docker inspect --format='{{.State.Status}}' ${container_name} 2>/dev/null || echo "unknown")
|
||||
if [ "$container_status" = "running" ]; then
|
||||
reset_restart_count "$container_name"
|
||||
return 0
|
||||
else
|
||||
log_error "${service_name}容器状态异常: ${container_status}"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_warning "${service_name}容器健康状态未知: ${health_status}"
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 重启容器
|
||||
restart_container() {
|
||||
local container_name=$1
|
||||
local service_name=$2
|
||||
|
||||
if ! should_restart_container "$container_name"; then
|
||||
log_error "${service_name}容器已达到最大重启次数限制,跳过重启"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local restart_count=$(increment_restart_count "$container_name")
|
||||
log_warning "${service_name}容器开始重启 (第${restart_count}次尝试)"
|
||||
|
||||
if docker restart "$container_name"; then
|
||||
log_info "${service_name}容器重启命令执行成功,等待启动..."
|
||||
sleep 30 # 等待容器启动
|
||||
return 0
|
||||
else
|
||||
log_error "${service_name}容器重启失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 监控单个服务
|
||||
monitor_service() {
|
||||
local container_name=$1
|
||||
local service_name=$2
|
||||
|
||||
check_container_health "$container_name" "$service_name"
|
||||
local health_result=$?
|
||||
|
||||
case $health_result in
|
||||
0)
|
||||
# 健康
|
||||
return 0
|
||||
;;
|
||||
1)
|
||||
# 不健康,尝试重启
|
||||
log_warning "${service_name}服务不健康,尝试重启..."
|
||||
restart_container "$container_name" "$service_name"
|
||||
return $?
|
||||
;;
|
||||
2)
|
||||
# 启动中或状态未知,继续监控
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 发送告警通知(可扩展)
|
||||
send_alert() {
|
||||
local message=$1
|
||||
local severity=$2
|
||||
|
||||
log_error "告警: $message"
|
||||
|
||||
# 这里可以添加更多告警方式,如:
|
||||
# - 发送邮件
|
||||
# - 发送到Slack
|
||||
# - 发送到监控系统
|
||||
# - 写入系统日志
|
||||
|
||||
# 示例:写入系统日志
|
||||
if command -v logger >/dev/null 2>&1; then
|
||||
logger -t "docker-monitor" -p user.error "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主监控循环
|
||||
main_monitor_loop() {
|
||||
log_info "Docker服务监控开始,监控间隔: ${MONITOR_INTERVAL}秒"
|
||||
|
||||
# 定义要监控的服务
|
||||
local services=(
|
||||
"knowledge_base_mysql:MySQL数据库"
|
||||
"knowledge_base_redis:Redis缓存"
|
||||
"knowledge_base_meilisearch:Meilisearch搜索"
|
||||
"knowledge_base_app:Web应用"
|
||||
"knowledge_base_queue:队列处理器"
|
||||
)
|
||||
|
||||
while true; do
|
||||
local failed_services=0
|
||||
local total_services=${#services[@]}
|
||||
|
||||
log_info "开始监控检查 (共${total_services}个服务)"
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
IFS=':' read -ra SERVICE_PARTS <<< "$service"
|
||||
local container_name="${SERVICE_PARTS[0]}"
|
||||
local service_name="${SERVICE_PARTS[1]}"
|
||||
|
||||
if ! monitor_service "$container_name" "$service_name"; then
|
||||
((failed_services++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $failed_services -gt 0 ]; then
|
||||
local message="监控检查完成,发现 ${failed_services}/${total_services} 个服务存在问题"
|
||||
log_warning "$message"
|
||||
|
||||
if [ $failed_services -ge $((total_services / 2)) ]; then
|
||||
send_alert "超过一半的服务出现问题: $message" "critical"
|
||||
fi
|
||||
else
|
||||
log_success "所有服务运行正常"
|
||||
fi
|
||||
|
||||
log_info "等待 ${MONITOR_INTERVAL} 秒后进行下次检查..."
|
||||
sleep "$MONITOR_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
log_info "监控脚本正在退出..."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 设置信号处理
|
||||
trap cleanup SIGINT SIGTERM
|
||||
|
||||
# 显示使用帮助
|
||||
show_help() {
|
||||
echo "Docker服务监控脚本"
|
||||
echo ""
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " -i, --interval SECONDS 监控间隔(默认: 60秒)"
|
||||
echo " -r, --max-restarts NUM 最大重启尝试次数(默认: 3次)"
|
||||
echo " -c, --cooldown SECONDS 重启冷却时间(默认: 300秒)"
|
||||
echo " -l, --log-file PATH 日志文件路径(默认: ./storage/logs/monitor.log)"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo ""
|
||||
echo "环境变量:"
|
||||
echo " MONITOR_INTERVAL 监控间隔"
|
||||
echo " MAX_RESTART_ATTEMPTS 最大重启尝试次数"
|
||||
echo " RESTART_COOLDOWN 重启冷却时间"
|
||||
echo " LOG_FILE 日志文件路径"
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-i|--interval)
|
||||
MONITOR_INTERVAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--max-restarts)
|
||||
MAX_RESTART_ATTEMPTS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--cooldown)
|
||||
RESTART_COOLDOWN="$2"
|
||||
shift 2
|
||||
;;
|
||||
-l|--log-file)
|
||||
LOG_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知选项: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 如果脚本被直接执行
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main_monitor_loop
|
||||
fi
|
||||
40
docker/mysql/my.cnf
Normal file
40
docker/mysql/my.cnf
Normal file
@@ -0,0 +1,40 @@
|
||||
# MySQL生产环境配置
|
||||
[mysqld]
|
||||
# 基础配置
|
||||
default-authentication-plugin=mysql_native_password
|
||||
character-set-server=utf8mb4
|
||||
collation-server=utf8mb4_unicode_ci
|
||||
default-time-zone='+08:00'
|
||||
|
||||
# 性能优化
|
||||
innodb_buffer_pool_size=512M
|
||||
innodb_log_file_size=128M
|
||||
innodb_flush_log_at_trx_commit=2
|
||||
innodb_flush_method=O_DIRECT
|
||||
|
||||
# 连接配置
|
||||
max_connections=200
|
||||
max_connect_errors=1000
|
||||
wait_timeout=600
|
||||
interactive_timeout=600
|
||||
|
||||
# 查询缓存
|
||||
query_cache_type=1
|
||||
query_cache_size=64M
|
||||
query_cache_limit=2M
|
||||
|
||||
# 日志配置
|
||||
slow_query_log=1
|
||||
slow_query_log_file=/var/log/mysql/slow.log
|
||||
long_query_time=2
|
||||
log_queries_not_using_indexes=1
|
||||
|
||||
# 安全配置
|
||||
skip-name-resolve
|
||||
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO
|
||||
|
||||
[mysql]
|
||||
default-character-set=utf8mb4
|
||||
|
||||
[client]
|
||||
default-character-set=utf8mb4
|
||||
48
docker/octane-health-check.sh
Normal file
48
docker/octane-health-check.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Octane HTTP服务器健康检查脚本
|
||||
# 用于Docker健康检查,支持Swoole和RoadRunner
|
||||
|
||||
set -e
|
||||
|
||||
# 检查Octane进程是否运行
|
||||
if ! pgrep -f "octane:start" > /dev/null; then
|
||||
echo "Octane HTTP服务器进程未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查HTTP端口是否可访问
|
||||
OCTANE_PORT=${OCTANE_PORT:-8000}
|
||||
if ! curl -f -s "http://localhost:${OCTANE_PORT}/health" > /dev/null 2>&1; then
|
||||
echo "Octane HTTP服务器端口 ${OCTANE_PORT} 不可访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Laravel应用是否可以连接到数据库和Redis
|
||||
if ! php -r "
|
||||
try {
|
||||
require_once '/var/www/html/vendor/autoload.php';
|
||||
\$app = require_once '/var/www/html/bootstrap/app.php';
|
||||
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
// 检查数据库连接
|
||||
\Illuminate\Support\Facades\DB::connection()->getPdo();
|
||||
|
||||
// 检查Redis连接
|
||||
if (config('cache.default') === 'redis') {
|
||||
\Illuminate\Support\Facades\Cache::store('redis')->put('octane_health_check', 'ok', 10);
|
||||
\Illuminate\Support\Facades\Cache::store('redis')->forget('octane_health_check');
|
||||
}
|
||||
|
||||
echo 'OK';
|
||||
} catch (Exception \$e) {
|
||||
echo 'ERROR: ' . \$e->getMessage();
|
||||
exit(1);
|
||||
}
|
||||
"; then
|
||||
echo "Octane服务器依赖服务检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Octane HTTP服务器健康检查通过"
|
||||
exit 0
|
||||
165
docker/one-click-deploy.sh
Executable file
165
docker/one-click-deploy.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 一键部署脚本 - Laravel知识库系统
|
||||
# 整合镜像导出、压缩、传输和部署功能
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] ✓${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ✗${NC} $1"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
一键部署脚本 - Laravel知识库系统
|
||||
|
||||
用法: $0 [模式] [选项]
|
||||
|
||||
模式:
|
||||
export 导出Docker镜像
|
||||
deploy 部署到OpenEuler服务器
|
||||
full 完整流程 (导出+部署)
|
||||
|
||||
选项:
|
||||
-h, --help 显示帮助信息
|
||||
-c, --compress 启用压缩
|
||||
-v, --verify 验证完整性
|
||||
--server HOST 目标服务器地址
|
||||
--user USER SSH用户名
|
||||
--deploy-dir DIR 部署目录
|
||||
|
||||
示例:
|
||||
$0 export -c -v # 导出并压缩镜像
|
||||
$0 deploy --server 192.168.1.100 # 部署到服务器
|
||||
$0 full -c --server 192.168.1.100 # 完整流程
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# 默认配置
|
||||
MODE=""
|
||||
COMPRESS=false
|
||||
VERIFY=false
|
||||
SERVER=""
|
||||
USER="deploy"
|
||||
DEPLOY_DIR="/opt/knowledge-base"
|
||||
|
||||
# 解析参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
export|deploy|full)
|
||||
MODE="$1"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-c|--compress)
|
||||
COMPRESS=true
|
||||
shift
|
||||
;;
|
||||
-v|--verify)
|
||||
VERIFY=true
|
||||
shift
|
||||
;;
|
||||
--server)
|
||||
SERVER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--user)
|
||||
USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--deploy-dir)
|
||||
DEPLOY_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
log_error "未知参数: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$MODE" ]]; then
|
||||
log_error "请指定模式: export, deploy, 或 full"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 导出镜像
|
||||
export_images() {
|
||||
log "开始导出Docker镜像..."
|
||||
|
||||
local export_args=""
|
||||
if [[ "$COMPRESS" == true ]]; then
|
||||
export_args="$export_args -c"
|
||||
fi
|
||||
if [[ "$VERIFY" == true ]]; then
|
||||
export_args="$export_args -v"
|
||||
fi
|
||||
|
||||
if "$SCRIPT_DIR/export-images.sh" $export_args; then
|
||||
log_success "镜像导出完成"
|
||||
else
|
||||
log_error "镜像导出失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 部署到服务器
|
||||
deploy_to_server() {
|
||||
if [[ -z "$SERVER" ]]; then
|
||||
log_error "请指定服务器地址 --server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "开始部署到服务器: $SERVER"
|
||||
|
||||
# 传输文件
|
||||
log "传输部署文件..."
|
||||
scp -r "${PROJECT_ROOT}/docker-images" "${USER}@${SERVER}:/tmp/"
|
||||
scp "${SCRIPT_DIR}/deploy-to-openeuler.sh" "${USER}@${SERVER}:/tmp/"
|
||||
|
||||
# 远程部署
|
||||
log "执行远程部署..."
|
||||
ssh "${USER}@${SERVER}" "sudo /tmp/deploy-to-openeuler.sh -d $DEPLOY_DIR /tmp/docker-images"
|
||||
|
||||
log_success "部署完成"
|
||||
}
|
||||
|
||||
# 主流程
|
||||
case "$MODE" in
|
||||
export)
|
||||
export_images
|
||||
;;
|
||||
deploy)
|
||||
deploy_to_server
|
||||
;;
|
||||
full)
|
||||
export_images
|
||||
deploy_to_server
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "操作完成!"
|
||||
44
docker/php/php.ini
Normal file
44
docker/php/php.ini
Normal file
@@ -0,0 +1,44 @@
|
||||
# PHP生产环境配置
|
||||
|
||||
# 基础设置
|
||||
memory_limit = 256M
|
||||
max_execution_time = 60
|
||||
max_input_time = 60
|
||||
post_max_size = 100M
|
||||
upload_max_filesize = 100M
|
||||
max_file_uploads = 20
|
||||
|
||||
# 错误报告(生产环境)
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
log_errors = On
|
||||
error_log = /var/log/php_errors.log
|
||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||
|
||||
# 会话设置
|
||||
session.save_handler = redis
|
||||
session.save_path = "tcp://redis:6379"
|
||||
session.gc_maxlifetime = 1440
|
||||
session.cookie_lifetime = 0
|
||||
session.cookie_secure = 0
|
||||
session.cookie_httponly = 1
|
||||
session.use_strict_mode = 1
|
||||
|
||||
# OPcache设置
|
||||
opcache.enable = 1
|
||||
opcache.enable_cli = 1
|
||||
opcache.memory_consumption = 128
|
||||
opcache.interned_strings_buffer = 8
|
||||
opcache.max_accelerated_files = 4000
|
||||
opcache.revalidate_freq = 2
|
||||
opcache.fast_shutdown = 1
|
||||
opcache.validate_timestamps = 0
|
||||
|
||||
# 时区设置
|
||||
date.timezone = Asia/Shanghai
|
||||
|
||||
# 其他设置
|
||||
expose_php = Off
|
||||
allow_url_fopen = On
|
||||
allow_url_include = Off
|
||||
default_charset = "UTF-8"
|
||||
41
docker/queue-health-check.sh
Executable file
41
docker/queue-health-check.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 队列处理器健康检查脚本
|
||||
# 用于Docker健康检查
|
||||
|
||||
set -e
|
||||
|
||||
# 检查队列进程是否运行
|
||||
if ! pgrep -f "queue:work" > /dev/null; then
|
||||
echo "队列处理器进程未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Laravel应用是否可以连接到数据库和Redis
|
||||
if ! php -r "
|
||||
try {
|
||||
require_once '/var/www/html/vendor/autoload.php';
|
||||
\$app = require_once '/var/www/html/bootstrap/app.php';
|
||||
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
// 检查数据库连接
|
||||
\Illuminate\Support\Facades\DB::connection()->getPdo();
|
||||
|
||||
// 检查Redis连接
|
||||
if (config('cache.default') === 'redis') {
|
||||
\Illuminate\Support\Facades\Cache::store('redis')->put('queue_health_check', 'ok', 10);
|
||||
\Illuminate\Support\Facades\Cache::store('redis')->forget('queue_health_check');
|
||||
}
|
||||
|
||||
echo 'OK';
|
||||
} catch (Exception \$e) {
|
||||
echo 'ERROR: ' . \$e->getMessage();
|
||||
exit(1);
|
||||
}
|
||||
"; then
|
||||
echo "队列处理器依赖服务检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "队列处理器健康检查通过"
|
||||
exit 0
|
||||
53
docker/redis/redis.conf
Normal file
53
docker/redis/redis.conf
Normal file
@@ -0,0 +1,53 @@
|
||||
# Redis生产环境配置
|
||||
|
||||
# 网络配置
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
timeout 300
|
||||
tcp-keepalive 60
|
||||
|
||||
# 内存配置
|
||||
maxmemory 512mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir /data
|
||||
|
||||
# AOF配置
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 日志配置
|
||||
loglevel notice
|
||||
logfile ""
|
||||
|
||||
# 安全配置
|
||||
protected-mode no
|
||||
# requirepass your_redis_password_here
|
||||
|
||||
# 性能配置
|
||||
tcp-backlog 511
|
||||
databases 16
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
|
||||
# 客户端配置
|
||||
maxclients 10000
|
||||
|
||||
# 慢日志配置
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# 延迟监控
|
||||
latency-monitor-threshold 100
|
||||
400
docker/setup-env.sh
Executable file
400
docker/setup-env.sh
Executable file
@@ -0,0 +1,400 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 环境配置设置脚本
|
||||
# 用于初始化和配置Docker部署环境
|
||||
|
||||
set -e
|
||||
|
||||
echo "==================================="
|
||||
echo "Docker环境配置设置"
|
||||
echo "==================================="
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 默认值
|
||||
DEFAULT_ENV="production"
|
||||
DEFAULT_DB_PASSWORD="secure_password_$(date +%s)"
|
||||
DEFAULT_MEILISEARCH_KEY="master_key_$(openssl rand -hex 16)"
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " -e, --env ENV 设置环境类型 (production|development) [默认: production]"
|
||||
echo " -i, --interactive 交互式配置"
|
||||
echo " -f, --force 强制覆盖现有配置文件"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 -e production 设置生产环境"
|
||||
echo " $0 -e development 设置开发环境"
|
||||
echo " $0 -i 交互式配置"
|
||||
}
|
||||
|
||||
# 解析命令行参数
|
||||
ENV_TYPE="$DEFAULT_ENV"
|
||||
INTERACTIVE=false
|
||||
FORCE=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-e|--env)
|
||||
ENV_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-i|--interactive)
|
||||
INTERACTIVE=true
|
||||
shift
|
||||
;;
|
||||
-f|--force)
|
||||
FORCE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知选项: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 验证环境类型
|
||||
if [[ "$ENV_TYPE" != "production" && "$ENV_TYPE" != "development" ]]; then
|
||||
echo -e "${RED}错误: 环境类型必须是 'production' 或 'development'${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}配置环境类型: $ENV_TYPE${NC}"
|
||||
|
||||
# 交互式配置
|
||||
if [ "$INTERACTIVE" = true ]; then
|
||||
echo ""
|
||||
echo "交互式配置模式"
|
||||
echo "-----------------------------------"
|
||||
|
||||
read -p "应用名称 [知识库系统]: " app_name
|
||||
app_name=${app_name:-"知识库系统"}
|
||||
|
||||
read -p "应用URL [http://localhost]: " app_url
|
||||
app_url=${app_url:-"http://localhost"}
|
||||
|
||||
read -p "数据库名称 [knowledge_base]: " db_name
|
||||
db_name=${db_name:-"knowledge_base"}
|
||||
|
||||
read -p "数据库用户名 [knowledge_user]: " db_user
|
||||
db_user=${db_user:-"knowledge_user"}
|
||||
|
||||
read -s -p "数据库密码: " db_password
|
||||
echo ""
|
||||
|
||||
read -s -p "Meilisearch主密钥: " meilisearch_key
|
||||
echo ""
|
||||
|
||||
if [ "$ENV_TYPE" = "production" ]; then
|
||||
read -p "SMTP主机: " mail_host
|
||||
read -p "SMTP端口 [587]: " mail_port
|
||||
mail_port=${mail_port:-587}
|
||||
read -p "SMTP用户名: " mail_username
|
||||
read -s -p "SMTP密码: " mail_password
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
# 非交互式配置,使用默认值
|
||||
app_name="知识库系统"
|
||||
app_url="http://localhost"
|
||||
db_name="knowledge_base"
|
||||
db_user="knowledge_user"
|
||||
db_password="$DEFAULT_DB_PASSWORD"
|
||||
meilisearch_key="$DEFAULT_MEILISEARCH_KEY"
|
||||
fi
|
||||
|
||||
# 生成APP_KEY
|
||||
echo ""
|
||||
echo "生成应用密钥..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
if command -v php >/dev/null 2>&1; then
|
||||
# 如果有PHP,使用Laravel生成密钥
|
||||
if [ -f "artisan" ]; then
|
||||
app_key=$(php artisan key:generate --show)
|
||||
echo -e "${GREEN}✓ 使用Laravel生成APP_KEY${NC}"
|
||||
else
|
||||
# 生成base64编码的随机密钥
|
||||
app_key="base64:$(openssl rand -base64 32)"
|
||||
echo -e "${GREEN}✓ 使用OpenSSL生成APP_KEY${NC}"
|
||||
fi
|
||||
else
|
||||
# 生成base64编码的随机密钥
|
||||
app_key="base64:$(openssl rand -base64 32)"
|
||||
echo -e "${GREEN}✓ 使用OpenSSL生成APP_KEY${NC}"
|
||||
fi
|
||||
|
||||
# 确定环境文件名
|
||||
if [ "$ENV_TYPE" = "production" ]; then
|
||||
env_file=".env.production"
|
||||
compose_file="docker-compose.yml"
|
||||
else
|
||||
env_file=".env.development"
|
||||
compose_file="docker-compose.dev.yml"
|
||||
app_name="${app_name}-开发"
|
||||
db_name="${db_name}_dev"
|
||||
db_user="dev_user"
|
||||
app_url="http://localhost:8000"
|
||||
fi
|
||||
|
||||
# 检查文件是否存在
|
||||
if [ -f "$env_file" ] && [ "$FORCE" = false ]; then
|
||||
echo -e "${YELLOW}警告: $env_file 已存在${NC}"
|
||||
read -p "是否覆盖? (y/N): " overwrite
|
||||
if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then
|
||||
echo "取消操作"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建环境文件
|
||||
echo ""
|
||||
echo "创建环境配置文件..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
cat > "$env_file" << EOF
|
||||
# $ENV_TYPE 环境配置
|
||||
# 由 setup-env.sh 自动生成于 $(date)
|
||||
|
||||
APP_NAME="$app_name"
|
||||
APP_ENV=$ENV_TYPE
|
||||
APP_KEY=$app_key
|
||||
APP_DEBUG=$([ "$ENV_TYPE" = "development" ] && echo "true" || echo "false")
|
||||
APP_URL=$app_url
|
||||
|
||||
APP_LOCALE=zh_CN
|
||||
APP_FALLBACK_LOCALE=zh_CN
|
||||
APP_FAKER_LOCALE=zh_CN
|
||||
|
||||
BCRYPT_ROUNDS=$([ "$ENV_TYPE" = "development" ] && echo "10" || echo "12")
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_LEVEL=$([ "$ENV_TYPE" = "development" ] && echo "debug" || echo "info")
|
||||
|
||||
# 数据库配置 - 使用Docker容器名称进行服务间通信
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=$db_name
|
||||
DB_USERNAME=$db_user
|
||||
DB_PASSWORD=$db_password
|
||||
|
||||
# 会话和缓存配置
|
||||
SESSION_DRIVER=redis
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
|
||||
CACHE_STORE=redis
|
||||
CACHE_PREFIX=$([ "$ENV_TYPE" = "development" ] && echo "kb_dev_cache" || echo "kb_cache")
|
||||
|
||||
# Redis配置 - 使用Docker容器名称进行服务间通信
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 队列配置 - 使用Redis作为队列驱动
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
# 文件系统配置
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
EOF
|
||||
|
||||
# 添加邮件配置
|
||||
if [ "$ENV_TYPE" = "production" ] && [ -n "$mail_host" ]; then
|
||||
cat >> "$env_file" << EOF
|
||||
# 邮件配置 - 生产环境SMTP设置
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=$mail_host
|
||||
MAIL_PORT=${mail_port:-587}
|
||||
MAIL_USERNAME=$mail_username
|
||||
MAIL_PASSWORD=$mail_password
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS="noreply@your-domain.com"
|
||||
MAIL_FROM_NAME="\${APP_NAME}"
|
||||
|
||||
EOF
|
||||
else
|
||||
cat >> "$env_file" << EOF
|
||||
# 邮件配置 - $([ "$ENV_TYPE" = "development" ] && echo "开发环境使用日志" || echo "生产环境SMTP设置")
|
||||
MAIL_MAILER=$([ "$ENV_TYPE" = "development" ] && echo "log" || echo "smtp")
|
||||
$([ "$ENV_TYPE" = "production" ] && cat << PROD_MAIL
|
||||
MAIL_HOST=your-smtp-host.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=your-email@domain.com
|
||||
MAIL_PASSWORD=your-email-password-change-this
|
||||
MAIL_ENCRYPTION=tls
|
||||
PROD_MAIL
|
||||
)
|
||||
MAIL_FROM_ADDRESS="$([ "$ENV_TYPE" = "development" ] && echo "dev@knowledge-base.local" || echo "noreply@your-domain.com")"
|
||||
MAIL_FROM_NAME="\${APP_NAME}"
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 添加Meilisearch配置
|
||||
cat >> "$env_file" << EOF
|
||||
# Meilisearch配置 - 使用Docker容器名称进行服务间通信
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
MEILISEARCH_KEY=$meilisearch_key
|
||||
|
||||
# 文档转换配置
|
||||
DOCUMENT_CONVERSION_DRIVER=pandoc
|
||||
PANDOC_PATH=/usr/bin/pandoc
|
||||
CONVERSION_TIMEOUT=300
|
||||
CONVERSION_QUEUE=documents
|
||||
CONVERSION_RETRY_TIMES=3
|
||||
CONVERSION_RETRY_DELAY=60
|
||||
|
||||
# Markdown配置
|
||||
MARKDOWN_RENDERER=commonmark
|
||||
MARKDOWN_SANITIZE=true
|
||||
MARKDOWN_PREVIEW_LENGTH=500
|
||||
MARKDOWN_MAX_FILE_SIZE=10485760
|
||||
|
||||
# 存储配置
|
||||
DOCUMENTS_DISK=documents
|
||||
MARKDOWN_DISK=markdown
|
||||
STORAGE_ORGANIZE_BY_DATE=true
|
||||
EOF
|
||||
|
||||
# 添加开发环境特定配置
|
||||
if [ "$ENV_TYPE" = "development" ]; then
|
||||
cat >> "$env_file" << EOF
|
||||
|
||||
# 开发工具配置
|
||||
TELESCOPE_ENABLED=true
|
||||
DEBUGBAR_ENABLED=true
|
||||
VITE_APP_NAME="\${APP_NAME}"
|
||||
|
||||
# 开发环境特定配置
|
||||
PHP_IDE_CONFIG=serverName=knowledge-base-dev
|
||||
XDEBUG_MODE=develop,debug
|
||||
XDEBUG_CONFIG=client_host=host.docker.internal client_port=9003
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ 环境配置文件已创建: $env_file${NC}"
|
||||
|
||||
# 创建.env符号链接
|
||||
if [ "$FORCE" = true ] || [ ! -f ".env" ]; then
|
||||
ln -sf "$env_file" .env
|
||||
echo -e "${GREEN}✓ 已创建 .env 符号链接指向 $env_file${NC}"
|
||||
fi
|
||||
|
||||
# 创建存储目录
|
||||
echo ""
|
||||
echo "创建存储目录..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
if [ "$ENV_TYPE" = "development" ]; then
|
||||
storage_dirs=(
|
||||
"storage/dev/mysql"
|
||||
"storage/dev/redis"
|
||||
"storage/dev/meilisearch"
|
||||
"storage/dev/app"
|
||||
"storage/dev/app/private/documents"
|
||||
"storage/dev/app/public"
|
||||
"storage/dev/logs"
|
||||
"storage/dev/logs/app"
|
||||
"storage/dev/logs/queue"
|
||||
)
|
||||
else
|
||||
storage_dirs=(
|
||||
"storage/mysql"
|
||||
"storage/redis"
|
||||
"storage/meilisearch"
|
||||
"storage/app"
|
||||
"storage/app/private/documents"
|
||||
"storage/app/public"
|
||||
"storage/logs"
|
||||
"storage/logs/app"
|
||||
"storage/logs/queue"
|
||||
)
|
||||
fi
|
||||
|
||||
for dir in "${storage_dirs[@]}"; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
mkdir -p "$dir"
|
||||
echo -e "${GREEN}✓ 创建目录: $dir${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}目录已存在: $dir${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
# 设置目录权限
|
||||
echo ""
|
||||
echo "设置目录权限..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# Laravel需要写入权限的目录
|
||||
laravel_dirs=(
|
||||
"storage"
|
||||
"bootstrap/cache"
|
||||
)
|
||||
|
||||
for dir in "${laravel_dirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
chmod -R 775 "$dir"
|
||||
echo -e "${GREEN}✓ 设置权限: $dir${NC}"
|
||||
fi
|
||||
done
|
||||
|
||||
# 显示配置摘要
|
||||
echo ""
|
||||
echo "==================================="
|
||||
echo "配置摘要"
|
||||
echo "==================================="
|
||||
|
||||
echo "环境类型: $ENV_TYPE"
|
||||
echo "配置文件: $env_file"
|
||||
echo "Compose文件: $compose_file"
|
||||
echo "应用名称: $app_name"
|
||||
echo "应用URL: $app_url"
|
||||
echo "数据库名: $db_name"
|
||||
echo "数据库用户: $db_user"
|
||||
echo "APP_KEY: ${app_key:0:20}..."
|
||||
echo "Meilisearch密钥: ${meilisearch_key:0:20}..."
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
echo "下一步操作"
|
||||
echo "==================================="
|
||||
|
||||
echo "1. 验证环境配置:"
|
||||
echo " ./docker/validate-env.sh"
|
||||
echo ""
|
||||
echo "2. 启动Docker服务:"
|
||||
if [ "$ENV_TYPE" = "development" ]; then
|
||||
echo " docker-compose -f docker-compose.dev.yml up -d"
|
||||
else
|
||||
echo " docker-compose up -d"
|
||||
fi
|
||||
echo ""
|
||||
echo "3. 测试网络连接:"
|
||||
echo " ./docker/test-network.sh"
|
||||
echo ""
|
||||
echo "4. 初始化应用:"
|
||||
echo " docker exec knowledge_base_app php artisan migrate"
|
||||
echo " docker exec knowledge_base_app php artisan db:seed"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ 环境配置完成!${NC}"
|
||||
116
docker/start-production.sh
Executable file
116
docker/start-production.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laravel知识库系统 - 生产环境启动脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 启动Laravel知识库系统生产环境..."
|
||||
|
||||
# 检查必要文件
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "❌ 错误: .env文件不存在"
|
||||
echo "请复制.env.production为.env并配置相应参数"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
echo "❌ 错误: docker-compose.yml文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建必要的目录
|
||||
echo "📁 创建存储目录..."
|
||||
mkdir -p storage/mysql
|
||||
mkdir -p storage/redis
|
||||
mkdir -p storage/meilisearch
|
||||
mkdir -p storage/logs/app
|
||||
mkdir -p storage/logs/queue
|
||||
mkdir -p storage/app/public
|
||||
mkdir -p storage/app/documents
|
||||
mkdir -p storage/app/markdown
|
||||
|
||||
# 设置目录权限
|
||||
echo "🔐 设置目录权限..."
|
||||
chmod -R 755 storage/
|
||||
chmod -R 755 bootstrap/cache/
|
||||
|
||||
# 构建应用镜像
|
||||
echo "🏗️ 构建Docker镜像..."
|
||||
docker-compose build --no-cache app
|
||||
|
||||
# 启动服务
|
||||
echo "🔄 启动服务..."
|
||||
docker-compose up -d
|
||||
|
||||
# 等待服务启动
|
||||
echo "⏳ 等待服务启动..."
|
||||
sleep 30
|
||||
|
||||
# 检查服务状态
|
||||
echo "🔍 检查服务状态..."
|
||||
docker-compose ps
|
||||
|
||||
# 运行Laravel初始化命令
|
||||
echo "🔧 运行Laravel初始化..."
|
||||
docker-compose exec app php artisan key:generate --force
|
||||
docker-compose exec app php artisan migrate --force
|
||||
docker-compose exec app php artisan config:cache
|
||||
docker-compose exec app php artisan route:cache
|
||||
docker-compose exec app php artisan view:cache
|
||||
docker-compose exec app php artisan storage:link
|
||||
|
||||
# 设置文件权限
|
||||
echo "📝 设置应用权限..."
|
||||
docker-compose exec app chown -R www-data:www-data /var/www/html/storage
|
||||
docker-compose exec app chown -R www-data:www-data /var/www/html/bootstrap/cache
|
||||
|
||||
# 健康检查
|
||||
echo "🏥 执行健康检查..."
|
||||
sleep 10
|
||||
|
||||
# 检查Web应用 (Swoole)
|
||||
if curl -f http://localhost:8000/health > /dev/null 2>&1; then
|
||||
echo "✅ Web应用健康检查通过"
|
||||
else
|
||||
# 如果没有专门的健康检查路由,尝试访问根路径
|
||||
if curl -f http://localhost:8000/ > /dev/null 2>&1; then
|
||||
echo "✅ Web应用健康检查通过"
|
||||
else
|
||||
echo "❌ Web应用健康检查失败"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查MySQL
|
||||
if docker-compose exec mysql mysqladmin ping -h localhost --silent; then
|
||||
echo "✅ MySQL健康检查通过"
|
||||
else
|
||||
echo "❌ MySQL健康检查失败"
|
||||
fi
|
||||
|
||||
# 检查Redis
|
||||
if docker-compose exec redis redis-cli ping > /dev/null 2>&1; then
|
||||
echo "✅ Redis健康检查通过"
|
||||
else
|
||||
echo "❌ Redis健康检查失败"
|
||||
fi
|
||||
|
||||
# 检查Meilisearch
|
||||
if curl -f http://localhost:7700/health > /dev/null 2>&1; then
|
||||
echo "✅ Meilisearch健康检查通过"
|
||||
else
|
||||
echo "❌ Meilisearch健康检查失败"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 生产环境启动完成!"
|
||||
echo ""
|
||||
echo "📊 服务访问地址:"
|
||||
echo " Web应用: http://localhost:8000"
|
||||
echo " Meilisearch: http://localhost:7700"
|
||||
echo ""
|
||||
echo "🔧 管理命令:"
|
||||
echo " 查看日志: docker-compose logs -f"
|
||||
echo " 停止服务: docker-compose down"
|
||||
echo " 重启服务: docker-compose restart"
|
||||
echo " 查看状态: docker-compose ps"
|
||||
echo ""
|
||||
325
docker/start-with-monitoring.sh
Executable file
325
docker/start-with-monitoring.sh
Executable file
@@ -0,0 +1,325 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 启动Docker服务并开始监控
|
||||
# 用于生产环境的完整启动流程
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查Docker和docker-compose是否可用
|
||||
check_prerequisites() {
|
||||
log_info "检查系统环境..."
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
log_error "Docker未安装或不在PATH中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker-compose >/dev/null 2>&1 && ! docker compose version >/dev/null 2>&1; then
|
||||
log_error "docker-compose未安装或不在PATH中"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
log_error "Docker服务未运行或无权限访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "系统环境检查通过"
|
||||
}
|
||||
|
||||
# 创建必要的目录
|
||||
create_directories() {
|
||||
log_info "创建必要的存储目录..."
|
||||
|
||||
local dirs=(
|
||||
"./storage/mysql"
|
||||
"./storage/redis"
|
||||
"./storage/meilisearch"
|
||||
"./storage/app"
|
||||
"./storage/app/private/documents"
|
||||
"./storage/app/public"
|
||||
"./storage/logs"
|
||||
"./storage/logs/app"
|
||||
"./storage/logs/queue"
|
||||
)
|
||||
|
||||
for dir in "${dirs[@]}"; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
mkdir -p "$dir"
|
||||
log_info "创建目录: $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# 设置适当的权限
|
||||
chmod -R 755 ./storage
|
||||
|
||||
log_success "存储目录创建完成"
|
||||
}
|
||||
|
||||
# 检查环境变量配置
|
||||
check_environment() {
|
||||
log_info "检查环境变量配置..."
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
log_warning ".env文件不存在,将从.env.example创建"
|
||||
if [ -f ".env.example" ]; then
|
||||
cp .env.example .env
|
||||
log_info "已从.env.example创建.env文件,请检查配置"
|
||||
else
|
||||
log_error ".env.example文件不存在,无法创建环境配置"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查关键环境变量
|
||||
local required_vars=("APP_KEY" "DB_PASSWORD")
|
||||
local missing_vars=()
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if ! grep -q "^${var}=" .env || grep -q "^${var}=$" .env; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_vars[@]} -gt 0 ]; then
|
||||
log_warning "以下环境变量未设置或为空:"
|
||||
for var in "${missing_vars[@]}"; do
|
||||
echo " - $var"
|
||||
done
|
||||
log_warning "请在.env文件中设置这些变量"
|
||||
fi
|
||||
|
||||
log_success "环境变量检查完成"
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
start_services() {
|
||||
log_info "启动Docker服务..."
|
||||
|
||||
# 停止现有服务(如果有)
|
||||
if docker-compose ps -q | grep -q .; then
|
||||
log_info "停止现有服务..."
|
||||
docker-compose down
|
||||
fi
|
||||
|
||||
# 构建镜像(如果需要)
|
||||
log_info "构建应用镜像..."
|
||||
docker-compose build --no-cache
|
||||
|
||||
# 启动服务
|
||||
log_info "启动所有服务..."
|
||||
docker-compose up -d
|
||||
|
||||
log_success "服务启动命令执行完成"
|
||||
}
|
||||
|
||||
# 等待服务就绪
|
||||
wait_for_services() {
|
||||
log_info "等待服务启动完成..."
|
||||
|
||||
local max_wait=300 # 最大等待时间(秒)
|
||||
local wait_time=0
|
||||
local check_interval=10
|
||||
|
||||
while [ $wait_time -lt $max_wait ]; do
|
||||
log_info "检查服务状态... (${wait_time}/${max_wait}秒)"
|
||||
|
||||
if ./docker/check-services.sh >/dev/null 2>&1; then
|
||||
log_success "所有服务启动完成并通过健康检查"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep $check_interval
|
||||
wait_time=$((wait_time + check_interval))
|
||||
done
|
||||
|
||||
log_error "服务启动超时,请检查日志"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 显示服务状态
|
||||
show_service_status() {
|
||||
log_info "当前服务状态:"
|
||||
docker-compose ps
|
||||
|
||||
echo ""
|
||||
log_info "服务访问地址:"
|
||||
echo " Web应用: http://localhost"
|
||||
echo " MySQL: localhost:3306"
|
||||
echo " Redis: localhost:6379"
|
||||
echo " Meilisearch: http://localhost:7700"
|
||||
|
||||
echo ""
|
||||
log_info "日志查看命令:"
|
||||
echo " 所有服务: docker-compose logs -f"
|
||||
echo " Web应用: docker-compose logs -f app"
|
||||
echo " 队列处理: docker-compose logs -f queue"
|
||||
echo " 数据库: docker-compose logs -f mysql"
|
||||
}
|
||||
|
||||
# 启动监控
|
||||
start_monitoring() {
|
||||
log_info "启动服务监控..."
|
||||
|
||||
# 检查是否已有监控进程在运行
|
||||
if pgrep -f "monitor-services.sh" >/dev/null; then
|
||||
log_warning "监控进程已在运行,跳过启动"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 在后台启动监控
|
||||
nohup ./docker/monitor-services.sh > ./storage/logs/monitor-output.log 2>&1 &
|
||||
local monitor_pid=$!
|
||||
|
||||
echo $monitor_pid > ./storage/logs/monitor.pid
|
||||
log_success "监控进程已启动 (PID: $monitor_pid)"
|
||||
|
||||
log_info "监控日志文件: ./storage/logs/monitor.log"
|
||||
log_info "监控输出文件: ./storage/logs/monitor-output.log"
|
||||
}
|
||||
|
||||
# 显示使用帮助
|
||||
show_help() {
|
||||
echo "Docker服务启动和监控脚本"
|
||||
echo ""
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --no-monitor 不启动监控进程"
|
||||
echo " --skip-build 跳过镜像构建"
|
||||
echo " --skip-wait 跳过服务就绪等待"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo ""
|
||||
echo "此脚本将执行以下操作:"
|
||||
echo " 1. 检查系统环境"
|
||||
echo " 2. 创建必要的目录"
|
||||
echo " 3. 检查环境变量配置"
|
||||
echo " 4. 启动Docker服务"
|
||||
echo " 5. 等待服务就绪"
|
||||
echo " 6. 显示服务状态"
|
||||
echo " 7. 启动监控进程"
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
log_info "正在清理..."
|
||||
|
||||
# 停止监控进程
|
||||
if [ -f "./storage/logs/monitor.pid" ]; then
|
||||
local monitor_pid=$(cat ./storage/logs/monitor.pid)
|
||||
if kill -0 $monitor_pid 2>/dev/null; then
|
||||
log_info "停止监控进程 (PID: $monitor_pid)"
|
||||
kill $monitor_pid
|
||||
fi
|
||||
rm -f ./storage/logs/monitor.pid
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 设置信号处理
|
||||
trap cleanup SIGINT SIGTERM
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local no_monitor=false
|
||||
local skip_build=false
|
||||
local skip_wait=false
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--no-monitor)
|
||||
no_monitor=true
|
||||
shift
|
||||
;;
|
||||
--skip-build)
|
||||
skip_build=true
|
||||
shift
|
||||
;;
|
||||
--skip-wait)
|
||||
skip_wait=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知选项: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "Docker服务启动和监控"
|
||||
echo "时间: $(date)"
|
||||
echo "========================================"
|
||||
|
||||
# 执行启动流程
|
||||
check_prerequisites
|
||||
create_directories
|
||||
check_environment
|
||||
|
||||
if [ "$skip_build" = false ]; then
|
||||
start_services
|
||||
else
|
||||
log_info "跳过镜像构建,直接启动服务..."
|
||||
docker-compose up -d
|
||||
fi
|
||||
|
||||
if [ "$skip_wait" = false ]; then
|
||||
wait_for_services
|
||||
else
|
||||
log_warning "跳过服务就绪等待"
|
||||
fi
|
||||
|
||||
show_service_status
|
||||
|
||||
if [ "$no_monitor" = false ]; then
|
||||
start_monitoring
|
||||
|
||||
echo ""
|
||||
log_success "服务启动完成,监控已开始"
|
||||
log_info "使用 Ctrl+C 停止监控并退出"
|
||||
log_info "或使用 'docker-compose down' 停止所有服务"
|
||||
|
||||
# 等待用户中断
|
||||
while true; do
|
||||
sleep 60
|
||||
done
|
||||
else
|
||||
log_success "服务启动完成(未启动监控)"
|
||||
fi
|
||||
}
|
||||
|
||||
# 如果脚本被直接执行
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
210
docker/stop-monitoring.sh
Executable file
210
docker/stop-monitoring.sh
Executable file
@@ -0,0 +1,210 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 停止Docker服务监控脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 停止监控进程
|
||||
stop_monitoring() {
|
||||
log_info "停止监控进程..."
|
||||
|
||||
local monitor_pid_file="./storage/logs/monitor.pid"
|
||||
local stopped=false
|
||||
|
||||
# 通过PID文件停止
|
||||
if [ -f "$monitor_pid_file" ]; then
|
||||
local monitor_pid=$(cat "$monitor_pid_file")
|
||||
if kill -0 $monitor_pid 2>/dev/null; then
|
||||
log_info "通过PID文件停止监控进程 (PID: $monitor_pid)"
|
||||
kill $monitor_pid
|
||||
sleep 2
|
||||
|
||||
# 确认进程已停止
|
||||
if ! kill -0 $monitor_pid 2>/dev/null; then
|
||||
log_success "监控进程已停止"
|
||||
stopped=true
|
||||
else
|
||||
log_warning "监控进程未响应TERM信号,尝试强制停止..."
|
||||
kill -9 $monitor_pid 2>/dev/null || true
|
||||
sleep 1
|
||||
if ! kill -0 $monitor_pid 2>/dev/null; then
|
||||
log_success "监控进程已强制停止"
|
||||
stopped=true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_warning "PID文件中的进程不存在"
|
||||
fi
|
||||
rm -f "$monitor_pid_file"
|
||||
fi
|
||||
|
||||
# 通过进程名停止(备用方法)
|
||||
if [ "$stopped" = false ]; then
|
||||
log_info "通过进程名查找并停止监控进程..."
|
||||
local pids=$(pgrep -f "monitor-services.sh" || true)
|
||||
|
||||
if [ -n "$pids" ]; then
|
||||
for pid in $pids; do
|
||||
log_info "停止监控进程 (PID: $pid)"
|
||||
kill $pid 2>/dev/null || true
|
||||
done
|
||||
|
||||
sleep 2
|
||||
|
||||
# 检查是否还有进程运行
|
||||
local remaining_pids=$(pgrep -f "monitor-services.sh" || true)
|
||||
if [ -n "$remaining_pids" ]; then
|
||||
log_warning "强制停止剩余的监控进程..."
|
||||
for pid in $remaining_pids; do
|
||||
kill -9 $pid 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
log_success "所有监控进程已停止"
|
||||
stopped=true
|
||||
else
|
||||
log_info "未找到运行中的监控进程"
|
||||
stopped=true
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 停止Docker服务
|
||||
stop_services() {
|
||||
log_info "停止Docker服务..."
|
||||
|
||||
if docker-compose ps -q | grep -q .; then
|
||||
docker-compose down
|
||||
log_success "Docker服务已停止"
|
||||
else
|
||||
log_info "没有运行中的Docker服务"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理日志文件
|
||||
cleanup_logs() {
|
||||
local cleanup_logs=$1
|
||||
|
||||
if [ "$cleanup_logs" = true ]; then
|
||||
log_info "清理监控日志文件..."
|
||||
|
||||
local log_files=(
|
||||
"./storage/logs/monitor.log"
|
||||
"./storage/logs/monitor-output.log"
|
||||
"./storage/logs/restart_counters"
|
||||
)
|
||||
|
||||
for item in "${log_files[@]}"; do
|
||||
if [ -f "$item" ]; then
|
||||
rm -f "$item"
|
||||
log_info "删除文件: $item"
|
||||
elif [ -d "$item" ]; then
|
||||
rm -rf "$item"
|
||||
log_info "删除目录: $item"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "日志文件清理完成"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示使用帮助
|
||||
show_help() {
|
||||
echo "停止Docker服务监控脚本"
|
||||
echo ""
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --stop-services 同时停止Docker服务"
|
||||
echo " --cleanup-logs 清理监控日志文件"
|
||||
echo " --all 停止监控、服务并清理日志"
|
||||
echo " -h, --help 显示此帮助信息"
|
||||
echo ""
|
||||
echo "默认情况下,此脚本只停止监控进程,不影响Docker服务。"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local stop_services_flag=false
|
||||
local cleanup_logs_flag=false
|
||||
|
||||
# 解析命令行参数
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--stop-services)
|
||||
stop_services_flag=true
|
||||
shift
|
||||
;;
|
||||
--cleanup-logs)
|
||||
cleanup_logs_flag=true
|
||||
shift
|
||||
;;
|
||||
--all)
|
||||
stop_services_flag=true
|
||||
cleanup_logs_flag=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知选项: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "停止Docker服务监控"
|
||||
echo "时间: $(date)"
|
||||
echo "========================================"
|
||||
|
||||
# 执行停止操作
|
||||
stop_monitoring
|
||||
|
||||
if [ "$stop_services_flag" = true ]; then
|
||||
stop_services
|
||||
fi
|
||||
|
||||
cleanup_logs "$cleanup_logs_flag"
|
||||
|
||||
echo ""
|
||||
log_success "操作完成"
|
||||
|
||||
if [ "$stop_services_flag" = false ]; then
|
||||
log_info "Docker服务仍在运行,使用 'docker-compose down' 停止服务"
|
||||
fi
|
||||
}
|
||||
|
||||
# 如果脚本被直接执行
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
22
docker/stop-production.sh
Executable file
22
docker/stop-production.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laravel知识库系统 - 生产环境停止脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "🛑 停止Laravel知识库系统生产环境..."
|
||||
|
||||
# 停止所有服务
|
||||
echo "⏹️ 停止Docker服务..."
|
||||
docker-compose down
|
||||
|
||||
# 可选:清理未使用的镜像和容器
|
||||
read -p "是否清理未使用的Docker资源? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "🧹 清理Docker资源..."
|
||||
docker system prune -f
|
||||
docker volume prune -f
|
||||
fi
|
||||
|
||||
echo "✅ 生产环境已停止"
|
||||
45
docker/supervisor/supervisord.conf
Normal file
45
docker/supervisor/supervisord.conf
Normal file
@@ -0,0 +1,45 @@
|
||||
# Supervisor配置文件 - Swoole版本
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
childlogdir=/var/log/supervisor/
|
||||
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisor.sock
|
||||
chmod=0700
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///var/run/supervisor.sock
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
# Swoole HTTP服务器 (Laravel Octane)
|
||||
[program:swoole]
|
||||
command=php /var/www/html/artisan octane:start --host=0.0.0.0 --port=8000 --workers=4
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
numprocs=1
|
||||
startsecs=0
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
stderr_logfile=/var/log/supervisor/%(program_name)s_stderr.log
|
||||
stderr_logfile_maxbytes=10MB
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)s_stdout.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
user=www-data
|
||||
|
||||
# Laravel队列处理器
|
||||
[program:laravel-worker]
|
||||
command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/supervisor/laravel-worker.log
|
||||
stopwaitsecs=3600
|
||||
user=www-data
|
||||
48
docker/swoole-health-check.sh
Executable file
48
docker/swoole-health-check.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Swoole HTTP服务器健康检查脚本
|
||||
# 用于Docker健康检查
|
||||
|
||||
set -e
|
||||
|
||||
# 检查Swoole进程是否运行
|
||||
if ! pgrep -f "octane:start" > /dev/null; then
|
||||
echo "Swoole HTTP服务器进程未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查HTTP服务是否响应
|
||||
if ! curl -f -s http://localhost:8000/health > /dev/null 2>&1; then
|
||||
# 如果没有专门的健康检查路由,尝试访问根路径
|
||||
if ! curl -f -s http://localhost:8000/ > /dev/null 2>&1; then
|
||||
echo "Swoole HTTP服务器无响应"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查Laravel应用是否可以连接到数据库和缓存
|
||||
if ! php -r "
|
||||
try {
|
||||
require_once '/var/www/html/vendor/autoload.php';
|
||||
\$app = require_once '/var/www/html/bootstrap/app.php';
|
||||
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
// 检查数据库连接
|
||||
\Illuminate\Support\Facades\DB::connection()->getPdo();
|
||||
|
||||
// 检查缓存连接
|
||||
\Illuminate\Support\Facades\Cache::put('swoole_health_check', 'ok', 10);
|
||||
\Illuminate\Support\Facades\Cache::forget('swoole_health_check');
|
||||
|
||||
echo 'OK';
|
||||
} catch (Exception \$e) {
|
||||
echo 'ERROR: ' . \$e->getMessage();
|
||||
exit(1);
|
||||
}
|
||||
"; then
|
||||
echo "Swoole服务器依赖服务检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Swoole HTTP服务器健康检查通过"
|
||||
exit 0
|
||||
61
docker/test-build.sh
Executable file
61
docker/test-build.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像测试构建脚本
|
||||
# 用于快速验证Dockerfile语法和基础构建
|
||||
|
||||
set -e
|
||||
|
||||
# 配置变量
|
||||
IMAGE_NAME="knowledge-base-app"
|
||||
IMAGE_TAG="test"
|
||||
PLATFORM="linux/amd64"
|
||||
|
||||
echo "开始测试Docker镜像构建..."
|
||||
echo "镜像名称: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
echo "目标平台: ${PLATFORM}"
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "错误: Docker未运行或无法访问"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 只构建到base阶段进行快速测试
|
||||
echo "正在构建基础镜像阶段..."
|
||||
docker build \
|
||||
--platform ${PLATFORM} \
|
||||
--target base \
|
||||
--tag ${IMAGE_NAME}:${IMAGE_TAG}-base \
|
||||
--file Dockerfile \
|
||||
.
|
||||
|
||||
# 验证基础阶段构建结果
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 基础镜像构建成功!"
|
||||
|
||||
# 显示镜像信息
|
||||
echo ""
|
||||
echo "基础镜像信息:"
|
||||
docker images ${IMAGE_NAME}:${IMAGE_TAG}-base
|
||||
|
||||
# 检查镜像架构
|
||||
echo ""
|
||||
echo "镜像架构信息:"
|
||||
docker inspect ${IMAGE_NAME}:${IMAGE_TAG}-base --format='{{.Architecture}}'
|
||||
|
||||
# 测试PHP版本
|
||||
echo ""
|
||||
echo "PHP版本信息:"
|
||||
docker run --rm ${IMAGE_NAME}:${IMAGE_TAG}-base php -v
|
||||
|
||||
# 测试Pandoc
|
||||
echo ""
|
||||
echo "Pandoc版本信息:"
|
||||
docker run --rm ${IMAGE_NAME}:${IMAGE_TAG}-base pandoc --version | head -1
|
||||
|
||||
echo ""
|
||||
echo "基础镜像测试完成!"
|
||||
else
|
||||
echo "❌ 基础镜像构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
72
docker/test-compose-config.sh
Executable file
72
docker/test-compose-config.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker Compose 配置验证脚本
|
||||
# 验证 Swoole 集成的 docker-compose.yml 配置
|
||||
|
||||
set -e
|
||||
|
||||
echo "开始验证 Docker Compose 配置..."
|
||||
|
||||
# 检查 docker-compose.yml 语法
|
||||
echo "1. 检查 docker-compose.yml 语法..."
|
||||
if docker-compose config > /dev/null 2>&1; then
|
||||
echo "✓ docker-compose.yml 语法正确"
|
||||
else
|
||||
echo "✗ docker-compose.yml 语法错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查必要的服务是否存在
|
||||
echo "2. 检查必要的服务..."
|
||||
services=$(docker-compose config --services)
|
||||
|
||||
required_services=("app" "mysql" "redis" "meilisearch")
|
||||
for service in "${required_services[@]}"; do
|
||||
if echo "$services" | grep -q "^$service$"; then
|
||||
echo "✓ 服务 $service 已配置"
|
||||
else
|
||||
echo "✗ 缺少服务 $service"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查是否没有 Nginx 服务(应该被移除)
|
||||
if echo "$services" | grep -q "^nginx$"; then
|
||||
echo "✗ 发现 Nginx 服务,应该已被移除"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Nginx 服务已正确移除"
|
||||
fi
|
||||
|
||||
# 检查应用服务的端口配置
|
||||
echo "3. 检查端口配置..."
|
||||
app_ports=$(docker-compose config | grep -A 20 "app:" | grep -A 5 "ports:" | grep "8000")
|
||||
if [ -n "$app_ports" ]; then
|
||||
echo "✓ 应用服务端口 8000 已正确配置"
|
||||
else
|
||||
echo "✗ 应用服务端口配置错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查健康检查配置
|
||||
echo "4. 检查健康检查配置..."
|
||||
healthcheck=$(docker-compose config | grep -A 10 "healthcheck:" | grep "swoole-health-check")
|
||||
if [ -n "$healthcheck" ]; then
|
||||
echo "✓ Swoole 健康检查已配置"
|
||||
else
|
||||
echo "✗ Swoole 健康检查配置缺失"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查服务依赖关系
|
||||
echo "5. 检查服务依赖关系..."
|
||||
depends_on=$(docker-compose config | grep -A 10 "depends_on:")
|
||||
if echo "$depends_on" | grep -q "mysql" && echo "$depends_on" | grep -q "redis" && echo "$depends_on" | grep -q "meilisearch"; then
|
||||
echo "✓ 服务依赖关系正确配置"
|
||||
else
|
||||
echo "✗ 服务依赖关系配置错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ 所有配置验证通过!"
|
||||
echo "Docker Compose 配置已成功更新为 Swoole 架构"
|
||||
106
docker/test-config.sh
Executable file
106
docker/test-config.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laravel知识库系统 - Docker配置测试脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "🧪 测试Docker配置..."
|
||||
|
||||
# 测试docker-compose配置语法
|
||||
echo "📋 检查docker-compose.yml语法..."
|
||||
if docker-compose config --quiet; then
|
||||
echo "✅ docker-compose.yml语法正确"
|
||||
else
|
||||
echo "❌ docker-compose.yml语法错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试Dockerfile语法
|
||||
echo "🐳 检查Dockerfile语法..."
|
||||
if [ -f "Dockerfile" ]; then
|
||||
echo "✅ Dockerfile文件存在"
|
||||
else
|
||||
echo "❌ Dockerfile文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查必要的配置文件
|
||||
echo "📁 检查配置文件..."
|
||||
|
||||
required_files=(
|
||||
"docker/mysql/my.cnf"
|
||||
"docker/redis/redis.conf"
|
||||
"docker/php/php.ini"
|
||||
"docker/supervisor/supervisord.conf"
|
||||
)
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "✅ $file 存在"
|
||||
else
|
||||
echo "❌ $file 不存在"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查存储目录
|
||||
echo "📂 检查存储目录..."
|
||||
required_dirs=(
|
||||
"storage/mysql"
|
||||
"storage/redis"
|
||||
"storage/meilisearch"
|
||||
"storage/logs/app"
|
||||
"storage/logs/queue"
|
||||
)
|
||||
|
||||
for dir in "${required_dirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
echo "✅ $dir 目录存在"
|
||||
else
|
||||
echo "⚠️ $dir 目录不存在,将创建..."
|
||||
mkdir -p "$dir"
|
||||
echo "✅ $dir 目录已创建"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查脚本权限
|
||||
echo "🔐 检查脚本权限..."
|
||||
scripts=(
|
||||
"docker/start-production.sh"
|
||||
"docker/stop-production.sh"
|
||||
"docker/check-services.sh"
|
||||
)
|
||||
|
||||
for script in "${scripts[@]}"; do
|
||||
if [ -x "$script" ]; then
|
||||
echo "✅ $script 可执行"
|
||||
else
|
||||
echo "⚠️ $script 不可执行,正在修复..."
|
||||
chmod +x "$script"
|
||||
echo "✅ $script 权限已修复"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查环境变量模板
|
||||
echo "🔧 检查环境配置..."
|
||||
if [ -f ".env.production" ]; then
|
||||
echo "✅ .env.production 模板存在"
|
||||
else
|
||||
echo "❌ .env.production 模板不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f ".env" ]; then
|
||||
echo "✅ .env 文件存在"
|
||||
else
|
||||
echo "⚠️ .env 文件不存在,建议复制 .env.production"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 Docker配置测试完成!"
|
||||
echo ""
|
||||
echo "📝 下一步操作:"
|
||||
echo "1. 复制环境配置: cp .env.production .env"
|
||||
echo "2. 编辑环境配置: nano .env"
|
||||
echo "3. 启动服务: ./docker/start-production.sh"
|
||||
echo "4. 检查状态: ./docker/check-services.sh"
|
||||
283
docker/test-health-checks.sh
Executable file
283
docker/test-health-checks.sh
Executable file
@@ -0,0 +1,283 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 健康检查功能测试脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 测试脚本语法
|
||||
test_script_syntax() {
|
||||
log_info "测试脚本语法..."
|
||||
|
||||
local scripts=(
|
||||
"docker/check-services.sh"
|
||||
"docker/monitor-services.sh"
|
||||
"docker/start-with-monitoring.sh"
|
||||
"docker/stop-monitoring.sh"
|
||||
"docker/queue-health-check.sh"
|
||||
)
|
||||
|
||||
local errors=0
|
||||
|
||||
for script in "${scripts[@]}"; do
|
||||
if [ -f "$script" ]; then
|
||||
if bash -n "$script"; then
|
||||
log_success "$script 语法正确"
|
||||
else
|
||||
log_error "$script 语法错误"
|
||||
((errors++))
|
||||
fi
|
||||
else
|
||||
log_error "$script 文件不存在"
|
||||
((errors++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "所有脚本语法检查通过"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个语法错误"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试脚本权限
|
||||
test_script_permissions() {
|
||||
log_info "测试脚本执行权限..."
|
||||
|
||||
local scripts=(
|
||||
"docker/check-services.sh"
|
||||
"docker/monitor-services.sh"
|
||||
"docker/start-with-monitoring.sh"
|
||||
"docker/stop-monitoring.sh"
|
||||
"docker/queue-health-check.sh"
|
||||
)
|
||||
|
||||
local errors=0
|
||||
|
||||
for script in "${scripts[@]}"; do
|
||||
if [ -f "$script" ]; then
|
||||
if [ -x "$script" ]; then
|
||||
log_success "$script 有执行权限"
|
||||
else
|
||||
log_error "$script 没有执行权限"
|
||||
((errors++))
|
||||
fi
|
||||
else
|
||||
log_error "$script 文件不存在"
|
||||
((errors++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "所有脚本权限检查通过"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个权限问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Docker配置文件
|
||||
test_docker_configs() {
|
||||
log_info "测试Docker配置文件..."
|
||||
|
||||
local configs=(
|
||||
"docker-compose.yml"
|
||||
"Dockerfile"
|
||||
"docker/php/php.ini"
|
||||
"docker/supervisor/supervisord.conf"
|
||||
"docker/redis/redis.conf"
|
||||
"docker/mysql/my.cnf"
|
||||
"docker/supervisor/supervisord.conf"
|
||||
)
|
||||
|
||||
local errors=0
|
||||
|
||||
for config in "${configs[@]}"; do
|
||||
if [ -f "$config" ]; then
|
||||
log_success "$config 存在"
|
||||
else
|
||||
log_error "$config 不存在"
|
||||
((errors++))
|
||||
fi
|
||||
done
|
||||
|
||||
# 测试docker-compose语法
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
if docker-compose config >/dev/null 2>&1; then
|
||||
log_success "docker-compose.yml 语法正确"
|
||||
else
|
||||
log_error "docker-compose.yml 语法错误"
|
||||
((errors++))
|
||||
fi
|
||||
else
|
||||
log_warning "docker-compose 未安装,跳过语法检查"
|
||||
fi
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "所有配置文件检查通过"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个配置问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试健康检查端点配置
|
||||
test_healthcheck_configs() {
|
||||
log_info "测试健康检查配置..."
|
||||
|
||||
local errors=0
|
||||
|
||||
# 检查docker-compose中的健康检查配置
|
||||
if grep -q "healthcheck:" docker-compose.yml; then
|
||||
log_success "docker-compose.yml 包含健康检查配置"
|
||||
else
|
||||
log_error "docker-compose.yml 缺少健康检查配置"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# 检查Laravel健康检查路由
|
||||
if grep -q "/health" routes/web.php; then
|
||||
log_success "Laravel 健康检查路由已配置"
|
||||
else
|
||||
log_error "Laravel 健康检查路由未配置"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
# 检查队列健康检查脚本
|
||||
if [ -f "docker/queue-health-check.sh" ] && [ -x "docker/queue-health-check.sh" ]; then
|
||||
log_success "队列健康检查脚本存在且可执行"
|
||||
else
|
||||
log_error "队列健康检查脚本问题"
|
||||
((errors++))
|
||||
fi
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "健康检查配置检查通过"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个健康检查配置问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试存储目录结构
|
||||
test_storage_structure() {
|
||||
log_info "测试存储目录结构..."
|
||||
|
||||
local required_dirs=(
|
||||
"./storage"
|
||||
"./storage/logs"
|
||||
)
|
||||
|
||||
local errors=0
|
||||
|
||||
for dir in "${required_dirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
log_success "目录存在: $dir"
|
||||
else
|
||||
log_warning "目录不存在,将创建: $dir"
|
||||
mkdir -p "$dir"
|
||||
if [ -d "$dir" ]; then
|
||||
log_success "成功创建目录: $dir"
|
||||
else
|
||||
log_error "无法创建目录: $dir"
|
||||
((errors++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_success "存储目录结构检查通过"
|
||||
return 0
|
||||
else
|
||||
log_error "发现 $errors 个存储目录问题"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主测试函数
|
||||
main() {
|
||||
echo "========================================"
|
||||
echo "健康检查功能测试"
|
||||
echo "时间: $(date)"
|
||||
echo "========================================"
|
||||
|
||||
local total_tests=0
|
||||
local failed_tests=0
|
||||
|
||||
# 执行所有测试
|
||||
tests=(
|
||||
"test_script_syntax:脚本语法测试"
|
||||
"test_script_permissions:脚本权限测试"
|
||||
"test_docker_configs:Docker配置测试"
|
||||
"test_healthcheck_configs:健康检查配置测试"
|
||||
"test_storage_structure:存储目录测试"
|
||||
)
|
||||
|
||||
for test in "${tests[@]}"; do
|
||||
IFS=':' read -ra TEST_PARTS <<< "$test"
|
||||
local test_func="${TEST_PARTS[0]}"
|
||||
local test_name="${TEST_PARTS[1]}"
|
||||
|
||||
((total_tests++))
|
||||
|
||||
echo ""
|
||||
log_info "开始 $test_name..."
|
||||
|
||||
if $test_func; then
|
||||
log_success "$test_name 通过"
|
||||
else
|
||||
log_error "$test_name 失败"
|
||||
((failed_tests++))
|
||||
fi
|
||||
done
|
||||
|
||||
# 输出总结
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "测试完成"
|
||||
echo "总测试数: $total_tests"
|
||||
echo "失败: $failed_tests"
|
||||
echo "成功: $((total_tests - failed_tests))"
|
||||
echo "========================================"
|
||||
|
||||
if [ $failed_tests -gt 0 ]; then
|
||||
log_error "发现 $failed_tests 个问题,请检查配置"
|
||||
exit 1
|
||||
else
|
||||
log_success "所有测试通过,健康检查功能配置正确"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 如果脚本被直接执行
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
215
docker/test-network.sh
Executable file
215
docker/test-network.sh
Executable file
@@ -0,0 +1,215 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker网络连接测试脚本
|
||||
# 用于测试容器间的网络连接和服务可用性
|
||||
|
||||
set -e
|
||||
|
||||
echo "==================================="
|
||||
echo "Docker网络连接测试"
|
||||
echo "==================================="
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 测试计数
|
||||
TEST_COUNT=0
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
# 测试函数
|
||||
test_connection() {
|
||||
local test_name=$1
|
||||
local container=$2
|
||||
local target=$3
|
||||
local port=$4
|
||||
local timeout=${5:-5}
|
||||
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试 $test_name: "
|
||||
|
||||
if docker exec "$container" timeout "$timeout" bash -c "echo > /dev/tcp/$target/$port" 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试HTTP连接
|
||||
test_http() {
|
||||
local test_name=$1
|
||||
local container=$2
|
||||
local url=$3
|
||||
local expected_code=${4:-200}
|
||||
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试 $test_name: "
|
||||
|
||||
if docker exec "$container" curl -s -o /dev/null -w "%{http_code}" "$url" | grep -q "$expected_code"; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查容器是否运行
|
||||
check_container() {
|
||||
local container=$1
|
||||
if ! docker ps --format "table {{.Names}}" | grep -q "^$container$"; then
|
||||
echo -e "${RED}错误: 容器 $container 未运行${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "检查容器状态..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# 定义容器名称
|
||||
APP_CONTAINER="knowledge_base_app"
|
||||
MYSQL_CONTAINER="knowledge_base_mysql"
|
||||
REDIS_CONTAINER="knowledge_base_redis"
|
||||
MEILISEARCH_CONTAINER="knowledge_base_meilisearch"
|
||||
QUEUE_CONTAINER="knowledge_base_queue"
|
||||
|
||||
# 检查所有容器是否运行
|
||||
containers=("$APP_CONTAINER" "$MYSQL_CONTAINER" "$REDIS_CONTAINER" "$MEILISEARCH_CONTAINER" "$QUEUE_CONTAINER")
|
||||
all_running=true
|
||||
|
||||
for container in "${containers[@]}"; do
|
||||
if check_container "$container"; then
|
||||
echo -e "${GREEN}✓ $container 正在运行${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ $container 未运行${NC}"
|
||||
all_running=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_running" = false ]; then
|
||||
echo -e "${RED}部分容器未运行,请先启动所有服务${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "测试网络连接..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# 测试应用容器到数据库的连接
|
||||
test_connection "应用->MySQL" "$APP_CONTAINER" "mysql" "3306"
|
||||
|
||||
# 测试应用容器到Redis的连接
|
||||
test_connection "应用->Redis" "$APP_CONTAINER" "redis" "6379"
|
||||
|
||||
# 测试应用容器到Meilisearch的连接
|
||||
test_connection "应用->Meilisearch" "$APP_CONTAINER" "meilisearch" "7700"
|
||||
|
||||
# 测试队列容器到数据库的连接
|
||||
test_connection "队列->MySQL" "$QUEUE_CONTAINER" "mysql" "3306"
|
||||
|
||||
# 测试队列容器到Redis的连接
|
||||
test_connection "队列->Redis" "$QUEUE_CONTAINER" "redis" "6379"
|
||||
|
||||
echo ""
|
||||
echo "测试HTTP服务..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# 测试Meilisearch HTTP API
|
||||
test_http "Meilisearch健康检查" "$APP_CONTAINER" "http://meilisearch:7700/health"
|
||||
|
||||
# 测试应用HTTP服务(如果有健康检查端点)
|
||||
if docker exec "$APP_CONTAINER" curl -s -f "http://localhost/health" >/dev/null 2>&1; then
|
||||
test_http "应用健康检查" "$APP_CONTAINER" "http://localhost/health"
|
||||
else
|
||||
echo "应用健康检查端点不可用,跳过测试"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "测试服务功能..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# 测试MySQL连接
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试MySQL数据库连接: "
|
||||
if docker exec "$MYSQL_CONTAINER" mysql -u root -p"${DB_PASSWORD:-secure_root_password}" -e "SELECT 1;" >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
|
||||
# 测试Redis连接
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试Redis连接: "
|
||||
if docker exec "$REDIS_CONTAINER" redis-cli ping | grep -q "PONG"; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
|
||||
# 测试Laravel数据库连接
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试Laravel数据库连接: "
|
||||
if docker exec "$APP_CONTAINER" php artisan tinker --execute="echo DB::connection()->getPdo() ? 'OK' : 'FAIL';" 2>/dev/null | grep -q "OK"; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
|
||||
# 测试Laravel Redis连接
|
||||
((TEST_COUNT++))
|
||||
echo -n "测试Laravel Redis连接: "
|
||||
if docker exec "$APP_CONTAINER" php artisan tinker --execute="echo Redis::ping() ? 'OK' : 'FAIL';" 2>/dev/null | grep -q "OK"; then
|
||||
echo -e "${GREEN}✓ 通过${NC}"
|
||||
((PASS_COUNT++))
|
||||
else
|
||||
echo -e "${RED}✗ 失败${NC}"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "测试网络信息..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
# 显示网络信息
|
||||
echo "Docker网络信息:"
|
||||
docker network ls | grep knowledge_base
|
||||
|
||||
echo ""
|
||||
echo "容器IP地址:"
|
||||
for container in "${containers[@]}"; do
|
||||
if check_container "$container" >/dev/null 2>&1; then
|
||||
ip=$(docker inspect "$container" --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
|
||||
echo "$container: $ip"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
echo "测试结果汇总"
|
||||
echo "==================================="
|
||||
|
||||
echo "总测试数: $TEST_COUNT"
|
||||
echo -e "通过: ${GREEN}$PASS_COUNT${NC}"
|
||||
echo -e "失败: ${RED}$FAIL_COUNT${NC}"
|
||||
|
||||
if [ $FAIL_COUNT -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ 所有网络测试通过!${NC}"
|
||||
echo "Docker网络配置正常,服务间通信正常。"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ 有 $FAIL_COUNT 个测试失败${NC}"
|
||||
echo "请检查网络配置和服务状态。"
|
||||
exit 1
|
||||
fi
|
||||
97
docker/test-persistence.sh
Executable file
97
docker/test-persistence.sh
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 数据持久化测试脚本
|
||||
# 验证所有数据卷映射是否正确配置
|
||||
|
||||
set -e
|
||||
|
||||
echo "开始测试数据持久化配置..."
|
||||
|
||||
# 检查存储目录是否存在
|
||||
check_directory() {
|
||||
local dir=$1
|
||||
local description=$2
|
||||
|
||||
if [ -d "$dir" ]; then
|
||||
echo "✓ $description 目录存在: $dir"
|
||||
return 0
|
||||
else
|
||||
echo "✗ $description 目录不存在: $dir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查docker-compose配置中的卷映射
|
||||
check_volume_mapping() {
|
||||
local volume_name=$1
|
||||
local description=$2
|
||||
|
||||
if docker-compose config | grep -q "$volume_name"; then
|
||||
echo "✓ $description 卷映射已配置: $volume_name"
|
||||
return 0
|
||||
else
|
||||
echo "✗ $description 卷映射未配置: $volume_name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "检查存储目录结构..."
|
||||
|
||||
# 检查所有必要的存储目录
|
||||
check_directory "storage/mysql" "MySQL数据库存储"
|
||||
check_directory "storage/redis" "Redis缓存存储"
|
||||
check_directory "storage/meilisearch" "Meilisearch搜索存储"
|
||||
check_directory "storage/app" "Laravel应用存储"
|
||||
check_directory "storage/app/private/documents" "文档上传存储"
|
||||
check_directory "storage/app/public" "公共文件存储"
|
||||
check_directory "storage/logs" "日志存储"
|
||||
check_directory "storage/logs/app" "应用日志存储"
|
||||
check_directory "storage/logs/queue" "队列日志存储"
|
||||
|
||||
echo ""
|
||||
echo "检查Docker Compose卷映射配置..."
|
||||
|
||||
# 检查所有卷映射配置
|
||||
check_volume_mapping "mysql_data" "MySQL数据"
|
||||
check_volume_mapping "redis_data" "Redis数据"
|
||||
check_volume_mapping "meilisearch_data" "Meilisearch数据"
|
||||
check_volume_mapping "storage_data" "应用存储数据"
|
||||
check_volume_mapping "documents_data" "文档存储数据"
|
||||
check_volume_mapping "public_data" "公共文件数据"
|
||||
check_volume_mapping "app_logs" "应用日志"
|
||||
check_volume_mapping "queue_logs" "队列日志"
|
||||
check_volume_mapping "laravel_logs" "Laravel日志"
|
||||
|
||||
echo ""
|
||||
echo "检查目录权限..."
|
||||
|
||||
# 检查关键目录的权限
|
||||
for dir in storage/mysql storage/redis storage/meilisearch storage/app storage/logs; do
|
||||
if [ -d "$dir" ]; then
|
||||
perms=$(stat -f "%A" "$dir" 2>/dev/null || stat -c "%a" "$dir" 2>/dev/null || echo "unknown")
|
||||
echo "✓ $dir 权限: $perms"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "数据持久化配置测试完成!"
|
||||
|
||||
# 创建测试文件验证映射
|
||||
echo ""
|
||||
echo "创建测试文件验证目录映射..."
|
||||
|
||||
test_file="storage/app/test-persistence-$(date +%s).txt"
|
||||
echo "测试数据持久化配置 - $(date)" > "$test_file"
|
||||
|
||||
if [ -f "$test_file" ]; then
|
||||
echo "✓ 测试文件创建成功: $test_file"
|
||||
rm "$test_file"
|
||||
echo "✓ 测试文件清理完成"
|
||||
else
|
||||
echo "✗ 测试文件创建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "所有数据持久化配置检查通过!"
|
||||
235
docker/validate-deployment.sh
Executable file
235
docker/validate-deployment.sh
Executable file
@@ -0,0 +1,235 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Laravel知识库系统 - 部署验证脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 验证Docker部署配置..."
|
||||
echo ""
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 检查函数
|
||||
check_requirement() {
|
||||
local name=$1
|
||||
local command=$2
|
||||
local version_flag=$3
|
||||
|
||||
echo -n "检查 $name: "
|
||||
if command -v $command >/dev/null 2>&1; then
|
||||
if [ -n "$version_flag" ]; then
|
||||
version=$($command $version_flag 2>/dev/null | head -n1)
|
||||
echo -e "${GREEN}✅ 已安装${NC} ($version)"
|
||||
else
|
||||
echo -e "${GREEN}✅ 已安装${NC}"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ 未安装${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查系统要求
|
||||
echo "📋 系统要求检查:"
|
||||
check_requirement "Docker" "docker" "--version"
|
||||
check_requirement "Docker Compose" "docker-compose" "--version"
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查Docker服务状态
|
||||
echo "🐳 Docker服务状态:"
|
||||
if docker info >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ Docker服务运行正常${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Docker服务未运行${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查配置文件
|
||||
echo "📁 配置文件检查:"
|
||||
config_files=(
|
||||
"docker-compose.yml:Docker Compose配置"
|
||||
"Dockerfile:Docker镜像配置"
|
||||
".env.production:环境变量模板"
|
||||
"docker/mysql/my.cnf:MySQL配置"
|
||||
"docker/redis/redis.conf:Redis配置"
|
||||
)
|
||||
|
||||
for item in "${config_files[@]}"; do
|
||||
file=$(echo $item | cut -d: -f1)
|
||||
desc=$(echo $item | cut -d: -f2)
|
||||
|
||||
if [ -f "$file" ]; then
|
||||
echo -e "${GREEN}✅${NC} $desc ($file)"
|
||||
else
|
||||
echo -e "${RED}❌${NC} $desc ($file) - 文件不存在"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查存储目录
|
||||
echo "📂 存储目录检查:"
|
||||
storage_dirs=(
|
||||
"storage/mysql:MySQL数据目录"
|
||||
"storage/redis:Redis数据目录"
|
||||
"storage/meilisearch:Meilisearch数据目录"
|
||||
"storage/logs/app:应用日志目录"
|
||||
"storage/logs/queue:队列日志目录"
|
||||
"storage/app:应用存储目录"
|
||||
)
|
||||
|
||||
for item in "${storage_dirs[@]}"; do
|
||||
dir=$(echo $item | cut -d: -f1)
|
||||
desc=$(echo $item | cut -d: -f2)
|
||||
|
||||
if [ -d "$dir" ]; then
|
||||
echo -e "${GREEN}✅${NC} $desc ($dir)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️${NC} $desc ($dir) - 目录不存在,将在启动时创建"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查管理脚本
|
||||
echo "🔧 管理脚本检查:"
|
||||
scripts=(
|
||||
"docker/start-production.sh:启动脚本"
|
||||
"docker/stop-production.sh:停止脚本"
|
||||
"docker/check-services.sh:状态检查脚本"
|
||||
"docker/test-config.sh:配置测试脚本"
|
||||
)
|
||||
|
||||
for item in "${scripts[@]}"; do
|
||||
script=$(echo $item | cut -d: -f1)
|
||||
desc=$(echo $item | cut -d: -f2)
|
||||
|
||||
if [ -f "$script" ]; then
|
||||
if [ -x "$script" ]; then
|
||||
echo -e "${GREEN}✅${NC} $desc ($script) - 可执行"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️${NC} $desc ($script) - 不可执行"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}❌${NC} $desc ($script) - 文件不存在"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查Docker Compose配置
|
||||
echo "🔍 Docker Compose配置验证:"
|
||||
if docker-compose config --quiet 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Docker Compose配置语法正确${NC}"
|
||||
|
||||
# 显示服务列表
|
||||
echo ""
|
||||
echo "📊 配置的服务:"
|
||||
services=$(docker-compose config --services)
|
||||
for service in $services; do
|
||||
echo -e "${GREEN} ✓${NC} $service"
|
||||
done
|
||||
else
|
||||
echo -e "${RED}❌ Docker Compose配置语法错误${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 检查环境配置
|
||||
echo "🔧 环境配置检查:"
|
||||
if [ -f ".env" ]; then
|
||||
echo -e "${GREEN}✅ .env 文件存在${NC}"
|
||||
|
||||
# 检查关键配置项
|
||||
required_vars=("APP_KEY" "DB_PASSWORD" "MEILISEARCH_KEY")
|
||||
for var in "${required_vars[@]}"; do
|
||||
if grep -q "^${var}=" .env && ! grep -q "^${var}=$" .env; then
|
||||
echo -e "${GREEN} ✓${NC} $var 已配置"
|
||||
else
|
||||
echo -e "${YELLOW} ⚠️${NC} $var 未配置或为空"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ .env 文件不存在${NC}"
|
||||
echo " 建议执行: cp .env.production .env"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 系统资源检查
|
||||
echo "💾 系统资源检查:"
|
||||
|
||||
# 检查可用内存 (macOS兼容)
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
# Linux系统
|
||||
available_memory=$(free -m | awk 'NR==2{printf "%.0f", $7}')
|
||||
elif command -v vm_stat >/dev/null 2>&1; then
|
||||
# macOS系统
|
||||
page_size=$(vm_stat | grep "page size" | awk '{print $8}')
|
||||
free_pages=$(vm_stat | grep "Pages free" | awk '{print $3}' | sed 's/\.//')
|
||||
available_memory=$((free_pages * page_size / 1024 / 1024))
|
||||
else
|
||||
available_memory=0
|
||||
fi
|
||||
|
||||
if [ "$available_memory" -ge 4096 ]; then
|
||||
echo -e "${GREEN}✅ 可用内存: ${available_memory}MB (推荐: 4GB+)${NC}"
|
||||
elif [ "$available_memory" -ge 2048 ]; then
|
||||
echo -e "${YELLOW}⚠️ 可用内存: ${available_memory}MB (推荐: 4GB+)${NC}"
|
||||
elif [ "$available_memory" -gt 0 ]; then
|
||||
echo -e "${RED}❌ 可用内存: ${available_memory}MB (推荐: 4GB+)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ 无法检测内存使用情况${NC}"
|
||||
fi
|
||||
|
||||
# 检查可用磁盘空间 (macOS兼容)
|
||||
if df -h . >/dev/null 2>&1; then
|
||||
available_disk=$(df -h . | awk 'NR==2{print $4}' | sed 's/G.*//' | sed 's/[^0-9].*//')
|
||||
if [ -n "$available_disk" ] && [ "$available_disk" -ge 10 ]; then
|
||||
echo -e "${GREEN}✅ 可用磁盘: ${available_disk}GB+ (推荐: 10GB+)${NC}"
|
||||
elif [ -n "$available_disk" ] && [ "$available_disk" -ge 5 ]; then
|
||||
echo -e "${YELLOW}⚠️ 可用磁盘: ${available_disk}GB+ (推荐: 10GB+)${NC}"
|
||||
elif [ -n "$available_disk" ]; then
|
||||
echo -e "${RED}❌ 可用磁盘: ${available_disk}GB+ (推荐: 10GB+)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ 无法检测磁盘使用情况${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ 无法检测磁盘使用情况${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 总结
|
||||
echo "📋 验证总结:"
|
||||
echo ""
|
||||
echo "✅ 配置完整性: 所有必要的配置文件都已就位"
|
||||
echo "✅ 脚本可用性: 管理脚本已准备就绪"
|
||||
echo "✅ Docker环境: Docker和Docker Compose可用"
|
||||
echo ""
|
||||
|
||||
if [ -f ".env" ]; then
|
||||
echo -e "${GREEN}🚀 系统已准备就绪!${NC}"
|
||||
echo ""
|
||||
echo "下一步操作:"
|
||||
echo "1. 启动服务: ./docker/start-production.sh"
|
||||
echo "2. 检查状态: ./docker/check-services.sh"
|
||||
echo "3. 访问应用: http://localhost"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ 需要完成环境配置${NC}"
|
||||
echo ""
|
||||
echo "下一步操作:"
|
||||
echo "1. 复制配置: cp .env.production .env"
|
||||
echo "2. 编辑配置: nano .env"
|
||||
echo "3. 启动服务: ./docker/start-production.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
142
docker/validate-env.sh
Executable file
142
docker/validate-env.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 环境变量验证脚本
|
||||
# 用于验证Docker部署所需的环境变量是否正确配置
|
||||
|
||||
set -e
|
||||
|
||||
echo "==================================="
|
||||
echo "环境变量验证脚本"
|
||||
echo "==================================="
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 错误计数
|
||||
ERROR_COUNT=0
|
||||
|
||||
# 验证函数
|
||||
validate_var() {
|
||||
local var_name=$1
|
||||
local var_value=$2
|
||||
local is_required=${3:-true}
|
||||
local description=$4
|
||||
|
||||
if [ -z "$var_value" ]; then
|
||||
if [ "$is_required" = true ]; then
|
||||
echo -e "${RED}✗ 错误: $var_name 未设置${NC} - $description"
|
||||
((ERROR_COUNT++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ 警告: $var_name 未设置${NC} - $description (可选)"
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}✓ $var_name 已设置${NC} - $description"
|
||||
fi
|
||||
}
|
||||
|
||||
# 验证必需的环境变量
|
||||
echo "检查必需的环境变量..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "APP_KEY" "$APP_KEY" true "应用加密密钥"
|
||||
validate_var "DB_PASSWORD" "$DB_PASSWORD" true "数据库密码"
|
||||
validate_var "MEILISEARCH_KEY" "$MEILISEARCH_KEY" true "Meilisearch主密钥"
|
||||
|
||||
echo ""
|
||||
echo "检查应用配置..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "APP_NAME" "$APP_NAME" false "应用名称"
|
||||
validate_var "APP_ENV" "$APP_ENV" false "应用环境"
|
||||
validate_var "APP_DEBUG" "$APP_DEBUG" false "调试模式"
|
||||
validate_var "APP_URL" "$APP_URL" false "应用URL"
|
||||
|
||||
echo ""
|
||||
echo "检查数据库配置..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "DB_CONNECTION" "$DB_CONNECTION" false "数据库连接类型"
|
||||
validate_var "DB_HOST" "$DB_HOST" false "数据库主机"
|
||||
validate_var "DB_PORT" "$DB_PORT" false "数据库端口"
|
||||
validate_var "DB_DATABASE" "$DB_DATABASE" false "数据库名称"
|
||||
validate_var "DB_USERNAME" "$DB_USERNAME" false "数据库用户名"
|
||||
|
||||
echo ""
|
||||
echo "检查Redis配置..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "REDIS_HOST" "$REDIS_HOST" false "Redis主机"
|
||||
validate_var "REDIS_PORT" "$REDIS_PORT" false "Redis端口"
|
||||
validate_var "REDIS_PASSWORD" "$REDIS_PASSWORD" false "Redis密码"
|
||||
|
||||
echo ""
|
||||
echo "检查Meilisearch配置..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "MEILISEARCH_HOST" "$MEILISEARCH_HOST" false "Meilisearch主机"
|
||||
validate_var "SCOUT_DRIVER" "$SCOUT_DRIVER" false "搜索驱动"
|
||||
|
||||
echo ""
|
||||
echo "检查邮件配置..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
validate_var "MAIL_MAILER" "$MAIL_MAILER" false "邮件驱动"
|
||||
if [ "$MAIL_MAILER" = "smtp" ]; then
|
||||
validate_var "MAIL_HOST" "$MAIL_HOST" true "SMTP主机"
|
||||
validate_var "MAIL_PORT" "$MAIL_PORT" true "SMTP端口"
|
||||
validate_var "MAIL_USERNAME" "$MAIL_USERNAME" true "SMTP用户名"
|
||||
validate_var "MAIL_PASSWORD" "$MAIL_PASSWORD" true "SMTP密码"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
|
||||
# 检查APP_KEY格式
|
||||
if [ -n "$APP_KEY" ]; then
|
||||
if [[ $APP_KEY == base64:* ]]; then
|
||||
echo -e "${GREEN}✓ APP_KEY 格式正确${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ 错误: APP_KEY 格式不正确,应该以 'base64:' 开头${NC}"
|
||||
((ERROR_COUNT++))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查默认密码
|
||||
if [ "$DB_PASSWORD" = "secure_password_change_this" ] || [ "$DB_PASSWORD" = "secure_password_change_this_in_production" ]; then
|
||||
echo -e "${YELLOW}⚠ 警告: 数据库密码使用默认值,建议更改${NC}"
|
||||
fi
|
||||
|
||||
if [ "$MEILISEARCH_KEY" = "your-master-key-change-this-in-production" ]; then
|
||||
echo -e "${YELLOW}⚠ 警告: Meilisearch密钥使用默认值,建议更改${NC}"
|
||||
fi
|
||||
|
||||
# 检查环境特定配置
|
||||
if [ "$APP_ENV" = "production" ]; then
|
||||
echo ""
|
||||
echo "生产环境额外检查..."
|
||||
echo "-----------------------------------"
|
||||
|
||||
if [ "$APP_DEBUG" = "true" ]; then
|
||||
echo -e "${YELLOW}⚠ 警告: 生产环境不建议启用调试模式${NC}"
|
||||
fi
|
||||
|
||||
if [ "$LOG_LEVEL" = "debug" ]; then
|
||||
echo -e "${YELLOW}⚠ 警告: 生产环境建议使用 info 或更高级别的日志${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==================================="
|
||||
|
||||
# 输出结果
|
||||
if [ $ERROR_COUNT -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ 环境变量验证通过!${NC}"
|
||||
echo "可以继续进行Docker部署。"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ 发现 $ERROR_COUNT 个错误${NC}"
|
||||
echo "请修复上述错误后再进行部署。"
|
||||
exit 1
|
||||
fi
|
||||
225
docker/validate-storage-config.sh
Executable file
225
docker/validate-storage-config.sh
Executable file
@@ -0,0 +1,225 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 数据持久化配置验证脚本
|
||||
# 全面验证Docker部署中的存储配置
|
||||
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo "数据持久化和目录映射配置验证"
|
||||
echo "=========================================="
|
||||
|
||||
# 颜色定义
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 成功和失败计数
|
||||
SUCCESS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
# 检查函数
|
||||
check_success() {
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
((SUCCESS_COUNT++))
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
((FAIL_COUNT++))
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "1. 检查存储目录结构..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 检查所有必要的存储目录
|
||||
directories=(
|
||||
"storage/mysql:MySQL数据库存储目录"
|
||||
"storage/redis:Redis缓存存储目录"
|
||||
"storage/meilisearch:Meilisearch搜索存储目录"
|
||||
"storage/app:Laravel应用存储目录"
|
||||
"storage/app/private:私有文件存储目录"
|
||||
"storage/app/private/documents:文档上传存储目录"
|
||||
"storage/app/private/markdown:Markdown文件存储目录"
|
||||
"storage/app/public:公共文件存储目录"
|
||||
"storage/framework:Laravel框架缓存目录"
|
||||
"storage/framework/cache:缓存目录"
|
||||
"storage/framework/sessions:会话目录"
|
||||
"storage/framework/views:视图缓存目录"
|
||||
"storage/logs:日志存储目录"
|
||||
"storage/logs/app:应用日志存储目录"
|
||||
"storage/logs/queue:队列日志存储目录"
|
||||
)
|
||||
|
||||
for dir_info in "${directories[@]}"; do
|
||||
IFS=':' read -r dir desc <<< "$dir_info"
|
||||
[ -d "$dir" ]
|
||||
check_success "$desc: $dir"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "2. 检查Docker Compose卷映射配置..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 检查docker-compose.yml中的卷映射
|
||||
volumes=(
|
||||
"mysql_data:MySQL数据卷"
|
||||
"redis_data:Redis数据卷"
|
||||
"meilisearch_data:Meilisearch数据卷"
|
||||
"storage_data:应用存储数据卷"
|
||||
"documents_data:文档存储数据卷"
|
||||
"public_data:公共文件数据卷"
|
||||
"app_logs:应用日志卷"
|
||||
"queue_logs:队列日志卷"
|
||||
"laravel_logs:Laravel日志卷"
|
||||
)
|
||||
|
||||
for volume_info in "${volumes[@]}"; do
|
||||
IFS=':' read -r volume desc <<< "$volume_info"
|
||||
docker-compose config 2>/dev/null | grep -q "$volume"
|
||||
check_success "$desc: $volume"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "3. 检查服务容器的卷映射..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 检查应用容器的卷映射
|
||||
app_volumes=(
|
||||
"./:/var/www/html:项目代码目录映射"
|
||||
"storage_data:/var/www/html/storage:应用存储目录映射"
|
||||
"documents_data:/var/www/html/storage/app/private/documents:文档存储目录映射"
|
||||
"public_data:/var/www/html/storage/app/public:公共文件目录映射"
|
||||
"app_logs:/var/log:应用日志目录映射"
|
||||
"laravel_logs:/var/www/html/storage/logs:Laravel日志目录映射"
|
||||
)
|
||||
|
||||
for volume_info in "${app_volumes[@]}"; do
|
||||
IFS=':' read -r volume_mapping path desc <<< "$volume_info"
|
||||
docker-compose config 2>/dev/null | grep -q "$volume_mapping"
|
||||
check_success "应用容器 - $desc"
|
||||
done
|
||||
|
||||
# 检查队列容器的卷映射
|
||||
queue_volumes=(
|
||||
"./:/var/www/html:项目代码目录映射"
|
||||
"storage_data:/var/www/html/storage:应用存储目录映射"
|
||||
"documents_data:/var/www/html/storage/app/private/documents:文档存储目录映射"
|
||||
"public_data:/var/www/html/storage/app/public:公共文件目录映射"
|
||||
"queue_logs:/var/log:队列日志目录映射"
|
||||
"laravel_logs:/var/www/html/storage/logs:Laravel日志目录映射"
|
||||
)
|
||||
|
||||
for volume_info in "${queue_volumes[@]}"; do
|
||||
IFS=':' read -r volume_mapping path desc <<< "$volume_info"
|
||||
docker-compose config 2>/dev/null | grep -q "$volume_mapping"
|
||||
check_success "队列容器 - $desc"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "4. 检查数据卷绑定配置..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 检查bind mount配置
|
||||
bind_mounts=(
|
||||
"storage/mysql:MySQL数据绑定"
|
||||
"storage/redis:Redis数据绑定"
|
||||
"storage/meilisearch:Meilisearch数据绑定"
|
||||
"storage/app:应用存储绑定"
|
||||
"storage/logs/app:应用日志绑定"
|
||||
"storage/logs/queue:队列日志绑定"
|
||||
"storage/logs:Laravel日志绑定"
|
||||
)
|
||||
|
||||
for mount_info in "${bind_mounts[@]}"; do
|
||||
IFS=':' read -r mount_path desc <<< "$mount_info"
|
||||
# 检查相对路径或绝对路径
|
||||
if docker-compose config 2>/dev/null | grep -q "device:.*$mount_path" || docker-compose config 2>/dev/null | grep -q "device: ./$mount_path"; then
|
||||
check_success "$desc: $mount_path"
|
||||
else
|
||||
echo -e "${RED}✗${NC} $desc: $mount_path"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "5. 检查目录权限..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 检查关键目录的权限
|
||||
permission_dirs=(
|
||||
"storage/mysql"
|
||||
"storage/redis"
|
||||
"storage/meilisearch"
|
||||
"storage/app"
|
||||
"storage/logs"
|
||||
)
|
||||
|
||||
for dir in "${permission_dirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
perms=$(stat -f "%A" "$dir" 2>/dev/null || stat -c "%a" "$dir" 2>/dev/null || echo "unknown")
|
||||
if [ "$perms" = "755" ] || [ "$perms" = "unknown" ]; then
|
||||
check_success "$dir 权限正确: $perms"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} $dir 权限可能需要调整: $perms"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} $dir 目录不存在"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "6. 验证配置文件语法..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 验证docker-compose.yml语法
|
||||
docker-compose config --quiet 2>/dev/null
|
||||
check_success "Docker Compose配置文件语法正确"
|
||||
|
||||
echo ""
|
||||
echo "7. 创建测试文件验证写入权限..."
|
||||
echo "----------------------------------------"
|
||||
|
||||
# 测试各个存储目录的写入权限
|
||||
test_dirs=(
|
||||
"storage/app:应用存储目录"
|
||||
"storage/logs:日志存储目录"
|
||||
"storage/mysql:MySQL存储目录"
|
||||
"storage/redis:Redis存储目录"
|
||||
"storage/meilisearch:Meilisearch存储目录"
|
||||
)
|
||||
|
||||
for dir_info in "${test_dirs[@]}"; do
|
||||
IFS=':' read -r dir desc <<< "$dir_info"
|
||||
test_file="$dir/test-write-$(date +%s).tmp"
|
||||
|
||||
if echo "test" > "$test_file" 2>/dev/null; then
|
||||
rm -f "$test_file" 2>/dev/null
|
||||
check_success "$desc 写入权限正常"
|
||||
else
|
||||
echo -e "${RED}✗${NC} $desc 写入权限异常"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "验证结果汇总"
|
||||
echo "=========================================="
|
||||
|
||||
echo -e "成功检查项: ${GREEN}$SUCCESS_COUNT${NC}"
|
||||
echo -e "失败检查项: ${RED}$FAIL_COUNT${NC}"
|
||||
|
||||
if [ $FAIL_COUNT -eq 0 ]; then
|
||||
echo -e "\n${GREEN}🎉 所有数据持久化配置检查通过!${NC}"
|
||||
echo "系统已准备好进行Docker部署。"
|
||||
exit 0
|
||||
else
|
||||
echo -e "\n${RED}❌ 发现 $FAIL_COUNT 个配置问题,请修复后重新验证。${NC}"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user