vLLM 内网离线部署：脱敏后的服务端与客户端脚本
前面整理这套 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 改成了交互输入或环境变量读取，不再写死。 把虚拟环境和模型目录改成了通用路径示例。 四、实际使用时的最小流程如果只看最短路径，实际落地可以按下面这几步走： 在私有源服务器执行服务端脚本，确认 APT 源和 PyPI 源可以访问。 提前把模型文件放到目标 GPU 机器的本地目录。 在目标 GPU 机器执行客户端脚本，按提示输入模型路径、GPU 数量、端口等参数。 通过 systemctl status vllm、journalctl -u vllm -f 和 curl /v1/models 做验证。 五、几个值得保留的注意点原始脚本里有些做法虽然简单，但很值得保留： 把依赖下载和服务部署拆成两段，这样扩容节点时会轻很多。 从第一天开始就用 systemd 管服务，后面排障成本会低很多。 尽量把模型路径、端口、并行度这些参数做成输入项，不要直接写死。 涉及密钥的部分一定改成环境变量或一次性传参，不要继续硬编码。 六、收尾这篇文章的重点不是重新设计一套新方案，而是把原来能跑通的脚本，以不暴露生产信息的方式整理出来。后面如果要继续完善，我更倾向于沿着两个方向补：一是把版本约束写得更严格一些，二是把日志、监控和健康检查再往前推进一步。至少在当前阶段，这套做法已经能把“内网环境下怎么把 vLLM 服务稳定交付出去”这件事讲清楚，也方便后续继续演进。