├── CloudFlare Worker.md ├── README.md └── index.html /CloudFlare Worker.md: -------------------------------------------------------------------------------- 1 | ### 有老哥反应CloudFlre Worker 用不了,部署会报错 2 | 3 | 那就用这个吧 4 | 5 | ### worker.js 6 | ``` 7 | // _worker.js 8 | import HTML from './docker.html'; 9 | // Docker镜像仓库主机地址 10 | let hub_host = 'registry-1.docker.io' 11 | // Docker认证服务器地址 12 | const auth_url = 'https://auth.docker.io' 13 | // 自定义的工作服务器地址 14 | let workers_url = 'https://你的域名' 15 | 16 | // 根据主机名选择对应的上游地址 17 | function routeByHosts(host) { 18 | // 定义路由表 19 | const routes = { 20 | // 生产环境 21 | "quay": "quay.io", 22 | "gcr": "gcr.io", 23 | "k8s-gcr": "k8s.gcr.io", 24 | "k8s": "registry.k8s.io", 25 | "ghcr": "ghcr.io", 26 | "cloudsmith": "docker.cloudsmith.io", 27 | 28 | // 测试环境 29 | "test": "registry-1.docker.io", 30 | }; 31 | 32 | if (host in routes) return [ routes[host], false ]; 33 | else return [ hub_host, true ]; 34 | } 35 | 36 | /** @type {RequestInit} */ 37 | const PREFLIGHT_INIT = { 38 | // 预检请求配置 39 | headers: new Headers({ 40 | 'access-control-allow-origin': '*', // 允许所有来源 41 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法 42 | 'access-control-max-age': '1728000', // 预检请求的缓存时间 43 | }), 44 | } 45 | 46 | /** 47 | * 构造响应 48 | * @param {any} body 响应体 49 | * @param {number} status 响应状态码 50 | * @param {Object} headers 响应头 51 | */ 52 | function makeRes(body, status = 200, headers = {}) { 53 | headers['access-control-allow-origin'] = '*' // 允许所有来源 54 | return new Response(body, { status, headers }) // 返回新构造的响应 55 | } 56 | 57 | /** 58 | * 构造新的URL对象 59 | * @param {string} urlStr URL字符串 60 | */ 61 | function newUrl(urlStr) { 62 | try { 63 | return new URL(urlStr) // 尝试构造新的URL对象 64 | } catch (err) { 65 | return null // 构造失败返回null 66 | } 67 | } 68 | 69 | function isUUID(uuid) { 70 | // 定义一个正则表达式来匹配 UUID 格式 71 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 72 | 73 | // 使用正则表达式测试 UUID 字符串 74 | return uuidRegex.test(uuid); 75 | } 76 | 77 | async function nginx() { 78 | const text = ` 79 | 80 | 81 | 82 | Welcome to nginx! 83 | 90 | 91 | 92 |

Welcome to nginx!

93 |

If you see this page, the nginx web server is successfully installed and 94 | working. Further configuration is required.

95 | 96 |

For online documentation and support please refer to 97 | nginx.org.
98 | Commercial support is available at 99 | nginx.com.

100 | 101 |

Thank you for using nginx.

