Docker镜像代理

修改

  • 2024年6月9日:添加方案3

前言

近期,由于一些原因导致无法正常拉取docker镜像(或者拉取速度极慢),而且国内dockerhub加速镜像相继下架,如果设置服务器的代理会增加一层nat和产生额外配置修改。
这里使用代理方式,以小改动、影响继续使用官方dockerhub。

从docker自身上修改可以有两个方案
1. 使用其它镜像源
2. 使用代理
3. cloudflare worker建加速镜像源

都需要使用到daemon.json文件,docker守护进程(daemon)的配置文件,用于管理docker守护进程的参数。
文件目录为

/etc/docker/daemon.json

(没有文件、目录就自己创建)


1. 其它镜像源

mkdir -p /etc/docker;
tee /etc/docker/daemon.json <<- EOF
{
  "registry-mirrors": [
    "https://xxx.com",
    "https://abc.com"
    ]
}
EOF

配置后需要重启docker才能生效


2. 代理方式

mkdir -p /etc/docker;
tee /etc/docker/daemon.json <<- EOF
{
  "proxies": {
    "http-proxy": "http://proxy.example.com:3128",
    "https-proxy": "http://proxy.example.com:3128",
    "no-proxy": "localhost,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
  }
}
EOF
  • http-proxyhttps-proxy是代理IP:端口
    (还支持socks5)
  • no-proxy是不进行代理的ip、域名等

上面的172.16.0.0/12 表示从 172.16.0.0 到 172.31.255.255 的所有地址
可以改为自己docker只在使用的网络类型ip段,用以下命令查看:

docker network inspect [网络类型]

# docker network inspect bridge

配置后需要重启docker才能生效


3. cf worker加速镜像源

(域名dns解析托管在cf)

访问Cloudflare Workers构建Workers应用程序,命名,完成,编辑代码,复制下面代码替换:

'use strict';

const HUB_HOST = 'registry-1.docker.io';
const AUTH_URL = 'https://auth.docker.io';
const WORKERS_URL = 'https://你的域名';
const ASSET_URL = 'https://hunshcn.github.io/gh-proxy/';
const PREFIX = '/';
const Config = { jsdelivr: 0 };
const whiteList = [];

const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i;
const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob|raw)\/.*$/i;
const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i;
const exp4 = /^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+?\/.+$/i;
const exp5 = /^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i;
const exp6 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/tags.*$/i;

/** @type {RequestInit} */
const PREFLIGHT_INIT = {
    // @ts-ignore
    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',
    }),
};

/**
 * Create a new response.
 * @param {any} body
 * @param {number} [status=200]
 * @param {Object<string, string>} headers
 * @returns {Response}
 */
function makeResponse(body, status = 200, headers = {}) {
    headers['access-control-allow-origin'] = '*';
    return new Response(body, { status, headers });
}

/**
 * Create a new URL object.
 * @param {string} urlStr
 * @returns {URL|null}
 */
function createURL(urlStr) {
    try {
        return new URL(urlStr);
    } catch (err) {
        return null;
    }
}

addEventListener('fetch', (event) => {
    event.respondWith(handleFetchEvent(event).catch(err => makeResponse(`cfworker error:\n${err.stack}`, 502)));
});

/**
 * Handle the fetch event.
 * @param {FetchEvent} event
 * @returns {Promise<Response>}
 */
async function handleFetchEvent(event) {
    const req = event.request;
    let url = new URL(req.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.startsWith('/token') || url.pathname.startsWith('/v2')) {
        return handleDockerProxy(req, url);
    }

    if (url.pathname.startsWith(PREFIX)) {
        return handleGitHubProxy(req, url);
    }

    return makeResponse('Not Found', 404);
}

/**
 * Handle token requests and Docker proxy.
 * @param {Request} req
 * @param {URL} url
 * @returns {Promise<Response>}
 */
async function handleDockerProxy(req, url) {
    if (url.pathname === '/token') {
        const tokenURL = AUTH_URL + url.pathname + url.search;
        const headers = new Headers({
            'Host': 'auth.docker.io',
            'User-Agent': req.headers.get('User-Agent'),
            'Accept': req.headers.get('Accept'),
            'Accept-Language': req.headers.get('Accept-Language'),
            'Accept-Encoding': req.headers.get('Accept-Encoding'),
            'Connection': 'keep-alive',
            'Cache-Control': 'max-age=0'
        });
        return fetch(new Request(tokenURL, req), { headers });
    }

    // 修改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;
    const headers = new Headers({
        'Host': HUB_HOST,
        'User-Agent': req.headers.get('User-Agent'),
        'Accept': req.headers.get('Accept'),
        'Accept-Language': req.headers.get('Accept-Language'),
        'Accept-Encoding': req.headers.get('Accept-Encoding'),
        'Connection': 'keep-alive',
        'Cache-Control': 'max-age=0'
    });

    if (req.headers.has('Authorization')) {
        headers.set('Authorization', req.headers.get('Authorization'));
    }

    const response = await fetch(new Request(url, req), { headers });
    const responseHeaders = new Headers(response.headers);
    const status = response.status;

    if (responseHeaders.get('Www-Authenticate')) {
        const authHeader = responseHeaders.get('Www-Authenticate');
        const re = new RegExp(AUTH_URL, 'g');
        responseHeaders.set('Www-Authenticate', authHeader.replace(re, WORKERS_URL));
    }

    if (responseHeaders.get('Location')) {
        return handleHttpRedirect(req, responseHeaders.get('Location'));
    }

    responseHeaders.set('access-control-expose-headers', '*');
    responseHeaders.set('access-control-allow-origin', '*');
    responseHeaders.set('Cache-Control', 'max-age=1500');
    responseHeaders.delete('Content-Security-Policy');
    responseHeaders.delete('Content-Security-Policy-Report-Only');
    responseHeaders.delete('Clear-Site-Data');

    return new Response(response.body, { status, headers: responseHeaders });
}

