提交 e44e9787 authored 作者: 陈泽健's avatar 陈泽健

feat(script): 实现服务日志导出功能

- 新增 pscp 路径检测逻辑,支持本地、远程更新目录及系统 PATH 查找
- 添加新统一平台与传统平台的日志路径配置
- 实现 Export-ServiceLogs 函数,支持按平台类型导出对应服务日志
- 支持日志导出结果展示与失败原因记录
- 在主流程中集成日志导出选项,用户可选择是否导出日志
- 更新健康报告展示逻辑,新增日志导出结果展示部分
- 补充相关日志记录与错误处理机制
上级 1e747885
# 容器升级需求说明文档
## 📋 概述
本系统由三个核心 Shell 脚本组成,用于实现容器化服务的远程部署与升级管理:
1. **`remote_update.sh`**:远程升级控制脚本,负责从主服务器向目标服务器传输镜像和部署脚本,并触发远端部署流程,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\remote_update.sh
2. **`container_update.sh`**:容器部署执行脚本,支持交互式和命令行两种模式,可单独执行进行本地容器部署,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\container_update.sh
3. **`upload_to_nas.sh`**:镜像打包上传脚本,负责将容器镜像打包并上传至公司 NAS 网盘,路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\upload_to_nas.sh
### 背景
目前系统支持多种容器服务(Java、Redis、EMQX、Python、Nacos、Nginx)的部署与升级,需要区分两种平台环境:
- **新统一平台**:使用 `/data/` 目录结构
- **传统平台**:使用 `/var/www/` 目录结构
**现有功能**
- ✅ 远程升级指定服务器的容器版本 `[已实现]`
1. java容器远程更新(传统平台和新统一平台已验证通过)
2. uemqx容器(传统平台和新统一平台已验证通过)
3. uredis容器(传统平台和新统一平台已验证通过)
4. upython容器()
5. nacos容器(新统一平台已验证通过)
6. nginx容器(新统一平台已验证通过)
7. mysql容器(未验证)
- ✅ 自动校验目标服务器架构(仅支持 x86)`[已实现]`
- ✅ 自动递增容器编号避免命名冲突 `[已实现]`
- ✅ 支持多种容器类型的差异化部署 `[已实现]`
- ✅ 支持预设服务器和手动输入服务器信息(IP/端口/用户名/密码)`[已实现]`
- ✅ EMQX 文件同步(配置、数据、日志目录)`[已实现]`
- ✅ Nginx 文件同步(配置、HTML、证书目录)`[已实现]`
- ✅ 自动校验目标服务器是新统一平台目录还是传统平台目录,通过宿主机上的目录来判断,存在/data/services目录的是新统一平台,不存在的则是传统平台 `[已实现]`
- ✅ 自动校验目标服务器上的容器是否已更新 `[已实现]`
- ✅ 远程更新完成后需要将目标服务器上的镜像包清理 `[已实现]`
- ⏸️ Python 文件同步 `[功能保留,暂不启用]`
- ✅ 将主服务器上的容器镜像及部署脚本打包上传至网盘 `[已实现]`
- ✅ container_update.sh 支持单独执行(交互式模式)`[已实现]`
**待实现功能**
- ❌ 最终将sh脚本改为电脑命令行工具上可执行的脚本格式 `[待最后开发]`
---
## 🎯 功能实现总览
> 最后更新时间:2025-12-07
### 远程升级功能 (`remote_update.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 服务器选择 | 支持预设服务器列表和手动输入(IP/端口/用户名/密码) | ✅ 已实现 |
| 自定义端口 | 支持自定义 SSH 端口(默认 22) | ✅ 已实现 |
| 架构校验 | 校验目标服务器是否为 x86 架构 | ✅ 已实现 |
| 镜像传输 | 自动传输镜像文件和部署脚本 | ✅ 已实现 |
| 容器停止 | 自动停止远端旧容器 | ✅ 已实现 |
| 平台识别 | 自动检测目标服务器平台类型(检测 /data/services 目录) | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像版本是否已更新 | ✅ 已实现 |
| EMQX 同步 | 同步 EMQX 配置、数据、日志目录 | ✅ 已实现 |
| Python 同步 | 同步 Python 代码和配置 | ⏸️ 暂停 |
| Nginx 同步 | 同步 Nginx 配置、HTML、证书 | ✅ 已实现 |
| 容器编号 | 自动递增容器编号 | ✅ 已实现 |
| 远端执行 | 调用远端部署脚本 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
### 容器部署功能 (`container_update.sh`)
| 容器类型 | 新平台 | 传统平台 | 备注 |
|----------|--------|----------|------|
| Java (ujava) | ✅ | ✅ | 完整端口映射和目录挂载 |
| Redis (uredis) | ✅ | ✅ | 支持配置迁移和端口释放 |
| EMQX (uemqx) | ✅ | ✅ | 多端口映射,完整目录挂载 |
| Python (upython) | ✅ | ✅ | 容器部署正常,暂不同步文件 |
| Nacos (unacos) | ✅ | ❌ | 仅支持新平台,单机模式 |
| Nginx (unginx) | ✅ | ❌ | 仅支持新平台,完整目录挂载 |
### 交互式部署功能 (`container_update.sh` 交互式模式)
| 步骤 | 功能描述 | 状态 |
|------|----------|------|
| 步骤1 | 询问更新哪个容器 | ✅ 已实现 |
| 步骤2 | 检查压缩包中是否存在所需镜像和配置文件 | ✅ 已实现 |
| 步骤3 | 判断当前服务器是传统平台还是新统一平台 | ✅ 已实现 |
| 步骤4 | 检查并停止正在运行的同类型容器(不删除) | ✅ 已实现 |
| 步骤5 | 备份原有配置文件 | ❌ 待开发 |
| 步骤6 | 更新配置文件并替换IP地址为当前服务器IP | ❌ 待开发 |
| 步骤7 | 执行容器部署操作 | ✅ 已实现 |
| 步骤8 | 验证容器部署是否成功 | ✅ 已实现 |
| 步骤9 | 记录操作日志到当前目录(日志审计) | ✅ 已实现 |
### 镜像上传功能 (`upload_to_nas.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 目录打包 | 将指定目录压缩为 tar.gz 格式 | ✅ 已实现 |
| NAS 挂载 | 自动挂载公司 SMB 网盘 | ✅ 已实现 |
| 进度显示 | 打包和上传过程显示进度条 | ✅ 已实现 |
| 密码加密 | 网盘密码使用 base64 加密存储 | ✅ 已实现 |
### 新增脚本remote_update_win
> 参考脚本:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\remote_update.sh
> 新增脚本:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\远程容器更新\remote_update_win
> 脚本要求:最好能够直接运行,不用安装依赖
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 服务器选择 | 支持预设服务器列表和手动输入(IP/端口/用户名/密码) | ❌ 待开发 |
| 自定义端口 | 支持自定义 SSH 端口(默认 22) | ❌ 待开发 |
| 架构校验 | 校验目标服务器是否为 x86 架构 | ❌ 待开发 |
| 镜像传输 | 自动传输镜像文件和部署脚本 | ❌ 待开发|
| 容器停止 | 自动停止远端旧容器 | ❌ 待开发 |
| 平台识别 | 自动检测目标服务器平台类型(检测 /data/services 目录) | ❌ 待开发 |
| 版本校验 | 自动校验远端容器镜像版本是否已更新 | ❌ 待开发 |
| EMQX 同步 | 同步 EMQX 配置、数据、日志目录 | ❌ 待开发 |
| Python 同步 | 同步 Python 代码和配置 | ⏸️ 暂停 |
| Nginx 同步 | 同步 Nginx 配置、HTML、证书 | ❌ 待开发 |
| 容器编号 | 自动递增容器编号 | ❌ 待开发 |
| 远端执行 | 调用远端部署脚本 | ❌ 待开发 |
| 日志审计 | 每一步骤的日志都需要记录到log文件中 | ❌ 待开发 |
### 待开发功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 批量部署 | 一次升级多台服务器 | ❌ 待开发 |
| 版本管理 | 记录部署版本和时间 | ❌ 待开发 |
---
\ No newline at end of file
...@@ -65,6 +65,7 @@ $SSH_TIMEOUT = 30 ...@@ -65,6 +65,7 @@ $SSH_TIMEOUT = 30
# PuTTY 工具路径 # PuTTY 工具路径
$script:PLINK_PATH = $null $script:PLINK_PATH = $null
$script:PSCP_PATH = $null
$script:PreferredSSHTool = $null $script:PreferredSSHTool = $null
# ================================ # ================================
...@@ -172,6 +173,36 @@ $DNSTestDomains = @( ...@@ -172,6 +173,36 @@ $DNSTestDomains = @(
"www.aliyun.com" "www.aliyun.com"
) )
# ================================
# 日志导出配置
# ================================
# 新统一平台日志路径配置
$NewPlatformLogs = @(
@{ Name = "auth_log.out"; RemotePath = "/data/services/api/auth/auth-sso-auth/log.out" }
@{ Name = "gatway_log.out"; RemotePath = "/data/services/api/auth/auth-sso-gatway/log.out" }
@{ Name = "system_log.out"; RemotePath = "/data/services/api/auth/auth-sso-system/log.out" }
@{ Name = "对内2.0_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-meeting2.0/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "对内3.0_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-meeting3.0/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "对外服务_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-meeting-extapi/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "信息调度_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-message-scheduling/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "MQTT_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-mqtt/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "定时任务_ubains-INFO-AND-ERROR.log"; RemotePath = "/data/services/api/java-meeting/java-quartz/logs/ubains-INFO-AND-ERROR.log" }
)
# 传统平台 ujava 日志路径配置
$OldPlatformUjavaLogs = @(
@{ Name = "对内后端_ubains-INFO-AND-ERROR.log"; RemotePath = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log" }
@{ Name = "对外后端_ubains-INFO-AND-ERROR.log"; RemotePath = "/var/www/java/external-meeting-api/logs/ubains-INFO-AND-ERROR.log" }
)
# 传统平台 upython 日志路径配置
$OldPlatformUpythonLogs = @(
@{ Name = "运维集控_error.log"; RemotePath = "/var/www/html/log/error.log" }
@{ Name = "运维集控_uinfo.log"; RemotePath = "/var/www/html/log/uinfo.log" }
@{ Name = "运维集控_uwsgi.log"; RemotePath = "/var/www/html/log/uwsgi.log" }
)
# ================================ # ================================
# 日志函数 # 日志函数
# ================================ # ================================
...@@ -284,6 +315,35 @@ function Test-Dependencies { ...@@ -284,6 +315,35 @@ function Test-Dependencies {
return $false return $false
} }
# 检查 pscp(用于文件传输/日志导出)
if ($script:PreferredSSHTool -eq "plink") {
# 1. 优先检查脚本同目录下的 pscp.exe
$localPscpPath = Join-Path $SCRIPT_DIR "pscp.exe"
if (Test-Path $localPscpPath) {
$script:PSCP_PATH = $localPscpPath
Write-Log -Level "INFO" -Message " pscp 已找到 (本地): $localPscpPath"
}
else {
# 2. 检查远程容器更新目录下的 pscp.exe
$remoteUpdatePscpPath = Join-Path (Split-Path $SCRIPT_DIR -Parent) "远程容器更新\pscp.exe"
if (Test-Path $remoteUpdatePscpPath) {
$script:PSCP_PATH = $remoteUpdatePscpPath
Write-Log -Level "INFO" -Message " pscp 已找到 (远程容器更新目录): $remoteUpdatePscpPath"
}
else {
# 3. 检查系统 PATH 中的 pscp
try {
$systemPscp = Get-Command pscp -ErrorAction Stop
$script:PSCP_PATH = $systemPscp.Source
Write-Log -Level "INFO" -Message " pscp 已找到 (系统): $($script:PSCP_PATH)"
}
catch {
Write-Log -Level "WARN" -Message " pscp.exe 未找到,日志导出功能将不可用"
}
}
}
}
Write-Log -Level "INFO" -Message "系统依赖检查通过 (使用 $script:PreferredSSHTool 进行密码认证)" Write-Log -Level "INFO" -Message "系统依赖检查通过 (使用 $script:PreferredSSHTool 进行密码认证)"
return $true return $true
} }
...@@ -1209,6 +1269,183 @@ function Test-ServerResources { ...@@ -1209,6 +1269,183 @@ function Test-ServerResources {
return $results return $results
} }
y
# ================================
# 服务日志导出
# ================================
function Export-ServiceLogs {
param(
[hashtable]$Server,
[string]$PlatformType,
[hashtable]$SystemInfo
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 服务日志导出 =========="
# 检查 pscp 是否可用
if (-not $script:PSCP_PATH -or -not (Test-Path $script:PSCP_PATH)) {
Write-Log -Level "ERROR" -Message "pscp.exe 未找到,无法导出日志"
Write-Log -Level "ERROR" -Message "请将 pscp.exe 放在脚本同目录下"
Write-Log -Level "ERROR" -Message "下载地址: https://the.earth.li/~sgtatham/putty/latest/w64/pscp.exe"
return @{
Success = $false
ExportedFiles = @()
FailedFiles = @()
ExportDir = $null
}
}
# 创建导出目录(以服务器IP和时间戳命名)
$exportTimestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$exportDirName = "logs_$($Server.IP)_$exportTimestamp"
$exportDir = Join-Path $SCRIPT_DIR $exportDirName
if (-not (Test-Path $exportDir)) {
New-Item -ItemType Directory -Path $exportDir -Force | Out-Null
}
Write-Log -Level "INFO" -Message "日志导出目录: $exportDir"
$exportedFiles = @()
$failedFiles = @()
$logsToExport = @()
# 根据平台类型选择要导出的日志
if ($PlatformType -eq "new") {
# 新统一平台
Write-Log -Level "INFO" -Message "平台类型: 新统一平台"
if ($SystemInfo.HasUjava) {
Write-Log -Level "INFO" -Message "检测到 ujava 容器,准备导出 Java 服务日志..."
$logsToExport += $NewPlatformLogs
}
else {
Write-Log -Level "WARN" -Message "未检测到 ujava 容器,跳过 Java 服务日志导出"
}
}
else {
# 传统平台
Write-Log -Level "INFO" -Message "平台类型: 传统平台"
if ($SystemInfo.HasUjava) {
Write-Log -Level "INFO" -Message "检测到 ujava 容器,准备导出 Java 服务日志..."
$logsToExport += $OldPlatformUjavaLogs
}
else {
Write-Log -Level "WARN" -Message "未检测到 ujava 容器,跳过 Java 服务日志导出"
}
if ($SystemInfo.HasUpython) {
Write-Log -Level "INFO" -Message "检测到 upython 容器,准备导出 Python 服务日志..."
$logsToExport += $OldPlatformUpythonLogs
}
else {
Write-Log -Level "WARN" -Message "未检测到 upython 容器,跳过 Python 服务日志导出"
}
}
if ($logsToExport.Count -eq 0) {
Write-Log -Level "WARN" -Message "没有需要导出的日志文件"
return @{
Success = $true
ExportedFiles = @()
FailedFiles = @()
ExportDir = $exportDir
}
}
Write-Log -Level "INFO" -Message "共 $($logsToExport.Count) 个日志文件待导出..."
Write-Host ""
# 逐个导出日志文件
foreach ($logConfig in $logsToExport) {
$localFileName = $logConfig.Name
$remotePath = $logConfig.RemotePath
$localPath = Join-Path $exportDir $localFileName
Write-Log -Level "INFO" -Message "正在导出: $localFileName"
Write-Log -Level "INFO" -Message " 远程路径: $remotePath"
# 先检查远程文件是否存在
$checkCmd = "[ -f '$remotePath' ] && echo 'EXISTS' || echo 'NOT_EXISTS'"
$checkResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkCmd
if ($checkResult.Output -match 'NOT_EXISTS') {
Write-Log -Level "WARN" -Message " [跳过] 文件不存在: $remotePath"
$failedFiles += @{
Name = $localFileName
RemotePath = $remotePath
Reason = "文件不存在"
}
continue
}
# 使用 pscp 下载文件
$pscpArgs = @(
"-P", $Server.Port,
"-l", $Server.User,
"-pw", $Server.Pass,
"-batch",
"$($Server.User)@$($Server.IP):$remotePath",
$localPath
)
try {
$pscpResult = & $script:PSCP_PATH @pscpArgs 2>&1
$exitCode = $LASTEXITCODE
# 如果失败且是因为主机密钥问题,则自动接受密钥后重试
if ($exitCode -ne 0 -and ($pscpResult -match "host key" -or $pscpResult -match "Cannot confirm")) {
$cmdLine = "echo y | `"$($script:PSCP_PATH)`" -P $($Server.Port) -l $($Server.User) -pw `"$($Server.Pass)`" `"$($Server.User)@$($Server.IP):$remotePath`" `"$localPath`""
$pscpResult = cmd /c $cmdLine 2>&1
$exitCode = $LASTEXITCODE
}
if ($exitCode -eq 0 -and (Test-Path $localPath)) {
$fileSize = (Get-Item $localPath).Length
$fileSizeKB = [math]::Round($fileSize / 1024, 2)
Write-Log -Level "SUCCESS" -Message " [成功] 已导出 ($fileSizeKB KB)"
$exportedFiles += @{
Name = $localFileName
RemotePath = $remotePath
LocalPath = $localPath
Size = $fileSize
}
}
else {
Write-Log -Level "ERROR" -Message " [失败] 导出失败"
$failedFiles += @{
Name = $localFileName
RemotePath = $remotePath
Reason = "下载失败: $pscpResult"
}
}
}
catch {
Write-Log -Level "ERROR" -Message " [失败] 导出异常: $_"
$failedFiles += @{
Name = $localFileName
RemotePath = $remotePath
Reason = "异常: $_"
}
}
}
Write-Host ""
Write-Log -Level "INFO" -Message "日志导出完成: 成功 $($exportedFiles.Count) 个,失败 $($failedFiles.Count) 个"
if ($exportedFiles.Count -gt 0) {
Write-Log -Level "SUCCESS" -Message "导出目录: $exportDir"
}
return @{
Success = ($exportedFiles.Count -gt 0)
ExportedFiles = $exportedFiles
FailedFiles = $failedFiles
ExportDir = $exportDir
}
}
# ================================ # ================================
# 检测容器内端口服务 # 检测容器内端口服务
...@@ -1272,7 +1509,8 @@ function Show-HealthReport { ...@@ -1272,7 +1509,8 @@ function Show-HealthReport {
[array]$UpythonResults, [array]$UpythonResults,
[array]$UpythonVoiceResults, [array]$UpythonVoiceResults,
[array]$DNSResults, [array]$DNSResults,
[hashtable]$ResourceResults [hashtable]$ResourceResults,
[hashtable]$LogExportResults
) )
Write-Host "" Write-Host ""
...@@ -1459,6 +1697,34 @@ function Show-HealthReport { ...@@ -1459,6 +1697,34 @@ function Show-HealthReport {
Write-Host " 未检测到任何服务" -ForegroundColor Yellow Write-Host " 未检测到任何服务" -ForegroundColor Yellow
} }
# 日志导出结果
if ($LogExportResults) {
Write-Host "【日志导出结果】" -ForegroundColor Yellow
if ($LogExportResults.Success -and $LogExportResults.ExportedFiles.Count -gt 0) {
Write-Host " 导出状态: 成功" -ForegroundColor Green
Write-Host " 导出目录: $($LogExportResults.ExportDir)" -ForegroundColor Cyan
Write-Host " 成功导出 $($LogExportResults.ExportedFiles.Count) 个文件:" -ForegroundColor Green
foreach ($file in $LogExportResults.ExportedFiles) {
$sizeKB = [math]::Round($file.Size / 1024, 2)
Write-Host " - $($file.Name) ($sizeKB KB)" -ForegroundColor Gray
}
}
elseif ($LogExportResults.ExportedFiles.Count -eq 0 -and $LogExportResults.FailedFiles.Count -eq 0) {
Write-Host " 导出状态: 无需导出的日志文件" -ForegroundColor Yellow
}
else {
Write-Host " 导出状态: 部分失败" -ForegroundColor Yellow
}
if ($LogExportResults.FailedFiles.Count -gt 0) {
Write-Host " 失败 $($LogExportResults.FailedFiles.Count) 个文件:" -ForegroundColor Red
foreach ($file in $LogExportResults.FailedFiles) {
Write-Host " - $($file.Name): $($file.Reason)" -ForegroundColor Gray
}
}
Write-Host ""
}
Write-Host "==================================================================" -ForegroundColor Cyan Write-Host "==================================================================" -ForegroundColor Cyan
Write-Host "" Write-Host ""
Write-Host "日志文件: $LOG_FILE" Write-Host "日志文件: $LOG_FILE"
...@@ -1573,6 +1839,19 @@ function Main { ...@@ -1573,6 +1839,19 @@ function Main {
Write-Host "" Write-Host ""
$resourceResults = Test-ServerResources -Server $server $resourceResults = Test-ServerResources -Server $server
# 询问是否导出日志
Write-Host ""
Write-Host "==================================================================" -ForegroundColor Cyan
$exportChoice = Read-Host "是否导出服务日志到本地? (y/n) [默认: n]"
$logExportResults = $null
if ($exportChoice -eq "y" -or $exportChoice -eq "Y") {
$logExportResults = Export-ServiceLogs -Server $server -PlatformType $platformType -SystemInfo $systemInfo
}
else {
Write-Log -Level "INFO" -Message "跳过日志导出"
}
# 生成检测报告 # 生成检测报告
Show-HealthReport -Server $server -PlatformType $platformType -SystemInfo $systemInfo ` Show-HealthReport -Server $server -PlatformType $platformType -SystemInfo $systemInfo `
-UjavaContainerResults $ujavaContainerResults ` -UjavaContainerResults $ujavaContainerResults `
...@@ -1580,7 +1859,8 @@ function Main { ...@@ -1580,7 +1859,8 @@ function Main {
-UpythonResults $upythonResults ` -UpythonResults $upythonResults `
-UpythonVoiceResults $upythonVoiceResults ` -UpythonVoiceResults $upythonVoiceResults `
-DNSResults $dnsResults ` -DNSResults $dnsResults `
-ResourceResults $resourceResults -ResourceResults $resourceResults `
-LogExportResults $logExportResults
} }
# 执行主函数 # 执行主函数
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论