【转载】利用Cloudflare自建Docker镜像加速

Tag: dockes://blog.ydxian.xyz/archives/cf-docker

大家好,我是羊刀仙。

Docker镜像拉取一直是个比较让人头疼的事情,尤其是不久之前那档子公告发布后~

本期内容为通过 Cloudflare Workers 搭建一个免费的Docker镜像加速服务。一个Cloudflare账户和一个域名即可搞定

后面我也会再发一篇同样十分稳定的真付费方式,大家可以关注下。

CloudFlare拥有全球化的CDN网络,加速效果很不错;Workers提供了免费的计算资源,几乎无成本;配置简单,全程配置仅需动手;安全可靠,不用担心镜像源跑路。

关于Cloudflare账户注册不赘述,像申请QQ账号一样简单。

首先要转移域名DNS服务,让Cloudflare接管,相关操作可以看之前文章,但为了大家阅读方便这里再重复一遍~这里以腾讯云为例。

打开Cloudflare主页击点添加站点

进入下图页面填写腾讯云购买的域名,点击继续

跳转页面拉至底部,选择Free套餐,无需绑卡,再次点击继续

如下图所示,这是我之前VPS服务器(已到期)的解析记录,现在没啥用了可以全都删掉。如果各位的还有用,可以进行保留~空白域名的各位就更不要管了,直接扔一边,咱们还是点击继续

接下来的界面,便可以看到Cloudflare分配给我们的DNS服务器地址

将这俩复制,回到腾讯云的控制台,找到对应的域名,修改DNS服务器

粘贴进去后,进行提交即可!

剩下的就是回到Cloudflare等,等到你的网站项目如下图这样(自己刷新页面),右下角的服务也可以顺便启用一下

返回进入Cloudflare主界面,左侧栏点击Workers 和 Pages,右侧点击创建Workers

命名后直接点击部署:

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

可以看到worker.js文件,将下面这一大串复制粘贴进去,并点击右上角的部署:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
'use strict';
const hub_host = 'registry-1.docker.io';
const auth_url = 'https://auth.docker.io';
const workers_url = 'https://你的域名';

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测试,可以顺手把设置为默认勾选上

也不晓得啥镜像比较大,全靠手速截图一张,速度还是很不错的

通过Docker Compose部署也非常简单只需改动一个位置:

如下图所示,其他全部保持原样子,不过如果一个应用包含多个容器记得每个image都要改掉

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

感谢观看,本文完。