使用Cloudflare加速Docker镜像下载

前言

最近国内有些DockerDocker镜像源下架了,例如上海交通大学开源镜像站

有点儿害怕后面下载不了镜像了,刚好1Panel社区就有帖子说明如何自建Docker镜像加速了(还是免费的哟),所以想自建一个镜像加速以自用。

准备

  1. 一个域名:本文演示的域名通过腾讯云购买
  2. Cloudflare账号

开始

Cloudflare

Cloudflare官网注册账号。

点击添加站点,按照步骤将自己的网站添加进去。

  1. 域:添加自己的域名。

  2. 计划:选择最后的Free即可。

  3. DNS:等Cloudflare扫描一下就好啦。

  4. 激活:需要到域名注册机构修改一下DNS服务,下一节配置

    您的已分配的 Cloudflare 名称服务器:

    1
    gabriella.ns.cloudflare.com
    1
    vern.ns.cloudflare.com
  5. 检查:点击继续前往xxxx.xxx概览就能到网站控制台了。

image-20240613164132812

依次走完这5步就添加网站成功了,还需要激活DNS代理,

域名

这里演示腾讯云域名如何配置Cloudflare的DNS服务器。

在腾讯云控制台找到域名注册->我的域名以进入域名控制台,点击更多->修改 DNS 服务器->自定义DNS,填上上一节第四步分配的 Cloudflare 名称服务器。

您的已分配的 Cloudflare 名称服务器:

1
gabriella.ns.cloudflare.com
1
vern.ns.cloudflare.com

image-20240613165908095

接下来就是等待Cloudflare发送域名激活成功的邮件。

Cloudflare Workers

最关键的部分来了,到cloudflare控制台Workers 和 Pages页面,点击创建->创建 Worker,为项目命名后点击保存完成,然后点击编辑代码,写入以下内容(一定要将“你的域名”替换掉!):

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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
'use strict'

const hub_host = 'registry-1.docker.io'
const auth_url = 'https://auth.docker.io'
const workers_url = 'https://你的域名'
/**
* static files (404.html, sw.js, conf.js)
*/

/** @type {RequestInit} */
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',
}),
}

/**
* @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 })
}


/**
* @param {string} urlStr
*/
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)
})


/**
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const getReqHeader = (key) => e.request.headers.get(key);

let url = new URL(e.request.url);

// 修改 pre head get 请求
// 是否含有 %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}`)
}

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)
}

// 修改 head 请求
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;

}


/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers

// preflight
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
* @param {RequestInit} reqInit
*/
async function proxy(urlObj, reqInit, rawLen) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)

// verify
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就创建成功了,现在需要绑定域名使用。

返回Workers 和 Pages -> 你的项目,点击设置->触发器->添加自定义域,可以添加一个二级域名,Cloudflare会自动生成DNS解析

image-20240613170852571

测试

到现在就可以测试是否可以使用了,执行以下代码测试是否能够下载镜像(一定要将“你的域名”替换掉!):

1
docker pull 你的域名/hello-world

拉取成功就说明配置成功啦!

其他

1Panel

根据1Panel文档-功能手册-容器-配置,把自己的域名配置上去就可以了,访问速度很快。

参考

使用Cloudflare Workers自建docker镜像加速

给网站套上Cloudflare(以腾讯云为例)