102 | 103 | 104 | ` 105 | return text ; 106 | } 107 | 108 | export default { 109 | async fetch(request, env, ctx) { 110 | const getReqHeader = (key) => request.headers.get(key); // 获取请求头 111 | 112 | let url = new URL(request.url); // 解析请求URL 113 | workers_url = `https://${url.hostname}`; 114 | const pathname = url.pathname; 115 | const hostname = url.searchParams.get('hubhost') || url.hostname; 116 | const hostTop = hostname.split('.')[0];// 获取主机名的第一部分 117 | const checkHost = routeByHosts(hostTop); 118 | hub_host = checkHost[0]; // 获取上游地址 119 | const fakePage = checkHost[1]; 120 | console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`); 121 | const isUuid = isUUID(pathname.split('/')[1].split('/')[0]); 122 | 123 | const conditions = [ 124 | isUuid, 125 | pathname.includes('/_'), 126 | pathname.includes('/r'), 127 | pathname.includes('/v2/user'), 128 | pathname.includes('/v2/orgs'), 129 | pathname.includes('/v2/_catalog'), 130 | pathname.includes('/v2/categories'), 131 | pathname.includes('/v2/feature-flags'), 132 | pathname.includes('search'), 133 | pathname.includes('source'), 134 | pathname === '/', 135 | pathname === '/favicon.ico', 136 | pathname === '/auth/profile', 137 | ]; 138 | 139 | if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) { 140 | if (env.URL302){ 141 | return Response.redirect(env.URL302, 302); 142 | } else if (env.URL){ 143 | if (env.URL.toLowerCase() == 'nginx'){ 144 | //首页改成一个nginx伪装页 145 | return new Response(await nginx(), { 146 | headers: { 147 | 'Content-Type': 'text/html; charset=UTF-8', 148 | }, 149 | }); 150 | } else return fetch(new Request(env.URL, request)); 151 | } 152 | 153 | const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search); 154 | 155 | // 复制原始请求的标头 156 | const headers = new Headers(request.headers); 157 | 158 | // 确保 Host 头部被替换为 hub.docker.com 159 | headers.set('Host', 'registry.hub.docker.com'); 160 | 161 | const newRequest = new Request(newUrl, { 162 | method: request.method, 163 | headers: headers, 164 | body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null, 165 | redirect: 'follow' 166 | }); 167 | 168 | return fetch(newRequest); 169 | } 170 | 171 | // 修改包含 %2F 和 %3A 的请求 172 | if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) { 173 | let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F'); 174 | url = new URL(modifiedUrl); 175 | console.log(`handle_url: ${url}`) 176 | } 177 | 178 | // 处理token请求 179 | if (url.pathname.includes('/token')) { 180 | let token_parameter = { 181 | headers: { 182 | 'Host': 'auth.docker.io', 183 | 'User-Agent': getReqHeader("User-Agent"), 184 | 'Accept': getReqHeader("Accept"), 185 | 'Accept-Language': getReqHeader("Accept-Language"), 186 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 187 | 'Connection': 'keep-alive', 188 | 'Cache-Control': 'max-age=0' 189 | } 190 | }; 191 | let token_url = auth_url + url.pathname + url.search 192 | return fetch(new Request(token_url, request), token_parameter) 193 | } 194 | 195 | // 修改 /v2/ 请求路径 196 | if (/^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) { 197 | url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/'); 198 | console.log(`modified_url: ${url.pathname}`) 199 | } 200 | 201 | // 更改请求的主机名 202 | url.hostname = hub_host; 203 | 204 | // 构造请求参数 205 | let parameter = { 206 | headers: { 207 | 'Host': hub_host, 208 | 'User-Agent': getReqHeader("User-Agent"), 209 | 'Accept': getReqHeader("Accept"), 210 | 'Accept-Language': getReqHeader("Accept-Language"), 211 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 212 | 'Connection': 'keep-alive', 213 | 'Cache-Control': 'max-age=0' 214 | }, 215 | cacheTtl: 3600 // 缓存时间 216 | }; 217 | 218 | // 添加Authorization头 219 | if (request.headers.has("Authorization")) { 220 | parameter.headers.Authorization = getReqHeader("Authorization"); 221 | } 222 | 223 | // 发起请求并处理响应 224 | let original_response = await fetch(new Request(url, request), parameter) 225 | let original_response_clone = original_response.clone(); 226 | let original_text = original_response_clone.body; 227 | let response_headers = original_response.headers; 228 | let new_response_headers = new Headers(response_headers); 229 | let status = original_response.status; 230 | 231 | // 修改 Www-Authenticate 头 232 | if (new_response_headers.get("Www-Authenticate")) { 233 | let auth = new_response_headers.get("Www-Authenticate"); 234 | let re = new RegExp(auth_url, 'g'); 235 | new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); 236 | } 237 | 238 | // 处理重定向 239 | if (new_response_headers.get("Location")) { 240 | return httpHandler(request, new_response_headers.get("Location")) 241 | } 242 | 243 | // 返回修改后的响应 244 | let response = new Response(original_text, { 245 | status, 246 | headers: new_response_headers 247 | }) 248 | return response; 249 | } 250 | }; 251 | 252 | /** 253 | * 处理HTTP请求 254 | * @param {Request} req 请求对象 255 | * @param {string} pathname 请求路径 256 | */ 257 | function httpHandler(req, pathname) { 258 | const reqHdrRaw = req.headers 259 | 260 | // 处理预检请求 261 | if (req.method === 'OPTIONS' && 262 | reqHdrRaw.has('access-control-request-headers') 263 | ) { 264 | return new Response(null, PREFLIGHT_INIT) 265 | } 266 | 267 | let rawLen = '' 268 | 269 | const reqHdrNew = new Headers(reqHdrRaw) 270 | 271 | const refer = reqHdrNew.get('referer') 272 | 273 | let urlStr = pathname 274 | 275 | const urlObj = newUrl(urlStr) 276 | 277 | /** @type {RequestInit} */ 278 | const reqInit = { 279 | method: req.method, 280 | headers: reqHdrNew, 281 | redirect: 'follow', 282 | body: req.body 283 | } 284 | return proxy(urlObj, reqInit, rawLen) 285 | } 286 | 287 | /** 288 | * 代理请求 289 | * @param {URL} urlObj URL对象 290 | * @param {RequestInit} reqInit 请求初始化对象 291 | * @param {string} rawLen 原始长度 292 | */ 293 | async function proxy(urlObj, reqInit, rawLen) { 294 | const res = await fetch(urlObj.href, reqInit) 295 | const resHdrOld = res.headers 296 | const resHdrNew = new Headers(resHdrOld) 297 | 298 | // 验证长度 299 | if (rawLen) { 300 | const newLen = resHdrOld.get('content-length') || '' 301 | const badLen = (rawLen !== newLen) 302 | 303 | if (badLen) { 304 | return makeRes(res.body, 400, { 305 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 306 | 'access-control-expose-headers': '--error', 307 | }) 308 | } 309 | } 310 | const status = res.status 311 | resHdrNew.set('access-control-expose-headers', '*') 312 | resHdrNew.set('access-control-allow-origin', '*') 313 | resHdrNew.set('Cache-Control', 'max-age=1500') 314 | 315 | // 删除不必要的头 316 | resHdrNew.delete('content-security-policy') 317 | resHdrNew.delete('content-security-policy-report-only') 318 | resHdrNew.delete('clear-site-data') 319 | 320 | return new Response(res.body, { 321 | status, 322 | headers: resHdrNew 323 | }) 324 | } 325 | ``` 326 | 327 | 328 | ### docker.html 329 | ``` 330 | 331 | 332 | 333 | 334 | 335 | 镜像使用说明 336 | 394 | 395 | 396 | 397 |
398 |

