Using Cloudflare Worker to accelerate Docker images
The request was rejected because it was considered high risk
目录7 个章节
Preface
Developers have it harder now 🥱. Nowadays, Docker mirrors (Docker镜像) within China are also all gone 🈚️. Without mirrors, using Docker 🦈 is extremely difficult, and proxies 🌍 are very slow.
The only remaining option now is to build your own mirror, which is a pain 😭.
There are open-source projects on GitHub that can help you quickly set up your own mirror registry, but it’s still somewhat troublesome. Fortunately, Cloudflare is still alive for now 😮💨.
This article documents the method of using Cloudflare Worker to set up a Docker mirror (Docker镜像) 📒.
About Workers
Cloudflare Workers is a serverless computing platform provided by Cloudflare that allows you to run code written in languages like JavaScript, Rust, C, C++, and COBOL on Cloudflare's global edge network. It is primarily used to build high-performance, highly scalable web applications and APIs. 👍
Cloudflare Workers is ideal for handling various network requests, such as API gateways, content customization, and dynamic rendering, and can also be used to modify or determine how network requests are responded to. It empowers developers to rapidly deploy applications and services globally.
Some key features:
- Edge Computing: Since code is executed directly on Cloudflare's edge nodes, it can significantly reduce data transmission time and improve response speed.
- Serverless Architecture: You don't need to manage any servers or instances; just focus on writing and deploying code. Cloudflare takes care of running and scaling the code.
- Pay-as-you-go Billing: Cloudflare Workers' billing model is based on the number of requests and execution time, rather than pre-allocated resources, meaning you pay only for what you use.
- Highly Integrated: It can be tightly integrated with other Cloudflare services (like KV storage, Durable Objects, etc.), facilitating data storage and state management.
- Security: Since Workers run in an isolated environment, they provide a higher level of security compared to traditional servers or containers.
Create a worker
Open the Cloudflare Worker and Pages page, create a worker
Give it a descriptive name, such as docker-proxy or something similar
Then click the【Deploy】button
Edit Code
Once deployment is complete✅, click the [Edit Code] button next to it.
Input the following JavaScript code 👨🏫
You need to replace workers_url with your own domain, for example https://docker.example.com.
// Docker image registry host address
let hub_host = 'registry-1.docker.io'
// Docker authentication server URL
const auth_url = 'https://auth.docker.io'
// Custom worker server URL
let workers_url = 'https://your-domain'
let blockCrawlerUA = ['netcraft'];
// Select upstream address based on host name function routeByHosts(host) { // Define routing table const routes = { // Production environment "quay": "quay.io", "gcr": "gcr.io", "k8s-gcr": "k8s.gcr.io", "k8s": "registry.k8s.io", "ghcr": "ghcr.io", "cloudsmith": "docker.cloudsmith.io",
// Test environment
"test": "registry-1.docker.io",
};
if (host in routes) return [ routes[host], false ];
else return [ hub_host, true ];
}
/** @type {RequestInit} / const PREFLIGHT_INIT = { // Preflight request configuration headers: new Headers({ 'access-control-allow-origin': '', // Allow all origins 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // Allowed HTTP methods 'access-control-max-age': '1728000', // Cache duration for preflight requests }), }
/**
- Construct response
- @param {any} body Response body
- @param {number} status Response status code
- @param {Object<string, string>} headers Response headers / function makeRes(body, status = 200, headers = {}) { headers['access-control-allow-origin'] = '' // Allow all origins return new Response(body, { status, headers }) // Return a newly constructed response }
/**
* Construct a new URL object
* @param {string} urlStr URL string
*/
function newUrl(urlStr) {
try {
return new URL(urlStr) // Attempt to create a new URL object
} catch (err) {
return null // Return null if creation fails
}
}
function isUUID(uuid) {
// Define a regular expression to match UUID format
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;
// Use a regular expression to test the UUID string 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 ;
```markdown
export default {
async fetch(request, env, ctx) {
const getReqHeader = (key) => request.headers.get(key); // Get request header
let url = new URL(request.url); // Parse the request URL
const userAgentHeader = request.headers.get('User-Agent');
const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
if (env.UA) blockBotUA = blockBotUA.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]; // Get the first part of the hostname
const checkHost = routeByHosts(hostTop);
hub_host = checkHost[0]; // Get the upstream address
const fakePage = checkHost[1];
console.log(`Domain head: ${hostTop}\nReverse proxy address: ${hub_host}\nFake homepage: ${fakePage}`);
const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);
if (blockCrawlerUA.some(fxxk => userAgent.includes(fxxk)) && blockCrawlerUA.length > 0){
//Redirect homepage to an nginx decoy page
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
}
Translation notes:
屏蔽爬虫UA→blockCrawlerUA(a variable storing user-agent strings to block)首页改成一个nginx伪装页→ "Redirect homepage to an nginx decoy page"
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); // Get the complete registry URL
// Copy the original request headers const headers = new Headers(request.headers);
// Ensure the Host header is replaced with 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);
}
// Modify requests containing %2F and %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})
}
// Handle token request 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) }
// Modify /v2/ request path
if (/^/v2/[^/]+/[^/]+/[^/]+$/.test(url.pathname) && !/^/v2/library/.test(url.pathname)) {
url.pathname = url.pathname.replace(//v2//, '/v2/library/');
console.log(modified_url: ${url.pathname})
}
// Modify the request's hostname url.hostname = hub_host;
// Construct request parameters
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 // Cache duration
};
// 添加Authorization头
if (request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
// Initiate request and process response
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));
}
// Handle redirect if (new_response_headers.get("Location")) { return httpHandler(request, new_response_headers.get("Location")) }
// Return the modified response
let response = new Response(original_text, {
status,
headers: new_response_headers
})
return response;
}
};
/**
- Handle HTTP Requests (Process)
- @param {Request} req The request object
- @param {string} pathname The request path */ function httpHandler(req, pathname) { const reqHdrRaw = req.headers
// Handle preflight request
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 = new URL(urlStr)
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
}
return proxy(urlObj, reqInit, rawLen)
/**
- Proxy request
- @param {URL} urlObj URL object
- @param {RequestInit} reqInit Request initialization object
- @param {string} rawLen Original length */ async function proxy(urlObj, reqInit, rawLen) { const res = await fetch(urlObj.href, reqInit) const resHdrOld = res.headers const resHdrNew = new Headers(resHdrOld)
// Verify length 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')
// Remove unnecessary headers 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, ','); // Replace spaces, double quotes, single quotes and newline characters with commas
//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 ;
}
OK👌, after entering the code, click [Deploy] again. Once the deployment is successful, you can open the docker🦈hub page.
Setting up the Domain
Didn't we just configure the domain in the code earlier❓
We need to continue with the configuration now.
Go back to the【Workers and Pages / docker-proxy】configuration page.
Click【Settings】-【Triggers】🥬
Under【Custom domains】, click to add, and enter the domain name, for example 🌰 docker.example.com
All done🤝. Cloudflare will automatically apply for an SSL certificate and handle everything else✔️
Configure Registry
Edit the /etc/docker/daemon.json file
{
"registry-mirrors": ["https://docker.example.com"],
"insecure-registries": ["docker.example.com"]
}
Restart Docker
sudo systemctl daemon-reload
sudo systemctl restart docker
Then, when you pull images, the speed will be fast like taking off 🛫✈️.
References
_EOF_
Using Cloudflare Worker to accelerate Docker images
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
使用python-slim镜像遇到无法使用PostgreSQL的问题
python-slim镜像用PostgreSQL?报错libpq.so.5缺失,谁在乎?换psycopg2-binary搞定,或者手动安装库,麻烦死了。slim镜像小,PostgreSQL却不行,缺库谁管?用预编译包省事点。
2024-08-20 · docker · 约 1 分钟
国内使用docker镜像加速器
国内镜像源 国内的镜像源有 docker官方中国区 https://registry.docker-cn.com 网易 http://hub-mirror.c.163.com ustc http://docker.mirrors.ustc.edu.cn 阿里云 http://<你的ID>.mirror.aliyuncs.com 注意 注意registry-mirrors千万不要用https,而是用
2021-12-18 · docker · 约 1 分钟
在python项目的docker镜像里使用pdm管理依赖
PDM在Docker里替代pip?改Dockerfile调compose,禁用虚拟环境设路径,uwsgi要gcc镜像slim不行。PDM比poetry顺,勉强能用。
2024-08-11 · docker · 约 1 分钟
解决Centos下docker容器无法联网
vi /etc/sysctl.conf 找到net.ipv4.ip_forward 设置允许转发 # Controls IP packet forwarding net.ipv4.ip_forward = 1 #使之生效 sysctl -p #重启docker systemctl restart docker
2021-12-18 · docker · 约 1 分钟
讨论
共 0 条加载评论中…