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 服务稳定交付出去”这件事讲清楚,也方便后续继续演进。