使用Cloudflare Worker加速docker镜像

前言

开发者越来越难了🥱,现在国内的docker镜像也都🈚️了,没有镜像要使用docker🦈太难了,代理🌍又很慢

现在就只剩下自建镜像的办法了😭

GitHub上有开源项目可以快速搭建自己的镜像库,不过还是有点麻烦,还好Cloudflare暂时还活着😮‍💨

本文记录一下使用 Cloudflare Worker 搭建 docker 镜像的方法📒

关于 worker

Cloudflare Workers 是 Cloudflare 提供的一个无服务器(serverless)计算平台,允许你在 Cloudflare 的全球边缘网络上运行 JavaScript、Rust、C、C++ 和 COBOL 等编写的代码。它主要用于创建高性能、高可扩展性的网页应用程序和API。👍

Cloudflare Workers 非常适合处理各种网络请求,例如 API网关、内容定制和动态渲染等任务,同时还可以用来修改或决定网络请求如何响应。它为开发人员提供了在全球范围内快速部署应用程序和服务的能力。

一些关键特性:

  1. 边缘计算:由于代码直接在 Cloudflare 的边缘节点上执行,可以显著减少数据传输时间,提高响应速度。
  2. 无服务器架构:你不需要管理任何服务器或实例,只需关注代码的编写和部署。Cloudflare 负责代码的运行和扩展。
  3. 按需计费:Cloudflare Workers 的计费模式基于请求次数和执行时间,而不是预先设定的资源分配,这意味着你可以根据实际使用量付费。
  4. 高度集成:它可以与 Cloudflare 的其他服务(如 KV 存储、Durable Objects 等)紧密集成,方便数据存储和状态管理。
  5. 安全性:由于 Workers 运行在隔离的环境中,它们提供了与传统服务器或容器相比更高的安全级别。

创建 worker

打开 Cloudflare 的 Worker and Pages 页面,创建一个 worker

起个直观的名字,比如 docker-proxy 之类的

然后点【部署】按钮

编辑代码

部署完成之后✅,点旁边的【编辑代码】按钮

把下面的 JavaScript👨‍🏫 代码输进去

需要把 workers_url 替换成自己的域名,比如 https://docker.example.com

