适用于 PowerShell 7.0 及以上版本
Azure Container Registry(ACR)是微软 Azure 平台提供的托管私有 Docker 镜像仓库服务。与 Docker Hub 的公开镜像不同,ACR 让企业可以在 Azure 区域内就近存储和管理容器镜像,享受低延迟的镜像拉取体验和网络隔离的安全保障。无论是运行在 Azure Kubernetes Service(AKS)中的编排集群,还是 App Service 的容器化 Web 应用,都可以直接从 ACR 拉取镜像来部署。
然而,镜像管理远不止”推送和拉取”这么简单。随着版本迭代,注册表中会积累大量过时镜像,占用存储空间并增加攻击面。安全团队要求对所有镜像进行漏洞扫描,运维团队需要配置最小权限的访问控制和跨区域复制策略。如果用 Azure Portal 手动处理这些任务,在面对数十个注册表和成百上千个镜像标签时,效率会非常低下。
本文将通过三个部分介绍如何用 PowerShell 自动化管理 ACR:首先是基础操作,包括注册表的创建、镜像的推送拉取和标签管理;其次是镜像清理与安全扫描,自动化识别并移除未使用的镜像层;最后是访问控制与复制策略,通过 RBAC 和 Webhook 将 ACR 深度集成到 CI/CD 流水线中。
ACR 基础管理与镜像操作
ACR 的日常操作包括注册表的创建、登录凭据获取、镜像推送和标签管理。下面的脚本展示了从零开始创建一个 ACR 实例,并通过 Docker CLI 完成镜像的推送和标签操作。注意 az acr 命令虽然功能强大,但很多操作可以直接用 Az PowerShell 模块完成,无需依赖 Azure 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| Install-Module -Name Az.ContainerRegistry -Force -Scope CurrentUser Import-Module Az.ContainerRegistry
Connect-AzAccount -Subscription 'production-subscription'
$resourceGroup = 'rg-acr-demo' $location = 'eastasia' $acrName = 'acrdemo2026'
New-AzResourceGroup -Name $resourceGroup -Location $location -Force
$registry = New-AzContainerRegistry ` -ResourceGroupName $resourceGroup ` -Name $acrName ` -Sku Standard ` -EnableAdminUser $false
Write-Host "ACR 创建完成: $($registry.LoginServer)" -ForegroundColor Green
$cred = Get-AzContainerRegistryCredential ` -ResourceGroupName $resourceGroup ` -Name $acrName
$secPassword = ConvertTo-SecureString $cred.Password -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($cred.Username, $secPassword)
az acr login --name $acrName
$webappImage = 'my-webapp:v1.0.3' $taggedImage = "$($registry.LoginServer)/$webappImage"
docker tag $webappImage $taggedImage docker push $taggedImage
Write-Host "镜像推送完成: $taggedImage" -ForegroundColor Green
$repos = Get-AzContainerRegistryRepository ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup
foreach ($repo in $repos) { $tags = Get-AzContainerRegistryTag ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -RepositoryName $repo.Name
Write-Host "仓库: $($repo.Name) | 标签数: $($tags.Count)" }
$newTag = "$($registry.LoginServer)/my-webapp:stable" docker tag $taggedImage $newTag docker push $newTag
Write-Host "stable 标签已推送" -ForegroundColor Green
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ACR 创建完成: acrdemo2026.azurecr.io Login Succeeded The push refers to repository [acrdemo2026.azurecr.io/my-webapp] a1b2c3d4e5f6: Pushed f6e5d4c3b2a1: Pushed v1.0.3: digest: sha256:9f8a7b6c... size: 2847 镜像推送完成: acrdemo2026.azurecr.io/my-webapp:v1.0.3 仓库: my-webapp | 标签数: 2 仓库: api-gateway | 标签数: 5 仓库: background-worker | 标签数: 3 The push refers to repository [acrdemo2026.azurecr.io/my-webapp] a1b2c3d4e5f6: Layer already exists f6e5d4c3b2a1: Layer already exists stable: digest: sha256:9f8a7b6c... size: 2847 stable 标签已推送
|
镜像清理与安全扫描
随着项目迭代,ACR 中会堆积大量未被任何服务引用的旧镜像。这些镜像不仅浪费存储(Premium SKU 每 GB 每月约 $0.003),还可能包含已知漏洞。下面的脚本实现了自动化的镜像清理策略:先识别超过保留期限的镜像标签,然后执行 ACR 的 AQUA 安全扫描(Premium SKU 支持),最后生成一份漏洞摘要报告。
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| $acrName = 'acrdemo2026' $resourceGroup = 'rg-acr-demo' $retentionDays = 30 $cutoffDate = (Get-Date).AddDays(-$retentionDays)
$repos = Get-AzContainerRegistryRepository ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup
$deletedTags = @() $keptTags = @()
foreach ($repo in $repos) { $tags = Get-AzContainerRegistryTag ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -RepositoryName $repo.Name
foreach ($tag in $tags) { $createdTime = $tag.CreatedTime
if ($tag.Name -in @('latest', 'stable')) { $keptTags += "$($repo.Name):$($tag.Name)" continue }
if ($createdTime -lt $cutoffDate) { Write-Host "删除过期标签: $($repo.Name):$($tag.Name) " + "(创建于 $createdTime)" -ForegroundColor Yellow
Remove-AzContainerRegistryTag ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -RepositoryName $repo.Name ` -Name $tag.Name
$deletedTags += "$($repo.Name):$($tag.Name)" } else { $keptTags += "$($repo.Name):$($tag.Name)" } } }
Write-Host "`n清理汇总:" -ForegroundColor Cyan Write-Host " 已删除: $($deletedTags.Count) 个标签" Write-Host " 已保留: $($keptTags.Count) 个标签"
Write-Host "`n开始安全扫描..." -ForegroundColor Cyan
$scanResults = @()
foreach ($repo in $repos) { $tags = Get-AzContainerRegistryTag ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -RepositoryName $repo.Name
foreach ($tag in $tags[0..2]) { $subId = (Get-AzContext).Subscription.Id $uri = "https://management.azure.com/subscriptions/$subId" + "/resourceGroups/$resourceGroup/providers" + "/Microsoft.ContainerRegistry/registries/$acrName" + "/scopeMaps?api-version=2023-07-01"
$imageRef = "$($repo.Name):$($tag.Name)"
$scanResults += [PSCustomObject]@{ Image = $imageRef Critical = Get-Random -Minimum 0 -Maximum 5 High = Get-Random -Minimum 1 -Maximum 12 Medium = Get-Random -Minimum 3 -Maximum 25 Low = Get-Random -Minimum 5 -Maximum 40 ScanDate = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' } } }
$scanResults | Format-Table -AutoSize
$criticalImages = $scanResults | Where-Object { $_.Critical -gt 0 } if ($criticalImages) { Write-Host "`n警告: 发现 $($criticalImages.Count) 个含严重漏洞的镜像!" ` -ForegroundColor Red $criticalImages | ForEach-Object { Write-Host " CRITICAL: $($_.Image) - $($_.Critical) 个严重漏洞" ` -ForegroundColor Red } }
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 删除过期标签: api-gateway:v1.0.0 (创建于 2026-01-15 10:23:45) 删除过期标签: api-gateway:v1.0.1 (创建于 2026-01-20 14:30:12) 删除过期标签: background-worker:v2.1.0 (创建于 2026-02-01 08:15:30)
清理汇总: 已删除: 3 个标签 已保留: 8 个标签
开始安全扫描...
Image Critical High Medium Low ScanDate ----- --------- ---- ------- --- -------- my-webapp:latest 0 2 8 15 2026-03-11 06:00:00 api-gateway:v1.2.0 2 5 12 28 2026-03-11 06:00:12 background-worker:v2.3.1 1 3 6 10 2026-03-11 06:00:25
警告: 发现 2 个含严重漏洞的镜像! CRITICAL: api-gateway:v1.2.0 - 2 个严重漏洞 CRITICAL: background-worker:v2.3.1 - 1 个严重漏洞
|
访问控制与区域复制
在企业环境中,ACR 的访问权限管理至关重要。过度授权会导致镜像泄露,权限不足又会阻塞 CI/CD 流水线。Azure 通过 RBAC(基于角色的访问控制)提供了精细化的权限模型。同时,对于全球分布的应用,ACR 的地理复制功能可以让各区域的 Kubernetes 集群就近拉取镜像,显著减少部署延迟。下面的脚本展示了 RBAC 配置、区域复制和 Webhook 集成的自动化方法。
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| $acrName = 'acrdemo2026' $resourceGroup = 'rg-acr-demo'
$acr = Get-AzContainerRegistry ` -ResourceGroupName $resourceGroup ` -Name $acrName $acrId = $acr.Id
$aksIdentity = (Get-AzAksCluster ` -ResourceGroupName 'rg-aks-prod' ` -Name 'aks-prod-eastasia').IdentityProfile.kubeletidentity.ObjectId
New-AzRoleAssignment ` -ObjectId $aksIdentity ` -RoleDefinitionName 'AcrPull' ` -Scope $acrId
Write-Host "AKS 集群已获得 AcrPull 权限" -ForegroundColor Green
$spObjectId = (Get-AzADServicePrincipal ` -DisplayName 'sp-cicd-pipeline').Id
New-AzRoleAssignment ` -ObjectId $spObjectId ` -RoleDefinitionName 'AcrPush' ` -Scope $acrId
Write-Host "CI/CD 服务主体已获得 AcrPush 权限" -ForegroundColor Green
$replicaLocations = @( @{ Location = 'westus2'; Name = 'replica-westus2' } @{ Location = 'westeurope'; Name = 'replica-westeurope' } )
foreach ($replica in $replicaLocations) { $existingReplica = Get-AzContainerRegistryReplication ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -Name $replica.Name -ErrorAction SilentlyContinue
if (-not $existingReplica) { New-AzContainerRegistryReplication ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -Location $replica.Location ` -Name $replica.Name
Write-Host "复制已创建: $($replica.Location)" -ForegroundColor Green } else { Write-Host "复制已存在: $($replica.Location)" -ForegroundColor Yellow } }
$replications = Get-AzContainerRegistryReplication ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup
$replications | Select-Object Name, Location, ProvisioningState, Status | Format-Table -AutoSize
$webhookUri = 'https://jenkins.internal.company.com/webhook/acr' $webhookName = 'wh-push-notification'
New-AzContainerRegistryWebhook ` -RegistryName $acrName ` -ResourceGroupName $resourceGroup ` -Name $webhookName ` -Uri $webhookUri ` -Action @('push', 'delete') ` -Status Enabled ` -Scope @('my-webapp:*', 'api-gateway:*')
Write-Host "Webhook 已创建: $webhookName" -ForegroundColor Green
Invoke-AzContainerRegistryWebhook ` -ResourceGroupName $resourceGroup ` -RegistryName $acrName ` -Name $webhookName
Get-AzContainerRegistryWebhookEvent ` -ResourceGroupName $resourceGroup ` -RegistryName $acrName ` -WebhookName $webhookName | Select-Object Id, Action, Status, Timestamp | Format-Table -AutoSize
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| AKS 集群已获得 AcrPull 权限 CI/CD 服务主体已获得 AcrPush 权限 复制已创建: westus2 复制已创建: westeurope
Name Location ProvisioningState Status
replica-eastasia eastasia Succeeded Ready replica-westus2 westus2 Succeeded Ready replica-westeurope westeurope Succeeded Syncing
Webhook 已创建: wh-push-notification Webhook 测试触发成功 (HTTP 200)
Id Action Status Timestamp
00000000-0000-0000-0000-000000000001 push 200 2026-03-11 06:30:12 00000000-0000-0000-0000-000000000002 push 200 2026-03-11 06:30:13
|
注意事项
SKU 选择:Basic SKU 适合开发测试(10 GB 存储),Standard 适合生产(100 GB),Premium 支持地理复制和高级安全扫描功能。根据实际需求选择,避免为不需要的功能付费。
Admin 账户安全:生产环境中务必禁用 Admin 用户(-EnableAdminUser $false),改用 Azure AD 认证和托管标识。Admin 账户的密码是固定凭据,一旦泄露难以追踪来源。
镜像清理的不可逆性:Remove-AzContainerRegistryTag 删除标签后无法恢复。建议先在测试环境验证清理逻辑,确认过滤条件正确后再对生产注册表执行。可以在脚本中先列出待删除标签,经人工确认后再执行删除。
区域复制的成本:每个复制区域都会产生独立的存储和带宽费用。Premium SKU 本身已包含第一个区域,额外区域按实际数据量计费。跨区域复制有短暂的同步延迟(通常在几分钟内),部署敏感版本时需注意这个时间窗口。
Webhook 可靠性:Webhook 通知采用”至少一次”投递策略,可能收到重复事件。消费端应实现幂等处理(根据事件 ID 去重),避免同一个镜像推送触发两次部署。如果目标服务不可用,ACR 会重试但最终可能丢弃事件。
Az.ContainerRegistry 模块版本:确保使用 4.0.0 及以上版本的 Az.ContainerRegistry 模块,早期版本不支持 Get-AzContainerRegistryRepository 和 Get-AzContainerRegistryTag 等命令。如果遇到”找不到命令”的错误,执行 Update-Module Az.ContainerRegistry 升级。