Cloudflare 部署 Docker 镜像站,加速国内镜像拉取

在国内使用 Docker 时,从官方镜像源拉取镜像常常面临速度慢甚至拉取失败的问题。通过在 Cloudflare 上部署 Docker 镜像站,我们可以有效解决这一困境,实现更快速、稳定的镜像拉取。本文将详细介绍如何利用 Cloudflare Workers 完成这一部署,关键要点如下:

  1. 准备工作:明确 Docker 官方镜像源和自定义代理镜像源地址。

  2. 代码实现:通过 Cloudflare Workers 的 JavaScript 代码实现镜像代理逻辑。

  3. 配置与测试:配置相关设置并进行镜像拉取测试。

一、准备工作

在开始部署前,我们需要确定两个关键的地址:

二、代码实现

以下是实现镜像代理的核心代码:

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
const DOCKER_REGISTRY = 'https://registry-1.docker.io'
const PROXY_REGISTRY = 'https://docker.waitli.top'
const HTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="https://voxsay.com/assets/img/favicons/favicon.ico">
<title>镜像代理使用说明</title>
<style>
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.header {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
padding: 20px 0;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.container {
max-width: 800px;
margin: 40px auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 10px;
}
.content {
margin-bottom: 20px;
}
.footer {
text-align: center;
padding: 20px 0;
background-color: #333;
color: #fff;
}
pre {
background-color: #272822;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
code {
font-family: 'Source Code Pro', monospace;
}
a {
font-weight: bold;
color: #ffffff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
@media (max-width: 600px) {
.container {
margin: 20px;
padding: 15px;
}
.header {
padding: 15px 0;
}
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="header">
<h1>Docker 镜像代理使用说明</h1>
</div>
<div class="container">
<div class="content">
<p>拉取镜像</p>
<pre><code># 拉取 redis 镜像(不带命名空间)
docker pull {:host}/redis

# 拉取 rabbitmq 镜像
docker pull {:host}/library/rabbitmq

# 拉取 postgresql 镜像
docker pull {:host}/bitnami/postgresql</code></pre><p>重命名镜像</p>
<pre><code># 重命名 redis 镜像
docker tag {:host}/library/redis redis

# 重命名 postgresql 镜像
docker tag {:host}/bitnami/postgresql bitnami/postgresql</code></pre><p>添加镜像源</p>
<pre><code># 添加镜像代理到 Docker 镜像源
sudo tee /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://{:host}"]
}
EOF</code></pre>
</div>
</div>
<div class="footer">
<p>©2024 <a href="https://www.waitli.top">www.waitli.top</a>. All rights reserved. Powered by <a href="https://cloudflare.com">Cloudflare</a>.</p>
</div>
</body>
</html>
`
addEventListener('fetch', (event) => {
event.passThroughOnException()
event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
const url = new URL(request.url)
const path = url.pathname
if (path === '/v2/') {
return challenge(DOCKER_REGISTRY, url.host)
} else if (path === '/auth/token') {
return getToken(url)
} else if (url.pathname === '/') {
return home(url.host);
}

const parts = path.split('/')
if (parts.length === 5) {
parts.splice(2, 0, 'library')
const newUrl = new URL(PROXY_REGISTRY)
newUrl.pathname = parts.join('/')
return Response.redirect(newUrl.toString(), 301)
}

return getData(DOCKER_REGISTRY, request)
}

async function challenge(upstream, host) {
const url = new URL(upstream + '/v2/')
const response = await fetch(url)
const responseBody = await response.text()
const headers = new Headers()
headers.set('WWW-Authenticate', `Bearer realm="https://${host}/auth/token",service="docker-proxy-worker"`)
return new Response(responseBody, {
status: response.status,
statusText: response.statusText,
headers
})
}

async function getToken(originUrl) {
let scope = processScope(originUrl)
const url = new URL('https://auth.docker.io/token')
url.searchParams.set('service','registry.docker.io')
url.searchParams.set('scope', scope)
const response = await fetch(url)
return response
}

async function getData(upstream, req) {
const originUrl = new URL(req.url)
const url = new URL(upstream + originUrl.pathname)
const request = new Request(url, {
method: req.method,
headers: req.headers,
redirect: 'follow'
})

const response = await fetch(request)
return response
}

function processScope(url) {
let scope = url.searchParams.get('scope')
let parts = scope.split(':')
if (parts.length === 3 &&!parts[1].includes('/')) {
parts[1] = 'library/' + parts[1]
scope = parts.join(':')
}
return scope
}

function home(host) {
return new Response(HTML.replace(/{:host}/g, host), {
status: 200,
headers: {
"Content-Type": "text/html",
}
})
}

这段代码主要做了以下几件事:

  1. 定义常量:包括 Docker 官方镜像源和自定义代理镜像源,以及用于展示使用说明的 HTML 模板。

  2. 请求处理:通过handleRequest函数根据不同的请求路径进行相应处理,如处理认证请求、重定向请求等。

  3. 镜像代理:在特定条件下,将请求重定向到自定义代理镜像源,实现镜像代理功能。

三、配置与测试

  1. 部署到 Cloudflare Workers:将上述代码部署到 Cloudflare Workers 中,按照 Cloudflare 的部署流程进行操作。

  2. 测试镜像拉取:在本地 Docker 环境中,按照以下步骤测试镜像拉取:

  • 拉取镜像
1
2
3
4
5
6
7
8
# 拉取 redis 镜像(不带命名空间)
docker pull https://docker.waitli.top/redis

# 拉取 rabbitmq 镜像
docker pull https://docker.waitli.top/library/rabbitmq

# 拉取 postgresql 镜像
docker pull https://docker.waitli.top/bitnami/postgresql
  • 重命名镜像
1
2
3
4
5
# 重命名 redis 镜像
docker tag https://docker.waitli.top/library/redis redis

# 重命名 postgresql 镜像
docker tag https://docker.waitli.top/bitnami/postgresql bitnami/postgresql
  • 添加镜像源
1
2
3
4
5
6
# 添加镜像代理到 Docker 镜像源
sudo tee /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://docker.waitli.top"]
}
EOF

通过以上步骤,你已经成功在 Cloudflare 上部署了 Docker 镜像站,国内用户可以通过这个代理镜像源更快速地拉取所需镜像。如果你在部署过程中遇到任何问题,欢迎在评论区留言交流。