前言

  • 最近国内的docker镜像站已经算是全军覆没了,具体什么原因也无从知晓,在拉取镜像的时候直接显示连接超时,为了能正常拉取镜像,最好的办法就是搭建一个自己专属的代理,这里我在GitHub上找到了一个非常不错的项目,可以完美解决镜像无法拉取的情况,而且还可以搭建出一个hub镜像站,方便搜索镜像名称。
    docker-pull-timeout

什么是Cloudflare Wokers

  • Cloudflare Workers 是 Cloudflare 提供的一个服务器less(无服务器)计算服务,它允许开发者在 Cloudflare 的全球边缘网络上运行 JavaScript、Rust 或其他 WASM(WebAssembly)支持的语言编写的代码。通过这种方式,你的代码能够在离用户最近的地理位置上运行,从而实现低延迟和高性能的用户体验。
    以下是 Cloudflare Workers 的一些主要特点:
  1. 无服务器计算:
    你不需要管理或维护服务器,而是只需要关注编写和部署代码。Cloudflare 会为你处理基础设施和扩展问题。
  2. 边缘计算:
    代码直接运行在 Cloudflare 的全球边缘网络上,而不是集中在某个地区的服务器上。这意味着你的代码可以在离用户最近的地方运行,实现低延迟和高速度。
  3. 多语言支持:
    虽然最初是为 JavaScript 设计的,但现在 Cloudflare Workers 也支持 Rust 和任何能编译成 WebAssembly 的语言。
  4. 简单的部署和管理:
    Cloudflare 提供了简单的命令行工具和管理界面,使得部署和管理你的 Workers 变得非常简单。
  5. 内置的键值存储:
    Cloudflare Workers 附带了一个名为 Workers KV 的内置键值存储解决方案,使得你可以在边缘网络上存储和检索数据。
  6. HTTP 路由和请求处理:
    你可以轻松地创建 HTTP 路由,处理 HTTP 请求和响应,以及修改传入和传出的 HTTP 流量。
  7. 安全性和隐私:
    Cloudflare Workers 运行在一个安全的沙箱环境中,以保护你的代码和数据。
  8. 集成和生态系统:
    Cloudflare Workers 可以与 Cloudflare 的其他产品和服务集成,如 Cloudflare Pages、Durable Objects 和 Cloudflare Access 等。
    Cloudflare Workers 为开发者提供了一个灵活、高性能、并且易于使用的边缘计算平台,使得你可以构建和部署全球分布式应用。

准备工作

部署方式

  • 打开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将会正常打开,这表示部署已经完成
    cf-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
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://请替换为您自己的Worker自定义域名"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
  • 接下来实际测试一下,按照刚刚的方法设置一下镜像加速,再次拉取之前超时的镜像,这次就很顺利的拉下来了
    docker-pull-ok