MoreRSS

site iconLucien修改

有个文本分享项目:PasteMe,阿里,杭州。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Lucien的 RSS 预览

Webhook 配置备忘

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'

zsh 配置备忘

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 来下载一个模型。

安装 git-lfs

我们前往 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

使用 git clone 下载模型

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!,但是存在效率问题,主要体现在两点:

  1. TTFT,Time to first token,也就是首字响应时长
  2. 吞吐量,每秒的 Token 数量,单位:tokens/s

有许多优化方法能缓解这些问题,开源社区有很多推理加速的方案,比如:

还有一些面向易用性的方案,比如:

个人在这里最推荐的是 vLLM,稳定、易用、性能这几个方面的表现较为均衡,没有明显的短板。

在这里也小小的安利一下 SGLang,它的吞吐相比 vLLM 有优势,Benchmark 见:Achieving Faster Open-Source Llama3 Serving with SGLang Runtime (vs. TensorRT-LLM, vLLM)

接下来我将基于上一篇博客配置的环境来讲一讲较为便捷的部署方式,以及一些面向生产部署的经验。

Docker 部署

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}

一些经验

  1. 监控和灾备很重要,尤其是对于一个实时业务系统来说,相比 CPU、硬盘、内存、电源,GPU 的故障率尤其高。
  2. 降低 TTFT 和提升吞吐可以同时通过增加并行度来实现(多卡推理),但超过一个甜点值提升就很微弱了,对于 72B 的模型来说,4 卡 SXM 是一个比较有显著收益的选择。
  3. 对于显卡较多或比较分散的小伙伴来说,比较建议的是通过 Kubernetes 来管理所有的显卡,会省去很多的工作量。
  4. 需要根据业务场景和模型大小在 PCIe 与 SXM 之间做出选择,如果对推理效率没有过高的要求,3090 或 A100 能满足大部分的场景。如果需要推理超长上下文或很大的模型,建议 SXM,PCIe 的卡间通信是个灾难。

从零开始实践大模型 - 配置环境

2024-06-17 14:45:00

本文地址:blog.lucien.ink/archives/549

本文将介绍在面向深度学习时,推荐的环境配置以及一些使用 Linux 的习惯。

本文的部分内容与 Debian 下 CUDA 生产环境配置笔记 有所重叠,但也有些许的不一样,在正文中不额外注明。

前言

本文将主要分 4 部分:

  1. 配置 SSH 登陆
  2. 安装显卡驱动
  3. 安装 Docker 并配置“Docker 显卡驱动”
  4. 切换至普通用户并安装 miniconda

配置 SSH 登陆

在安装完系统并重启之后,首先看到的是一个登陆界面,在这里输入我们在安装阶段设定好的 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 个解决方案:

  1. 先临时打开 PermitRootLogin yes,用 ssh 拷过去后再关掉
  2. 本地在 ~/.ssh 目录下用 python3 -m http.server 3000 起一个 HTTP 文件服务,然后去服务器上执行 wget
  3. 使用 PasteMe 来传输,在这里不赘述

基础软件

在这里使用 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

这一步是必要的,因为 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 来检查某张显卡该模式的状态。

安装 NVSwtich 驱动

如果读者使用的不是 SXM 的卡,请跳过这一步,如果不明白这里是在说什么,也可以先跳过

对于 H100 SXMA100 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-1550.54.15-1550.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

Docker CE 软件仓库
export DOWNLOAD_URL="https://mirrors.tuna.tsinghua.edu.cn/docker-ce"
wget -O- https://get.docker.com/ | sh

令 Docker 能够调用显卡

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 有两种方案:

  1. rootless: 新建一个隔离的 docker 环境(类似 conda 的多环境)
  2. root: 赋予直接操作 root docker 的权限

Docker rootless mode

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 究竟有哪些镜像与容器

Docker root mode

直接用 root 用户执行:

usermod -aG docker foo

然后重新登陆 foo 用户,就能开始使用 docker 了。值得注意的是,此时在 docker 内部创建的所有文件都归属于 root 用户。除非显式指定 docker 中的文件权限归属。