// Docker镜像仓库主机地址 let hub_host = 'registry-1.docker.io' // Docker认证服务器地址 const auth_url = 'https://auth.docker.io' // 自定义的工作服务器地址 let workers_url = 'https://你的域名'  let 屏蔽爬虫UA = ['netcraft'];  // 根据主机名选择对应的上游地址 function routeByHosts(host) {   // 定义路由表   const routes = {     // 生产环境     "quay": "quay.io",     "gcr": "gcr.io",     "k8s-gcr": "k8s.gcr.io",     "k8s": "registry.k8s.io",     "ghcr": "ghcr.io",     "cloudsmith": "docker.cloudsmith.io",      // 测试环境     "test": "registry-1.docker.io",   };    if (host in routes) return [ routes[host], false ];   else return [ hub_host, true ]; }  /** @type {RequestInit} */ const PREFLIGHT_INIT = {   // 预检请求配置   headers: new Headers({     'access-control-allow-origin': '*', // 允许所有来源     'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法     'access-control-max-age': '1728000', // 预检请求的缓存时间   }), }  /**  * 构造响应  * @param {any} body 响应体  * @param {number} status 响应状态码  * @param {Object<string, string>} headers 响应头  */ function makeRes(body, status = 200, headers = {}) {   headers['access-control-allow-origin'] = '*' // 允许所有来源   return new Response(body, { status, headers }) // 返回新构造的响应 }  /**  * 构造新的URL对象  * @param {string} urlStr URL字符串  */ function newUrl(urlStr) {   try {     return new URL(urlStr) // 尝试构造新的URL对象   } catch (err) {     return null // 构造失败返回null   } }  function isUUID(uuid) {   // 定义一个正则表达式来匹配 UUID 格式   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;    // 使用正则表达式测试 UUID 字符串   return uuidRegex.test(uuid); }  async function nginx() {   const text = `   <!DOCTYPE html>   <html>   <head>   <title>Welcome to nginx!</title>   <style>     body {       width: 35em;       margin: 0 auto;       font-family: Tahoma, Verdana, Arial, sans-serif;     }   </style>   </head>   <body>   <h1>Welcome to nginx!</h1>   <p>If you see this page, the nginx web server is successfully installed and   working. Further configuration is required.</p>    <p>For online documentation and support please refer to   <a href="http://nginx.org/">nginx.org</a>.<br/>   Commercial support is available at   <a href="http://nginx.com/">nginx.com</a>.</p>    <p><em>Thank you for using nginx.</em></p>   </body>   </html>   `   return text ; }  export default {   async fetch(request, env, ctx) {     const getReqHeader = (key) => request.headers.get(key); // 获取请求头      let url = new URL(request.url); // 解析请求URL     const userAgentHeader = request.headers.get('User-Agent');     const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";     if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA));     workers_url = `https://${url.hostname}`;     const pathname = url.pathname;     const hostname = url.searchParams.get('hubhost') || url.hostname;      const hostTop = hostname.split('.')[0];// 获取主机名的第一部分     const checkHost = routeByHosts(hostTop);     hub_host = checkHost[0]; // 获取上游地址     const fakePage = checkHost[1];     console.log(`域名头部: ${hostTop}n反代地址: ${hub_host}n伪装首页: ${fakePage}`);     const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);      if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0){       //首页改成一个nginx伪装页       return new Response(await nginx(), {         headers: {           'Content-Type': 'text/html; charset=UTF-8',         },       });     }      const conditions = [       isUuid,       pathname.includes('/_'),       pathname.includes('/r'),       pathname.includes('/v2/user'),       pathname.includes('/v2/orgs'),       pathname.includes('/v2/_catalog'),       pathname.includes('/v2/categories'),       pathname.includes('/v2/feature-flags'),       pathname.includes('search'),       pathname.includes('source'),       pathname === '/',       pathname === '/favicon.ico',       pathname === '/auth/profile',     ];      if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) {       if (env.URL302){         return Response.redirect(env.URL302, 302);       } else if (env.URL){         if (env.URL.toLowerCase() == 'nginx'){           //首页改成一个nginx伪装页           return new Response(await nginx(), {             headers: {               'Content-Type': 'text/html; charset=UTF-8',             },           });         } else return fetch(new Request(env.URL, request));       }        const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search);        // 复制原始请求的标头       const headers = new Headers(request.headers);        // 确保 Host 头部被替换为 hub.docker.com       headers.set('Host', 'registry.hub.docker.com');        const newRequest = new Request(newUrl, {         method: request.method,         headers: headers,         body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null,         redirect: 'follow'       });        return fetch(newRequest);     }      // 修改包含 %2F 和 %3A 的请求     if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) {       let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F');       url = new URL(modifiedUrl);       console.log(`handle_url: ${url}`)     }      // 处理token请求     if (url.pathname.includes('/token')) {       let token_parameter = {         headers: {           'Host': 'auth.docker.io',           'User-Agent': getReqHeader("User-Agent"),           'Accept': getReqHeader("Accept"),           'Accept-Language': getReqHeader("Accept-Language"),           'Accept-Encoding': getReqHeader("Accept-Encoding"),           'Connection': 'keep-alive',           'Cache-Control': 'max-age=0'         }       };       let token_url = auth_url + url.pathname + url.search       return fetch(new Request(token_url, request), token_parameter)     }      // 修改 /v2/ 请求路径     if (/^/v2/[^/]+/[^/]+/[^/]+$/.test(url.pathname) && !/^/v2/library/.test(url.pathname)) {       url.pathname = url.pathname.replace(//v2//, '/v2/library/');       console.log(`modified_url: ${url.pathname}`)     }      // 更改请求的主机名     url.hostname = hub_host;      // 构造请求参数     let parameter = {       headers: {         'Host': hub_host,         'User-Agent': getReqHeader("User-Agent"),         'Accept': getReqHeader("Accept"),         'Accept-Language': getReqHeader("Accept-Language"),         'Accept-Encoding': getReqHeader("Accept-Encoding"),         'Connection': 'keep-alive',         'Cache-Control': 'max-age=0'       },       cacheTtl: 3600 // 缓存时间     };      // 添加Authorization头     if (request.headers.has("Authorization")) {       parameter.headers.Authorization = getReqHeader("Authorization");     }      // 发起请求并处理响应     let original_response = await fetch(new Request(url, request), parameter)     let original_response_clone = original_response.clone();     let original_text = original_response_clone.body;     let response_headers = original_response.headers;     let new_response_headers = new Headers(response_headers);     let status = original_response.status;      // 修改 Www-Authenticate 头     if (new_response_headers.get("Www-Authenticate")) {       let auth = new_response_headers.get("Www-Authenticate");       let re = new RegExp(auth_url, 'g');       new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));     }      // 处理重定向     if (new_response_headers.get("Location")) {       return httpHandler(request, new_response_headers.get("Location"))     }      // 返回修改后的响应     let response = new Response(original_text, {       status,       headers: new_response_headers     })     return response;   } };  /**  * 处理HTTP请求  * @param {Request} req 请求对象  * @param {string} pathname 请求路径  */ function httpHandler(req, pathname) {   const reqHdrRaw = req.headers    // 处理预检请求   if (req.method === 'OPTIONS' &&       reqHdrRaw.has('access-control-request-headers')      ) {     return new Response(null, PREFLIGHT_INIT)   }    let rawLen = ''    const reqHdrNew = new Headers(reqHdrRaw)    const refer = reqHdrNew.get('referer')    let urlStr = pathname    const urlObj = newUrl(urlStr)    /** @type {RequestInit} */   const reqInit = {     method: req.method,     headers: reqHdrNew,     redirect: 'follow',     body: req.body   }   return proxy(urlObj, reqInit, rawLen) }  /**  * 代理请求  * @param {URL} urlObj URL对象  * @param {RequestInit} reqInit 请求初始化对象  * @param {string} rawLen 原始长度  */ async function proxy(urlObj, reqInit, rawLen) {   const res = await fetch(urlObj.href, reqInit)   const resHdrOld = res.headers   const resHdrNew = new Headers(resHdrOld)    // 验证长度   if (rawLen) {     const newLen = resHdrOld.get('content-length') || ''     const badLen = (rawLen !== newLen)      if (badLen) {       return makeRes(res.body, 400, {         '--error': `bad len: ${newLen}, except: ${rawLen}`,         'access-control-expose-headers': '--error',       })     }   }   const status = res.status   resHdrNew.set('access-control-expose-headers', '*')   resHdrNew.set('access-control-allow-origin', '*')   resHdrNew.set('Cache-Control', 'max-age=1500')    // 删除不必要的头   resHdrNew.delete('content-security-policy')   resHdrNew.delete('content-security-policy-report-only')   resHdrNew.delete('clear-site-data')    return new Response(res.body, {     status,     headers: resHdrNew   }) }  async function ADD(envadd) {   var addtext = envadd.replace(/[   |"'rn]+/g, ',').replace(/,+/g, ',');  // 将空格、双引号、单引号和换行符替换为逗号   //console.log(addtext);   if (addtext.charAt(0) == ',') addtext = addtext.slice(1);   if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1);   const add = addtext.split(',');   //console.log(add);   return add ; } 

OK👌,代码输入之后,重新点击【部署】,部署成功就可以打开 docker🦈hub 页面了

设置域名

刚才不是在代码里配置了域名吗❓

接下来还得继续配置一下

返回【Workers 和 Pages / docker-proxy】配置页面

点【设置】-【触发器】🥬

在【自定义域】里点添加,输入域名,例如🌰 docker.example.com

搞定🤝,cloudflare 会自动申请 SSL 证书什么的✔️

配置registry

修改 /etc/docker/daemon.json 文件

{   "registry-mirrors": ["https://docker.example.com"],   "insecure-registries": ["docker.example.com"] } 

重新启动 docker

sudo systemctl daemon-reload sudo systemctl restart docker 

接下来再 pull image 速度就快到起飞🛫了✈️

参考资料

发表评论

评论已关闭。

相关文章