前言
- 最近国内的docker镜像站已经算是全军覆没了,具体什么原因也无从知晓,在拉取镜像的时候直接显示连接超时,为了能正常拉取镜像,最好的办法就是搭建一个自己专属的代理,这里我在GitHub上找到了一个非常不错的项目,可以完美解决镜像无法拉取的情况,而且还可以搭建出一个hub镜像站,方便搜索镜像名称。
什么是Cloudflare Wokers
- Cloudflare Workers 是 Cloudflare 提供的一个服务器less(无服务器)计算服务,它允许开发者在 Cloudflare 的全球边缘网络上运行 JavaScript、Rust 或其他 WASM(WebAssembly)支持的语言编写的代码。通过这种方式,你的代码能够在离用户最近的地理位置上运行,从而实现低延迟和高性能的用户体验。
以下是 Cloudflare Workers 的一些主要特点:
- 无服务器计算:
你不需要管理或维护服务器,而是只需要关注编写和部署代码。Cloudflare 会为你处理基础设施和扩展问题。 - 边缘计算:
代码直接运行在 Cloudflare 的全球边缘网络上,而不是集中在某个地区的服务器上。这意味着你的代码可以在离用户最近的地方运行,实现低延迟和高速度。 - 多语言支持:
虽然最初是为 JavaScript 设计的,但现在 Cloudflare Workers 也支持 Rust 和任何能编译成 WebAssembly 的语言。 - 简单的部署和管理:
Cloudflare 提供了简单的命令行工具和管理界面,使得部署和管理你的 Workers 变得非常简单。 - 内置的键值存储:
Cloudflare Workers 附带了一个名为 Workers KV 的内置键值存储解决方案,使得你可以在边缘网络上存储和检索数据。 - HTTP 路由和请求处理:
你可以轻松地创建 HTTP 路由,处理 HTTP 请求和响应,以及修改传入和传出的 HTTP 流量。 - 安全性和隐私:
Cloudflare Workers 运行在一个安全的沙箱环境中,以保护你的代码和数据。 - 集成和生态系统:
Cloudflare Workers 可以与 Cloudflare 的其他产品和服务集成,如 Cloudflare Pages、Durable Objects 和 Cloudflare Access 等。
Cloudflare Workers 为开发者提供了一个灵活、高性能、并且易于使用的边缘计算平台,使得你可以构建和部署全球分布式应用。
准备工作
- 一个 Cloudflare 账号,没有的话直接注册一个就行,这里也把注册链接贴出来:https://dash.cloudflare.com/sign-up
- Cloudflare 账号下需要添加一个域名,推荐到腾讯云注册一个域名,首年只需几块钱,特别便宜,这里也把优惠链接贴出:https://cloud.tencent.com/act/pro/domain_sales?from=19501 注册好域名之后,打开Cloudflare的控制台,点击添加站点,输入新注册的域名,之后按照提示,打开腾讯云的控制台,点击新注册的域名,打开修改DNS服务器,把原来的地址全部删掉,输入刚刚Cloudflare给的两个地址即可,稍等一会,再次打开Cloudflare的控制台,就能看到域名已经显示活动了,这就添加成功了
部署方式
- 打开Cloudflare的控制台,点Workers 和 Pages,在点击创建-创建Worker,这里可以自定义想要的名称,也可以使用默认的,我就输了个docker-proxy,点击创建,然后直接点编辑代码
- 打开项目地址:https://github.com/cmliu/CF-Workers-docker.io/blob/main/_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
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342// _worker.js
// Docker镜像仓库主机地址
let hub_host = 'registry-1.docker.io'
// Docker认证服务器地址
const auth_url = 'https://auth.docker.io'
// 自定义的工作服务器地址
let workers_url = 'https://你的域名地址比如 docker.mydomain.com'
let 屏蔽爬虫UA = ['netcraft'];
// 根据主机名选择对应的上游地址
function routeByHosts(host) {
// 定义路由表
const routes = {
// 生产环境
"quay": "quay.io",
"gcr": "gcr.io",
"k8s-gcr": "k8s.gcr.io",
"k8s": "registry.k8s.io",
"ghcr": "ghcr.io",
"cloudsmith": "docker.cloudsmith.io",
// 测试环境
"test": "registry-1.docker.io",
};
if (host in routes) return [ routes[host], false ];
else return [ hub_host, true ];
}
/** @type {RequestInit} */
const PREFLIGHT_INIT = {
// 预检请求配置
headers: new Headers({
'access-control-allow-origin': '*', // 允许所有来源
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法
'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 }) // 返回新构造的响应
}
/**
* 构造新的URL对象
* @param {string} urlStr URL字符串
*/
function newUrl(urlStr) {
try {
return new URL(urlStr) // 尝试构造新的URL对象
} catch (err) {
return null // 构造失败返回null
}
}
function isUUID(uuid) {
// 定义一个正则表达式来匹配 UUID 格式
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
// 使用正则表达式测试 UUID 字符串
return uuidRegex.test(uuid);
}
async function nginx() {
const text = `
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
`
return text ;
}
export default {
async fetch(request, env, ctx) {
const getReqHeader = (key) => request.headers.get(key); // 获取请求头
let url = new URL(request.url); // 解析请求URL
const userAgentHeader = request.headers.get('User-Agent');
const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA));
workers_url = `https://${url.hostname}`;
const pathname = url.pathname;
const hostname = url.searchParams.get('hubhost') || url.hostname;
const hostTop = hostname.split('.')[0];// 获取主机名的第一部分
const checkHost = routeByHosts(hostTop);
hub_host = checkHost[0]; // 获取上游地址
const fakePage = checkHost[1];
console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`);
const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);
if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0){
//首页改成一个nginx伪装页
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
}
const conditions = [
isUuid,
pathname.includes('/_'),
pathname.includes('/r'),
pathname.includes('/v2/user'),
pathname.includes('/v2/orgs'),
pathname.includes('/v2/_catalog'),
pathname.includes('/v2/categories'),
pathname.includes('/v2/feature-flags'),
pathname.includes('search'),
pathname.includes('source'),
pathname === '/',
pathname === '/favicon.ico',
pathname === '/auth/profile',
];
if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) {
if (env.URL302){
return Response.redirect(env.URL302, 302);
} else if (env.URL){
if (env.URL.toLowerCase() == 'nginx'){
//首页改成一个nginx伪装页
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
} else return fetch(new Request(env.URL, request));
}
const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search);
// 复制原始请求的标头
const headers = new Headers(request.headers);
// 确保 Host 头部被替换为 hub.docker.com
headers.set('Host', 'registry.hub.docker.com');
const newRequest = new Request(newUrl, {
method: request.method,
headers: headers,
body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null,
redirect: 'follow'
});
return fetch(newRequest);
}
// 修改包含 %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}`)
}
// 处理token请求
if (url.pathname.includes('/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, request), token_parameter)
}
// 修改 /v2/ 请求路径
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 // 缓存时间
};
// 添加Authorization头
if (request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
// 发起请求并处理响应
let original_response = await fetch(new Request(url, 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;
// 修改 Www-Authenticate 头
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(request, new_response_headers.get("Location"))
}
// 返回修改后的响应
let response = new Response(original_text, {
status,
headers: new_response_headers
})
return response;
}
};
/**
* 处理HTTP请求
* @param {Request} req 请求对象
* @param {string} pathname 请求路径
*/
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)
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
}
return proxy(urlObj, reqInit, rawLen)
}
/**
* 代理请求
* @param {URL} urlObj URL对象
* @param {RequestInit} reqInit 请求初始化对象
* @param {string} 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
})
}
async function ADD(envadd) {
var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号
//console.log(addtext);
if (addtext.charAt(0) == ',') addtext = addtext.slice(1);
if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1);
const add = addtext.split(',');
//console.log(add);
return add ;
} - 直接把原来的代码全选删除,把上面的代码复制进去,按照注释修改自定义服务器地址之后点击部署,待显示版本已保存的时候,点击左上角的箭头,在点击设置-触发器,把刚刚的自定义服务器地址添加到自定义域中,在添加一个路由地址,按照这个格式填即可:docker.mydomain.com/*
- 稍等一会,在浏览器打开刚刚的地址试一下,不出意外的话,dockerhub将会正常打开,这表示部署已经完成
使用说明
例如您的Workers项目域名为:docker.fxxk.dedyn.io
;
1.官方镜像路径前面加域名
1 | docker pull docker.fxxk.dedyn.io/stilleshan/frpc:latest |
1 | docker pull docker.fxxk.dedyn.io/library/nginx:stable-alpine3.19-perl |
2.一键设置镜像加速
修改文件 /etc/docker/daemon.json
(如果不存在则创建)
1 | sudo mkdir -p /etc/docker |
- 接下来实际测试一下,按照刚刚的方法设置一下镜像加速,再次拉取之前超时的镜像,这次就很顺利的拉下来了
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ui_beam の Blog!
评论
TwikooLivere