镜像使用说明

399 |
400 |
401 |
402 |

为了加速镜像拉取,你可以使用以下命令设置 registry mirror:

403 |
sudo tee /etc/docker/daemon.json <<EOF
404 | {
405 | "registry-mirrors": ["https://{{host}}"]
406 | }
407 | EOF
408 |

为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库:

409 |
docker pull {{host}}/library/alpine:latest # 拉取 library 镜像
410 | docker pull {{host}}/coredns/coredns:latest # 拉取 coredns 镜像
411 |
412 |
413 | 417 | 418 | 419 | ``` 420 | 421 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # built-docker 2 | 中国大陆自建Docker教程 3 | 4 | ## 自建Docker Hub加速镜像 5 | 在使用Docker的过程中会大量的涉及Pull镜像的操作,但是由于官方的镜像服务器在国外再加上某个防火墙的阻拦,导致直接拉取镜像非常困难(看脸)。所以通常的操作是设置一个由国内厂商、机构提供的加速镜像,来提高拉取镜像的速度。但是随着Docker hub限制了未注册用户的拉取频率、各大厂商、机构开始将加速镜像转为内部使用,个人用户拉取镜像变得越来越困难。在长期拉取镜像速度看脸的头疼之下,尝试通过 Nginx 和 Cloudflare Worker 两种方案以及两种方案的组合方案自建Docker hub加速镜像来解决这个问题。 6 | 7 | ## 自建加速镜像 8 | 9 | 在尝试搭建之前找了挺多资料,主流的方式有:使用官方提供的 registry,第三方的 Nexus、Harbor。但是使用 registry 搭建一直没有成功,客户端一直报找不到指定镜像;使用 Nexus 搭建又有些太复杂。最后自己总结出来了这两个比较方便的方案。 10 | 11 | 一点小发现: 12 | 在使用 Nginx 搭建时,发现服务器的流量很小,经过检查 Nginx 的日志后发现,Docker hub 镜像仓库返回的下载地址是需要 307 跳转的,而跳转后的地址依然下载很慢,所以需要在服务端处理这个跳转,将跳转后的数据返回客户端。 13 | 14 | # 方案一:使用 Nginx 搭建 15 | 系统:Debian 12 服务器:[棉花云](https://www.88sup.com) 洛杉矶 16 | 提到要加速一个网站,自然就能想到使用 Nginx 反代一下了。接下来是具体的配置方案 17 | 18 | ## 安装 Nginx 19 | ``` 20 | sudo apt update 21 | sudo apt install nginx 22 | ``` 23 | ## 防火墙放行指定端口 24 | 我这里使用的防火墙是系统自带的 UFW,并且没有开厂商提供的防火墙(忘记是关掉了还是本来就没有,反正没开),所以只需要 sudo ufw allow ‘Nginx Full’ 一条命令即可,这样就会放行 IPv4 和 IPv6 的 80 和 443 端口(当然也可以手动 sudo ufw allow 443 这样只开放 443 端口) 25 | 如果没有使用防火墙,就不用设置这一步,如果还使用了厂商提供的防火墙,就需要在厂商的面板处同样开放这些端口 26 | 27 | ## 配置 Nginx 28 | 29 | 使用命令 sudo vim /etc/nginx/nginx.conf 编辑 Nginx 配置,在 http 块下增加一个 server 块 30 | 31 | /etc/nginx/nginx.conf 32 | 33 | ``` 34 | #反代docker hub镜像源 35 | server { 36 | listen 443 ssl; 37 | server_name 域名; 38 | 39 | ssl_certificate 证书地址; 40 | ssl_certificate_key 密钥地址; 41 | 42 | ssl_session_timeout 24h; 43 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; 44 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 45 | 46 | location / { 47 | proxy_pass https://registry-1.docker.io; # Docker Hub 的官方镜像仓库 48 | proxy_set_header Host registry-1.docker.io; 49 | proxy_set_header X-Real-IP $remote_addr; 50 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 51 | proxy_set_header X-Forwarded-Proto $scheme; 52 | 53 | # 关闭缓存 54 | proxy_buffering off; 55 | 56 | # 转发认证相关的头部 57 | proxy_set_header Authorization $http_authorization; 58 | proxy_pass_header Authorization; 59 | 60 | # 对 upstream 状态码检查,实现 error_page 错误重定向 61 | proxy_intercept_errors on; 62 | # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。 63 | recursive_error_pages on; 64 | # 根据状态码执行对应操作,以下为301、302、307状态码都会触发 65 | error_page 301 302 307 = @handle_redirect; 66 | 67 | } 68 | location @handle_redirect { 69 | resolver 1.1.1.1; 70 | set $saved_redirect_location '$upstream_http_location'; 71 | proxy_pass $saved_redirect_location; 72 | } 73 | } 74 | ``` 75 | 76 | 然后按 Esc ,输入 : wq 保存退出即可 77 | 78 | 重新加载 Nginx 配置 输入命令 sudo nginx -s reload,没有报错就说明配置已经生效 79 | 80 | # 方案二:使用 CloudFlare Worker 搭建 81 | 作为一个贫穷(doge)的用户,可以免费使用的 CloudFlare Worker 自然要想方设法的用用了,虽然 CloudFlare Worker 的访问速度在国内也不算稳定,但在 CloudFlare 的边缘网络的加持下,白天的速度还是非常可观的,晚上会比较慢但还是比直接使用官方的镜像源要快上很多(又不要钱,要啥自行车.jpg) 这里是在 [基于 Cloudflare Worker 的容器镜像加速器](https://github.com/Doublemine/container-registry-worker) 的基础上稍作修改 82 | 83 | 在面板左侧找到 Workers 和 Pages,然后点击右侧的 创建应用程序、创建 Worker,修改一个好记的名字,部署 84 | 85 | 接下来编辑代码,将 worker.js 的内容替换为下面内容 86 | 87 | worker.js 88 | ``` 89 | import HTML from './docker.html'; 90 | 91 | export default { 92 | async fetch(request) { 93 | const url = new URL(request.url); 94 | const path = url.pathname; 95 | const originalHost = request.headers.get("host"); 96 | const registryHost = "registry-1.docker.io"; 97 | 98 | if (path.startsWith("/v2/")) { 99 | const headers = new Headers(request.headers); 100 | headers.set("host", registryHost); 101 | 102 | const registryUrl = `https://${registryHost}${path}`; 103 | const registryRequest = new Request(registryUrl, { 104 | method: request.method, 105 | headers: headers, 106 | body: request.body, 107 | // redirect: "manual", 108 | redirect: "follow", 109 | }); 110 | 111 | const registryResponse = await fetch(registryRequest); 112 | 113 | console.log(registryResponse.status); 114 | 115 | const responseHeaders = new Headers(registryResponse.headers); 116 | responseHeaders.set("access-control-allow-origin", originalHost); 117 | responseHeaders.set("access-control-allow-headers", "Authorization"); 118 | return new Response(registryResponse.body, { 119 | status: registryResponse.status, 120 | statusText: registryResponse.statusText, 121 | headers: responseHeaders, 122 | }); 123 | } else { 124 | return new Response(HTML.replace(/{{host}}/g, originalHost), { 125 | status: 200, 126 | headers: { 127 | "content-type": "text/html" 128 | } 129 | }); 130 | } 131 | } 132 | } 133 | ``` 134 | 这里相比原项目,将 redirect: “manual” 修改为了 redirect: “follow”,目的是为了让脚本自行处理 307 跳转,直接返回给我们跳转后的数据。 135 | 新建一个名为 docker.html 的 文件,内容如下 136 | 137 | docker.html 138 | ``` 139 | 140 | 141 | 142 | 143 | 144 | Mirror Usage 145 | 191 | 192 | 193 |
194 |

