前言
开发者越来越难了🥱,现在国内的docker镜像也都🈚️了,没有镜像要使用docker🦈太难了,代理🌍又很慢
现在就只剩下自建镜像的办法了😭
GitHub上有开源项目可以快速搭建自己的镜像库,不过还是有点麻烦,还好Cloudflare暂时还活着😮💨
本文记录一下使用 Cloudflare Worker 搭建 docker 镜像的方法📒
关于 worker
Cloudflare Workers 是 Cloudflare 提供的一个无服务器(serverless)计算平台,允许你在 Cloudflare 的全球边缘网络上运行 JavaScript、Rust、C、C++ 和 COBOL 等编写的代码。它主要用于创建高性能、高可扩展性的网页应用程序和API。👍
Cloudflare Workers 非常适合处理各种网络请求,例如 API网关、内容定制和动态渲染等任务,同时还可以用来修改或决定网络请求如何响应。它为开发人员提供了在全球范围内快速部署应用程序和服务的能力。
一些关键特性:
- 边缘计算:由于代码直接在 Cloudflare 的边缘节点上执行,可以显著减少数据传输时间,提高响应速度。
- 无服务器架构:你不需要管理任何服务器或实例,只需关注代码的编写和部署。Cloudflare 负责代码的运行和扩展。
- 按需计费:Cloudflare Workers 的计费模式基于请求次数和执行时间,而不是预先设定的资源分配,这意味着你可以根据实际使用量付费。
- 高度集成:它可以与 Cloudflare 的其他服务(如 KV 存储、Durable Objects 等)紧密集成,方便数据存储和状态管理。
- 安全性:由于 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(/[ |"'\r\n]+/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-reloadsudo systemctl restart docker
接下来再 pull image 速度就快到起飞🛫了✈️