Files
vllm-r9700-container/build_and_run.sh
T

526 lines
15 KiB
Bash
Raw Normal View History

2026-03-10 23:09:05 +08:00
#!/bin/bash
2026-03-10 23:24:27 +08:00
# vLLM R9700 Container 智能部署脚本
# 自动检测 Git 代码更新,智能构建和部署
2026-03-10 23:09:05 +08:00
set -e
2026-03-11 00:36:26 +08:00
# ============== 日志配置 ==============
LOG_DIR="/opt/webhook"
LOG_FILE="${LOG_DIR}/vllm-deploy-$(date +%Y%m%d-%H%M%S).log"
LATEST_LOG="${LOG_DIR}/vllm-deploy-latest.log"
# 创建日志目录
mkdir -p "$LOG_DIR"
# 重定向所有输出到日志文件
exec > >(tee -a "$LOG_FILE") 2>&1
# 创建最新日志的软链接
ln -sf "$LOG_FILE" "$LATEST_LOG"
2026-03-10 23:57:37 +08:00
# ============== 配置变量(可修改) ==============
# 项目目录
PROJECT_DIR="/opt/project/vllm-r9700-container"
# 挂载配置
MODEL_DIR="${MODEL_DIR:-/opt/model}" # 本地模型目录(独立于项目目录)
CONFIG_FILE="${CONFIG_FILE:-${PROJECT_DIR}/model_config.yaml}" # 本地配置文件(项目根目录下)
# 容器内配置
LOCAL_MODEL_DIR="${LOCAL_MODEL_DIR:-/model}" # 容器内模型目录(与 Dockerfile 保持一致)
CONFIG_FILE_IN_CONTAINER="/etc/vllm/model_config.yaml" # 容器内配置文件路径
# Docker 镜像配置
2026-03-10 23:09:05 +08:00
IMAGE_NAME="custom-vllm-r9700"
IMAGE_TAG="latest"
CONTAINER_NAME="custom-vllm-r9700"
2026-03-10 23:57:37 +08:00
# Git 配置
2026-03-10 23:24:27 +08:00
GIT_BRANCH="${GIT_BRANCH:-main}"
2026-03-10 23:57:37 +08:00
# ================================================
2026-03-10 23:09:05 +08:00
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
2026-03-10 23:24:27 +08:00
CYAN='\033[0;36m'
2026-03-10 23:09:05 +08:00
NC='\033[0m' # No Color
# 打印带颜色的消息
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
2026-03-10 23:24:27 +08:00
print_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
2026-03-10 23:09:05 +08:00
# 显示使用帮助
show_usage() {
echo "用法:$0 [选项]"
echo ""
echo "选项:"
2026-03-10 23:24:27 +08:00
echo " -f, --force 强制重新构建镜像(忽略 Git 状态)"
2026-03-10 23:09:05 +08:00
echo " -s, --stop 仅停止并删除容器"
echo " -d, --detach 后台运行容器(默认)"
echo " -i, --interactive 交互式运行容器"
echo " -c, --config FILE 指定配置文件路径"
echo " -m, --models DIR 指定模型目录路径"
echo " -p, --port PORT 指定服务端口"
2026-03-10 23:24:27 +08:00
echo " -b, --branch NAME 指定 Git 分支(默认:main)"
2026-03-15 22:29:11 +08:00
echo " --http-proxy URL 指定构建时的 HTTP 代理 (例如: http://192.168.0.10:7890)"
echo " --socks-proxy HOST:PORT 指定构建时的 SOCKS 代理 (例如: 192.168.0.10:1080)"
2026-03-10 23:09:05 +08:00
echo " -h, --help 显示此帮助信息"
echo ""
echo "示例:"
2026-03-10 23:24:27 +08:00
echo " $0 # 智能部署(检测 Git 更新)"
echo " $0 -f # 强制重新构建并部署"
2026-03-10 23:09:05 +08:00
echo " $0 -s # 停止并删除容器"
echo " $0 -p 8080 # 使用端口 8080 运行"
2026-03-10 23:24:27 +08:00
echo " $0 -b master # 使用 master 分支"
2026-03-10 23:09:05 +08:00
exit 0
}
# 解析命令行参数
2026-03-10 23:24:27 +08:00
FORCE_BUILD=false
2026-03-10 23:09:05 +08:00
STOP_ONLY=false
DETACH=true
CUSTOM_CONFIG=""
2026-03-15 22:29:11 +08:00
HTTP_PROXY_ARG=""
SOCKS_PROXY_ARG=""
2026-03-10 23:09:05 +08:00
CUSTOM_MODELS=""
CUSTOM_PORT=""
while [[ $# -gt 0 ]]; do
case $1 in
2026-03-10 23:24:27 +08:00
-f|--force)
FORCE_BUILD=true
2026-03-10 23:09:05 +08:00
shift
;;
2026-03-15 22:29:11 +08:00
--http-proxy)
HTTP_PROXY_ARG="$2"
shift 2
;;
--socks-proxy)
SOCKS_PROXY_ARG="$2"
shift 2
;;
2026-03-10 23:09:05 +08:00
-s|--stop)
STOP_ONLY=true
shift
;;
-d|--detach)
DETACH=true
shift
;;
-i|--interactive)
DETACH=false
shift
;;
-c|--config)
CUSTOM_CONFIG="$2"
shift 2
;;
-m|--models)
CUSTOM_MODELS="$2"
shift 2
;;
-p|--port)
CUSTOM_PORT="$2"
shift 2
;;
2026-03-10 23:24:27 +08:00
-b|--branch)
GIT_BRANCH="$2"
shift 2
;;
2026-03-10 23:09:05 +08:00
-h|--help)
show_usage
;;
*)
print_error "未知选项:$1"
show_usage
;;
esac
done
# 停止并删除容器
stop_container() {
print_info "停止并删除容器..."
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
print_success "容器已停止"
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
print_success "容器已删除"
else
print_warning "容器不存在"
fi
}
# 如果只需要停止容器,直接退出
if [ "$STOP_ONLY" = true ]; then
stop_container
exit 0
fi
# 检查 Docker 是否运行
check_docker() {
if ! docker info > /dev/null 2>&1; then
print_error "Docker 未运行或无权限访问"
print_error "请确保 Docker 服务正在运行,并且当前用户有 Docker 访问权限"
exit 1
fi
print_success "Docker 检查通过"
}
2026-03-10 23:24:27 +08:00
# 检查并切换到项目目录
check_project_dir() {
print_step "检查项目目录..."
if [ ! -d "$PROJECT_DIR" ]; then
print_error "项目目录不存在:${PROJECT_DIR}"
exit 1
fi
# 切换到项目目录
cd "$PROJECT_DIR"
print_info "项目目录:${PROJECT_DIR}"
print_success "已切换到项目目录"
}
# 检查 Git 仓库
check_git() {
if [ ! -d ".git" ]; then
print_warning "当前目录不是 Git 仓库"
return 1
fi
# 检查 git 命令是否存在
if ! command -v git &> /dev/null; then
print_warning "Git 未安装,跳过 Git 检查"
return 1
fi
return 0
}
# 拉取最新代码
pull_latest_code() {
print_step "拉取最新代码..."
# 保存当前分支
current_branch=$(git rev-parse --abbrev-ref HEAD)
# 尝试拉取
if git fetch origin "${GIT_BRANCH}" 2>/dev/null; then
# 检查是否有更新
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/"${GIT_BRANCH}")
if [ "$LOCAL" != "$REMOTE" ]; then
print_info "检测到代码更新,正在更新..."
if git pull origin "${GIT_BRANCH}" 2>/dev/null; then
print_success "代码已更新到最新版本"
return 0 # 有更新
else
print_warning "代码拉取失败,继续执行"
return 1 # 无更新或失败
fi
else
print_info "代码已是最新版本"
return 1 # 无更新
fi
else
print_warning "无法连接到远程仓库,使用本地代码"
return 1 # 失败
fi
}
# 检查代码是否有本地修改
has_local_changes() {
# 检查是否有未提交的更改
if ! git diff --quiet 2>/dev/null; then
print_info "检测到未提交的更改"
return 0
fi
# 检查是否有暂存的更改
if ! git diff --cached --quiet 2>/dev/null; then
print_info "检测到暂存的更改"
return 0
fi
return 1 # 无更改
}
2026-03-10 23:09:05 +08:00
# 构建镜像
build_image() {
2026-03-15 22:50:28 +08:00
print_step "准备 ROCm SDK..."
SDK_FILE="therock.tar.gz"
if [ ! -f "$SDK_FILE" ]; then
print_warning "本地未发现 $SDK_FILE,开始宿主机预下载..."
# 自动检测 proxychains
PROXY_WRAPPER=""
if command -v proxychains4 >/dev/null 2>&1; then
PROXY_WRAPPER="proxychains4"
elif command -v proxychains >/dev/null 2>&1; then
PROXY_WRAPPER="proxychains"
fi
# 获取下载地址
BASE="https://therock-nightly-tarball.s3.amazonaws.com"
PREFIX="therock-dist-linux-gfx120X-all-7"
print_info "正在获取最新版本信息..."
CURL_CMD="curl -s"
[ -n "$PROXY_WRAPPER" ] && CURL_CMD="$PROXY_WRAPPER $CURL_CMD"
[ -z "$PROXY_WRAPPER" ] && [ -n "$HTTP_PROXY_ARG" ] && CURL_CMD="curl -x $HTTP_PROXY_ARG -s"
KEY=$($CURL_CMD "${BASE}?list-type=2&prefix=${PREFIX}" \
| tr '<' '\n' \
| grep -o "therock-dist-linux-gfx120X-all-7\..*\.tar\.gz" \
| sort -V | tail -n1)
if [ -n "$KEY" ]; then
DOWNLOAD_URL="${BASE}/${KEY}"
print_info "开始下载: $DOWNLOAD_URL"
# 优先使用 aria2c
if command -v aria2c >/dev/null 2>&1; then
ARIA2_CMD="aria2c -x 16 -s 16 -k 1M -c"
[ -n "$PROXY_WRAPPER" ] && ARIA2_CMD="$PROXY_WRAPPER $ARIA2_CMD"
[ -z "$PROXY_WRAPPER" ] && [ -n "$HTTP_PROXY_ARG" ] && ARIA2_CMD="$ARIA2_CMD --all-proxy=$HTTP_PROXY_ARG"
$ARIA2_CMD "$DOWNLOAD_URL" -o "$SDK_FILE"
else
DL_CMD="curl -L -C - "
[ -n "$PROXY_WRAPPER" ] && DL_CMD="$PROXY_WRAPPER $DL_CMD"
[ -z "$PROXY_WRAPPER" ] && [ -n "$HTTP_PROXY_ARG" ] && DL_CMD="curl -x $HTTP_PROXY_ARG -L -C - "
$DL_CMD "$DOWNLOAD_URL" -o "$SDK_FILE"
fi
else
print_error "无法获取 SDK 下载地址,请检查代理设置或手动下载。"
exit 1
fi
else
print_success "发现本地 $SDK_FILE,跳过下载。"
fi
2026-03-10 23:24:27 +08:00
print_step "开始构建 Docker 镜像..."
2026-03-10 23:09:05 +08:00
print_info "镜像名称:${IMAGE_NAME}:${IMAGE_TAG}"
2026-03-10 23:24:27 +08:00
print_info "构建目录:${PROJECT_DIR}"
2026-03-13 23:38:02 +08:00
print_info "使用缓存策略:未修改的层将使用缓存"
2026-03-10 23:09:05 +08:00
2026-03-10 23:24:27 +08:00
BUILD_START=$(date +%s)
2026-03-10 23:09:05 +08:00
2026-03-15 22:29:11 +08:00
# 准备构建参数
2026-03-17 22:02:45 +08:00
BUILD_ARGS="--network=host --build-arg MAX_JOBS=${MAX_JOBS:-32}"
2026-03-15 22:29:11 +08:00
if [ -n "$HTTP_PROXY_ARG" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg HTTP_PROXY=${HTTP_PROXY_ARG} --build-arg HTTPS_PROXY=${HTTP_PROXY_ARG}"
fi
if [ -n "$SOCKS_PROXY_ARG" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg SOCKS_PROXY=${SOCKS_PROXY_ARG}"
fi
if docker build ${BUILD_ARGS} -t "${IMAGE_NAME}:${IMAGE_TAG}" .; then
2026-03-10 23:24:27 +08:00
BUILD_END=$(date +%s)
BUILD_TIME=$((BUILD_END - BUILD_START))
print_success "镜像构建成功 (耗时:${BUILD_TIME}秒)"
2026-03-10 23:09:05 +08:00
docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
2026-03-10 23:24:27 +08:00
return 0 # 构建成功
2026-03-10 23:09:05 +08:00
else
print_error "镜像构建失败"
exit 1
fi
}
2026-03-10 23:24:27 +08:00
# 检查镜像是否存在
image_exists() {
docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" > /dev/null 2>&1
return $?
}
2026-03-10 23:09:05 +08:00
# 运行容器
run_container() {
2026-03-10 23:24:27 +08:00
print_step "开始部署容器..."
2026-03-10 23:09:05 +08:00
# 设置卷挂载参数
VOLUME_ARGS=""
# 模型目录
if [ -n "$CUSTOM_MODELS" ]; then
MODEL_DIR="$CUSTOM_MODELS"
fi
if [ -d "$MODEL_DIR" ]; then
VOLUME_ARGS="$VOLUME_ARGS -v ${MODEL_DIR}:${LOCAL_MODEL_DIR}"
print_info "模型目录:${MODEL_DIR} -> ${LOCAL_MODEL_DIR}"
else
print_warning "模型目录不存在:${MODEL_DIR}"
print_info "创建模型目录..."
mkdir -p "$MODEL_DIR"
VOLUME_ARGS="$VOLUME_ARGS -v ${MODEL_DIR}:${LOCAL_MODEL_DIR}"
fi
# 配置文件
if [ -n "$CUSTOM_CONFIG" ]; then
CONFIG_FILE="$CUSTOM_CONFIG"
fi
if [ -f "$CONFIG_FILE" ]; then
2026-03-10 23:57:37 +08:00
VOLUME_ARGS="$VOLUME_ARGS -v ${CONFIG_FILE}:${CONFIG_FILE_IN_CONTAINER}"
print_info "配置文件:${CONFIG_FILE} -> ${CONFIG_FILE_IN_CONTAINER}"
2026-03-10 23:09:05 +08:00
else
print_warning "配置文件不存在:${CONFIG_FILE}"
print_info "将使用容器内默认配置"
fi
# 端口设置
PORT_ARG="-p 8000:8000"
if [ -n "$CUSTOM_PORT" ]; then
PORT_ARG="-p ${CUSTOM_PORT}:8000"
fi
# 运行模式
RUN_MODE="-d"
if [ "$DETACH" = false ]; then
RUN_MODE="-it"
fi
# 构建运行命令
print_info "启动容器..."
docker run ${RUN_MODE} \
--name "${CONTAINER_NAME}" \
--device /dev/dri \
--device /dev/kfd \
--group-add video \
--group-add render \
--security-opt seccomp=unconfined \
${VOLUME_ARGS} \
${PORT_ARG} \
-e LOCAL_MODEL_DIR="${LOCAL_MODEL_DIR}" \
"${IMAGE_NAME}:${IMAGE_TAG}"
if [ $? -eq 0 ]; then
print_success "容器启动成功"
if [ "$DETACH" = true ]; then
echo ""
print_info "容器信息:"
echo " 容器名称:${CONTAINER_NAME}"
echo " 镜像:${IMAGE_NAME}:${IMAGE_TAG}"
echo " 状态:运行中"
echo " API 地址:http://localhost:8000"
echo ""
print_info "常用命令:"
echo " 查看日志:docker logs -f ${CONTAINER_NAME}"
echo " 停止容器:docker stop ${CONTAINER_NAME}"
echo " 启动容器:docker start ${CONTAINER_NAME}"
echo " 删除容器:docker rm -f ${CONTAINER_NAME}"
echo " 进入容器:docker exec -it ${CONTAINER_NAME} /bin/bash"
fi
else
print_error "容器启动失败"
exit 1
fi
}
2026-03-10 23:24:27 +08:00
# 智能部署主流程
smart_deploy() {
2026-03-10 23:09:05 +08:00
echo "=========================================="
2026-03-10 23:24:27 +08:00
echo "vLLM R9700 Container 智能部署"
2026-03-10 23:09:05 +08:00
echo "=========================================="
echo "开始时间:$(date '+%Y-%m-%d %H:%M:%S')"
2026-03-11 00:36:26 +08:00
echo "日志文件:${LOG_FILE}"
2026-03-10 23:09:05 +08:00
echo ""
2026-03-10 23:24:27 +08:00
# 检查并切换到项目目录
check_project_dir
2026-03-10 23:09:05 +08:00
# 检查 Docker
check_docker
# 停止旧容器
stop_container
2026-03-10 23:24:27 +08:00
# 检查是否需要重新构建
NEED_REBUILD=false
2026-03-10 23:09:05 +08:00
2026-03-10 23:24:27 +08:00
if [ "$FORCE_BUILD" = true ]; then
print_step "强制重新构建模式"
NEED_REBUILD=true
elif check_git; then
# 是 Git 仓库
print_step "检查代码更新..."
# 拉取最新代码并检查是否有更新
if pull_latest_code; then
NEED_REBUILD=true
print_info "代码有更新,需要重新构建"
elif has_local_changes; then
NEED_REBUILD=true
print_info "检测到本地更改,需要重新构建"
else
# 检查镜像是否存在
if ! image_exists; then
NEED_REBUILD=true
print_info "镜像不存在,需要构建"
else
print_info "代码无更新,跳过构建"
fi
fi
else
# 不是 Git 仓库或 Git 不可用
print_warning "无法检测 Git 状态"
# 检查镜像是否存在
if ! image_exists; then
NEED_REBUILD=true
print_info "镜像不存在,需要构建"
else
print_info "使用现有镜像"
fi
fi
# 构建镜像(如果需要)
if [ "$NEED_REBUILD" = true ]; then
echo ""
build_image
else
print_success "使用现有镜像"
2026-03-10 23:09:05 +08:00
fi
# 运行容器
2026-03-10 23:24:27 +08:00
echo ""
2026-03-10 23:09:05 +08:00
run_container
echo ""
echo "=========================================="
print_success "部署完成"
echo "=========================================="
echo "完成时间:$(date '+%Y-%m-%d %H:%M:%S')"
2026-03-11 00:36:26 +08:00
echo "日志文件:${LOG_FILE}"
echo "最新日志:${LATEST_LOG}"
2026-03-10 23:09:05 +08:00
}
# 执行主流程
2026-03-10 23:24:27 +08:00
smart_deploy