作为一名程序员,Docker几乎已经成为我们日常开发中不可或缺的一部分。但是最近,国内的一些Docker商业/教育镜像站突然被关闭,导致很多开发者在拉取镜像时速度非常慢,甚至无法拉取。这可真是让人头疼!😭
别担心,本文将手把手教你使用CloudFlare Workers搭建一个免费的Docker镜像加速服务,轻松解决镜像拉取速度慢的问题!🙌
镜像加速的重要性 在使用Docker部署应用时,镜像拉取的速度直接影响到开发和上线效率。如果每次构建镜像都要等半天,那工作效率可想而知有多低。而且在生产环境下,快速拉取镜像也是服务高可用的保证。
所以说,镜像加速绝对是一个刚需!🔥
常见的加速方案 为了解决拉取速度慢的问题,一般有几种常见的方案:
使用国内镜像源,如阿里云、网易等提供的免费镜像加速服务。
搭建私有Registry,在内网环境下使用。
利用CDN等加速服务对镜像仓库进行加速。
其中前两种方案都有一定的局限性,而且在当前的大环境下,公共镜像源的稳定性也不容乐观。😕
CloudFlare Workers方案 相比之下,利用CloudFlare Workers搭建私有加速服务是一个不错的选择。它有以下几个优点:
CloudFlare拥有全球化的CDN网络,加速效果非常好。
Workers提供了免费的计算资源,搭建成本低。
配置简单,只需要几行代码就可以实现。
安全可靠,不用担心镜像源跑路。
下面我就来介绍具体的操作步骤。
搭建步骤 注册一个CloudFlare账号,并登录Workers管理界面。
点击创建一个新的Worker。
将以下代码复制到代码编辑区:
注意 : 这里需要先把代码粘贴保存之后,才能绑定自定义域名,后面再来修改这里的域名!
'use strict' const hub_host = 'registry-1.docker.io' const auth_url = 'https://auth.docker.io' const workers_url = 'https://自己的域名' const PREFLIGHT_INIT = { 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); console .log (`handle_url: $ { url }` ) } 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/' ); 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 }; 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 = original_response_clone.body ; 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) const refer = reqHdrNew.get ('referer' ) 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 }) }
绑定一个自定义域名到该Worker。
配置Docker守护进程,将registry-mirrors
参数设置为你的域名。
{ "registry-mirrors" : [ "https://你自己的域名" ] }
搞定!现在当你拉取镜像时,就会自动通过你的加速服务进行获取,速度提升几倍不止!🚀
注意事项
免费版Workers对出口流量有一定限制,如果使用量大建议升级到付费版。
不同版本的Docker对配置文件的路径有所不同,需要根据实际情况进行修改。
在生产环境中,还要注意Worker脚本的安全性,以免被恶意利用。
参考 V2EX(https://global.v2ex.com/t/1007922 )
小结 Docker镜像加速作为容器化开发中的一个重要环节,对提升工作效率有很大帮助。利用CloudFlare Workers提供的边缘计算能力,我们可以低成本地搭建一套高性能的镜像加速方案,解决国内网络环境下拉取速度慢的问题。 希望通过本文的介绍,能为大家在日常开发中提供一些思路。Docker镜像加速的方案其实有很多,关键是要根据自己的实际情况,选择最合适的那一种。 你是否也在使用Docker呢?在镜像拉取方面有什么问题和经验,欢迎留言交流!😉