适用于 PowerShell 7.0 及以上版本
容器化已成为现代应用部署的标准方式。Docker Desktop 在 Windows 上的普及,使得运维人员需要将 Docker 管理纳入日常自动化工作流。虽然 Docker CLI 本身足够强大,但通过 PowerShell 的管道、对象处理和脚本能力,可以构建更灵活的容器管理方案——从批量操作到日志聚合,从健康检查到自动扩缩容。
本文将讲解如何用 PowerShell 高效管理 Docker 容器、镜像、网络和数据卷。
Docker 状态查询 通过 PowerShell 包装 Docker CLI 命令,可以获得更好的输出格式和过滤能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 docker version --format '{{.Server.Version}}' docker ps --format '{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { ID = $parts [0 ] Name = $parts [1 ] Image = $parts [2 ] Status = $parts [3 ] Ports = $parts [4 ] } } | Format-Table -AutoSize docker stats --no-stream --format '{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { Container = $parts [0 ] CPU = $parts [1 ] Memory = $parts [2 ] NetworkIO = $parts [3 ] } } | Sort-Object { [double ]($_ .CPU -replace '%' ,'' ) } -Descending | Format-Table -AutoSize docker images --format '{{.Repository}}|{{.Tag}}|{{.Size}}|{{.CreatedSince}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { Repository = $parts [0 ] Tag = $parts [1 ] Size = $parts [2 ] Created = $parts [3 ] } } | Format-Table -AutoSize
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 . . -- ---- ----- ------ ----- - . . . - >. . . - >--------- --- ------ --------- . . . - . . . . . . ---------- --- ---- -------
批量容器管理 在微服务架构中,经常需要对一组容器执行批量操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 function Get-DockerContainerSet { param ( [string ]$NameFilter , [string ]$LabelFilter , [ValidateSet ('running' ,'stopped' ,'all' )] [string ]$State = 'running' ) $args = @ ('ps' , '--format' , '{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.Labels}}' ) if ($State -eq 'all' ) { $args += '-a' } if ($State -eq 'stopped' ) { $args += '--filter' , 'status=exited' } if ($LabelFilter ) { $args += '--filter' , "label=$LabelFilter " } $containers = & docker $args 2 >$null | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { ID = $parts [0 ] Name = $parts [1 ] Image = $parts [2 ] Status = $parts [3 ] Labels = $parts [4 ] } } if ($NameFilter ) { $containers = $containers | Where-Object { $_ .Name -match $NameFilter } } return $containers } $webContainers = Get-DockerContainerSet -NameFilter 'web' -State 'running' foreach ($c in $webContainers ) { Write-Host "重启:$ ($c .Name)" -ForegroundColor Cyan docker restart $c .ID } $stopped = Get-DockerContainerSet -State 'stopped' if ($stopped ) { Write-Host "发现 $ ($stopped .Count) 个已停止的容器" -ForegroundColor Yellow $stopped | ForEach-Object { docker rm $_ .ID } Write-Host "已清理" -ForegroundColor Green } $projectContainers = Get-DockerContainerSet -LabelFilter 'com.docker.compose.project=myapp' $projectContainers | Format-Table -AutoSize
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 - - - - -- ---- ----- ------ ----- - . . . - . . .
容器日志聚合 管理多个容器时,聚合日志是常见的运维需求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function Get-DockerLogsAggregated { param ( [string []]$ContainerNames , [int ]$Tail = 100 , [datetime ]$Since ) $allLogs = [System.Collections.Generic.List [PSObject ]]::new() foreach ($name in $ContainerNames ) { $args = @ ('logs' , '--tail' , $Tail , '--timestamps' ) if ($Since ) { $args += @ ('--since' , $Since .ToString('yyyy-MM-ddTHH:mm:ss' )) } $args += $name $logs = & docker @args 2 >&1 | ForEach-Object { if ($_ -match '^(\d{4}-\d{2}-\d{2}T[\d:.]+)\s+(.+)$' ) { [PSCustomObject ]@ { Timestamp = $Matches [1 ] Container = $name Message = $Matches [2 ].Trim() Level = if ($Matches [2 ] -match 'ERROR|FATAL|CRITICAL' ) { 'Error' } elseif ($Matches [2 ] -match 'WARN' ) { 'Warning' } else { 'Info' } } } } $allLogs .AddRange($logs ) } $allLogs | Sort-Object Timestamp | Format-Table -AutoSize } Get-DockerLogsAggregated -ContainerNames @ ('web-app' , 'api' , 'redis' ) -Tail 50 | Where-Object Level -ne 'Info' | Format-Table -AutoSize $allLogs = Get-DockerLogsAggregated -ContainerNames @ ('web-app' , 'api' ) -Since (Get-Date ).AddHours(-1 )$allLogs | Where-Object { $_ .Message -match 'timeout|connection refused|OOM' }
执行结果示例:
1 2 3 4 5 6 7 8 9 10 Timestamp Container Message Level --------- --------- ------- ----- 2025-05 -20 T08:15:32Z api ERROR: Database connection timeout Error 2025-05 -20 T08:15:35Z api WARN: Retrying connection... Warning 2025-05 -20 T08:20:18Z web-app ERROR: Upstream timeout (504) Error Timestamp Container Message --------- --------- ------- 2025-05 -20 T08:15:32Z api ERROR: Database connection timeout 2025-05 -20 T08:20:18Z web-app ERROR: Upstream timeout (504)
容器健康检查 编写健康检查脚本,监控容器运行状态并在异常时告警:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 function Test-DockerHealth { param ( [string ]$WebhookUrl ) $containers = & docker ps -a --format '{{.ID}}|{{.Names}}|{{.Status}}|{{.Image}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { ID = $parts [0 ] Name = $parts [1 ] Status = $parts [2 ] Image = $parts [3 ] } } $unhealthy = @ () $healthy = @ () foreach ($c in $containers ) { $inspect = docker inspect $c .ID --format '{{.State.Status}}|{{.State.Health.Status}}' 2 >$null if ($inspect ) { $stateParts = $inspect -split '\|' $c | Add-Member -NotePropertyName 'State' -NotePropertyValue $stateParts [0 ] -Force $c | Add-Member -NotePropertyName 'Health' -NotePropertyValue $stateParts [1 ] -Force } if ($c .State -ne 'running' -or $c .Health -eq 'unhealthy' ) { $unhealthy += $c } else { $healthy += $c } } Write-Host "========== Docker 健康报告 ==========" -ForegroundColor Cyan Write-Host "健康:$ ($healthy .Count) | 异常:$ ($unhealthy .Count)" -ForegroundColor $ (if ($unhealthy .Count -gt 0 ) { 'Red' } else { 'Green' }) if ($unhealthy ) { Write-Host "`n异常容器:" -ForegroundColor Red $unhealthy | Format-Table Name, State, Health, Image -AutoSize if ($WebhookUrl ) { $body = @ { text = "Docker 告警:$ ($unhealthy .Count) 个容器异常`n" + ` ($unhealthy | ForEach-Object { "- $ ($_ .Name): $ ($_ .State)/$ ($_ .Health)" }) -join "`n" } | ConvertTo-Json Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $body -ContentType 'application/json' } } return @ { Healthy = $healthy Unhealthy = $unhealthy } } Test-DockerHealth -WebhookUrl 'https://hooks.slack.com/services/xxx'
执行结果示例:
1 2 3 4 5 6 7 8 ---- ----- ------ ----- - -
镜像清理与维护 Docker 镜像会占用大量磁盘空间,定期清理是必要的维护操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 function Optimize-DockerImages { param ( [int ]$KeepLastVersions = 3 ) $diskUsage = docker system df --format '{{.Type}}|{{.Size}}|{{.Reclaimable}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { Type = $parts [0 ] Size = $parts [1 ] Reclaimable = $parts [2 ] } } $diskUsage | Format-Table -AutoSize Write-Host "`n清理悬空镜像..." -ForegroundColor Yellow docker image prune -f $images = & docker images --format '{{.Repository}}|{{.Tag}}|{{.ID}}|{{.CreatedSince}}' | ForEach-Object { $parts = $_ -split '\|' [PSCustomObject ]@ { Repository = $parts [0 ] Tag = $parts [1 ] ID = $parts [2 ] Created = $parts [3 ] } } $groups = $images | Where-Object { $_ .Tag -ne '<none>' } | Group-Object Repository foreach ($group in $groups ) { if ($group .Count -gt $KeepLastVersions ) { $toRemove = $group .Group | Sort-Object Created | Select-Object -SkipLast $KeepLastVersions foreach ($img in $toRemove ) { Write-Host " 移除旧镜像:$ ($img .Repository):$ ($img .Tag)" -ForegroundColor DarkGray docker rmi $img .ID 2 >$null | Out-Null } } } docker system prune -f 2 >$null | Out-Null Write-Host "`n清理完成" -ForegroundColor Green docker system df } Optimize-DockerImages -KeepLastVersions 3
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TYPE SIZE RECLAIMABLEImages 12.45 GB 4.23 GB (34 %) Containers 0 B 0 B Local Volumes 1.2 GB 456 MB (38 %)清理悬空镜像... 移除旧镜像:myapp:v1 移除旧镜像:myapp:v1.5 移除旧镜像:nginx:1.24 清理完成 TYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 8 5 8.22 GB 1.2 GB (15 %) Containers 5 5 0 B 0 B Local Volumes 3 3 1.2 GB 0 B
注意事项
Docker Desktop 依赖 :Windows 上 Docker Desktop 需要 WSL2 或 Hyper-V,确保系统支持
管道编码 :Docker CLI 输出可能包含 ANSI 转义码,使用 --no-trunc 和 --format 模板获取干净输出
并发操作 :大量容器操作时避免逐个串行执行,考虑并行处理(使用 Start-Job 或 Runspace)
日志量控制 :生产容器日志量可能非常大,使用 -Tail 和 -Since 参数限制查询范围
镜像安全 :定期扫描镜像漏洞,使用 docker scout 或 Trivy 等工具
资源限制 :运行容器时始终设置 -m(内存限制)和 --cpus(CPU 限制),防止容器耗尽宿主机资源