Mirror Usage

195 |
196 |
197 |
198 |

镜像加速说明

199 |

200 | 为了加速镜像拉取,你可以使用以下命令设置registery mirror: 201 |

202 |
203 |             sudo tee /etc/docker/daemon.json <<EOF
204 |             {
205 |                 "registry-mirrors": ["https://{{host}}"]
206 |             }
207 |             EOF
208 |             
209 |
210 |

211 | 为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库: 212 |

213 |
214 |             docker pull {{host}}/library/alpine:latest # 拉取 library 镜像
215 |             docker pull {{host}}/coredns/coredns:latest # 拉取 library 镜像
216 |             
217 |
218 |
219 | 222 | 223 | 224 | ``` 225 | 接下来,点击右上角的 部署,稍等片刻 226 | 227 | 最后,返回面板,在 设置,触发器 处设置一个自己的域名,一切就大功告成了 228 | 不建议使用自带的 workers.dev 的域名,被墙了 229 | 230 | # 方案一、二整合 231 | 本来上面的两个方案是独立的,一个使用 Nginx 部署,一个使用 CloudFlare Worker 部署,但是就在我写这篇博客的时候,突然想到,为什么不能把上面的两个方案整合起来呢? 232 | 利用服务器搭建的 Nginx 作为中转,优先由服务器直连 Docker hub 的官方源,当服务器的 IP 请求次数超限后(会报 429 错误),就把请求转发到 CloudFlare Worker 部署的镜像源上,利用 CloudFlare Worker 再做一次中转。这样就即保证了使用服务器中转提高速度,又保证了不会因为服务器的 IP 请求速度过多而受限制,唯一的限制就是服务器的带宽和流量了,几乎完美!!! 233 | 234 | ## 部署方法: 235 | 将上面部署的 Nginx 配置替换为下面的配置并使用 sudo nginx -s reload 重新加载即可 236 | 237 | /etc/nginx/nginx.conf 238 | ``` 239 | #反代docker hub镜像源 240 | server { 241 | listen 443 ssl; 242 | server_name 域名; 243 | 244 | ssl_certificate 证书地址; 245 | ssl_certificate_key 密钥地址; 246 | 247 | proxy_ssl_server_name on; # 启用SNI 248 | 249 | ssl_session_timeout 24h; 250 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; 251 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 252 | 253 | location / { 254 | proxy_pass https://registry-1.docker.io; # Docker Hub 的官方镜像仓库 255 | 256 | proxy_set_header Host registry-1.docker.io; 257 | proxy_set_header X-Real-IP $remote_addr; 258 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 259 | proxy_set_header X-Forwarded-Proto $scheme; 260 | 261 | # 关闭缓存 262 | proxy_buffering off; 263 | 264 | # 转发认证相关的头部 265 | proxy_set_header Authorization $http_authorization; 266 | proxy_pass_header Authorization; 267 | 268 | # 对 upstream 状态码检查,实现 error_page 错误重定向 269 | proxy_intercept_errors on; 270 | # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。 271 | recursive_error_pages on; 272 | # 根据状态码执行对应操作,以下为301、302、307状态码都会触发 273 | #error_page 301 302 307 = @handle_redirect; 274 | 275 | error_page 429 = @handle_too_many_requests; 276 | } 277 | #处理重定向 278 | location @handle_redirect { 279 | resolver 1.1.1.1; 280 | set $saved_redirect_location '$upstream_http_location'; 281 | proxy_pass $saved_redirect_location; 282 | } 283 | # 处理429错误 284 | location @handle_too_many_requests { 285 | proxy_set_header Host 替换为在CloudFlare Worker设置的域名; # 替换为另一个服务器的地址 286 | proxy_pass http://替换为在CloudFlare Worker设置的域名; 287 | proxy_set_header Host $http_host; 288 | } 289 | } 290 | ``` 291 | 如果想要反代 ghcr 镜像源呢?只要参考上面的配置,将域名、header 修改一下即可 292 | 并且因为 ghcr 好像不像 docker hub 有下载频率的限制,所以也不用去 Cloudflare Worker 部署了,直接在服务器上部署一个就行。 293 | ``` 294 | #反代ghcr镜像源 295 | server { 296 | listen 443 ssl; 297 | server_name 域名; 298 | 299 | ssl_certificate 证书地址; 300 | ssl_certificate_key 密钥地址; 301 | proxy_ssl_server_name on; 302 | ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; 303 | #error_log /home/ubuntuago/proxy_docker.log debug; 304 | if ($blocked_agent) { 305 | return 403; 306 | } 307 | 308 | location / { 309 | proxy_pass https://ghcr.io; # Docker Hub 的官方镜像仓库 310 | 311 | proxy_set_header Host ghcr.io; 312 | proxy_set_header X-Real-IP $remote_addr; 313 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 314 | proxy_set_header X-Forwarded-Proto $scheme; 315 | 316 | # 关闭缓存 317 | proxy_buffering off; 318 | 319 | # 转发认证相关的头部 320 | proxy_set_header Authorization $http_authorization; 321 | proxy_pass_header Authorization; 322 | # 对 upstream 状态码检查,实现 error_page 错误重定向 323 | proxy_intercept_errors on; 324 | # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。 325 | recursive_error_pages on; 326 | # 根据状态码执行对应操作,以下为301、302、307状态码都会触发 327 | error_page 301 302 307 = @handle_redirect; 328 | 329 | #error_page 429 = @handle_too_many_requests; 330 | 331 | } 332 | #处理重定向 333 | location @handle_redirect { 334 | resolver 1.1.1.1; 335 | set $saved_redirect_location '$upstream_http_location'; 336 | proxy_pass $saved_redirect_location; 337 | } 338 | 339 | } 340 | ``` 341 | ## 新增的一些代码的作用: 342 | 343 | proxy_ssl_server_name on; 344 | HTTPS, 需要在握手时候验证证书, 所以在握手时候需要将域名告诉对方, 找到匹配的证书, 这就是 SNI 的工作. 否则会导致证书找不到而请求失败. 345 | 默认情况下, nginx 并不会开启 proxy_ssl_server_name, 也就是说不启用 SNI. 如果使用 nginx 反代一个虚拟主机的服务, 比如 Cloudflare Workers, 此时如果不开启 SNI, 会导致与 CF 握手时候, CF 并不清楚请求哪一个域名下的服务, 所以找不到匹配的证书, 因此会报 502 错误. 当然也可以使用 proxy_ssl_name 字段复写于最终服务器收到的域名. 346 | 引自:[SNI](https://faichou.com/sni/) 347 | 348 | 在尝试整合这两个方案的时候不知道这个参数,一直报 502..人都麻了 349 | 350 | error_page 429 = @handle_too_many_requests; 351 | 当错误代码为 429 时(即请求次数超限)转发到在 Cloudflare Workers 部署的镜像源 352 | 353 | 354 | proxy_set_header Host 替换为在 CloudFlare Worker 设置的域名; 355 | 将发给 CloudFlare Worker 的请求加上正确的域名方可让请求到达我们搭建的镜像源,如果不加会报 404 356 | 357 | # 总结 358 | 根据测试,自建一个 Docker hub 加速镜像完全可行,使用 Nginx 搭建会比较看重服务器的线路,但只要不算太差,就完全可用了。不处理跳转会比较节省流量,但是为了速度,节省这一点流量也没什么必要。有一定的成本,但是如果本身就已经买了服务器的话,搭建个镜像源就几乎是顺带的事情了。 359 | 而使用 CloudFlare Worker 搭建..只能说稍微好一点点,如果服务器需要大量拉取镜像的话或许会用到这个方案。 360 | 而将方案一、二整合,几乎就是现阶段最完美的方案了,速度快且不受拉取次数的限制,只要 Docker hub 别乱变,就可以用超久。 361 | 362 | # 如果失效,显示 363 | ``` 364 | root@VM-4-14-debian:~# docker pull hub1.nat.tf/library/alpine:latest 365 | Error response from daemon: Head "https://hub1.nat.tf/v2/library/alpine/manifests/latest": Get "https://auth.docker.io/token?scope=repository%3Alibrary%2Falpine%3Apull&service=registry.docker.io": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 366 | ``` 367 | # 那就把Nginx的改成如下,根据自己配置来调整,我是基于1panel的OpenResty搭建,记得重载配置 368 | ``` 369 | user root; 370 | worker_processes auto; 371 | error_log /var/log/nginx/error.log notice; 372 | error_log /dev/stdout notice; 373 | pid /var/run/nginx.pid; 374 | 375 | events { 376 | worker_connections 1024; 377 | } 378 | 379 | http { 380 | include mime.types; 381 | default_type application/octet-stream; 382 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 383 | '$status $body_bytes_sent "$http_referer" ' 384 | '"$http_user_agent" "$http_x_forwarded_for"'; 385 | server_tokens off; 386 | access_log /var/log/nginx/access.log main; 387 | access_log /dev/stdout main; 388 | sendfile on; 389 | 390 | server_names_hash_bucket_size 512; 391 | client_header_buffer_size 32k; 392 | client_max_body_size 50m; 393 | keepalive_timeout 60; 394 | keepalive_requests 100000; 395 | 396 | gzip on; 397 | gzip_min_length 1k; 398 | gzip_buffers 4 16k; 399 | gzip_http_version 1.1; 400 | gzip_comp_level 2; 401 | gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; 402 | gzip_vary on; 403 | gzip_proxied expired no-cache no-store private auth; 404 | gzip_disable "MSIE [1-6]\."; 405 | 406 | limit_conn_zone $binary_remote_addr zone=perip:10m; 407 | limit_conn_zone $server_name zone=perserver:10m; 408 | 409 | # 反代docker hub镜像源的服务器配置 410 | server { 411 | listen 443 ssl; 412 | server_name 域名; 413 | 414 | ssl_certificate 证书地址; 415 | ssl_certificate_key 密钥地址; 416 | 417 | ssl_session_timeout 24h; 418 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; 419 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 420 | 421 | location /v2/ { 422 | proxy_pass https://registry-1.docker.io; # Docker Hub 的官方镜像仓库 423 | proxy_set_header Host registry-1.docker.io; 424 | proxy_set_header X-Real-IP $remote_addr; 425 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 426 | proxy_set_header X-Forwarded-Proto $scheme; 427 | 428 | # 关闭缓存 429 | proxy_buffering off; 430 | 431 | # 转发认证相关的头部 432 | proxy_set_header Authorization $http_authorization; 433 | proxy_pass_header Authorization; 434 | 435 | # 重写 www-authenticate 头为你的反代地址 436 | proxy_hide_header www-authenticate; 437 | add_header www-authenticate 'Bearer realm="https://域名/token",service="registry.docker.io"' always; 438 | 439 | # 对 upstream 状态码检查,实现 error_page 错误重定向 440 | proxy_intercept_errors on; 441 | recursive_error_pages on; 442 | error_page 301 302 307 = @handle_redirect; 443 | } 444 | 445 | # 处理 Docker OAuth2 Token 认证请求 446 | location /token { 447 | resolver 1.1.1.1 valid=600s; 448 | proxy_pass https://auth.docker.io; # Docker 认证服务器 449 | 450 | proxy_set_header Host auth.docker.io; 451 | proxy_set_header X-Real-IP $remote_addr; 452 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 453 | proxy_set_header X-Forwarded-Proto $scheme; 454 | 455 | proxy_set_header Authorization $http_authorization; 456 | proxy_pass_header Authorization; 457 | 458 | proxy_buffering off; 459 | } 460 | 461 | location @handle_redirect { 462 | resolver 1.1.1.1; 463 | set $saved_redirect_location '$upstream_http_location'; 464 | proxy_pass $saved_redirect_location; 465 | } 466 | } 467 | 468 | include /usr/local/openresty/nginx/conf/conf.d/*.conf; 469 | include /usr/local/openresty/1pwaf/data/conf/waf.conf; 470 | } 471 | ``` 472 | 473 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 镜像使用说明 7 | 65 | 66 | 67 | 68 |
69 |

