由于众所周知的原因,Docker似乎在天朝被干掉了,主要太方便了,大家各种高科技墙翻来翻去,估计上面不能忍,出手了,苦了我们码农,这东西现在已经严重依赖了,突然取消,非常麻烦,今天抽空调查了下,似乎有好多方法,一一备注一下。
方法1,第一个想到的就是代理:
结果似乎代理要改好几个地方,run 和 pull利用代理方式都不一样。
详细在 https://neucrack.com/p/286 有提到,复杂…麻烦,可能直接在路由上做翻墙简单高效。
方法2,对于实在没办法路由翻墙的,直接使用Cloudflare的worker功能
前些日子由于OpenAI API接口国内被强,一个推荐的方案是直接使用worker来实现转发,同样的原理,搜寻了下,发现已经有人实现了,比如(https://github.com/ImSingee/hammal)。对于国内的机器,只要设定下镜像源就搞定了。而且速度相当不错,完全满足自己使用。
直接上 worker.js代码
addEventListener("fetch", (event) => {
event.passThroughOnException();
event.respondWith(handleRequest(event.request));
});
const dockerHub = "https://registry-1.docker.io";
const HTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Docker 镜像代理</title>
</head>
<body>
<div class="header">
<h1>Docker 镜像代理</h1>
</div>
</body>
</html>
`
const routes = {
// 替换为你的域名
"你的域名": dockerHub,
};
function routeByHosts(host) {
if (host in routes) {
return routes[host];
}
return "";
}
async function handleRequest(request) {
const url = new URL(request.url);
if (url.pathname == "/") {
return handleHomeRequest(url.host);
}
const upstream = routeByHosts(url.hostname);
if (!upstream) {
return createNotFoundResponse(routes);
}
const isDockerHub = upstream == dockerHub;
const authorization = request.headers.get("Authorization");
if (url.pathname == "/v2/") {
return handleFirstRequest(upstream, authorization, url.hostname);
}
// get token
if (url.pathname == "/v2/auth") {
return handleAuthRequest(upstream, url, isDockerHub, authorization);
}
// redirect for DockerHub library images
// Example: /v2/busybox/manifests/latest => /v2/library/busybox/manifests/latest
if (isDockerHub) {
const pathParts = url.pathname.split("/");
if (pathParts.length == 5) {
pathParts.splice(2, 0, "library");
const redirectUrl = new URL(url);
redirectUrl.pathname = pathParts.join("/");
return Response.redirect(redirectUrl.toString(), 301);
}
}
return handlePullRequest(upstream, request);
}
function parseAuthenticate(authenticateStr) {
// sample: Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io"
// match strings after =" and before "
const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g;
const matches = authenticateStr.match(re);
if (matches == null || matches.length < 2) {
throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`);
}
return {
realm: matches[0],
service: matches[1],
};
}
async function fetchToken(wwwAuthenticate, scope, authorization) {
const url = new URL(wwwAuthenticate.realm);
if (wwwAuthenticate.service.length) {
url.searchParams.set("service", wwwAuthenticate.service);
}
if (scope) {
url.searchParams.set("scope", scope);
}
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization);
}
return await fetch(url, { method: "GET", headers: headers });
}
function handleHomeRequest(host) {
return new Response(HTML.replace(/{:host}/g, host), {
status: 200,
headers: {
"content-type": "text/html",
}
})
}
async function handlePullRequest(upstream, request) {
const url = new URL(request.url);
const newUrl = new URL(upstream + url.pathname);
const newReq = new Request(newUrl, {
method: request.method,
headers: request.headers,
redirect: "follow",
});
return await fetch(newReq);
}
async function handleFirstRequest(upstream, authorization, hostname) {
const newUrl = new URL(upstream + "/v2/");
const headers = new Headers();
if (authorization) {
headers.set("Authorization", authorization);
}
// check if need to authenticate
const resp = await fetch(newUrl.toString(), {
method: "GET",
headers: headers,
redirect: "follow",
});
if (resp.status === 401) {
headers.set(
"Www-Authenticate",
`Bearer realm="https://${hostname}/v2/auth",service="cloudflare-docker-proxy"`
);
return new Response(JSON.stringify({ message: "Unauthorized" }), {
status: 401,
headers: headers,
});
} else {
return resp;
}
}
async function handleAuthRequest(upstream, url, isDockerHub, authorization) {
const newUrl = new URL(upstream + "/v2/");
const resp = await fetch(newUrl.toString(), {
method: "GET",
redirect: "follow",
});
if (resp.status !== 401) {
return resp;
}
const authenticateStr = resp.headers.get("WWW-Authenticate");
if (authenticateStr === null) {
return resp;
}
const wwwAuthenticate = parseAuthenticate(authenticateStr);
let scope = url.searchParams.get("scope");
// autocomplete repo part into scope for DockerHub library images
// Example: repository:busybox:pull => repository:library/busybox:pull
if (scope && isDockerHub) {
let scopeParts = scope.split(":");
if (scopeParts.length == 3 && !scopeParts[1].includes("/")) {
scopeParts[1] = "library/" + scopeParts[1];
scope = scopeParts.join(":");
}
}
return await fetchToken(wwwAuthenticate, scope, authorization);
}
const createNotFoundResponse = (routes) => new Response(
JSON.stringify({ routes }),
{
status: 404,
headers: {
"Content-Type": "application/json",
},
}
);
关于使用:
# 拉取 redis 官方镜像(不带命名空间)
docker pull 域名/redis
# 拉取 rabbitmq 官方镜像
docker pull 域名/library/rabbitmq
# 拉取 postgresql 非官方镜像
docker pull 域名/bitnami/postgresql
当然直接加到镜像源也是可以的
# 添加镜像代理到 Docker 镜像源
sudo tee /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://域名"]
}
EOF