flnxi-Wu/scripts/measure-image-size.ps1

179 lines
5.8 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 主应用镜像体积和分层检查工具
# 用于发布前记录体积基线,帮助判断哪些层需要继续瘦身。
param(
[Parameter(Mandatory = $true)]
[string]$Image,
[int]$TopLayers = 12,
[string]$OutputPath = "",
[int]$MaxImageSizeMB = 0,
[int]$MaxLayerCount = 0,
[int]$MaxLargestLayerMB = 0
)
$ErrorActionPreference = "Stop"
function Format-Bytes {
param([double]$Bytes)
if ($Bytes -ge 1GB) {
return ("{0:N2} GB" -f ($Bytes / 1GB))
}
if ($Bytes -ge 1MB) {
return ("{0:N2} MB" -f ($Bytes / 1MB))
}
if ($Bytes -ge 1KB) {
return ("{0:N2} KB" -f ($Bytes / 1KB))
}
return ("{0:N0} B" -f $Bytes)
}
function Convert-DockerSizeToBytes {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value) -or $Value -eq "0B") {
return 0
}
if ($Value -match '^([0-9.]+)\s*([kmgt]?B)$') {
$number = [double]$Matches[1]
switch ($Matches[2].ToUpperInvariant()) {
"KB" { return $number * 1KB }
"MB" { return $number * 1MB }
"GB" { return $number * 1GB }
"TB" { return $number * 1TB }
default { return $number }
}
}
return 0
}
function Shorten {
param([string]$Text, [int]$Length = 110)
if ([string]::IsNullOrWhiteSpace($Text)) {
return ""
}
$singleLine = ($Text -replace '\s+', ' ').Trim()
if ($singleLine.Length -le $Length) {
return $singleLine
}
return $singleLine.Substring(0, $Length - 3) + "..."
}
Write-Host "[INFO] 检查镜像:$Image" -ForegroundColor Cyan
$inspectJson = docker image inspect $Image --format '{{json .}}'
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($inspectJson)) {
throw "无法读取镜像信息:$Image"
}
$inspect = $inspectJson | ConvertFrom-Json
$historyRows = docker history $Image --no-trunc --format '{{json .}}' |
ForEach-Object {
if (-not [string]::IsNullOrWhiteSpace($_)) {
$_ | ConvertFrom-Json
}
}
$layers = @($historyRows | ForEach-Object {
[pscustomobject]@{
Id = $_.ID
Size = $_.Size
SizeBytes = Convert-DockerSizeToBytes $_.Size
CreatedBy = Shorten $_.CreatedBy
}
})
$largestLayers = @($layers | Sort-Object SizeBytes -Descending | Select-Object -First $TopLayers)
$layerCount = if ($inspect.RootFS -and $inspect.RootFS.Layers) { @($inspect.RootFS.Layers).Count } else { $layers.Count }
$totalSize = [double]$inspect.Size
$largestLayerSize = if ($largestLayers.Count -gt 0) { [double]$largestLayers[0].SizeBytes } else { 0 }
$thresholdProblems = New-Object System.Collections.Generic.List[string]
if ($MaxImageSizeMB -gt 0 -and $totalSize -gt ($MaxImageSizeMB * 1MB)) {
$thresholdProblems.Add("镜像总大小超过阈值:$(Format-Bytes $totalSize) > ${MaxImageSizeMB} MB")
}
if ($MaxLayerCount -gt 0 -and $layerCount -gt $MaxLayerCount) {
$thresholdProblems.Add("RootFS 层数超过阈值:$layerCount > $MaxLayerCount")
}
if ($MaxLargestLayerMB -gt 0 -and $largestLayerSize -gt ($MaxLargestLayerMB * 1MB)) {
$thresholdProblems.Add("最大分层超过阈值:$(Format-Bytes $largestLayerSize) > ${MaxLargestLayerMB} MB")
}
$report = New-Object System.Collections.Generic.List[string]
$report.Add("# FLNXI 镜像体积基线")
$report.Add("")
$report.Add("- 镜像:``$Image``")
$report.Add("- 总大小:$(Format-Bytes $totalSize)")
$report.Add("- RootFS 层数:$layerCount")
$report.Add("- 最大分层:$(Format-Bytes $largestLayerSize)")
$report.Add("- 生成时间:$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')")
if ($MaxImageSizeMB -gt 0 -or $MaxLayerCount -gt 0 -or $MaxLargestLayerMB -gt 0) {
$report.Add("")
$report.Add("## 阈值检查")
$report.Add("")
$report.Add("- 镜像总大小上限:$(if ($MaxImageSizeMB -gt 0) { "$MaxImageSizeMB MB" } else { '未设置' })")
$report.Add("- RootFS 层数上限:$(if ($MaxLayerCount -gt 0) { "$MaxLayerCount" } else { '未设置' })")
$report.Add("- 最大分层上限:$(if ($MaxLargestLayerMB -gt 0) { "$MaxLargestLayerMB MB" } else { '未设置' })")
if ($thresholdProblems.Count -eq 0) {
$report.Add("- 结果:通过")
} else {
$report.Add("- 结果:未通过")
foreach ($problem in $thresholdProblems) {
$report.Add(" - $problem")
}
}
}
$report.Add("")
$report.Add("## 最大分层")
$report.Add("")
$report.Add("| 排名 | 大小 | 指令 |")
$report.Add("| --- | ---: | --- |")
$rank = 1
foreach ($layer in $largestLayers) {
$createdBy = ($layer.CreatedBy -replace '\|', '\|')
$report.Add("| $rank | $($layer.Size) | ``$createdBy`` |")
$rank += 1
}
$report.Add("")
$report.Add("## 重点观察")
$report.Add("")
$report.Add("- 如果 `apk add`、字体、`ffmpeg`、`canvas` 相关层过大,优先评估是否能拆成可选运行能力。")
$report.Add("- 如果 `npm ci` 或 `node_modules` 层过大,优先检查生产依赖和 standalone 输出是否被重复复制。")
$report.Add("- 如果 `prisma` 相关层过大,先确认容器启动迁移是否必须携带完整 CLI,再决定是否拆分迁移镜像。")
$report.Add("- 发布前保留这个报告,后续升级才能看到镜像体积是否真的下降。")
$text = $report -join [Environment]::NewLine
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
Write-Host $text
} else {
$parent = Split-Path -Parent $OutputPath
if ($parent -and -not (Test-Path -LiteralPath $parent -PathType Container)) {
New-Item -ItemType Directory -Path $parent | Out-Null
}
$text | Out-File -LiteralPath $OutputPath -Encoding utf8
Write-Host "[OK] 已写入镜像体积报告:$OutputPath" -ForegroundColor Green
}
if ($thresholdProblems.Count -gt 0) {
foreach ($problem in $thresholdProblems) {
Write-Host "[ERROR] $problem" -ForegroundColor Red
}
exit 64
}