最近自己的docker总是抽风出现各种各样的问题,因为事情过于忙碌来不及处理,一些安装的项目秉持能用则用的心态便拖到如今。昨天升级自己的docker项目出现镜像错误问题,自己的镜像都是到处去网上找的,刚开始能用就没换掉,在google了一堆解决方法之后均无果,于是虚心请教杜老师,杜老师很爽快的给出了他的一篇教程,其实我看过这篇教程,但是因为自己下意识的觉得代理这一块对于我比较陌生,所以就没在意,今天按照教程做了之后立马成功,实在感谢杜老师。因为自己在操作的过程中还是有些微不同,所以还是选择复盘一下,这样也才能加深自己的印象,真正选择实操可以选择去看杜老师的这篇借助 CF 解决 Docker 镜像拉取问题


bug再现

  首先需要自己注册一个域名,其实我最开始拥有好几个域名,但是自己管理问题导致只剩下一个重要域名,注册新域名的时候竟然发现自己wangyunzi.xyz域名处于待售状态,说实话我都不知道这个域名是在哪一个平台上购买的了,我只记得好久没用了,应该没什么大问题,这次为了谨慎起见也是为了方便管理和续费,选择统一在阿里云上注册一个域名,希望大家都能统一注册管理自己的域名吧。

  将域名的DNS改成cloudfare,因为我的主域名也是在cloudfare,这样比较方便管理。点击进入新注册的域名,接着点击左侧的workers路由,继续点击管理workers,最后创建。

  创建后修改名称之后直接点击部署,之后复制需要的代码进去覆盖掉,然后记得修改第三行的域名成为自己最开始注册的域名。

let hub_host = 'registry-1.docker.io'
const auth_url = 'https://auth.docker.io'
let workers_url = 'https://docker.birdteam.net'
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 ];
}
const PREFLIGHT_INIT = {
	headers: new Headers({
		'access-control-allow-origin': '*',
		'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
		'access-control-max-age': '1728000',
	}),
}
function makeRes(body, status = 200, headers = {}) {
	headers['access-control-allow-origin'] = '*'
	return new Response(body, { status, headers })
}
function newUrl(urlStr) {
	try {
		return new URL(urlStr)
	} catch (err) {
		return null
	}
}
function isUUID(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;
	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);
		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){
			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'){
					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);
			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);
		}
		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.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)
		}
		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 (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;
		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;
	}
};
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)
	const reqInit = {
		method: req.method,
		headers: reqHdrNew,
		redirect: 'follow',
		body: req.body
	}
	return proxy(urlObj, reqInit, 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中,打开根目录/etc/docker/daemon.json文件,修改成下面:

{
    "registry-mirrors": [
        "https://wangyunzi.ren"
    ]
}

  至此,docker pull圆满成功,再次感谢杜老师的教程!