Prhub

#40275 [Docker] Non-root support for vllm-openai; add opt-in vllm-openai-nonroot target

原始 PR 作者 TheDuyIT 合并时间 2026-05-25 13:45 文件变更 7 提交数 9 评论 14 代码增减 +577 / -30

执行摘要

支持非 root 用户运行 vLLM Docker 容器

用户报告无法以非root用户运行vLLM Docker容器(Issue #31959)。此外,OpenShift等Kubernetes平台要求容器不能以root运行。本PR旨在提供向后兼容的非root支持,使现有root工作流不变,同时允许用户通过指定--user 2000:0或使用vllm-openai-nonroot镜像以非root运行。

本PR设计严谨,考虑了向后兼容性、多种运行场景(固定UID、任意UID、只读挂载)和安全性。对于部署管理员和容器化团队值得精读,尤其是Dockerfile中uv路径迁移技巧和入口包装器的cwd保留逻辑。可以作为如何在现有镜像中安全添加非root支持的范例。

讨论亮点

主要讨论围绕两点:

  • 安全性:review bot指出使用固定路径/tmp/vllm-home存在symlink攻击风险。作者回应已将回退路径改为mktemp产生的随机临时目录,消除了此风险。
  • 文档补充:审阅者Harry-Chen要求添加文档说明非root使用方式,作者已补充到docs/deployment/docker.md中。

实现拆解

  1. Dockerfile修改:将uv的二进制、管理Python和缓存目录从/root/.local迁移到/opt/uv(组0可写),创建vllm用户(UID 2000, GID 0)及其可写家目录/home/vllm,将/etc/passwd和/etc/group设为组0可写,以支持任意UID注入。
  2. 新增入口包装器(vllm-nonroot-entrypoint.sh):处理HOME回退(优先/home/vllm,否则mktemp创建临时目录)、USER/LOGNAME默认值设置、cwd可访问性检查(仅当完全不可访问时才回到HOME),以及passwd条目合成(通过VLLM_PASSWD_FILE测试钩子)。
  3. Shell单元测试(test_vllm_nonroot_entrypoint.sh):10个测试用例覆盖HOME保留、HOME回退、USER/LOGNAME默认值、cwd保留和只读cwd场景,通过stub vllm命令验证环境变量。
  4. Pre-commit钩子(.pre-commit-config.yaml):新增test-nonroot-entrypoint钩子,任何修改entrypoint相关文件时自动运行单元测试。
  5. BuildKite CI冒烟测试(.buildkite/image_build/image_build.yaml):在image-build步骤后添加两条非root验证:import vllm和文件系统权限检查。
  6. 文档更新(docs/deployment/docker.md):新增"Run as a non-root user"章节,说明使用方法和构建方式。
文件 模块 状态 重要度
docker/entrypoints/vllm-nonroot-entrypoint.sh 入口脚本 added 6.02
docker/Dockerfile Docker 构建 modified 5.65
docker/entrypoints/test_vllm_nonroot_entrypoint.sh 入口测试 added 6.16
.buildkite/image_build/image_build.yaml CI 构建 modified 4.4
.pre-commit-config.yaml 预提交钩子 modified 3.57
docs/deployment/docker.md 文档 modified 3.52

关键源码片段

docker/entrypoints/vllm-nonroot-entrypoint.sh infrastructure

新增入口包装器,实现非 root 运行时环境变量和目录的动态配置,是核心逻辑所在。

#!/bin/sh
# vllm-nonroot-entrypoint.sh
# 入口包装器,用于支持任意 UID 的非 root 运行(如 OpenShift)。
​
set -eu
​
# HOME 回退:如果未设置或不可写,则尝试使用 /home/vllm 或 mktemp 临时目录
if [ -z "${HOME:-}" ] || [ ! -w "${HOME}" ]; then
  if [ -w /home/vllm ]; then
    export HOME=/home/vllm
  else
    if _h="$(mktemp -d /tmp/vllm-home.XXXXXX 2>/dev/null)"; then
      export HOME="$_h"
      chmod 0700 "$HOME" 2>/dev/null || true
    else
      export HOME=/tmp
    fi
    unset _h
  fi
fi
​
# cwd 保留:只有 cwd 完全不可进入时才切到 HOME(不检查可写性)
if ! cd . 2>/dev/null; then
  cd "$HOME"
fi
​
# USER / LOGNAME 默认值
if [ -z "${USER:-}" ]; then
  export USER=vllm
fi
if [ -z "${LOGNAME:-}" ]; then
  export LOGNAME="$USER"
fi
​
# passwd 合成部分(省略,使用 VLLM_PASSWD_FILE 机制)
# exec vllm serve "$@"
docker/entrypoints/test_vllm_nonroot_entrypoint.sh test-coverage

Shell 单元测试,覆盖入口包装器的 10 种场景,通过 pre-commit 钩子自动执行,保障质量。

#!/bin/sh
# test_vllm_nonroot_entrypoint.sh
# 入口包装器的 Shell 单元测试。
# 通过环境变量赋值和运行包装器,断言输出符合预期。
​
set -eu
​
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WRAPPER="${SCRIPT_DIR}/vllm-nonroot-entrypoint.sh"
​
# 创建临时工作目录
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
​
# 创建 stub vllm 命令,用于输出环境变量和参数
mkdir -p "$WORKDIR/bin"
cat > "$WORKDIR/bin/vllm" <<'EOF'
#!/bin/sh
echo "ARGV=$*"
echo "HOME=${HOME-__unset__}"
echo "USER=${USER-__unset__}"
echo "LOGNAME=${LOGNAME-__unset__}"
echo "PWD=$(pwd)"
EOF
chmod +x "$WORKDIR/bin/vllm"
​
# 辅助函数:运行包装器并捕获输出
run_wrapper() {
  _out="$1"; shift
  _env=""
  while [ "${1:-}" != "--" ]; do
    _env="$_env $1"; shift
  done
  shift
  env -i PATH="$WORKDIR/bin:/usr/bin:/bin" $_env "$WRAPPER" "$@" > "$_out"
}
​
# 用例 1:当 HOME 可写且 USER/LOGNAME 已设置时,包装器应保留它们。
case1_home="$WORKDIR/case1-home"
mkdir -p "$case1_home"
out="$WORKDIR/case1.out"
run_wrapper "$out" "HOME=$case1_home" "USER=alice" "LOGNAME=alice" -- --model foo
grep -q "^HOME=$case1_home$" "$out" || fail "$out" "case1: HOME not preserved"
grep -q "^USER=alice$" "$out" || fail "$out" "case1: USER not preserved"
echo "PASS: case1"
​
# 用例 2:HOME 未设置时,应回退到 /home/vllm 或 mktemp 目录。
out="$WORKDIR/case2.out"
run_wrapper "$out" -- --model bar
grep -Eq '^HOME=/(home/vllm|tmp/vllm-home\.[^/]+)$' "$out" || fail "$out" "case2: HOME fallback failed"
echo "PASS: case2"

评论区精华

非根临时目录的 symlink 安全性问题 安全

depthfirst-app[bot] 指出使用固定路径 /tmp/vllm-home 存在 symlink 攻击风险,建议使用 mktemp。

结论:作者已将回退路径改为 mktemp -d /tmp/vllm-home.XXXXXX,避免了固定路径。 · 已解决

文档补充要求 documentation

审阅者 Harry-Chen 要求添加文档说明非 root 使用方式。

结论:作者在 docs/deployment/docker.md 中添加了 "Run as a non-root user" 章节。 · 已解决

风险与影响

回归风险低:所有变更不影响现有root默认工作流,新建的vllm-openai-nonroot目标为可选。安全性风险:入口包装器中的HOME回退逻辑如果处理不当可能造成临时目录泄露,但已使用mktemp -d避免固定路径,并设置了恰当权限。边缘情况:当/home/vllm不存在且mktemp失败时,将回退到/tmp(可写),但可能被其他用户读取;不过容器内通常没有其他用户。回退逻辑依赖文件系统状态,如果挂载卷导致权限意外可能触发异常。建议在非root使用前验证/home/vllm和/opt/uv/cache的权限。

用户:现在可以以非root用户运行vLLM Docker容器,满足安全合规需求。OpenShift用户可以构建vllm-openai-nonroot镜像直接使用任意UID。系统:无影响。团队:新增维护点(入口包装器、pre-commit钩子、BuildKite烟雾测试),但整体改动小,易于维护。影响范围:仅Docker部署相关,不涉及vLLM核心逻辑。

向后兼容性保证 临时目录 symlink 风险已缓解 HOME 回退机制边缘情况

关联 Issue

#31959 [Usage]: Cannot Run vLLM Docker Container as Non-Root User
#38552 openshift-compatibility

完整报告

参与讨论