51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

解决国内无法下载 Docker 镜像的问题

由于 Docker Hub 被国内封锁,如果需要拉取 Docker 官方镜像,目前有两种方式,自建镜像代理站和直接使用代理来解决。

一、Docker 拉取镜像请求分析 {#一docker-拉取镜像请求分析}

目前网上流传的 Docker 镜像代理方式主要分为 Nginx 和 Cloudflare Worker 两种方式,本着对技术的好奇心,让我们来分析其原理,然后分别通过 Nginx、Cloudflare Worker 实现 Docker 镜像代理。

为了分析其原理,我们需要先了解 docker pull 命令执行时的网络请求,这里我们首先在 Docker 客户端安装好 mitmproxy 工具。

1.mitmproxy 安装和 Docker 客户端配置 {#1mitmproxy-安装和-docker-客户端配置}

1.1.mitmproxy 安装 {#11mitmproxy-安装}

|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | # 安装 mitmproxy $ pip install mitmproxy # 将 mitmproxy 默认生成的证书添加到信任证书列表 $ cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt # 更新系统 CA 证书列表 $ update-ca-certificates # 启动监听 $ mitmproxy --listen-port 8080 |


1.2.Docker 客户端代理配置 {#12docker-客户端代理配置}

|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | # 创建目录 $ mkdir -p /etc/systemd/system/docker.service.d # 编辑 /etc/systemd/system/docker.service.d/http-proxy.conf 文件,添加以下内容: [Service] Environment="HTTP_PROXY=http://localhost:8080" Environment="HTTPS_PROXY=http://localhost:8080" # 重启 Docker 服务 $ systemctl daemon-reload $ systemctl restart docker |


2.原理分析 {#2原理分析}

借助 mitmproxy 工具,来抓取 docker pull hello-world 时发起的 HTTP 请求,如下图:

docker pull 请求抓包

Docker 镜像拉取过程中的网络请求主要包含认证、镜像元数据获取、配置文件获取和镜像层下载四个阶段,具体的过程如下:

  1. 认证过程:
    • Docker Client 首先向 Docker Registry 发送请求。
    • 收到 401 Unauthorized 响应后,Client 转向 Docker Authentication Server 获取令牌。
    • 获取令牌成功后,后续请求都会带上这个令牌。
  2. 镜像元数据获取:
    • Client 先获取镜像的最新摘要(digest)。
    • 然后获取完整的 manifest 数据。
    • 再获取第一个 manifest 的详细信息,包括配置和层信息。
  3. 配置文件获取:
    • Client 请求配置文件时,Registry 返回一个 307 重定向。
    • 重定向指向 Cloudflare Docker Registry。
  4. 镜像层下载:
    • 对每一层重复类似的过程:
      1. 向 Docker Registry 请求。
      2. 收到 307 重定向。
      3. 从 Cloudflare Docker Registry 下载实际数据。

为了便于理解,我们绘制出 Docker 拉取镜像的网络请求时序图:

网络请求

完整的请求和响应参数如下:

2.1.质询请求 {#21质询请求}

客户端请求:

|-----------|-------------------------------------------------------| | 1 | GET https://registry-1.docker.io/v2/ HTTP/1.1 |

服务端响应:

|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 | HTTP/1.1 401 Unauthorized WWW-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io" { "errors": [ { "code": "UNAUTHORIZED", "message": "authentication required", "detail": null } ] } |


2.2.获取令牌请求 {#22获取令牌请求}

客户端请求:

|-----------|------------------------------------------------------------------------------------------------------------------------------| | 1 | GET https://auth.docker.io/token?scope=repository%3Alibrary%2Fhello-world%3Apull&service=registry.docker.io HTTP/1.1 |

服务端响应:

|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | HTTP/1.1 200 OK content-type: application/json { "token": "<token>", "access_token": "<token>", "expires_in": 300, "issued_at": "2024-07-23T01:04:08.98927365Z" } |


2.3.获取镜像摘要 {#23获取镜像摘要}

客户端请求:

|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | HEAD https://registry-1.docker.io/v2/library/hello-world/manifests/latest HTTP/1.1 Accept: application/json Accept: application/vnd.docker.distribution.manifest.v2+json Accept: application/vnd.docker.distribution.manifest.list.v2+json Accept: application/vnd.oci.image.index.v1+json Accept: application/vnd.oci.image.manifest.v1+json Accept: application/vnd.docker.distribution.manifest.v1+prettyjws Authorization: Bearer <token> |

服务端响应:

|-------------|---------------------------------------------------------| | 1 2 | HTTP/1.1 200 OK docker-content-digest: <digest> |

2.4.获取镜像清单 {#24获取镜像清单}

客户端请求:

|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | GET https://registry-1.docker.io/v2/library/hello-world/manifests/<digest> HTTP/1.1 Accept: application/json Accept: application/vnd.docker.distribution.manifest.v2+json Accept: application/vnd.docker.distribution.manifest.list.v2+json Accept: application/vnd.oci.image.index.v1+json Accept: application/vnd.oci.image.manifest.v1+json Accept: application/vnd.docker.distribution.manifest.v1+prettyjws Authorization: Bearer <token> |

服务端响应:

|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | HTTP/1.1 200 OK docker-content-digest: <digest> { "manifests": [{ "annotations": {}, "digest": "sha256:e2fc4e5012d16e7fe466f5291c476431beaa1f9b90a5c2125b493ed28e2aba57", "mediaType": "application/vnd.oci.image.manifest.v1+json", "platform": { "architecture": "amd64", "os": "linux" }, "size": 861 },{},{},{}], "mediaType": "application/vnd.oci.image.index.v1+json", "schemaVersion": 2 } |


2.5.获取首层镜像清单 {#25获取首层镜像清单}

客户端请求:

|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | GET https://registry-1.docker.io/v2/library/hello-world/manifests/<first_manifest_digest> HTTP/1.1 Accept: application/vnd.docker.distribution.manifest.v1+prettyjws Accept: application/json Accept: application/vnd.docker.distribution.manifest.v2+json Accept: application/vnd.docker.distribution.manifest.list.v2+json Accept: application/vnd.oci.image.index.v1+json Accept: application/vnd.oci.image.manifest.v1+json Authorization: Bearer <token> |

服务端响应:

|---------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | HTTP/1.1 200 OK docker-content-digest: <digest> { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": "sha256:d2c94e258dcb3c5ac2798d32e1249e42ef01cba4841c2234249495f87264ac5a", "size": 581 }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:c1ec31eb59444d78df06a974d155e597c894ab4cda84f08294145e845394988e", "size": 2459 }, {}, {}, {} ], "annotations": {} } |


2.6.重定向到获取镜像配置信息地址 {#26重定向到获取镜像配置信息地址}

客户端请求:

|-------------|------------------------------------------------------------------------------------------------------------------------------| | 1 2 | GET https://registry-1.docker.io/v2/library/hello-world/blobs/<config.digest> HTTP/1.1 Authorization: Bearer <token> |

<config.noPrefixDigest>:包含前缀 sha256: 的配置信息摘要

服务端响应:

|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 | HTTP/1.1 307 Temporary Redirect Location: https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/d2/<config.noPrefixDigest>/data?verify=<verify> |

  • <config.noPrefixDigest>:去除前缀 sha256: 后的配置信息摘要
  • <verify>:Docker Registry 生成的验证参数

2.7.获取镜像配置信息 {#27获取镜像配置信息}

客户端请求:

|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 | GET https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/d2/<config.noPrefixDigest>/data?verify=<verify> HTTP/1.1 |

  • <config.noPrefixDigest> :去除前缀 sha256: 后的配置信息摘要
  • <verify>:Docker Registry 生成的验证参数

服务端响应:

|-------------|----------------------------------------------------------------| | 1 2 | HTTP/1.1 200 OK Content-Type: application/ostet-stream |

2.8.重定向到获取所有镜像层数据地址 {#28重定向到获取所有镜像层数据地址}

遍历镜像层所有 digest,循环重定向到获取镜像层数据地址。

客户端请求:

|-------------|---------------------------------------------------------------------------------------------------------------------------------| | 1 2 | GET https://registry-1.docker.io/v2/library/hello-world/blobs/<layers[x].digest> HTTP/1.1 Authorization: Bearer <token> |

layers[x].digest:包含前缀 sha256: 的镜像层摘要

服务端响应:

|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 | HTTP/1.1 307 Temporary Redirect Location: https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/c1/<layers[x].noPrefixDigest>/data?verify=<verify> |

  • <layers[x].noPrefixDigest>:去除前缀 sha256: 后的镜像层摘要
  • <verify>:Docker Registry 生成的验证参数

2.9.循环获取所有镜像层数据 {#29循环获取所有镜像层数据}

客户端请求:

|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 | GET https://production.cloudflare.docker.com/registry-v2/docker/registry/v2/blobs/sha256/c1/<layers[x].noPrefixDigest>/data?verify=<verify> HTTP/1.1 |

  • <layers[x].noPrefixDigest>:去除前缀 sha256: 后的镜像层摘要
  • <verify>:Docker Registry 生成的验证参数

服务端响应:

|-------------|----------------------------------------------------------------| | 1 2 | HTTP/1.1 200 OK Content-Type: application/ostet-stream |

二、自建镜像代理站 {#二自建镜像代理站}

1.使用 Nginx 自建 Docker 镜像代理 {#1使用-nginx-自建-docker-镜像代理}

了解完网络请求后,下面我们来分析下 Nginx 实现 Docker 镜像站的原理,Nginx 的配置如下:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 | worker_processes auto; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; listen 443 ssl; server_name 410006.xyz; `<span class="c1"># SSL configuration</span> <span class="kn">ssl_certificate</span> <span class="n">/etc/nginx/ssl/410006.xyz.cer</span><span class="p">;</span> <span class="kn">ssl_certificate_key</span> <span class="n">/etc/nginx/ssl/410006.xyz.key</span><span class="p">;</span> <span class="kn">ssl_protocols</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span> <span class="kn">ssl_ciphers</span> <span class="s">HIGH:!aNULL:!MD5</span><span class="p">;</span> <span class="c1"># Serve the home page</span> <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span> <span class="c1"># Docker Hub 的官方镜像仓库地址</span> <span class="kn">proxy_pass</span> <span class="s">https://registry-1.docker.io</span><span class="p">;</span> <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="s">registry-1.docker.io</span><span class="p">;</span> <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span> <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span> <span class="c1"># 关闭缓存</span> <span class="kn">proxy_buffering</span> <span class="no">off</span><span class="p">;</span> <span class="c1"># 转发认证相关的头部</span> <span class="kn">proxy_set_header</span> <span class="s">Authorization</span> <span class="nv">$http_authorization</span><span class="p">;</span> <span class="kn">proxy_pass_header</span> <span class="s">Authorization</span><span class="p">;</span> <span class="c1"># 对 upstream 状态码检查,实现 error_page 错误重定向</span> <span class="kn">proxy_intercept_errors</span> <span class="no">on</span><span class="p">;</span> <span class="c1"># error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向</span> <span class="kn">recursive_error_pages</span> <span class="no">on</span><span class="p">;</span> <span class="c1"># 根据状态码执行对应操作,以下为 301、302 和 307 状态码都会触发</span> <span class="kn">error_page</span> <span class="mi">301</span> <span class="mi">302</span> <span class="mi">307</span> <span class="p">=</span> <span class="s">@handle_redirect</span><span class="p">;</span> <span class="p">}</span> <span class="kn">location</span> <span class="s">@handle_redirect</span> <span class="p">{</span> <span class="kn">resolver</span> <span class="mf">1.1</span><span class="s">.1.1</span><span class="p">;</span> <span class="kn">set</span> <span class="nv">$saved_redirect_location</span> <span class="s">'</span><span class="nv">$upstream_http_location</span><span class="s">'</span><span class="p">;</span> <span class="kn">proxy_pass</span> <span class="nv">$saved_redirect_location</span><span class="p">;</span> <span class="p">}</span> ` } } |


以上配置主要包括:

  • 代理所有请求到上游服务器
  • 设置上游服务器请求头 Host
  • 传递请求头 Authorization 到上游服务器
  • 上游服务器 Authorization 响应头传回客户端
  • 跟随重定向:Nginx 内部重定向

上游服务器:这里指 Docker Registry。

同样的,我们还是借助 mitmproxy 工具来抓取使用 Nginx 代理后,Docker 客户端发起的网络请求,以 docker pull 410006.xyz/library/hello-world 为例,发起的网络请求如下:

docker pull 代理请求抓包

使用 Nginx 代理后,通过分析客户端发起的网络请求,得到 Docker 拉取镜像的网络请求时序图如下:

网络请求时序图

总结来看,使用 Nginx 可以方便快速的搭建 Docker 镜像代理站,但存在两点不足:

  • 一是无法使用 docker pull redis 这样的命令拉取镜像,需要携带默认命名空间 library,如:docker pull library/redis,这不符合我们日常 docker 命令的使用习惯。

  • 二是存在客户端直接发往 docker 认证服务器的请求,一旦 auth.docker.io 也被封锁,会导致代理站无法正常使用。

为了解决上面的两个问题,下面我们将 Nginx 换成 Cloudflare Worker 来实现镜像代理。

2.使用 Cloudflare Worker 自建 Docker 镜像代理 {#2使用-cloudflare-worker-自建-docker-镜像代理}

使用 Cloudflare Worker 代理后,Docker 拉取镜像的网络请求时序图如下:

网络请求时序图

完整的 Worker 脚本内容如下:

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | const DOCKER_REGISTRY = 'https://registry-1.docker.io' const PROXY_REGISTRY = 'https://docker.410006.xyz' const HTML = ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="shortcut icon" href="https://voxsay.com/assets/img/favicons/favicon.ico"> <title>镜像代理使用说明</title> <style> body { font-family: 'Roboto', sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; } .header { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; padding: 20px 0; text-align: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .container { max-width: 800px; margin: 40px auto; padding: 20px; background-color: #fff; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 10px; } .content { margin-bottom: 20px; } .footer { text-align: center; padding: 20px 0; background-color: #333; color: #fff; } pre { background-color: #272822; color: #f8f8f2; padding: 15px; border-radius: 5px; overflow-x: auto; } code { font-family: 'Source Code Pro', monospace; } a { font-weight: bold; color: #ffffff; text-decoration: none; } a:hover { text-decoration: underline; } @media (max-width: 600px) { .container { margin: 20px; padding: 15px; } .header { padding: 15px 0; } } </style> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet"> </head> <body> <div class="header"> <h1>Docker 镜像代理使用说明</h1> </div> <div class="container"> <div > <p>拉取镜像</p> <pre><code># 拉取 redis 镜像(不带命名空间) docker pull {:host}/redis 拉取 rabbitmq 镜像 ============== docker pull {:host}/library/rabbitmq 拉取 postgresql 镜像 ================ docker pull {:host}/bitnami/postgresql</code></pre><p>重命名镜像</p> <pre><code># 重命名 redis 镜像 docker tag {:host}/library/redis redis 重命名 postgresql 镜像 ================= docker tag {:host}/bitnami/postgresql bitnami/postgresql</code></pre><p>添加镜像源</p> <pre><code># 添加镜像代理到 Docker 镜像源 sudo tee /etc/docker/daemon.json &lt;&lt; EOF { "registry-mirrors": ["https://{:host}"] } EOF</code></pre> </div> </div> <div class="footer"> <p>©2024 <a href="https://voxsay.com">voxsay.com</a>. All rights reserved. Powered by <a href="https://cloudflare.com">Cloudflare</a>.</p> </div> </body> </html> ` addEventListener('fetch', (event) => { event.passThroughOnException() event.respondWith(handleRequest(event.request)) }) async function handleRequest(request) { const url = new URL(request.url) const path = url.pathname if (path === '/v2/') { return challenge(DOCKER_REGISTRY, url.host) } else if (path === '/auth/token') { return getToken(url) } else if (url.pathname === '/') { return home(url.host); } <span class="kd">const</span> <span class="nx">parts</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="nx">parts</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">5</span><span class="p">)</span> <span class="p">{</span> <span class="nx">parts</span><span class="p">.</span><span class="nf">splice</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">library</span><span class="dl">'</span><span class="p">)</span> <span class="kd">const</span> <span class="nx">newUrl</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">PROXY_REGISTRY</span><span class="p">)</span> <span class="nx">newUrl</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">=</span> <span class="nx">parts</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span> <span class="nx">Response</span><span class="p">.</span><span class="nf">redirect</span><span class="p">(</span><span class="nx">newUrl</span><span class="p">.</span><span class="nf">toString</span><span class="p">(),</span> <span class="mi">301</span><span class="p">)</span> <span class="p">}</span> <span class="k">return</span> <span class="nf">getData</span><span class="p">(</span><span class="nx">DOCKER_REGISTRY</span><span class="p">,</span> <span class="nx">request</span><span class="p">)</span> } async function challenge(upstream, host) { const url = new URL(upstream + '/v2/') const response = await fetch(url) const responseBody = await response.text() const headers = new Headers() headers.set('WWW-Authenticate', `Bearer realm="https://</span><span class="p">${</span><span class="nx">host</span><span class="p">}</span><span class="s2">/auth/token",service="docker-proxy-worker"`) return new Response(responseBody, { status: response.status, statusText: response.statusText, headers }) } async function getToken(originUrl) { let scope = processScope(originUrl) const url = new URL('https://auth.docker.io/token') url.searchParams.set('service', 'registry.docker.io') url.searchParams.set('scope', scope) const response = await fetch(url) return response } async function getData(upstream, req) { const originUrl = new URL(req.url) const url = new URL(upstream + originUrl.pathname) const request = new Request(url, { method: req.method, headers: req.headers, redirect: 'follow' }) <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="k">return</span> <span class="nx">response</span> } function processScope(url) { let scope = url.searchParams.get('scope') let parts = scope.split(':') if (parts.length === 3 && !parts[1].includes('/')) { parts[1] = 'library/' + parts[1] scope = parts.join(':') } return scope } function home(host) { return new Response(HTML.replace(/{:host}/g, host), { status: 200, headers: { "Content-Type": "text/html", } }) } |


总体实现过程如下:

  1. 处理质询请求,并修改 WWW-Authenticate 头以使用代理服务。
  2. 处理获取令牌请求,对于 scope 参数,为没有命名空间的镜像添加 library/ 前缀,然后将请求转发到 Docker 的获取令牌服务。
  3. 对于没有命名空间的镜像请求,会自动添加 library 命名空间,将请求转发到 Docker Registry 并返回响应或跟随重定向(Worker 内部重定向)后返回响应。

三、直接使用代理 {#三直接使用代理}

1.搭建 Trojan 服务端 {#1搭建-trojan-服务端}

|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | # 下载一键安装脚本 $ wget https://raw.githubusercontent.com/HarrisonWang/v2ray/main/install_v2ray.sh # 添加可执行权限 $ chmod +x install_v2ray.sh # 安装 $ ./install_v2ray.sh |


2.安装 Trojan 客户端 {#2安装-trojan-客户端}

|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | # 安装 Trojan $ sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/trojan-gfw/trojan-quickstart/master/trojan-quickstart.sh)" # 客户端配置 # 编辑配置,替换 your_vps_ip、your_password 和 your_domain.com 为您的实际值。 $ vim /usr/local/etc/trojan/config.json # 启动 $ systemctl start trojan |


3.Docker 客户端代理配置 {#3docker-客户端代理配置}

|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 | $ tee /etc/docker/daemon.json << EOF { "proxies": { "http-proxy": "socks5://192.168.208.55:1080", "https-proxy": "socks5://192.168.208.55:1080", "no-proxy": "127.0.0.0/8" } } $ systemctl restart docker |


4.测试拉取镜像 {#4测试拉取镜像}

|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 | $ docker pull hello-world Using default tag: latest latest: Pulling from library/hello-world c1ec31eb5944: Pull complete Digest: sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdcec6 Status: Downloaded newer image for hello-world:latest docker.io/library/hello-world:latest |

四、附录 {#四附录}

  1. Nginx 代理配置

  2. docker-proxy-worker

  3. HTTP 测试脚本

赞(0)
未经允许不得转载:工具盒子 » 解决国内无法下载 Docker 镜像的问题