Skip to content

Instantly share code, notes, and snippets.

@enihsyou
Last active April 2, 2025 07:57
Show Gist options
  • Save enihsyou/dafa9fca2e0db2623ecf7db500842b87 to your computer and use it in GitHub Desktop.
Save enihsyou/dafa9fca2e0db2623ecf7db500842b87 to your computer and use it in GitHub Desktop.
Docker Hub 镜像测速脚本

Docer Registry HTTP v2 API 需要使用 Bearer Token 才能访问,大大增加了访问难度,像 chsrc 这样的静态链接测速工具难以对 DockerHub 测速。

不测速就不知道各个镜像的质量,实际上很多网络上的镜像站点都是通过 Cloudflare Worker 搭建的。 我想要知道真实的下载速度,所以有了这个脚本。

  • 大部分逻辑来自 enihsyou/docker-mirror-hammal 和 Claude 3.5 Sonnet,为 Windows + Powershell 7 编写
  • 镜像列表来自互联网,感谢发布者
  • 本代码以 MIT 协议发布

用以下命令一键执行:

irm "https://gist.githubusercontent.com/enihsyou/dafa9fca2e0db2623ecf7db500842b87/raw/docker-mirror-speedtest.ps1" | iex

在我设备上的示例输出

$ .\scripts\speed-test.ps1
🚀 开始 Docker 镜像站测速...
❌ [docker.1panel.live] 测速异常: 无法获取认证信息 (403 Forbidden)
❌ [docker.1panel.top] 测速异常: 无法获取认证信息 (429 Too Many Requests)
🔑 [docker.m.daocloud.io] 需要认证,正在获取token...
📍 [docker.m.daocloud.io] 获取下载地址...
🏃 [docker.m.daocloud.io] 正在测速...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   513  100   513    0     0   6772      0 --:--:-- --:--:-- --:--:--  6932
100 20.0M  100 20.0M    0     0  21.6M      0 --:--:-- --:--:-- --:--:-- 40.4M
📊 [docker.m.daocloud.io] 测速结果: DNS解析时间: 0.016131s 连接时间: 0.030032s 总时间: 0.925078s 下载速度: 22670002B/s
❌ [docker.ketches.cn] 测速异常: 无法获取认证信息 (429 Too Many Requests)
🔑 [docker.1ms.run] 需要认证,正在获取token...
📍 [docker.1ms.run] 获取下载地址...
🏃 [docker.1ms.run] 正在测速...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 20.0M  100 20.0M    0     0  11.1M      0  0:00:01  0:00:01 --:--:-- 11.1M
📊 [docker.1ms.run] 测速结果: DNS解析时间: 0.013340s 连接时间: 0.016066s 总时间: 1.790445s 下载速度: 11713021B/s
☑️ [hub1.nat.tf] 无需认证,直接测速...
🏃 [hub1.nat.tf] 正在测速...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 41.8M  100 41.8M    0     0  8974k      0  0:00:04  0:00:04 --:--:-- 10.6M
📊 [hub1.nat.tf] 测速结果: DNS解析时间: 0.013273s 连接时间: 0.015449s 总时间: 4.778494s 下载速度: 9189851B/s
☑️ [hub2.nat.tf] 无需认证,直接测速...
🏃 [hub2.nat.tf] 正在测速...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 41.8M  100 41.8M    0     0  12.3M      0  0:00:03  0:00:03 --:--:-- 12.3M
📊 [hub2.nat.tf] 测速结果: DNS解析时间: 0.009979s 连接时间: 0.013772s 总时间: 3.385906s 下载速度: 12969542B/s
🔑 [hub.rat.dev] 需要认证,正在获取token...
📍 [hub.rat.dev] 获取下载地址...
🏃 [hub.rat.dev] 正在测速...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   264  100   264    0     0    197      0  0:00:01  0:00:01 --:--:--   197
100 20.0M  100 20.0M    0     0  66379      0  0:05:15  0:05:15 --:--:-- 32764
📊 [hub.rat.dev] 测速结果: DNS解析时间: 0.017731s 连接时间: 0.043667s 总时间: 315.935037s 下载速度: 66379B/s 


═══════════════ 测试总结 ═══════════════

% 基本统计
✓ 成功测试: 5
✗ 失败测试: 3
Σ 总计测试: 8

🏆 速度排名:
🥇 [ 1] docker.m.daocloud.io           21.62 MB/s
🥈 [ 2] hub2.nat.tf                    12.37 MB/s
🥉 [ 3] docker.1ms.run                 11.17 MB/s
   [ 4] hub1.nat.tf                     8.76 MB/s
   [ 5] hub.rat.dev                    64.82 KB/s

❌ 失败详情:
  • docker.1panel.live
    无法获取认证信息 (403 Forbidden)
  • docker.1panel.top
    无法获取认证信息 (429 Too Many Requests)
  • docker.ketches.cn
    无法获取认证信息 (429 Too Many Requests)
