#!/bin/bash # vLLM R9700 Container 智能部署脚本 # 自动检测 Git 代码更新,智能构建和部署 set -e # ============== 日志配置 ============== 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" # ============== 配置变量(可修改) ============== # 项目目录 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 镜像配置 IMAGE_NAME="custom-vllm-r9700" IMAGE_TAG="latest" CONTAINER_NAME="custom-vllm-r9700" # Git 配置 GIT_BRANCH="${GIT_BRANCH:-main}" # ================================================ # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' 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" } print_step() { echo -e "${CYAN}[STEP]${NC} $1" } # 显示使用帮助 show_usage() { echo "用法:$0 [选项]" echo "" echo "选项:" echo " -f, --force 强制重新构建镜像(忽略 Git 状态)" echo " -s, --stop 仅停止并删除容器" echo " -d, --detach 后台运行容器(默认)" echo " -i, --interactive 交互式运行容器" echo " -c, --config FILE 指定配置文件路径" echo " -m, --models DIR 指定模型目录路径" echo " -p, --port PORT 指定服务端口" echo " -b, --branch NAME 指定 Git 分支(默认:main)" echo " -h, --help 显示此帮助信息" echo "" echo "示例:" echo " $0 # 智能部署(检测 Git 更新)" echo " $0 -f # 强制重新构建并部署" echo " $0 -s # 停止并删除容器" echo " $0 -p 8080 # 使用端口 8080 运行" echo " $0 -b master # 使用 master 分支" exit 0 } # 解析命令行参数 FORCE_BUILD=false STOP_ONLY=false DETACH=true CUSTOM_CONFIG="" CUSTOM_MODELS="" CUSTOM_PORT="" while [[ $# -gt 0 ]]; do case $1 in -f|--force) FORCE_BUILD=true shift ;; -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 ;; -b|--branch) GIT_BRANCH="$2" shift 2 ;; -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 检查通过" } # 检查并切换到项目目录 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 # 无更改 } # 构建镜像 build_image() { print_step "开始构建 Docker 镜像..." print_info "镜像名称:${IMAGE_NAME}:${IMAGE_TAG}" print_info "构建目录:${PROJECT_DIR}" BUILD_START=$(date +%s) if docker build --no-cache -t "${IMAGE_NAME}:${IMAGE_TAG}" .; then BUILD_END=$(date +%s) BUILD_TIME=$((BUILD_END - BUILD_START)) print_success "镜像构建成功 (耗时:${BUILD_TIME}秒)" docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" return 0 # 构建成功 else print_error "镜像构建失败" exit 1 fi } # 检查镜像是否存在 image_exists() { docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" > /dev/null 2>&1 return $? } # 运行容器 run_container() { print_step "开始部署容器..." # 设置卷挂载参数 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 VOLUME_ARGS="$VOLUME_ARGS -v ${CONFIG_FILE}:${CONFIG_FILE_IN_CONTAINER}" print_info "配置文件:${CONFIG_FILE} -> ${CONFIG_FILE_IN_CONTAINER}" 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 } # 智能部署主流程 smart_deploy() { echo "==========================================" echo "vLLM R9700 Container 智能部署" echo "==========================================" echo "开始时间:$(date '+%Y-%m-%d %H:%M:%S')" echo "日志文件:${LOG_FILE}" echo "" # 检查并切换到项目目录 check_project_dir # 检查 Docker check_docker # 停止旧容器 stop_container # 检查是否需要重新构建 NEED_REBUILD=false 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 "使用现有镜像" fi # 运行容器 echo "" run_container echo "" echo "==========================================" print_success "部署完成" echo "==========================================" echo "完成时间:$(date '+%Y-%m-%d %H:%M:%S')" echo "日志文件:${LOG_FILE}" echo "最新日志:${LATEST_LOG}" } # 执行主流程 smart_deploy