前言 {#前言}
在 2024年06月的某一天,国内已要求所有提供镜像站和镜像加速服务的机构停止其服务。
我们有必要自建一个Docker镜像服务,可参考下面的方案
如果你能挂梯,可以不用往下面看了!!!
方案一:自建Cloudflare Workers {#方案一-自建Cloudflare-Workers}
如果不知道怎么自建 Cloudflare Workers 的,可以点击查看文章:https://blog.fjy.zone/archives/server-api-chatgpt#cloudflare-workers
Workers 代码,记得更改 workers_url
为你自己的域名:
'use strict';
const hub_host = 'registry-1.docker.io';
const auth_url = 'https://auth.docker.io';
const workers_url = 'https://xxx.com';
/\*\*
* static files (404.html, sw.js, conf.js)
*/
/\*\* @type {RequestInit} /
const PREFLIGHT_INIT = {
// 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',
}),
};
/\*\*
* @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 });
}
/\*\*
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr);
} catch (err) {
return null;
}
}
addEventListener('fetch', e =\> {
const ret = fetchHandler(e)
.catch(err =\> makeRes('cfworker error:\\n' + err.stack, 502));
e.respondWith(ret);
});
/\*\*
*
@param {FetchEvent} e
\*/
async function fetchHandler(e) {
const getReqHeader = (key) =\> e.request.headers.get(key);
let url = new URL(e.request.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 === '/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, e.request), token_parameter);
}
// 修改 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;
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 (e.request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
let original_response = await fetch(new Request(url, e.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(e.request, new_response_headers.get("Location"));
}
let response = new Response(original_text, {
status,
headers: new_response_headers
});
return response;
}
/\*\*
*
@param {Request} req
*
@param {string} pathname
\*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers;
// preflight
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
* `
`@param {RequestInit} reqInit
*/
async function proxy(urlObj, reqInit, rawLen) {
const res = await fetch(urlObj.href, reqInit);
const resHdrOld = res.headers;
const resHdrNew = new Headers(resHdrOld);
`
`
// verify
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
});
}
`
使用如下:
# docker pull xxx.com/node
方案二:自建镜像加速服务 {#方案二-自建镜像加速服务}
提供哪些功能 {#提供哪些功能}
- ✨️ 一键部署Docker镜像代理服务的功能,支持基于官方Docker Registry的镜像代理.
- ✨️ 支持多个镜像仓库的代理,包括Docker Hub、GitHub Container Registry (ghcr.io)、Quay Container Registry (quay.io)和 Kubernetes Container Registry (k8s.gcr.io)
- ✨️ 自动检查并安装所需的依赖软件,如Docker、Nginx等,并确保系统环境满足运行要求.
- ✨️ 自动清理注册表上传目录中的那些不再被任何镜像或清单引用的文件
- ✨️ 提供了重启服务、更新服务、更新配置和卸载服务的功能,方便用户进行日常管理和维护
- ✨️ 支持主流Linux发行版操作系统,例如centos、Ubuntu、Rocky、Debian、Rhel等
- ✨️ 支持主流ARCH架构下部署,包括linux/amd64、linux/arm64
通过脚本部署 {#通过脚本部署}
# CentOS
yum -y install wget curl
# ubuntu debian
apt -y install wget curl
`bash -c "$(curl -fsSL https://raw.githubusercontent.com/dqzboy/Docker-Proxy/main/install/DockerProxy_Install.sh)"
`
部署到Render {#部署到Render}
Docker部署 {#Docker部署}
准备工作 {#准备工作}
- 服务器/VPS(国外的,需要能直接访问 Docker 服务)
- 服务器需要安装docker、docker-compose环境
- 宝塔、Caddy、Nginx、Nginx Proxy Manager 等反代服务(需要反代 Registry 容器服务)
- GitHub项目地址:https://github.com/dqzboy/Docker-Proxy
部署镜像仓库代理 {#部署镜像仓库代理}
创建账号密码(可选)
配置账号密码:我们在进行拉取镜像时需要先 docker login 登入到我们的自建的代理镜像仓库,然后在进行拉取镜像,防止别人滥用我们的服务
[root@debian ~]# mkdir -p /data/registry-proxy && cd $_
[root@debian registry-proxy]# mkdir auth
[root@debian registry-proxy]# docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > auth/htpasswd
快速部署 {#快速部署}
-
下载 config 目录下对应的 yml 文件到你本地机器上
-
下载 docker-compose.yaml 文件到你本地机器上,并且与配置文件同级目录下
-
执行
docker compose
命令启动容器服务docker compose up -d
查看容器日志
docker logs -f [容器ID或名称]
分步骤部署 {#分步骤部署}
- 添加
docker-compose.yml
文件
-
使用密码的话把注释取消
[root@debian ~]# mkdir -p /data/registry-proxy && cd $_ [root@debian registry-proxy]# vim docker-compose.yml services: docker-hub: container_name: reg-docker-hub image: registry:latest restart: always volumes: - ./registry/data:/var/lib/registry - ./registry-hub.yml:/etc/docker/registry/config.yml #- ./auth:/auth ports: - 51000:5000 networks: - registry-net ## UI registry-ui: container_name: registry-ui image: dqzboy/docker-registry-ui:latest environment: - DOCKER_REGISTRY_URL=http://reg-docker-hub:5000 # [必须]使用 openssl rand -hex 16 生成唯一值 - SECRET_KEY_BASE=9f18244a1e1179fa5aa4a06a335d01b2 # 启用Image TAG 的删除按钮 - ENABLE_DELETE_IMAGES=true - NO_SSL_VERIFICATION=true restart: always ports: - 50000:8080 networks: - registry-net
networks: registry-net:
- 添加config.yml文件
注意:每个容器挂载对应的 config.yml
,这里名称与上面 docker-compose.yml
文件定义的挂载的名称保持一致;下面只是其中一个示例配置,其他的配置也一样,只需要更改 remoteurl 代理的地址即可
[root@debian registry-proxy]# vim registry-hub.yml
version: 0.1
log:
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
cache:
blobdescriptor: inmemory
blobdescriptorsize: 10000
maintenance:
uploadpurging:
enabled: true
age: 168h
interval: 24h
dryrun: false
readonly:
enabled: false
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
Access-Control-Allow-Origin: ['*']
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
Access-Control-Allow-Headers: ['Authorization', 'Accept', 'Cache-Control']
Access-Control-Max-Age: [1728000]
Access-Control-Allow-Credentials: [true]
Access-Control-Expose-Headers: ['Docker-Content-Digest']
#auth:
# htpasswd:
# realm: basic-realm
# path: /auth/htpasswd
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
`proxy:
remoteurl: https://registry-1.docker.io
username:
password:
`
- 启动容器服务
[root@debian registry-proxy]# docker compose up -d
检查启动容器状态
========
`[root@debian registry-proxy]# docker ps
`
- 配置Nginx反代
示例配置如下,根据自己的实际情况修改
[root@debian ~]# cd /etc/nginx/conf.d/
[root@debian conf.d]# vim registry-proxy.conf
## registry-ui
server {
listen 80;
listen 443 ssl;
## 填写绑定证书的域名
server_name ui.your_domain_name;
## 证书文件名称(填写你证书存放的路径和名称)
ssl_certificate your_domain_name.crt;
## 私钥文件名称(填写你证书存放的路径和名称)
ssl_certificate_key your_domain_name.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
ssl_buffer_size 8k;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
location / {
proxy_pass http://localhost:50000;
proxy_set_header Host $host;
proxy_set_header Origin $scheme://$host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Ssl on; # Optional
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $host;
}
}
docker hub
----------
server {
listen 80;
listen 443 ssl;
## 填写绑定证书的域名
server_name hub.your_domain_name;
## 证书文件名称(填写你证书存放的路径和名称)
ssl_certificate your_domain_name.crt;
## 私钥文件名称(填写你证书存放的路径和名称)
ssl_certificate_key your_domain_name.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
ssl_buffer_size 8k;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
location / {
proxy_pass http://localhost:51000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Nginx-Proxy true;
proxy_buffering off;
proxy_redirect off;
}
}
重载Nginx配置
=========
`[root@debian ~]# nginx -t
[root@debian ~]# nginx -s reload
`
- 解析DNS并使用
- 将我们在 Nginx 配置的域名,在DNS服务商进行解析,解析到部署镜像代理仓库的服务器上;
- 通过访问UI地址可以查看镜像仓库缓存的镜像;
- 通过使用对应的代理域名来下载我们之前无法下载的镜像;
使用自建的 Registry 地址替换官方的 Registry 地址拉取镜像
## 源:nginx:latest
## 替换
docker pull hub.your_domain_name/library/nginx:latest
访问 UI 页面就可以看到下载的镜像已经被缓存了
- 修改docker配置
在 docker 的 daemon.json
配置文件中添加上 docker.io
的代理域名,然后重启docker程序
[root@debian ~]# vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://hub.your_domain_name"],
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
`#重启docker
systemctl restart docker
`
然后就可以随意拉取Docker镜像了。
如果想配置 k8s 等服务,只需根据快速部署中的文件,添加对应的配置即可。
参考文章:https://www.dqzboy.com/8709.html
视频链接 {#视频链接}
- Bilibili:https://bilibili.com/video/BV1LS411N7eb
- Youtube:https://youtu.be/Ee8T9p9wSZ0