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

feat(server-check): 新增容器信息收集功能

- 实现 Test-ContainerInformation 函数用于收集服务器容器信息
- 支持 Docker 和 Podman 运行时检测与兼容
- 收集容器基本信息、状态、健康检查、重启策略等
- 获取容器网络设置、端口映射及挂载信息
- 在自检报告中新增容器信息展示模块
- 更新服务自检主流程以调用容器信息收集
- 优化日志导出函数中的条件判断逻辑
- 修复备份下载失败时的临时目录创建问题
- 调整 container_update.sh 中默认镜像版本至 v15
- 修改容器启动命令为完整写法以提升稳定性
- 更新需求文档中标记现场数据备份为已实现
- 在需求文档中新增容器信息收集与定时任务查询项
上级 0663da88
...@@ -204,7 +204,7 @@ ...@@ -204,7 +204,7 @@
2、数据库用户权限需要进入umysql容器内,数据库账号为root,密码为dNrprU&2S 2、数据库用户权限需要进入umysql容器内,数据库账号为root,密码为dNrprU&2S
注意:此检测函数需要在日志导出函数前执行,并且main主函数和日志记录函数都需要补充调用!将文件权限打印出来! 注意:此检测函数需要在日志导出函数前执行,并且main主函数和日志记录函数都需要补充调用!将文件权限打印出来!
##### 现场数据备份(实现): ##### 现场数据备份(✅ 已实现):
函数名称:DataBakup 函数名称:DataBakup
先判断目标服务器是新统一平台还是传统平台,再备份对应平台的服务包与配置文件等数据,最后在目标服务器上压缩成tar.gz格式文件导出到电脑上。 先判断目标服务器是新统一平台还是传统平台,再备份对应平台的服务包与配置文件等数据,最后在目标服务器上压缩成tar.gz格式文件导出到电脑上。
...@@ -223,5 +223,13 @@ ...@@ -223,5 +223,13 @@
2、数据库备份,账号为root,密码为dNrprU&2S,数据库容器是umysql容器,数据库是ubains和devops,数据库备份完成后也复制到/home/bakup目录下 2、数据库备份,账号为root,密码为dNrprU&2S,数据库容器是umysql容器,数据库是ubains和devops,数据库备份完成后也复制到/home/bakup目录下
最后将/home/bakup目录压缩成tar.gz格式文件并导出,文件命名补充时间戳,导出完成后清理/home目录下的这个备份文件。 最后将/home/bakup目录压缩成tar.gz格式文件并导出,文件命名补充时间戳,导出完成后清理/home目录下的这个备份文件。
##### 容器信息收集(待实现):
函数名称:Test-ContainerInformation
查询当前服务器上所有容器信息(包含未运行与运行中的信息),可以通过docker inspect来获取MAC地址、端口映射信息、启动文件位置。
信息打印排版:先打印运行中的容器信息,再打印未运行的容器信息。
##### 定时任务查询(待实现):
##### 服务自检报告输出(✅ 已实现): ##### 服务自检报告输出(✅ 已实现):
将服务自检的所有操作步骤与结果输出到日志文件中!自检报告需要补充成md格式! 将服务自检的所有操作步骤与结果输出到日志文件中!自检报告需要补充成md格式!
\ No newline at end of file
...@@ -1277,7 +1277,222 @@ function Test-ServerResources { ...@@ -1277,7 +1277,222 @@ function Test-ServerResources {
} }
# ================================ # ================================
# 服务日志导出 # 服务器容器信息收集
# ================================
function Test-ContainerInformation {
param(
[hashtable]$Server
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 容器信息收集 =========="
$results = @()
# 运行时检测(docker 优先,兼容 podman)
$runtime = 'docker'
$rtCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker --version 2>/dev/null || echo DOCKER_NOT_FOUND"
if ($rtCheck.Output -match 'DOCKER_NOT_FOUND' -or $rtCheck.ExitCode -ne 0) {
$rtCheck2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "podman --version 2>/dev/null || echo PODMAN_NOT_FOUND"
if ($rtCheck2.Output -match 'PODMAN_NOT_FOUND' -or $rtCheck2.ExitCode -ne 0) {
Write-Log -Level "WARN" -Message "目标服务器未检测到 Docker/Podman,跳过容器信息收集"
return $results
} else {
$runtime = 'podman'
Write-Log -Level "INFO" -Message "检测到 Podman 作为容器运行时"
}
}
# 获取所有容器(ID 与 Name)
$listCmd = "$runtime ps -a --format '{{.ID}} {{.Names}}' 2>/dev/null || true"
$listResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $listCmd
$lines = $listResult.Output -split "`n" | Where-Object { $_ -match '\S' } | ForEach-Object { $_.Trim() }
if (-not $lines -or $lines.Count -eq 0) {
Write-Log -Level "INFO" -Message "未发现任何容器"
return $results
}
$idNameMap = @{}
foreach ($line in $lines) {
$parts = $line -split ' ', 2
if ($parts.Count -eq 2) { $idNameMap[$parts[0]] = $parts[1] }
}
$allIds = ($idNameMap.Keys | ForEach-Object { $_ }) -join ' '
Write-Log -Level "INFO" -Message ("发现容器共 {0} 个" -f $idNameMap.Count)
# 一次性 inspect 所有容器(带 size)
$inspectCmd = "$runtime inspect --size $allIds 2>/dev/null"
$inspectResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $inspectCmd
if ($inspectResult.ExitCode -ne 0 -or -not $inspectResult.Output) {
Write-Log -Level "WARN" -Message "无法批量获取容器 inspect 信息"
return $results
}
try {
$inspectArr = $inspectResult.Output | ConvertFrom-Json
# 确保为数组
if ($inspectArr -isnot [System.Collections.IEnumerable]) { $inspectArr = @($inspectArr) }
} catch {
Write-Log -Level "WARN" -Message "inspect JSON 解析失败"
return $results
}
foreach ($info in $inspectArr) {
# 名称和ID
$fullId = if ($info.PSObject.Properties['Id']) { $info.Id } else { $null }
$shortId = if ($fullId) { ($fullId -replace '(.{12}).*', '$1') } else { '' }
$name = $null
if ($fullId) {
$shortKey = $fullId.Substring(0, [Math]::Min(12, $fullId.Length))
if ($idNameMap.ContainsKey($shortKey)) { $name = $idNameMap[$shortKey] }
}
if (-not $name -and $info.PSObject.Properties['Name'] -and $info.Name) { $name = ($info.Name -replace '^/','') }
if (-not $name -and $info.PSObject.Properties['Config'] -and $info.Config -and $info.Config.PSObject.Properties['Hostname']) { $name = $info.Config.Hostname }
if (-not $name) { $name = $shortId }
# 基本信息
$image = $null
if ($info.PSObject.Properties['Config'] -and $info.Config -and $info.Config.PSObject.Properties['Image']) { $image = $info.Config.Image }
$created = if ($info.PSObject.Properties['Created']) { $info.Created } else { $null }
$status = 'unknown'
$running = $false
if ($info.PSObject.Properties['State'] -and $info.State -and $info.State.PSObject.Properties['Status']) {
$status = $info.State.Status
$running = ($status -eq 'running')
}
$health = $null
if ($info.PSObject.Properties['State'] -and $info.State -and $info.State.PSObject.Properties['Health']) {
$healthObj = $info.State.PSObject.Properties['Health'].Value
if ($healthObj -and $healthObj.PSObject.Properties['Status']) { $health = $healthObj.Status }
}
$restartCount = 0
if ($info.PSObject.Properties['RestartCount']) { $restartCount = $info.RestartCount }
$restartPolicy = $null
if ($info.PSObject.Properties['HostConfig'] -and $info.HostConfig -and $info.HostConfig.PSObject.Properties['RestartPolicy']) {
$rpObj = $info.HostConfig.RestartPolicy
if ($rpObj -and $rpObj.PSObject.Properties['Name']) { $restartPolicy = $rpObj.Name }
}
$sizeRw = $null
if ($info.PSObject.Properties['SizeRw']) { $sizeRw = $info.SizeRw }
$sizeRoot = $null
if ($info.PSObject.Properties['SizeRootFs']) { $sizeRoot = $info.SizeRootFs }
# 端口映射(主机端口 -> 容器端口/协议)
$ports = @()
if ($info.PSObject.Properties['NetworkSettings'] -and $info.NetworkSettings -and $info.NetworkSettings.PSObject.Properties['Ports']) {
$portsObj = $info.NetworkSettings.Ports
if ($portsObj -is [System.Collections.IDictionary]) {
foreach ($k in $portsObj.Keys) {
$binding = $portsObj[$k]
if ($binding) {
foreach ($b in $binding) {
$hostIp = if ($b.PSObject.Properties['HostIp'] -and $b.HostIp) { $b.HostIp } else { '0.0.0.0' }
$hostPort = if ($b.PSObject.Properties['HostPort']) { $b.HostPort } else { '' }
$ports += ("{0}:{1}->{2}" -f $hostIp, $hostPort, $k)
}
} else {
$ports += ("- -> {0}" -f $k)
}
}
} else {
foreach ($prop in $portsObj.PSObject.Properties) {
$k = $prop.Name
$binding = $prop.Value
if ($binding) {
foreach ($b in $binding) {
$hostIp = if ($b.PSObject.Properties['HostIp'] -and $b.HostIp) { $b.HostIp } else { '0.0.0.0' }
$hostPort = if ($b.PSObject.Properties['HostPort']) { $b.HostPort } else { '' }
$ports += ("{0}:{1}->{2}" -f $hostIp, $hostPort, $k)
}
} else {
$ports += ("- -> {0}" -f $k)
}
}
}
}
# 网络信息(网络名:IP)
$networks = @()
$ipAddr = $null
if ($info.PSObject.Properties['NetworkSettings'] -and $info.NetworkSettings) {
if ($info.NetworkSettings.PSObject.Properties['Networks'] -and $info.NetworkSettings.Networks) {
$netsObj = $info.NetworkSettings.Networks
if ($netsObj -is [System.Collections.IDictionary]) {
foreach ($nk in $netsObj.Keys) {
$val = $netsObj[$nk]
$nip = if ($val -and $val.PSObject.Properties['IPAddress']) { $val.IPAddress } else { $null }
if (-not $ipAddr -and $nip) { $ipAddr = $nip }
$nipOut = if ($nip) { $nip } else { '-' }
$networks += ("{0}:{1}" -f $nk, $nipOut)
}
} else {
foreach ($prop in $netsObj.PSObject.Properties) {
$nk = $prop.Name
$val = $prop.Value
$nip = if ($val -and $val.PSObject.Properties['IPAddress']) { $val.IPAddress } else { $null }
if (-not $ipAddr -and $nip) { $ipAddr = $nip }
$nipOut = if ($nip) { $nip } else { '-' }
$networks += ("{0}:{1}" -f $nk, $nipOut)
}
}
}
if (-not $ipAddr -and $info.NetworkSettings.PSObject.Properties['IPAddress'] -and $info.NetworkSettings.IPAddress) { $ipAddr = $info.NetworkSettings.IPAddress }
}
# 挂载列表(Source:Destination(:rw/ro))
$mounts = @()
if ($info.PSObject.Properties['Mounts'] -and $info.Mounts) {
foreach ($m in $info.Mounts) {
$src = if ($m.PSObject.Properties['Source']) { $m.Source } else { '' }
$dst = if ($m.PSObject.Properties['Destination']) { $m.Destination } else { '' }
$rw = if ($m.PSObject.Properties['RW'] -and $m.RW) { 'rw' } else { 'ro' }
$mounts += ("{0}:{1}({2})" -f $src, $dst, $rw)
}
}
$item = @{
Name = $name
Id = $shortId
Image = $image
Status = $status
Running = $running
Health = $health
RestartPolicy = $restartPolicy
RestartCount = $restartCount
Created = $created
IPAddress = $ipAddr
Networks = $networks
Ports = $ports
Mounts = $mounts
SizeRw = $sizeRw
SizeRootFs = $sizeRoot
}
$results += $item
# 摘要日志
$icon = if ($running) { '✅' } else { '⛔' }
$portStr = if ($ports.Count -gt 0) { ($ports -join ', ') } else { '-' }
$netStr = if ($networks.Count -gt 0) { ($networks -join ', ') } else { '-' }
$szStrParts = @()
if ($sizeRw -ne $null) { $szStrParts += ("rw={0}" -f $sizeRw) }
if ($sizeRoot -ne $null) { $szStrParts += ("root={0}" -f $sizeRoot) }
$szOut = if ($szStrParts.Count -gt 0) { ($szStrParts -join ', ') } else { '-' }
$rpStr = if ($restartPolicy) { "; 重启策略: $restartPolicy" } else { '' }
$level = if ($running) { 'SUCCESS' } else { 'INFO' }
Write-Log -Level $level -Message "- $icon 名称: $($name) | 镜像: $($image) | 状态: $($status)$rpStr | IP: $($ipAddr)"
Write-Log -Level "INFO" -Message " 端口: $portStr"
Write-Log -Level "INFO" -Message " 网络: $netStr | 大小: $szOut"
if ($mounts.Count -gt 0) {
$showMounts = if ($mounts.Count -gt 3) { ($mounts | Select-Object -First 3) + '...' } else { $mounts }
Write-Log -Level "INFO" -Message (" 挂载: {0}" -f ($showMounts -join '; '))
}
}
Write-Log -Level "INFO" -Message "容器信息收集完成"
return $results
}
# ================================
# 日志导出
# ================================ # ================================
function Export-ServiceLogs { function Export-ServiceLogs {
param( param(
...@@ -1328,6 +1543,7 @@ function Export-ServiceLogs { ...@@ -1328,6 +1543,7 @@ function Export-ServiceLogs {
} }
else { else {
Write-Log -Level "WARN" -Message "未检测到 ujava 容器,跳过 Java 服务日志导出" Write-Log -Level "WARN" -Message "未检测到 ujava 容器,跳过 Java 服务日志导出"
} }
} }
else { else {
...@@ -1518,7 +1734,8 @@ function Show-HealthReport { ...@@ -1518,7 +1734,8 @@ function Show-HealthReport {
[hashtable]$ResourceResults, [hashtable]$ResourceResults,
[hashtable]$LogExportResults, [hashtable]$LogExportResults,
[hashtable]$NTPResults, [hashtable]$NTPResults,
[hashtable]$FilePermResults [hashtable]$FilePermResults,
[array]$ContainerInfo
) )
# Markdown 报告初始化(健壮性与回退) # Markdown 报告初始化(健壮性与回退)
...@@ -1737,6 +1954,53 @@ function Show-HealthReport { ...@@ -1737,6 +1954,53 @@ function Show-HealthReport {
} }
$md += "" $md += ""
# 容器信息
$md += "## 容器信息"
if (-not $ContainerInfo -or $ContainerInfo.Count -eq 0) {
Write-Host "【容器信息】未发现容器或容器运行时不可用" -ForegroundColor Yellow
$md += "- 未发现容器或容器运行时不可用"
}
else {
$runningCount = ($ContainerInfo | Where-Object { $_.Running }).Count
$stoppedCount = $ContainerInfo.Count - $runningCount
Write-Host "【容器信息】总数: $($ContainerInfo.Count) | 运行中: $runningCount | 已停止: $stoppedCount" -ForegroundColor Yellow
$md += "- 容器总数: $($ContainerInfo.Count)"
$md += "- 运行中: $runningCount"
$md += "- 已停止: $stoppedCount"
foreach ($c in $ContainerInfo) {
$statusIcon = if ($c.Running) { "✅" } else { "⛔" }
$ip = if ($c.IPAddress) { $c.IPAddress } else { "-" }
$health = if ($c.Health) { $c.Health } else { "-" }
$rp = if ($c.RestartPolicy) { $c.RestartPolicy } else { "-" }
$rc = if ($c.RestartCount -ne $null) { $c.RestartCount } else { "-" }
$ports = @(); if ($c.Ports -and $c.Ports.Count -gt 0) { $ports = $c.Ports }
$nets = @(); if ($c.Networks -and $c.Networks.Count -gt 0) { $nets = $c.Networks }
$showPorts = if ($ports.Count -gt 6) { ($ports | Select-Object -First 6) + "..." } else { $ports }
$showNets = if ($nets.Count -gt 6) { ($nets | Select-Object -First 6) + "..." } else { $nets }
$mountsStr = '-'
if ($c.Mounts -and $c.Mounts.Count -gt 0) {
$showMounts = $null
if ($c.Mounts.Count -gt 3) {
$showMounts = @()
$showMounts += ($c.Mounts | Select-Object -First 3)
$showMounts += '...'
}
else {
$showMounts = $c.Mounts
}
$mountsStr = ($showMounts -join '; ')
}
$szParts = @(); if ($c.SizeRw -ne $null) { $szParts += ("rw={0}" -f $c.SizeRw) }; if ($c.SizeRootFs -ne $null) { $szParts += ("root={0}" -f $c.SizeRootFs) }
$szStr = if ($szParts.Count -gt 0) { ($szParts -join ', ') } else { '-' }
$md += "- $statusIcon 名称: $($c.Name) | 镜像: $($c.Image) | 状态: $($c.Status) | 健康: $health | 重启: $rp/$rc | IP: $ip"
$md += " - 端口: $portsStr"
$md += " - 网络: $netsStr"
if ($mountsStr -ne '-') { $md += " - 挂载: $mountsStr" }
$md += " - 大小: $szStr"
}
}
$md += ""
# 总结 # 总结
Write-Host "==================================================================" -ForegroundColor Cyan Write-Host "==================================================================" -ForegroundColor Cyan
Write-Host "【检测总结】" -ForegroundColor Yellow Write-Host "【检测总结】" -ForegroundColor Yellow
...@@ -2172,7 +2436,7 @@ function DataBakup { ...@@ -2172,7 +2436,7 @@ function DataBakup {
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Log -Level "ERROR" -Message "[BAK] 下载备份失败,尝试使用 TEMP 目录重试" Write-Log -Level "ERROR" -Message "[BAK] 下载备份失败,尝试使用 TEMP 目录重试"
$fallbackDir = Join-Path $env:TEMP "ubains_downloads" $fallbackDir = Join-Path $env:TEMP "ubains_downloads"
if (-not (Test-Path $fallbackDir)) { New-Item -ItemType Directory -Path $fallbackDir | Out-Null } if (-not (Test-Path $fallbackDir)) { New-Item -ItemType Directory -Path $fallbackDir -Force | Out-Null }
$fallbackFile = Join-Path $fallbackDir $tarName $fallbackFile = Join-Path $fallbackDir $tarName
$pscpCmd2 = "`"$($script:PSCP_PATH)`" -batch -scp -P $($Server.Port) -pw `"$($Server.Pass)`" $($Server.User)@$($Server.IP):$tarPath `"$fallbackFile`"" $pscpCmd2 = "`"$($script:PSCP_PATH)`" -batch -scp -P $($Server.Port) -pw `"$($Server.Pass)`" $($Server.User)@$($Server.IP):$tarPath `"$fallbackFile`""
Write-Log -Level "INFO" -Message "[BAK] 重试下载命令: $pscpCmd2" Write-Log -Level "INFO" -Message "[BAK] 重试下载命令: $pscpCmd2"
...@@ -2305,7 +2569,29 @@ function Main { ...@@ -2305,7 +2569,29 @@ function Main {
# 服务器资源分析(所有平台都需要检测) # 服务器资源分析(所有平台都需要检测)
Write-Host "" Write-Host ""
$resourceResults = Test-ServerResources -Server $server $resourceResults = Test-ServerResources -Server $server
# 容器信息收集(加入到自检报告)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 容器信息(报告) =========="
$containerInfo = Test-ContainerInformation -Server $server
if (-not $containerInfo -or $containerInfo.Count -eq 0) {
Write-Log -Level "INFO" -Message "未发现容器或 Docker 不可用"
} else {
# 输出 Markdown 友好日志
Write-Log -Level "INFO" -Message "容器数量: $($containerInfo.Count)"
foreach ($c in $containerInfo) {
$statusIcon = if ($c.Running) { "✅" } else { "⛔" }
$ip = if ($c.IPAddress) { $c.IPAddress } else { "-" }
$ports = if ($c.Ports -and $c.Ports.Count -gt 0) { ($c.Ports -join ', ') } else { '-' }
$mounts = if ($c.Mounts -and $c.Mounts.Count -gt 0) { ($c.Mounts -join '; ') } else { '-' }
$szParts = @(); if ($c.SizeRw -ne $null) { $szParts += ("rw={0}" -f $c.SizeRw) }; if ($c.SizeRootFs -ne $null) { $szParts += ("root={0}" -f $c.SizeRootFs) }
$szStr = if ($szParts.Count -gt 0) { ($szParts -join ', ') } else { '-' }
Write-Log -Level "INFO" -Message "- $statusIcon 名称: $($c.Name) | 镜像: $($c.Image) | 状态: $($c.Status) | IP: $ip"
Write-Log -Level "INFO" -Message " 端口: $ports"
Write-Log -Level "INFO" -Message " 网络: $mounts | 大小: $szStr"
}
}
# 检测配置文件中的IP地址 # 检测配置文件中的IP地址
Write-Host "" Write-Host ""
if ($platformType -eq "new") { if ($platformType -eq "new") {
...@@ -2354,7 +2640,8 @@ function Main { ...@@ -2354,7 +2640,8 @@ function Main {
-ResourceResults $resourceResults ` -ResourceResults $resourceResults `
-LogExportResults $logExportResults ` -LogExportResults $logExportResults `
-NTPResults $ntpResults ` -NTPResults $ntpResults `
-FilePermResults $filePermResults -FilePermResults $filePermResults `
-ContainerInfo $containerInfo
} }
# 执行主函数 # 执行主函数
......
...@@ -1221,8 +1221,8 @@ python_oldplatform_x86() { ...@@ -1221,8 +1221,8 @@ python_oldplatform_x86() {
: "${sudoset:=}" : "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-upython}" local container_name="${DEPLOY_CONTAINER_NAME:-upython}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/python_v14.tar.gz}" local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/python_v15.tar.gz}"
local image_name="139.9.60.86:5000/upython:v14" local image_name="139.9.60.86:5000/upython:v15"
local host_html_dir="/var/www/html" local host_html_dir="/var/www/html"
local container_start_cmd="/var/www/html/start.sh" local container_start_cmd="/var/www/html/start.sh"
local mac_addr="02:42:ac:11:00:06" local mac_addr="02:42:ac:11:00:06"
...@@ -1265,16 +1265,7 @@ python_oldplatform_x86() { ...@@ -1265,16 +1265,7 @@ python_oldplatform_x86() {
fi fi
log "INFO" "🚀 正在启动 Python 容器: ${container_name}" log "INFO" "🚀 正在启动 Python 容器: ${container_name}"
$sudoset docker run -d \ $sudoset docker run -itd -p 8002:8002 -p 8000:8000 -p 8443:8443 -p 9009:9009 -v /var/www/html:/var/www/html -v /etc/localtime:/etc/localtime:ro --restart=always --mac-address="02:42:ac:11:00:06" --privileged --name=upython ${image_name} /var/www/html/start.sh
--name "${container_name}" \
--restart=always \
--mac-address="${mac_addr}" \
--privileged \
"${ports[@]}" \
-v "${host_html_dir}:/var/www/html" \
-v "/etc/localtime:/etc/localtime:ro" \
"${image_name}" \
"${container_start_cmd}"
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败" log "ERROR" "⛔ 容器启动失败"
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论