2025-06-06 23:05:18
本文地址:blog.lucien.ink/archives/552
将下列代码保存为 install.sh
,然后 bash install.sh
。
#!/usr/bin/env bash
set -e
wget 'https://github.mirrors.lucien.ink/https://github.com/adnanh/webhook/releases/download/2.8.2/webhook-linux-amd64.tar.gz'
tar -xzvf webhook-linux-amd64.tar.gz
mv webhook-linux-amd64 /usr/local/
cat << EOF > /etc/systemd/system/webhook.service
[Unit]
Description=Webhook server
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/webhook-linux-amd64/webhook \
-nopanic \
-hooks /etc/webhook/hooks.yaml \
-hotreload \
-logfile /var/log/webhook/webhook.log \
-port 9000
Restart=on-failure
User=root
Group=root
[Install]
WantedBy=multi-user.target
EOF
systemctl enable webhook
mkdir -p /etc/webhook/scripts
cat << EOF > /etc/webhook/hooks.yaml
- id: test
execute-command: "/etc/webhook/scripts/test.sh"
command-working-directory: "/root/"
trigger-rule:
match:
type: value
value: Bearer change-this
parameter:
source: header
name: Authorization
EOF
cat << EOF > /etc/webhook/scripts/test.sh
#!/usr/bin/env sh
echo foo > bar
EOF
chmod +x /etc/webhook/scripts/test.sh
mkdir -p /var/log/webhook
systemctl start webhook
curl 'http://localhost:9000/hooks/test' -H 'Authorization: Bearer change-this'
2025-01-04 17:31:00
本文地址:blog.lucien.ink/archives/551
在这里记录一下我自己的 zsh 配置。
mkdir -p "${HOME}/.local"
cd "${HOME}/.local"
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git --depth=1 -b master
git clone https://github.com/zsh-users/zsh-autosuggestions.git --depth=1 -b master
git clone https://github.com/romkatv/powerlevel10k.git --depth=1 -b master
cat << EOF >> ~/.zshrc
source "${HOME}/.local/zsh-autosuggestions/zsh-autosuggestions.zsh"
source "${HOME}/.local/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
source "${HOME}/.local/powerlevel10k/powerlevel10k.zsh-theme"
EOF
2024-12-06 01:08:52
本文地址:blog.lucien.ink/archives/550
以 Qwen2 为例,本章节将介绍在配置好环境后,如何快速启动一个模型服务,并将简单介绍面向生产的模型服务应该怎样部署。
以 Qwen2.5-0.5B-Instruct 为例,因为它的尺寸很小,架构也和 Qwen2.5-72B-Instruct 一样。
在这里,我们首先将 Qwen2.5-0.5B-Instruct
模型下载至本地,而在此之前,还需要再做一些准备。
在大部分情况下我都推荐使用 git clone
的方式,而不是使用官方提供的 Toolkit,因为 git clone
出来的文件夹可以通过 git pull
来追踪模型的每一次更改,而不用重新下载。
而 git 擅长管理的是可以用纯文本来表示的文件,而对于模型权重、可执行程序、Word、Excel 等这类不太文本友好的文件来说,一般会用 git-lfs
来进行管理,在这里我们不深入了解 git-lfs
是什么,仅仅简单描述如何正确使用 git pull
来下载一个模型。
我们前往 Releases git-lfs 页面获取 git-lfs 的下载地址:
wget 'https://github.com/git-lfs/git-lfs/releases/download/v3.5.1/git-lfs-linux-amd64-v3.5.1.tar.gz'
tar -xzvf 'git-lfs-linux-amd64-v3.5.1.tar.gz' -C /usr/local/
bash /usr/local/git-lfs-3.5.1/install.sh
git lfs install
Qwen2 目前在两个模型平台上可以下载到,一个是 HuggingFace,另一个是 ModelScope,对于大陆来说 ModelScope 是更快的。
git clone https://modelscope.cn/models/qwen/Qwen2.5-0.5B-Instruct
在这里我们使用 从零开始实践大模型 - 配置环境 中创建的 python3
环境
pip install transformers
from transformers import AutoModelForCausalLM, AutoTokenizer
device = "cuda"
model_path = "Qwen2.5-0.5B-Instruct" # 本地模型的路径
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype="auto").to(device)
tokenizer = AutoTokenizer.from_pretrained(model_path)
prompt = "介绍一下大模型"
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
generated_ids = model.generate(
**model_inputs,
max_new_tokens=512
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
如果环境配置正确,能得到类似的输出:
$ python main.py
大模型是人工智能领域的一个重要概念。大模型是指那些能够处理和理解大量复杂数据、具有高度抽象能力和大规模特征表示的大规模预训练模型。这些模型通常使用大量的标注数据进行训练,并且可以学习到更复杂的模式和关系。
在实际应用中,大模型被广泛应用于自然语言处理、计算机视觉、语音识别、推荐系统等多个领域。例如,在自然语言处理中,大模型可以用于提高机器翻译的准确性和效率;在计算机视觉中,它们可以增强图像识别和目标检测的能力;在语音识别中,大模型可以帮助实现更加精准的人工智能对话体验。
此外,大模型还被用于生成内容的创作,如文字生成、音乐合成等。随着计算能力的提升和算法的进步,大模型在未来可能会成为推动技术进步的重要力量之一
上述代码实现了最基本的 Hello World!
,但是存在效率问题,主要体现在两点:
有许多优化方法能缓解这些问题,开源社区有很多推理加速的方案,比如:
还有一些面向易用性的方案,比如:
个人在这里最推荐的是 vLLM,稳定、易用、性能这几个方面的表现较为均衡,没有明显的短板。
在这里也小小的安利一下 SGLang,它的吞吐相比 vLLM 有优势,Benchmark 见:Achieving Faster Open-Source Llama3 Serving with SGLang Runtime (vs. TensorRT-LLM, vLLM)。
接下来我将基于上一篇博客配置的环境来讲一讲较为便捷的部署方式,以及一些面向生产部署的经验。
Deploying with docker - vLLM
基于 vLLM 的官方文档,不难写出 compose.yaml
:
services:
vllm:
image: vllm/vllm-openai:v0.6.4.post1
volumes:
- ${PWD}/Qwen2.5-0.5B-Instruct:/model:ro
ports:
- 8000:8000
restart: always
entrypoint: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
command: ["--host", "0.0.0.0", "--port", "8000", "--model", "/model", "--served-model-name", "qwen2.5-0.5b-instruct"]
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["0"]
capabilities: [gpu]
然后执行 docker compose up -d
,就能快速启动一个 vLLM
的服务,通过 curl
验证:
$ curl localhost:8000/v1/chat/completions \
-d '{"model":"qwen2.5-0.5b-instruct","messages":[{"role":"user","content":"介绍一下大模型"}]}'
-H 'Content-Type: application/json'
可以得到类似的输出:
{"id":"chatcmpl-f1114b6c0184443da2efb51758c77bfe","object":"chat.completion","created":1733416711,"model":"qwen2.5-0.5b-instruct","choices":[{"index":0,"message":{"role":"assistant","content":"大模型通常指的是在自然语言处理(NLP)、计算机视觉等领域中,具有大量参数的深度学习模型。这些模型通过在大规模数据集上进行训练,能够学习到丰富的表示能力,从而在各种任务上表现出色。大模型的概念逐渐成为人工智能研究和应用的一个重要方向。下面是一些关于大模型的关键点:\n\n1. **参数量大**:大模型通常包含数亿甚至上百亿的参数,这使得模型能够捕捉到数据中的复杂模式和关系。\n\n2. **预训练与微调**:大模型通常首先在大规模语料库上进行预训练,然后在特定任务的数据集上进行微调,以适应具体的应用场景。\n\n3. **零样本学习与少样本学习能力**:得益于其强大的泛化能力,大模型在没有或仅有少量标注数据的情况下,也能表现得很出色。\n\n4. **提升任务性能**:在多项自然语言处理和计算机视觉任务中,大模型往往能够提供比传统模型更好的性能。\n\n5. **挑战与限制**:虽然大模型在性能上有显著提升,但它们也带来了计算资源消耗大、训练时间长、模型解释性差等问题。\n\n6. **发展趋势**:随着技术的进步,研究者们正在探索更高效、更节能的训练方法,以及如何减少模型的环境影响。\n\n大模型的发展正在推动人工智能技术的进步,并在各个领域展现出巨大的潜力,但同时,如何平衡性能提升与资源消耗、如何提高模型的可解释性等问题也是当前研究的热点。","tool_calls":[]},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usage":{"prompt_tokens":32,"total_tokens":355,"completion_tokens":323,"prompt_tokens_details":null},"prompt_logprobs":null}
2024-06-17 14:45:00
本文地址:blog.lucien.ink/archives/549
本文将介绍在面向深度学习时,推荐的环境配置以及一些使用 Linux 的习惯。
本文的部分内容与 Debian 下 CUDA 生产环境配置笔记 有所重叠,但也有些许的不一样,在正文中不额外注明。
本文将主要分 4 部分:
在安装完系统并重启之后,首先看到的是一个登陆界面,在这里输入我们在安装阶段设定好的 root 用户及密码即可。请注意,在输入密码的时候,是看不见自己输了什么、输了几个字符的。
登陆进 root 之后,在这里我们先什么都不做,先配置 root 用户的 ssh 登陆权限。在大部分的教程中都会直接在 /etc/ssh/sshd_config
中添加一行 PermitRootLogin yes
,在这里笔者是及其不推荐的。
对于 root 用户来说,推荐的方式是密钥登陆,在本地用 ssh-keygen
生成一个公私钥对,将本地生成的 ~/.ssh/id_rsa.pub
拷贝至服务器的 ~/.ssh/authorized_keys
中(如果服务器中提示 ~/.ssh
不存在则执行 mkdir ~/.ssh
创建一个就好)。
在这里给出简单的命令:
mkdir -p ~/.ssh
echo 'content of your id_rsa.pub' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
有些小伙伴会遇到如何把本地的 ~/.ssh/id_rsa.pub
弄到服务器中的问题,在这里提供 3 个解决方案:
PermitRootLogin yes
,用 ssh 拷过去后再关掉~/.ssh
目录下用 python3 -m http.server 3000
起一个 HTTP 文件服务,然后去服务器上执行 wget
在这里使用 TUNA 的 Debian 软件源 作为 APT mirror:
cat << EOF > /etc/apt/sources.list
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware
EOF
apt update # 更新索引
apt install curl wget screen git -y # 常用软件
apt update
apt install linux-headers-`uname -r` build-essential # CUDA 驱动的依赖
这一步是必要的,因为 Nouveau 也是 NVIDIA GPU 的驱动程序,参考 nouveau - 维基百科。
cat << EOF > /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
options nouveau modeset=0
EOF
update-initramfs -u
reboot
前往 Official Drivers | NVIDIA 下载显卡驱动,请注意,CUDA Toolkit 不要选 Any
,否则会获得一个十分旧的驱动,会影响 nvidia docker (CUDA >= 11.6) 的安装。
对于大部分服务器来说,操作系统选 Linux 64-bit
,语言推荐选 English (US)
。CUDA Toolkit 笔者在这里选择 12.4
版本,得到的下载链接为:NVIDIA-Linux-x86_64-550.90.07.run,下载到服务器上即可。
在这里我额外测试了一下,对于 Linux 64-bit
来说,不论是消费卡(RTX 4090、RTX 3090),还是面向数据中心的卡(H100、A100、V100、P4),驱动是一模一样的。
wget 'https://us.download.nvidia.com/tesla/550.90.07/NVIDIA-Linux-x86_64-550.90.07.run'
chmod +x NVIDIA-Linux-x86_64-550.90.07.run
./NVIDIA-Linux-x86_64-550.90.07.run -s --no-questions --accept-license --disable-nouveau --no-drm
在这之后,执行 nvidia-smi -L
应该能看到如下内容:
$ nvidia-smi -L
GPU 0: Tesla P4 (UUID: GPU-***)
GPU 1: Tesla P4 (UUID: GPU-***)
nvidia-persistenced 常驻
默认情况下,nvidia-smi
执行起来会很慢,它的等待时长会随着显卡数量的增加而增加。这是因为常驻模式(Persistence Mode)没有打开,对于服务器来说,强烈建议打开这一选项。
可以通过添加一个 启动项来保持常驻模式打开:
cat <<EOF >> /etc/systemd/system/nvidia-persistenced.service
[Unit]
Description=NVIDIA Persistence Daemon
Before=docker.service
Wants=syslog.target
[Service]
Type=forking
ExecStart=/usr/bin/nvidia-persistenced
ExecStopPost=/bin/rm -rf /var/run/nvidia-persistenced
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start nvidia-persistenced
systemctl enable nvidia-persistenced
可以通过 nvidia-smi -q -i 0 | grep Persistence
来检查某张显卡该模式的状态。
如果读者使用的不是 SXM 的卡,请跳过这一步,如果不明白这里是在说什么,也可以先跳过
对于 H100 SXM
、A100 SXM
等拥有 NVSwitch
的整机来说,需要额外安装 nvidia-fabricmanager
来启用对 NVSwitch 的支持。
前往 Index of cuda 搜索关键词 nvidia-fabricmanager
找到对应版本进行下载。
wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nvidia-fabricmanager-550_550.90.07-1_amd64.deb
dpkg -i nvidia-fabricmanager-550_550.90.07-1_amd64.deb
systemctl start nvidia-fabricmanager.service
systemctl enable nvidia-fabricmanager.service
请注意,这里的 nvidia-fabricmanager
需要与 CUDA Driver 版本匹配。
通过执行 nvidia-smi -q -i 0 | grep -i -A 2 Fabric
来验证 nvidia-fabricmanager
是否安装成功,看到 Success
代表成功。(参考资料:fabric-manager-user-guide.pdf,第 11 页)
$ nvidia-smi -q -i 0 | grep -i -A 2 Fabric
Fabric
State : Completed
Status : Success
笔者曾经遇到过下载的 CUDA 驱动版本并未被 APT 中的 nvidia-fabricmanager
支持的情况,比如通过执行 apt-cache madison nvidia-fabricmanager-550
可以发现,nvidia-fabricmanager-550
只支持 550.90.07-1
、550.54.15-1
、550.54.14-1
三个版本,这种时候可通过执行 ./NVIDIA-Linux-x86_64-550.90.07.run --uninstall
来卸载 CUDA 驱动,然后重新下载支持的驱动版本。
$ apt-cache madison nvidia-fabricmanager-550
nvidia-fabricmanager-550 | 550.90.07-1 | https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64 Packages
nvidia-fabricmanager-550 | 550.54.15-1 | https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64 Packages
nvidia-fabricmanager-550 | 550.54.14-1 | https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64 Packages
Docker CE 软件仓库
export DOWNLOAD_URL="https://mirrors.tuna.tsinghua.edu.cn/docker-ce"
wget -O- https://get.docker.com/ | sh
Installing the NVIDIA Container Toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt update
apt install -y nvidia-container-toolkit
nvidia-ctk runtime configure --runtime=docker # 这一步会修改 /etc/docker/daemon.json
systemctl restart docker
测试:
如果网络不通的话,在镜像名前面添加hub.uuuadc.top
以使用代理:hub.uuuadc.top/nvidia/cuda:11.6.2-base-ubuntu20.04
docker run --rm --gpus all nvidia/cuda:11.6.2-base-ubuntu20.04 nvidia-smi
如果能看到 nvidia-smi
的内容,则代表安装成功了。
让普通用户使用 docker 有两种方案:
Rootless mode | Docker Docs
在这里假设我们要用的用户名为 foo。对于 rootless 而言,需要先用 root 做一些配置:
apt-get install uidmap -y # 依赖
loginctl enable-linger foo # 允许 docker 在 foo 用户退出登录后继续运行
nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place # 避免潜在的权限问题,经过实测这条不是必要的
随后切换到 foo 用户,执行以下命令:
dockerd-rootless-setuptool.sh install # 配置
echo "export DOCKER_HOST=unix:///run/user/${UID}/docker.sock" >> ~/.bashrc # 添加环境变量
nvidia-ctk runtime configure --runtime=docker --config=$HOME/.config/docker/daemon.json # 给予 CUDA 权限
systemctl --user restart docker
到此,foo 这个用户就能开始使用 docker 了。值得注意的是,rootless 模式下的镜像、容器都是独立的,即便是 root 用户也看不到 foo 究竟有哪些镜像与容器。
直接用 root 用户执行:
usermod -aG docker foo
然后重新登陆 foo 用户,就能开始使用 docker 了。值得注意的是,此时在 docker 内部创建的所有文件都归属于 root 用户。除非显式指定 docker 中的文件权限归属。
首先登陆 foo 用户,随后我们从 Miniconda 下载 Miniconda: Miniconda3 Linux 64-bit
wget 'https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh'
bash Miniconda3-latest-Linux-x86_64.sh -b -p ${HOME}/.local/miniconda3
${HOME}/.local/miniconda3/bin/conda init
Anaconda 镜像使用帮助
mv "${HOME}/.local/miniconda3/.condarc" "${HOME}/.local/miniconda3/.condarc.bak"
cat << EOF > ~/.condarc
channels:
- defaults
show_channel_urls: true
default_channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
EOF
重新登陆以后可以看到,已经有 conda 环境了。
PyPI 镜像使用帮助
pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip3 config set global.trusted-host pypi.tuna.tsinghua.edu.cn # 当使用 http 或自签证书时需要这个配置
在这里笔者也不推荐直接使用 base 环境,我们新建一个环境:
conda create -n python3 python=3.12
conda config --set auto_activate_base false # 默认不激活 base 环境
echo 'conda activate python3' >> ~/.bashrc # 默认激活 python3 环境
重新登录后可看到 python3 已经作为默认环境了。
我们简单下载一个 torch
来验证环境安装的正确性:
pip3 install torch numpy
python3 -c 'import torch; print(torch.tensor(0).cuda())'
在这里再多啰嗦几句,希望能让后辈们少走些弯路:
安装 cudnn
conda install conda-forge::cudnn
安装 nvcc
conda install nvidia::cuda-nvcc
安装 gcc
conda install conda-forge::gcc
2024-06-16 23:29:00
本文地址:blog.lucien.ink/archives/548
本章节将介绍在面向深度学习时,推荐安装的系统以及对应的安装选项。
目前主流操作系统有 Linux、macOS、Winodws,如果不考虑日常当作个人电脑来使用的话,强烈建议使用 无图形化界面 的 Linux,因为图形化界面会占用一定的显存(虽然也有不占用显存且同样拥有图形化的方法,这不在本文的讨论范围)。
接下来就是 Linux 发行版的选择,大部分企业(包括 NVIDIA 自己)会选择 Ubuntu,因为内置的东西多,笔者在这里不选择 Ubuntu,也是因为它内置的东西太多了,比如 snap 和 systemd-resolved。
基于笔者的实践经验,推荐使用 Debian 作为操作系统,因为它足够精简,而基于这般精简,也不会在后续使用上产生任何额外的复杂,且行为都足够可控,故在本文包括后续的一系列文章中,都会使用 Debian 作为演示操作系统。
在这里特意注明下 Debian 的下载地址,以免大家被百度的广告误导:Installing Debian via the Internet。
在这里 强烈不推荐 选择中文,除非你准备好应对各种因中文字符而产生的问题。
美国或中国都可以,这会影响到安装完成后的时区:
United States
然后进入系统后再更改。other
里找 Asia
然后 China
。一律选 en_US.UTF-8
,可以规避很多潜在的问题。
如果只是单台服务器的话,这里随便填就好。
如果打算组建集群,这里就直接起个 node-0 之类的遍于自己区分的名字就好。
推荐直接用一整块硬盘,不启用 LVM 和加密。
在这里选择国内的镜像,否则会很慢。
在这里只选择 SSH 和基础工具就好,没有特殊需求不建议勾选图形化界面(Debian desktop environment)。
2024-06-09 22:12:47
本文地址:blog.lucien.ink/archives/547
本文主要参考自:自建Docker 镜像/源加速的方法
最近 Docker Hub 被禁一事引起了不小的波动,在这里简单讲下在这之后应该如何访问公开的 Docker Hub。
搭建的前提是有一个在 Cloudflare 中被管理的域名,此处不展开介绍,在这里假设这个域名是 your-domain.com
。
点击页面左侧的 Workers & Pages
,创建一个 Worker,填入以下内容。请注意将 your-domain.com
替换为你自己的域名。
'use strict'
const hub_host = 'registry-1.docker.io'
const auth_url = 'https://auth.docker.io'
const workers_url = 'https://your-domain.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);
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)
}
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"))
}
return new Response(original_text, {
status,
headers: new_response_headers
})
}
/**
* @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, 0)
}
/**
*
* @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
})
}
进入创建好的 Worker 的配置页面,在 Settings
Tab 中选择 Triggers
,点击 Add Custom Domain
,添加 your-domain.com
。
在 /etc/docker/daemon.json
加入以下内容:
{
"registry-mirrors": [
"https://your-domain.com"
]
}
然后重启 docker:systemctl restart docker
随后就能像往常一样直接 pull
了:
docker pull busybox:latest
docker pull mysql/mysql-server:latest
docker pull your-domain.com/library/busybox:latest
docker pull your-domain.com/mysql/mysql-server:latest
首先你需要一个能正常访问 Docker Hub 的机器,并在那台机器上正常安装 Docker。
找一个文件夹,编辑 compose.yml
文件,填入以下内容:
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io # 上游源
REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: inmemory # 内存缓存,去掉本行以直接使用硬盘
volumes:
- ./data:/var/lib/registry
然后执行 docker compose up -d
即可。
使用方法同上。