普通用户安装 conda 环境

首先登陆 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

配置 conda 镜像

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 镜像

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 或自签证书时需要这个配置

验证 Python 对 CUDA 的调用

在这里笔者也不推荐直接使用 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())'

尾声

建议

在这里再多啰嗦几句,希望能让后辈们少走些弯路:

  1. 任何行为,不论是安装软件、配环境、写代码还是一些系统操作,都应该将影响降低至 最小范围。比如将 nvcc、gcc 装至用户、环境级别而不是直接用 root 安装。
  2. 除了本章节的内容,在任何情况下,都不建议直接使用 root 账户进行操作,除非读者是一位对 Linux 非常熟悉的专家并且明白自己在做什么,否则会面临各种潜在的权限问题、崩溃、挖矿病毒、数据丢失等风险。
  3. 在任何情况下,都不应该操作 Linux 本身的 Python 环境,请使用 venv 或 conda
  4. 在任何情况下,都不应该随意变更宿主机的 CUDA 版本,请使用 docker
  5. 不建议在宿主机中安装 nvcc、TensoRT 等内容,据笔者观察,至少 90% 的用户他们并不明白自己在做什么,所以 请使用 conda 或 docker

备忘

  1. 安装 cudnn

    conda install conda-forge::cudnn
  2. 安装 nvcc

    conda install nvidia::cuda-nvcc
  3. 安装 gcc

    conda install conda-forge::gcc

Reference

从零开始实践大模型 - 安装系统

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

安装系统

启动页面

Installer menu

语言

在这里 强烈不推荐 选择中文,除非你准备好应对各种因中文字符而产生的问题。

Select a language

地区

美国或中国都可以,这会影响到安装完成后的时区:

  • 可以在这里先选择 United States 然后进入系统后再更改。
  • 也可以直接去 other 里找 Asia 然后 China

Select location other
Select Region
Select territory or area

编码

一律选 en_US.UTF-8,可以规避很多潜在的问题。

Configure locales

键盘布局

Configure the keyboard

主机名 & 域名

如果只是单台服务器的话,这里随便填就好。
如果打算组建集群,这里就直接起个 node-0 之类的遍于自己区分的名字就好。

The hostname for this system
Domain name

设定用户 & 密码

Set up root password
Set up normal user

硬盘分区

推荐直接用一整块硬盘,不启用 LVM 和加密。

Partition disks
Select disk
Partition Scheme
Disk_part Checkout
Verify disk part

配置 apt

在这里选择国内的镜像,否则会很慢。

Extra media
APT China
APT Tuna mirror
APT HTTP Proxy

是否参与数据采集

Survey

选择预装软件

在这里只选择 SSH 和基础工具就好,没有特殊需求不建议勾选图形化界面(Debian desktop environment)。

Software Selection

安装引导

GRUB
Choose Grub Device

重启进入系统

Complete
Bootloader

自建 Docker 镜像源

2024-06-09 22:12:47

本文地址:blog.lucien.ink/archives/547
本文主要参考自:自建Docker 镜像/源加速的方法

1. 简介

最近 Docker Hub 被禁一事引起了不小的波动,在这里简单讲下在这之后应该如何访问公开的 Docker Hub。

2. Cloudflare

2.1 搭建

搭建的前提是有一个在 Cloudflare 中被管理的域名,此处不展开介绍,在这里假设这个域名是 your-domain.com

2.1.1 创建 Worker

点击页面左侧的 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
    })
}

2.1.2 添加域名

进入创建好的 Worker 的配置页面,在 Settings Tab 中选择 Triggers,点击 Add Custom Domain,添加 your-domain.com

2.2 使用

2.2.1 配置为镜像

/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

2.2.2 直接使用

docker pull your-domain.com/library/busybox:latest
docker pull your-domain.com/mysql/mysql-server:latest

3. 使用 registry

首先你需要一个能正常访问 Docker Hub 的机器,并在那台机器上正常安装 Docker。

3.1 搭建

找一个文件夹,编辑 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 即可。

3.2 使用

使用方法同上。