# 本 Powershell 脚本用于 DockerHub 镜像的测速
# 传入域名列表,自动测速并按速度输出排名
#
# 使用默认配置
# .\docker-mirror-speedtest.ps1
#
# 自定义域名和测试目标
# .\docker-mirror-speedtest.ps1 -Domains @("docker.1ms.run", "hub1.nat.tf")
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
[string[]]$Domains = @(
# list from status monitor sites
# https://status.anye.xyz/status/docker
# https://status.1panel.top/status/docker
"docker.1panel.live",
"docker.1panel.top",
"docker.m.daocloud.io",
"docker.ketches.cn",
"docker.1ms.run",
"hub1.nat.tf",
"hub2.nat.tf",
"hub.rat.dev"
)
)
# 设置输出编码为UTF8,以支持特殊字符
$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$TestTarget = @{
Name = "Nginx"
Path = "library/nginx" # size: 43,913,652
Digest = "sha256:8c18fec49d4bdf0813737297228f0afbdc26ed7f8f3fd7e66048114d47d03168"
}
# 定义颜色输出函数
function Write-ColorOutput {
param ([string]$Text, [string]$Color = "White")
Write-Host $Text -ForegroundColor $Color
}
function Parse-WwwAuthenticate {
param (
[string]$AuthStr,
[hashtable]$Target
)
$splits = $AuthStr -split '\s+'
$bearer = $splits[0]
if (!$bearer -or $bearer.ToLower() -ne 'bearer') {
throw "Invalid Www-Authenticate: $AuthStr"
}
$params = $splits[1..($splits.Length - 1)] -join '' -split ','
$result = @{}
foreach ($param in $params) {
$kvPair = $param -split '=', 2
if ($kvPair.Length -eq 2) {
$k = $kvPair[0].Trim()
$v = $kvPair[1].Trim() -replace '^"|"$', ''
$result[$k] = $v
}
}
$result["scope"] = "respository:$($Target.Path):pull"
return $result
}
function Get-DockerToken {
param (
[string]$Realm,
[string]$Service,
[string]$Scope
)
$tokenUrl = [System.UriBuilder]$Realm
$query = [System.Web.HttpUtility]::ParseQueryString('')
$query['service'] = $Service
$query['scope'] = $Scope
$tokenUrl.Query = $query.ToString()
$response = Invoke-RestMethod -Uri $tokenUrl.Uri.ToString() -Method Get
return $response.token
}
# 添加结果收集类
class SpeedTestResult {
[string]$Domain
[string]$ImageName
[double]$Speed
[double]$ConnectTime
[bool]$Success
[string]$Error
[string] GetSpeedString() {
if (-not $this.Success) { return "N/A" }
$nums = $this.Speed
$unit = "B/s"
if ($nums -gt 1024 * 1024) {
$nums = [double]([math]::Round($nums / (1024 * 1024), 2))
$unit = "MB/s"
} elseif ($nums -gt 1024) {
$nums = [double]([math]::Round($nums / 1024, 2))
$unit = "KB/s"
}
return "$nums $unit"
}
}
$global:Results = [System.Collections.ArrayList]::new()
function Invoke-CurlCommand {
param (
[Parameter(Mandatory, Position = 0)]
[string[]]$Args,
[Parameter(Mandatory, Position = 1)]
[string]$Url
)
# 构建完整参数数组
$fullArgs = $Args + @(
"--user-agent", "curl/8.11.1 (docker-mirror-speedtest.ps1)",
"$Url"
)
# 调试输出完整命令
Write-Debug "即将执行: curl $($fullArgs -join ' ')"
return & curl $fullArgs
}
function Test-RegistrySpeed {
param (
[string]$Domain,
[hashtable]$Target
)
$dsize = 41943040 # 40MB
$skill = 102400 # 100KB/s
$result = [SpeedTestResult]::new()
$result.Domain = $Domain
$result.ImageName = $Target.Name
try {
Write-Debug "开始测试域名: $Domain"
$probAuthUrl = "https://$Domain/v2/"
$downloadUrl = "https://$Domain/v2/$($Target.Path)/blobs/$($Target.Digest)"
Write-Debug "测试URL: $downloadUrl"
# 初始请求
$args = @( "-I", "-L", "--max-time", "8" )
$response = Invoke-CurlCommand $args $probAuthUrl 2>&1
Write-Debug "收到响应:`n$($response | Out-String)"
# 检查状态码
$statusLine = $response | Where-Object { $_ -match "^HTTP/.+ (\d{3}) " } | Select-Object -First 1
if ($statusLine -match "200") {
# 直接可访问,无需认证
Write-ColorOutput "☑️ [$Domain] 无需认证,直接测速..."
$redirectUrl = $downloadUrl
} else {
# 需要认证,解析WWW-Authenticate头
$authHeader = $response | Where-Object { $_ -match "Www-Authenticate: " } | Select-Object -First 1
if ($authHeader) {
Write-ColorOutput "🔑 [$Domain] 需要认证,正在获取token..."
$authStr = ($authHeader -split ": ")[1]
Write-Debug "认证字符串: $authStr"
$auth = Parse-WwwAuthenticate $authStr -Target $Target
Write-Debug "解析后的认证信息: $($auth | ConvertTo-Json)"
$token = Get-DockerToken -Realm $auth.realm -Service $auth.service -Scope "repository:$($Target.Path)`:pull"
Write-Debug "获取到token: $($token.Substring(0, [Math]::Min(20, $token.Length)))..."
# 使用token获取重定向URL
Write-ColorOutput "📍 [$Domain] 获取下载地址..."
$args = @(
"-s", "-I",
"-H", "Authorization: Bearer $token",
"-w", "%{redirect_url}",
"-o", "nul")
$redirectUrl = Invoke-CurlCommand $args $downloadUrl
Write-Debug "重定向URL: $redirectUrl"
if ($null -eq $redirectUrl) {
throw "资源重定向失败"
}
} else {
$statusLine = $response | Where-Object { $_ -match "^HTTP/.+ (\d{3}.+)" } | ForEach-Object { $Matches.1 }
if ($statusLine) {
throw "无法获取认证信息 ($statusLine)"
}
$statusLine = $response | Where-Object { $_ -match "(curl:.+)" } | ForEach-Object { $Matches.1 }
if ($statusLine) {
throw "其他异常 ($statusLine)"
}
throw "未知错误: $($response | Out-String)"
}
}
# 进行速度测试
Write-ColorOutput "🏃 [$Domain] 正在测速..." "Blue"
$curl_args = @(
"-L",
"-w", "DNS解析时间: %{time_namelookup}s 连接时间: %{time_connect}s 总时间: %{time_total}s 下载速度: %{speed_download}B/s ",
"-o", "nul",
"--range", "0-$dsize",
"--speed-time", "5",
"--speed-limit", "$skill"
)
if ($token) {
$curl_args += @("-H", "Authorization: Bearer $token")
}
$resultOutput = Invoke-CurlCommand $curl_args $redirectUrl
Write-ColorOutput "📊 [$Domain] 测速结果: $resultOutput"
$resultOutput -match '连接时间: (?<ctime>\d+\.?\d*)s.+下载速度: (?<speed>\d+\.?\d*)B/s' | Out-Null
# 解析测速结果
$result.Speed = $Matches.speed
$result.ConnectTime = $Matches.ctime
$result.Success = $true
} catch {
$result.Error = $_.Exception.Message
$result.Success = $false
Write-ColorOutput "❌ [$Domain] 测速异常: $($result.Error)" "Red"
}
[void]$global:Results.Add($result)
return $result
}
function Show-TestSummary {
$successful = $global:Results | Where-Object { $_.Success }
$failed = $global:Results | Where-Object { -not $_.Success }
Write-Host "`n`n═══════════════ 测试总结 ═══════════════" -ForegroundColor Magenta
# 统计信息
Write-Host "✓ 成功测试: $($successful.Count)" -ForegroundColor Green
Write-Host "✗ 失败测试: $($failed.Count)" -ForegroundColor Red
Write-Host "Σ 总计测试: $($global:Results.Count)" -ForegroundColor Cyan
# 显示排名
if ($successful.Count -gt 0) {
Write-Host "`n🏆 速度排名:" -ForegroundColor Yellow
$rank = 1
$successful | Sort-Object Speed -Descending | ForEach-Object {
$color = switch ($rank) {
1 { "Yellow" }
2 { "White" }
3 { "DarkYellow" }
default { "Gray" }
}
$medal = switch ($rank) {
1 { "🥇" }
2 { "🥈" }
3 { "🥉" }
default { " " }
}
Write-Host ("{0} [{1,2}] {2,-30} {3,10}" -f $medal,
$rank,
"$($_.Domain)",
$_.GetSpeedString()) -ForegroundColor $color
$rank++
}
}
# 失败清单
if ($failed.Count -gt 0) {
Write-Host "`n❌ 失败详情:" -ForegroundColor Red
$failed | ForEach-Object {
Write-Host "$($_.Domain)" -ForegroundColor DarkRed
Write-Host " $($_.Error)" -ForegroundColor DarkGray
}
}
}
# 主程序入口
Write-ColorOutput "🚀 开始 Docker 镜像站测速..." "Cyan"
Write-Debug "测试域名列表: $($Domains -join ', ')"
try {
foreach ($domain in $Domains) {
Test-RegistrySpeed -Domain $domain -Target $TestTarget | Out-Null
}
} finally {
# 完成后显示总结
Show-TestSummary
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment