前面整理这套 vLLM 私有源部署方案时,我把重点放在了思路复盘上。这次换个更直接的版本:不改原始脚本文件,只做脱敏,把服务端部署脚本和客户端部署脚本的可公开版本贴出来,方便自己归档,也方便后续复用。

先说明一下边界:仓库里的原始脚本我没有修改,这篇文章里的代码块是单独整理出来的脱敏版本。处理方式主要是把内网 IP、固定端口、默认账号、硬编码 API Key 以及过于贴近生产环境的目录替换成占位写法,保留脚本结构和部署逻辑。

一、这套脚本解决什么问题

目标很明确:在一台可联网的内部服务器上准备 APT 和 PyPI 私有源,然后让目标 GPU 节点通过一键脚本完成 vLLM 环境部署、systemd 托管和基础验证。

整体分两段:

  • 服务端脚本:负责下载和暴露依赖。
  • 客户端脚本:负责安装和启动推理服务。

二、脱敏后的服务端部署脚本

下面这个版本对应“在私有源服务器本机运行”的服务端脚本。它会准备 APT 源、PyPI 源,并用 systemd 把两个服务托起来。

#!/bin/bash
# vLLM 私有源服务端部署脚本(脱敏版)
# 直接在私有源服务器本机执行

set -e

APT_REPO="/opt/vllm-private-repo/apt/ubuntu22.04/cuda-12.4"
PYPI_REPO="/opt/vllm-private-repo/pypi/packages/cuda-12.4/python3.10"
APT_PORT="${APT_PORT:-8888}"
PYPI_PORT="${PYPI_PORT:-9999}"

echo "======================================"
echo "开始部署 vLLM 私有源"
echo "======================================"

if [ "$EUID" -ne 0 ]; then
    echo "错误:请使用 root 用户运行此脚本"
    echo "使用: sudo bash deploy-server-local.sh"
    exit 1
fi

echo ""
echo "[1/8] 创建目录结构..."
mkdir -p "$APT_REPO"
mkdir -p "$PYPI_REPO"
echo "  ✓ 目录创建完成"

echo ""
echo "[2/8] 配置基础环境..."
apt-get update -qq
apt-get install -y python3-pip wget gpg dpkg-dev

echo "  配置 NVIDIA CUDA 仓库..."
wget -qO- https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub \
  | gpg --dearmor -o /usr/share/keyrings/cuda-archive-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/cuda-archive-keyring.gpg] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/ /' \
  > /etc/apt/sources.list.d/cuda-ubuntu2204-x86_64.list
apt-get update -qq
echo "  ✓ 基础环境配置完成"

echo ""
echo "[3/8] 安装 pypiserver..."
pip3 install pypiserver
echo "  ✓ pypiserver 安装完成"

echo ""
echo "[4/8] 下载 CUDA 及系统依赖..."
apt-get install -d \
    -o Dir::Cache::Archives="$APT_REPO/" \
    cuda-toolkit-12-4 \
    libcudnn8 \
    libcudnn8-dev \
    build-essential \
    ninja-build \
    python3.10-dev
echo "  ✓ CUDA 依赖下载完成"

echo ""
echo "[5/8] 下载 vLLM、ModelScope 及 Python 依赖..."
pip download \
    --index-url https://pypi.org/simple \
    --extra-index-url https://download.pytorch.org/whl/cu124 \
    --dest "$PYPI_REPO/" \
    vllm \
    modelscope
echo "  ✓ Python 依赖下载完成"

echo ""
echo "[6/8] 生成 APT 索引文件..."
cd "$APT_REPO"
dpkg-scanpackages . /dev/null > Packages
gzip -c Packages > Packages.gz
cd - > /dev/null
echo "  ✓ APT 索引生成完成"

echo ""
echo "[7/8] 部署 systemd 服务..."

cat > /etc/systemd/system/vllm-apt-source.service <<EOF
[Unit]
Description=vLLM APT Source Service
After=network.target