镜像使用说明

70 |
71 |
72 |
73 |

域名

74 |

哪个快用哪个,都添加到镜像加速也不是不行

75 |
hub.nat.tf
 76 | hub1.nat.tf
77 |

使用方法①

78 | 假如拉取原始镜像命令如下 79 |
docker pull whyour/qinglong:latest
80 | 仅需在原命令前缀加入加速镜像地址 例如: 81 |
docker pull hub.nat.tf/whyour/qinglong:latest
82 |

使用方法②

83 | 一键设置镜像加速:修改文件 /etc/docker/daemon.json(如果不存在则创建) 84 |
nano /etc/docker/daemon.json
85 | 修改JSON文件 更改为以下内容 然后保存 86 |
{"registry-mirrors": ["https://hub.nat.tf"]}
87 | 保存好之后 执行以下两条命令 88 |
sudo systemctl daemon-reload #重载systemd管理守护进程配置文件
89 |
sudo systemctl restart docker #重启 Docker 服务
90 |

使用方法③

91 | 为了加速镜像拉取,你可以使用以下命令设置 registry mirror: 92 |
sudo tee /etc/docker/daemon.json <<EOF
 93 | {
 94 | "registry-mirrors": ["https://hub.nat.tf"]
 95 | }
 96 | EOF
97 | 为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库: 98 |
docker pull hub.nat.tf/library/alpine:latest # 拉取 library 镜像
 99 | docker pull hub.nat.tf/coredns/coredns:latest # 拉取 coredns 镜像
100 |
101 |
102 | 106 | 107 | 108 | --------------------------------------------------------------------------------