Docker镜像提取失败?10分钟快速解决,简单稳定好用!Cloudflare篇
大家好,我是羊刀仙。
Docker镜像拉取一直是个比较让人头疼的事情,尤其是不久之前那档子公告发布后,作为NAS用户真的太痛了~
本期内容为通过 Cloudflare Workers 搭建一个免费的Docker镜像加速服务。一个Cloudflare账户和一个域名即可搞定,按文章步骤来,部署使用轻松加愉快。
后面我也会再发一篇同样十分稳定的真付费方式,大家可以关注下。
Cloudflare Workers方案优势
CloudFlare拥有全球化的CDN网络,加速效果很不错;
Workers提供了免费的计算资源,几乎无成本;
配置简单,全程配置仅需动手;
安全可靠,不用担心镜像源跑路。
关于Cloudflare账户注册不赘述,像申请QQ账号一样简单。
Cloudflare接管DNS解析
这一步是必须的。
首先要转移域名DNS服务,让Cloudflare接管,相关操作可以看之前文章,也是十分简单~这里以腾讯云为例。
开始部署服务
返回进入Cloudflare主界面,左侧栏点击Workers 和 Pages,右侧点击创建Workers:

命名后直接点击部署:

部署完毕后,点击编辑代码:

可以看到worker.js文件,将下面这一大串复制粘贴进去,第四行你的域名就是上面托管好的域名(建议二级域名),这里先不急着填写,点击右上角的部署:
如果代码有问题,可以移步我的BLOG查看,大妈可能吞格式,查起来太麻烦!
'use strict';
const hub_host = 'registry-1.docker.io';
const auth_url = 'https://auth.docker.io';
const workers_url = 'https://docker.ydxian.love';
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
};
function makeRes(body, status = 200, headers = {}) {
headers['access-control-allow-origin'] = '*';
return new Response(body, {
status,
headers
});
}
function newUrl(urlStr) {
try {
return new URL(urlStr);
} catch (err) {
return null;
}
}
addEventListener('fetch', e => {
const ret = fetchHandler(e).catch(err => makeRes('cfworker error:n' + err.stack, 502));
e.respondWith(ret);
});
async function fetchHandler(e) {
const getReqHeader = (key) => e.request.headers.get(key);
let url = new URL(e.request.url);
if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) {
let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F');
url = new URL(modifiedUrl);
}
if (url.pathname === '/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, e.request), token_parameter);
}
if (/^/v2/[^/]+/[^/]+/[^/]+$/.test(url.pathname) && !/^/v2/library/.test(url.pathname)) {
url.pathname = url.pathname.replace(//v2//, '/v2/library/');
}
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
};
if (e.request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
let original_response = await fetch(new Request(url, e.request), parameter);
let original_response_clone = original_response.clone();
let original_text = await original_response_clone.text();
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
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(e.request, new_response_headers.get("Location"));
}
let response = new Response(original_text, {
status,
headers: new_response_headers
});
return response;
}
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);
let urlStr = pathname;
const urlObj = newUrl(urlStr);
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
};
return proxy(urlObj, reqInit, 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
});
}

接着返回上一界面,如图所示,选中设置,找到触发器,点击添加自定义域:

可以用主域名,也可以用泛域名,看各位喜好了,我就用的二级域名。填写完后点击添加自定义域:

完事儿后等域名证书发放(等几分钟手动刷新页面),显示不活动或有效都证明成功,这时点击右上角的编辑代码:

把整个二级域名填写进去,填写格式请严格按照下图格式来,填好了点击右上角部署:

部署完后,就完全齐活儿啦。
实际测试
⭕️试试威联通NAS
熟悉的操作,打开Container Station,点击左侧存储库-添加

如下图所示,添加存储库并测试,连通性通过的话点击应用

接着点击左侧栏映像-提取,测试一下实际提取如何。同样的需要我们选择刚添加的库,这里以数据库项目mysql测试,可以顺手把设置为默认勾选上

速度还是很不错的,cloudflare 牛哇!

通过Docker Compose部署也非常简单只需改动一个位置:
image: antonyleons/ward
# 改为
image: docker.ydxian.love/antonyleons/ward
如下图所示,其他全部保持原样子,不过如果一个应用包含多个容器时,记得每个image都要改掉!

最后
有需要的朋友可以按文章步骤搞起来自用,不建议公开公用。本文涉及的域名服务不久也将撤销,届时也会失效。
感谢观看,本文完。

这只是个昵称而已
不知是自己操作没做对,还是Cloudflare系统问题。
校验提示文案
吹牛不用打草稿
校验提示文案
anymango
校验提示文案
烧钱的幺儿
校验提示文案
p1y1c1
校验提示文案
fun特稀
校验提示文案
mzisas
校验提示文案
残酷天使
校验提示文案
值友1552760488
校验提示文案
值友1816513438
校验提示文案
yulll
校验提示文案
crdz
校验提示文案
来自火星的懒牛
校验提示文案
zc40
校验提示文案
cansin
校验提示文案
HLAB
校验提示文案
louiskim
校验提示文案
LostAlone
校验提示文案
关门扎辫子
校验提示文案
LostAlone
校验提示文案
这只是个昵称而已
不知是自己操作没做对,还是Cloudflare系统问题。
校验提示文案
欣欣的
校验提示文案
有木有先生
校验提示文案
zc40
校验提示文案
裸奔的鸭
校验提示文案
时光微粒
校验提示文案
王守义十三香
校验提示文案
值友1156707776
校验提示文案
南天十字
校验提示文案
值友6639433181
校验提示文案
一片空白
校验提示文案
LostAlone
校验提示文案
来自火星的懒牛
校验提示文案
crdz
校验提示文案
yulll
校验提示文案
fun特稀
校验提示文案
关门扎辫子
校验提示文案
值友1816513438
校验提示文案
p1y1c1
校验提示文案
LostAlone
校验提示文案