[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/vllm-private-repo/apt
Environment="PYTHONUNBUFFERED=1"
ExecStart=/usr/bin/python3 -m http.server ${APT_PORT}
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

cat > /etc/systemd/system/vllm-pypi-source.service <<EOF
[Unit]
Description=vLLM PyPI Source Service
After=network.target

[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/vllm-private-repo/pypi
Environment="PYTHONUNBUFFERED=1"
ExecStart=/usr/bin/python3 -m pypiserver -p ${PYPI_PORT} -P . -a . packages
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable vllm-apt-source.service
systemctl enable vllm-pypi-source.service
systemctl restart vllm-apt-source.service
systemctl restart vllm-pypi-source.service
echo "  ✓ systemd 服务已启动"

echo ""
echo "[8/8] 验证服务状态..."
systemctl is-active --quiet vllm-apt-source.service
systemctl is-active --quiet vllm-pypi-source.service
echo "  ✓ 服务运行正常"

echo ""
echo "APT 源地址:  http://<private-repo-host>:${APT_PORT}"
echo "PyPI 源地址: http://<private-repo-host>:${PYPI_PORT}"

这个脚本的重点不是花哨,而是够直接:先把包下载下来,再把它们通过最轻量的 HTTP 服务暴露出去。对于中小规模内网环境,这种方式很容易落地。

三、脱敏后的客户端部署脚本

下面这个版本对应部署节点使用的客户端脚本。它的任务是把一台 GPU 机器标准化:配置私有源、安装依赖、准备 Python 环境、安装 vLLM,并生成 systemd 服务。

#!/bin/bash
# vLLM 客户端部署脚本(脱敏版)

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

PRIVATE_REPO_HOST="${PRIVATE_REPO_HOST:-<private-repo-host>}"
APT_PORT="${APT_PORT:-8888}"
PYPI_PORT="${PYPI_PORT:-9999}"

echo "======================================"
echo "     vLLM 客户端一键部署脚本"
echo "======================================"
echo ""

if [ "$EUID" -ne 0 ]; then
    echo -e "${RED}请使用 root 用户或 sudo 运行此脚本${NC}"
    exit 1
fi

echo -e "${BLUE}【步骤1】配置服务参数${NC}"
read -p "请输入模型名称(如:Qwen/Qwen2.5-7B-Instruct): " MODEL_NAME
read -p "请输入模型本地路径(如:/models/Qwen2.5-7B-Instruct): " MODEL_PATH
read -p "请输入 GPU 卡数量(如:4): " GPU_COUNT
read -p "请输入服务端口(默认:8090): " SERVICE_PORT
SERVICE_PORT=${SERVICE_PORT:-8090}

GPU_MEM_UTIL="0.90"
MAX_BATCH_TOKENS="16384"
MAX_NUM_SEQS="24"
MAX_MODEL_LEN="131072"
KV_CACHE_DTYPE="fp8"

read -p "请输入 swap-space(默认:8): " SWAP_SPACE
SWAP_SPACE=${SWAP_SPACE:-8}

read -p "请输入 tool-call-parser(可选): " TOOL_CALL_PARSER_VAL
read -p "请输入 reasoning-parser(可选): " REASONING_PARSER_VAL

echo ""
echo -e "${BLUE}选择部署模式:${NC}"
echo "  1) 使用虚拟环境(推荐)"
echo "  2) 直接安装到系统环境"
read -p "请选择(1/2,默认:1): " USE_VENV
USE_VENV=${USE_VENV:-1}

if [ "$USE_VENV" = "1" ]; then
    read -p "请输入虚拟环境路径(默认:/opt/vllm-env): " VENV_PATH
    VENV_PATH=${VENV_PATH:-/opt/vllm-env}
fi

read -p "请输入运行服务的用户名(默认:service-user): " SERVICE_USER
SERVICE_USER=${SERVICE_USER:-service-user}

read -p "请输入 API Key(留空则从环境变量 VLLM_API_KEY 读取): " API_KEY_VAL
API_KEY_VAL=${API_KEY_VAL:-$VLLM_API_KEY}

echo ""
echo -e "${GREEN}配置摘要:${NC}"
echo "  模型名称: $MODEL_NAME"
echo "  模型路径: $MODEL_PATH"
echo "  GPU 卡数: $GPU_COUNT"
echo "  服务端口: $SERVICE_PORT"
echo "  运行用户: $SERVICE_USER"
read -p "确认配置无误?(y/n): " CONFIRM
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
    exit 0
fi

echo ""
echo -e "${BLUE}【步骤2】配置私有源${NC}"
cat > /etc/apt/sources.list.d/vllm-private.list <<EOF
deb [trusted=yes] http://${PRIVATE_REPO_HOST}:${APT_PORT}/ubuntu22.04/cuda-12.4 /
EOF

mkdir -p /root/.pip
cat > /root/.pip/pip.conf <<EOF
[global]
index-url = http://${PRIVATE_REPO_HOST}:${PYPI_PORT}/simple
trusted-host = ${PRIVATE_REPO_HOST}
EOF

echo -e "${GREEN}✓ 私有源配置完成${NC}"

echo ""
echo -e "${BLUE}【步骤3】安装系统依赖${NC}"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
    cuda-toolkit-12-4 \
    libcudnn8 \
    libcudnn8-dev \
    build-essential \
    ninja-build \
    python3.10-dev \
    python3.10-venv

echo ""
echo -e "${BLUE}【步骤4】配置环境变量${NC}"
cat > /etc/profile.d/cuda.sh <<EOF
export PATH=/usr/local/cuda/bin:\$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:\$LD_LIBRARY_PATH
export CUDA_HOME=/usr/local/cuda
EOF

export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
export CUDA_HOME=/usr/local/cuda

echo ""
echo -e "${BLUE}【步骤5】安装 Python 依赖${NC}"
if [ "$USE_VENV" = "1" ]; then
    python3 -m venv "$VENV_PATH"
    "$VENV_PATH/bin/pip" install --upgrade pip
    "$VENV_PATH/bin/pip" install modelscope
    "$VENV_PATH/bin/pip" install vllm
    PYTHON_BIN="$VENV_PATH/bin/python"
else
    pip3 install --upgrade pip
    pip3 install modelscope vllm
    PYTHON_BIN="$(command -v python3)"
fi

echo ""
echo -e "${BLUE}【步骤6】生成 systemd 服务${NC}"

EXTRA_ARGS=""
if [ -n "$TOOL_CALL_PARSER_VAL" ]; then
    EXTRA_ARGS="$EXTRA_ARGS --tool-call-parser $TOOL_CALL_PARSER_VAL"
fi
if [ -n "$REASONING_PARSER_VAL" ]; then
    EXTRA_ARGS="$EXTRA_ARGS --reasoning-parser $REASONING_PARSER_VAL"
fi
if [ -n "$API_KEY_VAL" ]; then
    EXTRA_ARGS="$EXTRA_ARGS --api-key $API_KEY_VAL"
fi

cat > /etc/systemd/system/vllm.service <<EOF
[Unit]
Description=vLLM Service
After=network.target

[Service]
Type=simple
User=${SERVICE_USER}
WorkingDirectory=/home/${SERVICE_USER}
Environment=PYTHONUNBUFFERED=1
Environment=PATH=/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=LD_LIBRARY_PATH=/usr/local/cuda/lib64
ExecStart=${PYTHON_BIN} -m vllm.entrypoints.openai.api_server \\
  --model ${MODEL_PATH} \\
  --served-model-name ${MODEL_NAME} \\
  --host 0.0.0.0 \\
  --port ${SERVICE_PORT} \\
  --tensor-parallel-size ${GPU_COUNT} \\
  --gpu-memory-utilization ${GPU_MEM_UTIL} \\
  --swap-space ${SWAP_SPACE} \\
  --max-num-batched-tokens ${MAX_BATCH_TOKENS} \\
  --max-num-seqs ${MAX_NUM_SEQS} \\
  --max-model-len ${MAX_MODEL_LEN} \\
  --kv-cache-dtype ${KV_CACHE_DTYPE}${EXTRA_ARGS}
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable vllm
systemctl restart vllm

echo ""
echo -e "${BLUE}【步骤7】验证安装${NC}"
if command -v nvcc >/dev/null 2>&1; then
    echo -e "${GREEN}✓ CUDA 已安装${NC}"
else
    echo -e "${YELLOW}⚠ 未找到 nvcc 命令${NC}"
fi

systemctl status vllm --no-pager
echo ""
echo "测试接口:"
echo "curl http://127.0.0.1:${SERVICE_PORT}/v1/models"

和原始版本相比,这里主要做了几类脱敏:

  • 把私有源地址改成了 <private-repo-host>
  • 把默认服务用户改成了更通用的 service-user
  • 把 API Key 改成了交互输入或环境变量读取,不再写死。
  • 把虚拟环境和模型目录改成了通用路径示例。

四、实际使用时的最小流程

如果只看最短路径,实际落地可以按下面这几步走:

  1. 在私有源服务器执行服务端脚本,确认 APT 源和 PyPI 源可以访问。
  2. 提前把模型文件放到目标 GPU 机器的本地目录。
  3. 在目标 GPU 机器执行客户端脚本,按提示输入模型路径、GPU 数量、端口等参数。
  4. 通过 systemctl status vllmjournalctl -u vllm -fcurl /v1/models 做验证。

五、几个值得保留的注意点

原始脚本里有些做法虽然简单,但很值得保留:

  • 把依赖下载和服务部署拆成两段,这样扩容节点时会轻很多。
  • 从第一天开始就用 systemd 管服务,后面排障成本会低很多。
  • 尽量把模型路径、端口、并行度这些参数做成输入项,不要直接写死。
  • 涉及密钥的部分一定改成环境变量或一次性传参,不要继续硬编码。

六、收尾

这篇文章的重点不是重新设计一套新方案,而是把原来能跑通的脚本,以不暴露生产信息的方式整理出来。后面如果要继续完善,我更倾向于沿着两个方向补:一是把版本约束写得更严格一些,二是把日志、监控和健康检查再往前推进一步。

至少在当前阶段,这套做法已经能把“内网环境下怎么把 vLLM 服务稳定交付出去”这件事讲清楚,也方便后续继续演进。

标签: linux, ubuntu, python, api, openai, ai

添加新评论