巧用CF的Workers完美解决Docker镜像国内无法拉取

巧用CF解决Docker hub镜像国内无法拉取

1.前言简述

最近 Docker Hub 仓库国内无法拉取镜像的事情,在安全、运维、开发圈里都在被火热的讨论,也有人提出过解决方案,例如,前面作者使用 Github Action + 阿里云镜像服务 ACR 来实现Docker Hub 镜像的同步拉取(《运维 Tips | Docker Hub 仓库国内无法拉取镜像,如何应对?》),但是,这个方案需要每次更新镜像时都要到Github中进行触发执行Github Action,过程还是过于繁琐,而且对于国内用户来说,访问 Github 速度比较慢。

那还有其他稍微完美一点的解决方案吗,作者都说到这里了当然有了!

今天,作者在某特(X)上看到一个有趣的方案,使用 Cloudflare 提供的免费 Worker 服务,巧妙的来解决 Docker Hub 镜像源地址国内无法拉取的问题,作者实践后发现确实是目前比较完美的方案。

下面,就来看看作者是如何利用 cloudflare 的 Worker 服务,来解决 Docker Hub 镜像源国内无法拉取的问题。

2.前置需求

Domain 域名:域名注册国内的腾讯云

、阿里云、百度云、华为云都是可以的,如果不想花钱注册的也可去注册一个免费的http://eu.org域名(PS: 这也是作者实践使用的域名),申请教程:《免费注册申请永久的http://eu.org顶级域名创建属于自己的域名》, 注册视频:

weiyigeek.top-免费http://eu.org域名图

Cloudflare 账号:注册相信大家都能搞定,这里就不再赘述了,选择免费计划(Free Plans)即可(适用于非关键任务个人或业余项目)链接直达:https://dash.cloudflare.com/sign-up?pt=f

weiyigeek.top-注册cloudflare图

3.实践之路

  • Step 1.登录 Cloudflare 账号,进入到主页,点击网站,添加站点 w*.eu.org (PS: 由于Cloudflare Workers有每日请求数限制10w,所以这里作者将实践域名打码,作者只针对赞赏以及付费用户开放,其他看友请安装前置需求准备好对应环境,按照后续实践自行部署自己私有镜像拉取域名即可),同样选择免费计划(0/月)即可。

weiyigeek.top-添加站点图

指向 Cloudflare 提供的名称服务器。

bayan.ns.cloudflare.com

vita.ns.cloudflare.com

weiyigeek.top-更改域名DNS域名解析服务器图

  • Step 3.回到 Cloudflare 主页,点击Workers 和 Pages,然后创建应用程序, 命名为Hub, 按照提示点击完成即可。

weiyigeek.top-创建Workers应用程序图

  • Step 4.创建完成后,点击hub进入到项目业中,再点击编辑代码,将代码替换为如下内容,并且保存即可。(PS: 若显示有问题建议在UP主公众号文章中进行获取)
    ```js
    ‘usestrict’

consthub_host=’registry-1.docker.io’

constauth_url=’https://auth.docker.io

//请将hub.weiyigeek.eu.org替换为自己的域名

constworkers_url=’https://hub.weiyigeek.eu.org

constPREFLIGHT_INIT={

status:204,

headers:newHeaders({

‘access-control-allow-origin’:’*’,

‘access-control-allow-methods’:’GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS’,

‘access-control-max-age’:’1728000’,

}),

}

functionmakeRes(body,status=200,headers={}){

headers[‘access-control-allow-origin’]=’*’

returnnewResponse(body,{status,headers})

}

functionnewUrl(urlStr){

try{

returnnewURL(urlStr)

}catch(err){

returnnull

}

}

addEventListener(‘fetch’,e=>{

constret=fetchHandler(e)

.catch(err=>makeRes(‘cfworkererror:\n’+err.stack,502))

e.respondWith(ret)

})

asyncfunctionfetchHandler(e){

constgetReqHeader=(key)=>e.request.headers.get(key);

leturl=newURL(e.request.url);

if(url.pathname===’/token’){

lettoken_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’

}

};

lettoken_url=auth_url+url.pathname+url.search

returnfetch(newRequest(token_url,e.request),token_parameter)

}

url.hostname=hub_host;

letparameter={

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”);

}

letoriginal_response=awaitfetch(newRequest(url,e.request),parameter)

letoriginal_response_clone=original_response.clone();

letoriginal_text=original_response_clone.body;

letresponse_headers=original_response.headers;

letnew_response_headers=newHeaders(response_headers);

letstatus=original_response.status;

if(new_response_headers.get(“Www-Authenticate”)){

letauth=new_response_headers.get(“Www-Authenticate”);

letre=newRegExp(auth_url,’g’);

new_response_headers.set(“Www-Authenticate”,response_headers.get(“Www-Authenticate”).replace(re,workers_url));

}

if(new_response_headers.get(“Location”)){

returnhttpHandler(e.request,new_response_headers.get(“Location”))

}

letresponse=newResponse(original_text,{

status,

headers:new_response_headers

})

returnresponse;

}

functionhttpHandler(req,pathname){

constreqHdrRaw=req.headers

//preflight

if(req.method===’OPTIONS’&&

reqHdrRaw.has(‘access-control-request-headers’)

){

returnnewResponse(null,PREFLIGHT_INIT)

}

letrawLen=’’

constreqHdrNew=newHeaders(reqHdrRaw)

constrefer=reqHdrNew.get(‘referer’)

leturlStr=pathname

consturlObj=newUrl(urlStr)

/*@type{RequestInit}/

constreqInit={

method:req.method,

headers:reqHdrNew,

redirect:’follow’,

body:req.body

}

returnproxy(urlObj,reqInit,rawLen,0)

}

asyncfunctionproxy(urlObj,reqInit,rawLen){

constres=awaitfetch(urlObj.href,reqInit)

constresHdrOld=res.headers

constresHdrNew=newHeaders(resHdrOld)

//verify

if(rawLen){

constnewLen=resHdrOld.get(‘content-length’)||’’

constbadLen=(rawLen!==newLen)

if(badLen){

returnmakeRes(res.body,400,{

‘—error’:badlen:${newLen},except:${rawLen},

‘access-control-expose-headers’:’—error’,

})

}

}

conststatus=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’)

returnnewResponse(res.body,{

status,

headers:resHdrNew

})

}
```

weiyigeek.top-Workers反向代理脚本图

  • Step 5.点击右上角的部署即可完成部署,然后回到 hub 项目页,点击设置,点击触发器,再点击添加路由,输入hub.weiyigeek.eu.org/路由(注意末尾的/是不可缺少的),区域选择前面添加的weiyigeek.eu.org即可。

weiyigeek.top-为workers添加指定路由图

  • Step 6.回到cloudflare主页,点击前面添加的weiyigeek.eu.org域名,点击设置,点击DNS,点击添加A记录,设置名称为hub,地址为8.8.8.8(随意填),但是一定要启用代理哟。

weiyigeek.top-添加DNS解析记录图

  • Step 7.配置完成后,在我们的Linux服务器上,执行下述命令验证站点是否正常,以及是否可以正常拉取nginx:latest镜像。

验证

拉取

  • dockerpullhub.weiyigeek.eu.org/library/nginx:latest

查看

  • dockerimages|grep”eu.org”
  • weiyigeek.top-使用cf的workers服务拉取镜像图

至此,在Cloudflare 使用 Workers 服务,实现国内 Docker hub 镜像的拉取