DevOps

Скрипт для надёжного выключения Proxmox-сервера с контролируемой остановкой каждой гостевой системы

5 мин. чтения
Скрипт для надёжного выключения Proxmox-сервера с контролируемой остановкой каждой гостевой системы

Скрипт обеспечивает корректное и контролируемое выключение всех запущенных виртуальных машин (QEMU/KVM) и контейнеров (LXC) перед выключением самого хоста Proxmox. Он дожидается graceful shutdown каждой гостевой системы с настраиваемым таймаутом, а при недостижении ответа — применяет принудительную остановку, что минимизирует риск повреждения данных и гарантирует безопасное завершение работы всего виртуального окружения.

 

plaintext
#!/bin/bash # ============================================================================= # proxmox-shutdown.sh — корректное выключение всех VM, контейнеров и хоста # ============================================================================= # Использование: # chmod +x proxmox-shutdown.sh # sudo ./proxmox-shutdown.sh # # Параметры (необязательно): # TIMEOUT — время ожидания graceful-shutdown одной VM/CT (по умолчанию: 120 сек) # DRY_RUN — только вывод без реального выключения: DRY_RUN=1 ./proxmox-shutdown.sh # ============================================================================= set -euo pipefail TIMEOUT="${TIMEOUT:-120}" DRY_RUN="${DRY_RUN:-0}" LOG_FILE="/var/log/proxmox-shutdown.log" # ---------- Цвета и логирование ---------------------------------------------- RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m' CYAN='\033[0;36m'; NC='\033[0m'; BOLD='\033[1m' log() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"; } info() { log "${CYAN}[INFO]${NC} $*"; } ok() { log "${GREEN}[OK]${NC} $*"; } warn() { log "${YELLOW}[WARN]${NC} $*"; } err() { log "${RED}[ERROR]${NC} $*"; } # ---------- Проверка прав ---------------------------------------------------- if [[ $EUID -ne 0 ]]; then err "Скрипт должен запускаться от root (sudo)" exit 1 fi if ! command -v qm &>/dev/null || ! command -v pct &>/dev/null; then err "Команды qm/pct не найдены — скрипт должен запускаться на хосте Proxmox" exit 1 fi # ---------- Заголовок -------------------------------------------------------- echo "" echo -e "${BOLD}╔══════════════════════════════════════════╗${NC}" echo -e "${BOLD}║ Proxmox Graceful Shutdown Script ║${NC}" echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}" echo "" [[ "$DRY_RUN" == "1" ]] && warn "⚠ DRY RUN — реального выключения не будет" info "Таймаут на одну VM/CT: ${TIMEOUT} сек" info "Лог сохраняется в: $LOG_FILE" echo "" # ---------- Функция выключения VM (QEMU) ------------------------------------- shutdown_vm() { local vmid="$1" local name name=$(qm config "$vmid" 2>/dev/null | grep '^name:' | awk '{print $2}' || echo "vm-$vmid") local status status=$(qm status "$vmid" 2>/dev/null | awk '{print $2}') [[ "$status" != "running" ]] && { info "VM $vmid ($name) уже остановлена ($status), пропускаем"; return 0; } info "Останавливаем VM $vmid ($name)..." [[ "$DRY_RUN" == "1" ]] && return 0 # Попытка graceful shutdown qm shutdown "$vmid" --timeout "$TIMEOUT" 2>>"$LOG_FILE" && { ok "VM $vmid ($name) успешно остановлена" return 0 } warn "VM $vmid ($name) не ответила за ${TIMEOUT}с — принудительное выключение" qm stop "$vmid" 2>>"$LOG_FILE" && ok "VM $vmid ($name) принудительно остановлена" \ || err "Не удалось остановить VM $vmid ($name)!" } # ---------- Функция выключения контейнера (LXC) ------------------------------ shutdown_ct() { local ctid="$1" local name name=$(pct config "$ctid" 2>/dev/null | grep '^hostname:' | awk '{print $2}' || echo "ct-$ctid") local status status=$(pct status "$ctid" 2>/dev/null | awk '{print $2}') [[ "$status" != "running" ]] && { info "CT $ctid ($name) уже остановлен ($status), пропускаем"; return 0; } info "Останавливаем CT $ctid ($name)..." [[ "$DRY_RUN" == "1" ]] && return 0 # Graceful shutdown pct shutdown "$ctid" --timeout "$TIMEOUT" 2>>"$LOG_FILE" && { ok "CT $ctid ($name) успешно остановлен" return 0 } warn "CT $ctid ($name) не ответил за ${TIMEOUT}с — принудительное выключение" pct stop "$ctid" 2>>"$LOG_FILE" && ok "CT $ctid ($name) принудительно остановлен" \ || err "Не удалось остановить CT $ctid ($name)!" } # ---------- Сбор списков VM и CT --------------------------------------------- info "Получаем список запущенных VM и контейнеров..." VM_IDS=$(qm list 2>/dev/null | awk 'NR>1 && $3=="running" {print $1}' | sort -rn) CT_IDS=$(pct list 2>/dev/null | awk 'NR>1 && $2=="running" {print $1}' | sort -rn) VM_COUNT=$(echo "$VM_IDS" | grep -c '[0-9]' 2>/dev/null || echo 0) CT_COUNT=$(echo "$CT_IDS" | grep -c '[0-9]' 2>/dev/null || echo 0) info "Найдено запущенных VM: $VM_COUNT | CT: $CT_COUNT" echo "" # ---------- Останавливаем VM ------------------------------------------------- if [[ -n "$VM_IDS" ]]; then echo -e "${BOLD}── Виртуальные машины (QEMU/KVM) ──────────────────${NC}" for vmid in $VM_IDS; do shutdown_vm "$vmid" done echo "" fi # ---------- Останавливаем контейнеры ----------------------------------------- if [[ -n "$CT_IDS" ]]; then echo -e "${BOLD}── Контейнеры (LXC) ────────────────────────────────${NC}" for ctid in $CT_IDS; do shutdown_ct "$ctid" done echo "" fi # ---------- Ждём полной остановки всех гостей -------------------------------- if [[ "$DRY_RUN" != "1" ]]; then info "Проверяем, что все гостевые системы остановлены..." WAIT=0 while true; do STILL_RUNNING=$( { qm list 2>/dev/null | awk 'NR>1 && $3=="running" {print "VM:"$1}'; \ pct list 2>/dev/null | awk 'NR>1 && $2=="running" {print "CT:"$1}'; } | wc -l ) [[ "$STILL_RUNNING" -eq 0 ]] && break [[ "$WAIT" -ge 30 ]] && { warn "Остались незавершённые процессы, продолжаем..."; break; } sleep 2; WAIT=$((WAIT+2)) done ok "Все гостевые системы остановлены" fi # ---------- Выключаем хост --------------------------------------------------- echo "" echo -e "${BOLD}── Выключение хоста Proxmox ────────────────────────${NC}" if [[ "$DRY_RUN" == "1" ]]; then warn "DRY RUN: команда выключения хоста пропущена (shutdown -h now)" else ok "Выключаем хост Proxmox..." log "--- Конец лога ---" sleep 2 shutdown -h now "Proxmox graceful shutdown via script" fi echo "" ok "Скрипт завершён"

Как использовать:

plaintext
# Скопировать на хост Proxmox затем: chmod +x proxmox-shutdown.sh # Обычное выключение: sudo ./proxmox-shutdown.sh # Пробный прогон (ничего не выключает, только показывает что будет): DRY_RUN=1 ./proxmox-shutdown.sh # С увеличенным таймаутом (например, 3 минуты на VM): TIMEOUT=180 sudo ./proxmox-shutdown.sh

Порядок работы скрипта:

  1. Проверяет права root и наличие qm/pct
  2. Собирает список только запущенных VM и контейнеров
  3. Для каждой VM/CT сначала пытается graceful shutdown (через ACPI/гостевой агент)
  4. Если гость не ответил за TIMEOUT секунд — делает принудительную остановку (stop)
  5. Ждёт подтверждения что все гостевые системы действительно встали
  6. Выключает хост: shutdown -h now
  7. Всё логируется в /var/log/proxmox-shutdown.log

Теги статьи

Новые статьи на почту

Без спама, только полезное

Подписываясь, вы соглашаетесь с политикой конфиденциальности

Читайте также