/**
 * Handle GitHub proxy requests.
 * @param {Request} req
 * @param {URL} url
 * @returns {Promise<Response>}
 */
async function handleGitHubProxy(req, url) {
    let path = url.searchParams.get('q');
    if (path) {
        return Response.redirect('https://' + url.host + PREFIX + path, 301);
    }
    path = url.href.substr(url.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://');
    if (checkUrl(path)) {
        return httpHandler(req, path);
    } else if (path.search(exp2) === 0) {
        if (Config.jsdelivr) {
            const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh');
            return Response.redirect(newUrl, 302);
        } else {
            path = path.replace('/blob/', '/raw/');
            return httpHandler(req, path);
        }
    } else if (path.search(exp4) === 0) {
        const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com/, 'https://cdn.jsdelivr.net/gh');
        return Response.redirect(newUrl, 302);
    } else {
        return fetch(ASSET_URL + path);
    }
}

/**
 * Check if the URL matches GitHub patterns.
 * @param {string} url
 * @returns {boolean}
 */
function checkUrl(url) {
    return [exp1, exp2, exp3, exp4, exp5, exp6].some(exp => url.search(exp) === 0);
}

/**
 * Handle HTTP redirects.
 * @param {Request} req
 * @param {string} location
 * @returns {Promise<Response>}
 */
async function handleHttpRedirect(req, location) {
    const url = createURL(location);
    if (!url) {
        return makeResponse('Invalid URL', 400);
    }
    return proxyRequest(url, req);
}

/**
 * Handle HTTP requests.
 * @param {Request} req
 * @param {string} pathname
 * @returns {Promise<Response>}
 */
async function httpHandler(req, pathname) {
    if (req.method === 'OPTIONS' && req.headers.has('access-control-request-headers')) {
        return new Response(null, PREFLIGHT_INIT);
    }

    const headers = new Headers(req.headers);
    let flag = !whiteList.length;
    for (const i of whiteList) {
        if (pathname.includes(i)) {
            flag = true;
            break;
        }
    }
    if (!flag) {
        return new Response('blocked', { status: 403 });
    }

    if (pathname.search(/^https?:\/\//) !== 0) {
        pathname = 'https://' + pathname;
    }

    const url = createURL(pathname);
    return proxyRequest(url, { method: req.method, headers, body: req.body });
}

/**
 * Proxy a request.
 * @param {URL} url
 * @param {RequestInit} reqInit
 * @returns {Promise<Response>}
 */
async function proxyRequest(url, reqInit) {
    const response = await fetch(url.href, reqInit);
    const responseHeaders = new Headers(response.headers);

    if (responseHeaders.has('location')) {
        const location = responseHeaders.get('location');
        if (checkUrl(location)) {
            responseHeaders.set('location', PREFIX + location);
        } else {
            reqInit.redirect = 'follow';
            return proxyRequest(createURL(location), reqInit);
        }
    }

    responseHeaders.set('access-control-expose-headers', '*');
    responseHeaders.set('access-control-allow-origin', '*');
    responseHeaders.delete('content-security-policy');
    responseHeaders.delete('content-security-policy-report-only');
    responseHeaders.delete('clear-site-data');

    return new Response(response.body, {
        status: response.status,
        headers: responseHeaders,
    });
}

(此为融合docker pull加速和github下载,来自用GPT融了一个Cloudflare Workers的github下载 + Docke pull加速 – 软件开发 – LINUX DO

  • WORKERS_URL修改为自己的域名

当前为二次修改,原版本无法直接拉取dockerhub官方镜像,需要加上域名/library/xx,改动处有

  • 65、66行,添加修改head前的get请求
  • 98行,添加修改head请求

点击右上角部署

点击该workers项目的设置触发器添加路由,填写:
– 路由:上面的域名(后面带有/*)

test.abc.com/*
  • 区域:选择对应域名

保存,完成

测试输入

$ docker pull test.abc.com/node
Using default tag: latest                                                   
latest: Pulling from node                                                   
c6cf28de8a06: Downloading [=================================>     ]  45.06MB/49.58MB
891494355808: Downloading [===================================>   ] 22.72MB/24.05MB
...

测试

输入以下命令查看docker守护进程配置和状态

docker info

可以看到刚配置完的mirrors、proxy

可以使用time统计拉取镜像所耗费的总时间(测速前先移除本地的镜像)

docker rmi node:latest
time docker pull node:latest

额外

注意重启、开机问题,例如UNRAID重启后会清空,所以需要在/boot/config/go启动脚本中配置命令,上面根据自己所使用的命令复制一份到该文件即可。


参考

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