Pour eviter de perdre nos machines virtuelles, je vais vous montrer comment sauvegarder les machines virtuelles en se basant sur un fichier texte ou l'on indique les noms des VM.
Pour effectuer ce script, je me suis basé sur celui de ghettoVCB disponible sur le site VMware.
Dans mon cas, le script va :
- Lire le fichier texte nommé listSauvegarde
- Indiquer les l'ID des machines à sauvegarder dans un répertoire "vms_list" dans "/tmp"
- Avec l'ID il va supprimer l'ensemble des snapshots (obligatoire pour sauvegarder les VM)
- Créer un fichier de log
- Envoi le fichier de log par email
- Création d'une arborescence claire pour la sauvegarde
Dans un premier temps, il faut transferer gràce à WinSCP le fichier "sauvegarde.sh" dans le répertoire :"/vmfs/volumes/<datastore1>/" puis le rendre exécutable en SSH : (pour activer le SSH, vous pouvez consulter mon article))
chmod +x sauvegarde.sh
Ensuite, il faut créer le fichier "listSauvegarde" avec la commande :
vi listSauvegarde
Indiquer le nom des machinves virtuelles sous la forme suivante :
VM01 VM02 VM03
Sauvegarder et quitter gràce à ":wq".
Pour effectuer la sauvegarde, il faut saisir la commande suivante en SSH :
./sauvegarde.sh –f listSauvegarde
Pour automatiser l’execution des scripts, il existe sous linux « Cron » qui est l’equivalent des taches planifié sous Windows. Sous Esxi, le fichier de configuration se trouve « /var/spool/cron/crontabs/root ». J’ai rajouté deux lignes à la suite indiquant : Executer le script « sauvegarde.sh » en tenant compte du fichier « listeSauvegarde » le Vendredi à 22h01
« 0 20 * * 5 /vmfs/volumes/4d2aed1f-7a700542-ecbc-00237d9db922/sauvegarde.sh -f /vmfs/volumes/4d2aed1f-7a700542-ecbc-00237d9db922/listSauvegarde »
Pour que le fichier de configuration cron soit pris en compte, il faut redemarrer le service avec les commandes suivante :
Kill $(cat /vaar/run/crond.pid)
Busybox crond
Attention, lors du prochain redemarrage, le fichier de configuration cron sera reinitialisé. Pour eviter de perdre la configuration, j’ai rajouté dans le script de demarrage du serveur quatre ligne qui rajoutera automatiquement les lignes de configuration dans le fichier cron et redemarrera le service :
/bin/kill $(cat /var/run/crond.pid)
/bin/echo "0 20 * * 5 /vmfs/volumes/4d2aed1f-7a700542-ecbc-00237d9db922/sauvegarde.sh -f /vmfs/volumes/4d2aed1f-7a700542-ecbc-00237d9db922/listSauvegarde" >> /var/spool/cron/crontabs/root
/bin/busybox crond
Voici le script commenté : (Champ à modifier jusqu'a la ligne 125)
export VMWARE_CMD=/bin/vim-cmd while read line do echo "VM_NAME: $line" # get VM_ID ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]{3,}/ /g' | awk -F' ' '{}' | sed 's/] /]";"/g' | sed '1,1d' export VM_ID=`grep -E ""${line}"" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'` echo "VM_ID: $VM_ID" ${VMWARE_CMD} vmsvc/snapshot.removeall $VM_ID echo "--------------------" done < /vmfs/volumes/4d27644c-244cadc9-7b49-000c297ad13c/listSauvegarde # Chemin du fichier listSauvegarde ################################################################## # Author: William Lam # Created Date: 11/17/2008 # http://www.virtuallyghetto.com/ # http://communities.vmware.com/docs/DOC-8760 ################################################################## # directory that all VM backups should go (e.g. /vmfs/volumes/SAN_LUN1/mybackupdir) VM_BACKUP_VOLUME=/vmfs/volumes/Sauvegarde # Format output of VMDK backup # zeroedthick # 2gbsparse # thin # eagerzeroedthick DISK_BACKUP_FORMAT=thin # Number of backups for a given VM before deleting VM_BACKUP_ROTATION_COUNT=1 # Shutdown guestOS prior to running backups and power them back on afterwards # This feature assumes VMware Tools are installed, else they will not power down and loop forever # 1=on, 0 =off POWER_VM_DOWN_BEFORE_BACKUP=0 # enable shutdown code 1=on, 0 = off ENABLE_HARD_POWER_OFF=0 # if the above flag "ENABLE_HARD_POWER_OFF "is set to 1, then will look at this flag which is the # of iterations # the script will wait before executing a hard power off, this will be a multiple of 60seconds # (e.g) = 3, which means this will wait up to 180seconds (3min) before it just powers off the VM ITER_TO_WAIT_SHUTDOWN=3 # Number of iterations the script will wait before giving up on powering down the VM and ignoring it for backup # this will be a multiple of 60 (e.g) = 5, which means this will wait up to 300secs (5min) before it gives up POWER_DOWN_TIMEOUT=5 # enable compression with gzip+tar 1=on, 0=off ENABLE_COMPRESSION=0 ############################ ####### NEW PARAMS ######### ############################ # Disk adapter type: buslogic, lsilogic or ide ADAPTER_FORMAT=buslogic # Include VMs memory when taking snapshot VM_SNAPSHOT_MEMORY=1 # Quiesce VM when taking snapshot (requires VMware Tools to be installed) VM_SNAPSHOT_QUIESCE=1 ########################################################## # NON-PERSISTENT NFS-BACKUP ONLY # # ENABLE NON PERSISTENT NFS BACKUP 1=on, 0=off ENABLE_NON_PERSISTENT_NFS=1 # umount NFS datastore after backup is complete 1=yes, 0=no UNMOUNT_NFS=0 # IP Address of NFS Server NFS_SERVER="172.16.0.254" # Path of exported folder residing on NFS Server (e.g. /some/mount/point ) NFS_MOUNT=/ESXI # Non-persistent NFS datastore display name of choice NFS_LOCAL_NAME=Sauvegarde # Name of backup directory for VMs residing on the NFS volume NFS_VM_BACKUP_DIR=ESXI ############################ ######### EMAIL ############ ############################ # Email debug 1=yes, 0=no EMAIL_DEBUG=1 # Email log 1=yes, 0=no EMAIL_LOG=1 # Email SMTP server EMAIL_SERVER=172.16.0.253 # Email SMTP server port EMAIL_SERVER_PORT=25 # Email FROM EMAIL_FROM=ESXI@tiennot.fr # Email RCPT EMAIL_TO=romain@tiennot.fr ########################## DO NOT MODIFY PAST THIS LINE ########################## # RSYNC LINK 1=yes, 0 = no RSYNC_LINK=0 LOG_LEVEL="info" VMDK_FILES_TO_BACKUP="all" # default 15min timeout SNAPSHOT_TIMEOUT=15 LAST_MODIFIED_DATE=09/28/2010 VER=1 # Directory naming convention for backup rotations (please ensure there are no spaces!) VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F_%H-%M-%S)" printUsage() { echo "###############################################################################" echo "#" echo "# Script de sauvegarde ESXI" echo "# Author: William Lam" echo "# Modifier: Romain Tiennot" echo "# http://romain.tiennot.fr/" echo "# Created: 19/02/2011" echo "# Last modified: ${LAST_MODIFIED_DATE} version ${VER}" echo "#" echo "###############################################################################" echo echo "Usage: $0 -f [VM_BACKUP_UP_LIST] -c [VM_CONFIG_DIR] -l [LOG_FILE] -d [DEBUG_LEVEL] -g [GLOBAL_CONF] -e [VM_EXCLUSION_LIST]" echo echo "OPTIONS:" echo " -a Backup all VMs on host" echo " -f List of VMs to backup" echo " -c VM configuration directory for VM backups" echo " -g Path to global ghettoVCB configuration file" echo " -l File to output logging" echo " -d Debug level [info|debug|dryrun] (default: info)" echo echo "(e.g.)" echo -e "\nBackup VMs stored in a list" echo -e "\t$0 -f vms_to_backup" echo -e "\nBackup all VMs residing on this host" echo -e "\t$0 -a" echo -e "\nBackup all VMs residing on this host except for the VMs in the exclusion list" echo -e "\t$0 -a -e vm_exclusion_list" echo -e "\nBackup VMs based on specific configuration located in directory" echo -e "\t$0 -f vms_to_backup -c vm_backup_configs" echo -e "\nBackup VMs using global ghettoVCB configuration file" echo -e "\t$0 -f vms_to_backup -g /global/ghettoVCB.conf" echo -e "\nOutput will log to /tmp/ghettoVCB.log" echo -e "\t$0 -f vms_to_backup -l /tmp/ghettoVCB.log" echo -e "\nDry run (no backup will take place)" echo -e "\t$0 -f vms_to_backup -d dryrun" echo exit 1 } logger() { LOG_TYPE=$1 MSG=$2 if [[ "${LOG_LEVEL}" == "debug" ]] && [[ "${LOG_TYPE}" == "debug" ]] || [[ "${LOG_TYPE}" == "info" ]] || [[ "${LOG_TYPE}" == "dryrun" ]]; then TIME=$(date +%F" "%H:%M:%S) if [ "${LOG_TO_STDOUT}" -eq 1 ]; then echo -e "${TIME} -- ${LOG_TYPE}: ${MSG}" else echo -e "${TIME} -- ${LOG_TYPE}: ${MSG}" >> "${LOG_OUTPUT}" fi if [ "${EMAIL_LOG}" -eq 1 ]; then echo -ne "${TIME} -- ${LOG_TYPE}: ${MSG}\r\n" >> "${EMAIL_LOG_OUTPUT}" fi fi } sanityCheck() { NUM_OF_ARGS=$1 if [ "${USE_GLOBAL_CONF}" -eq 1 ]; then reConfigureGhettoVCBConfiguration "${GLOBAL_CONF}" fi #log to stdout or to logfile if [ -z "${LOG_OUTPUT}" ]; then LOG_TO_STDOUT=1 REDIRECT=/dev/null else LOG_TO_STDOUT=0 REDIRECT=${LOG_OUTPUT} echo "Logging output to \"${LOG_OUTPUT}\" ..." touch "${LOG_OUTPUT}" fi if [[ ${NUM_OF_ARGS} -lt 1 ]] || [[ ${NUM_OF_ARGS} -gt 12 ]]; then logger "info" "ERROR: Incorrect number of arguments!" printUsage fi if [[ ! -f "${VM_FILE}" ]] && [[ "${USE_VM_CONF}" -eq 0 ]] && [[ "${BACKUP_ALL_VMS}" -eq 0 ]]; then logger "info" "ERROR: \"${VM_FILE}\" is not valid VM input file!" printUsage fi if [[ ! -f "${VM_EXCLUSION_FILE}" ]] && [[ "${EXCLUDE_SOME_VMS}" -eq 1 ]]; then logger "info" "ERROR: \"${VM_EXCLUSION_FILE}\" is not valid VM exclusion input file!" printUsage fi if [[ ! -d "${CONFIG_DIR}" ]] && [[ "${USE_VM_CONF}" -eq 1 ]]; then logger "info" "ERROR: \"${CONFIG_DIR}\" is not valid directory!" printUsage fi if [[ ! -f "${GLOBAL_CONF}" ]] && [[ "${USE_GLOBAL_CONF}" -eq 1 ]]; then logger "info" "ERROR: \"${GLOBAL_CONF}\" is not valid global configuration file!" printUsage fi if [ -f /usr/bin/vmware-vim-cmd ]; then VMWARE_CMD=/usr/bin/vmware-vim-cmd VMKFSTOOLS_CMD=/usr/sbin/vmkfstools elif [ -f /bin/vim-cmd ]; then VMWARE_CMD=/bin/vim-cmd VMKFSTOOLS_CMD=/sbin/vmkfstools else logger "info" "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+ or 4.0+!" echo "ERROR: Unable to locate *vimsh*! You're not running ESX(i) 3.5+ or 4.0+!" exit 1 fi ESX_VERSION=$(vmware -v | awk '{print $3}') if [[ "${ESX_VERSION}" == "4.0.0" ]] || [[ "${ESX_VERSION}" == "4.1.0" ]]; then VER=4 else ESX_VERSION=$(vmware -v | awk '{print $4}') if [[ "${ESX_VERSION}" == "3.5.0" ]] || [[ "${ESX_VERSION}" == "3i" ]]; then VER=3 else echo "You're not running ESX(i) 3.5+ or 4.0+!" exit 1 fi fi if [[ "${EMAIL_LOG}" -eq 1 ]] && [[ -f /usr/bin/nc ]] || [[ -f /bin/nc ]]; then if [ -f /usr/bin/nc ]; then NC_BIN=/usr/bin/nc elif [ -f /bin/nc ]; then NC_BIN=/bin/nc fi echo -ne "HELO $(hostname -s)\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "MAIL FROM: <${EMAIL_FROM}>\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "RCPT TO: <${EMAIL_TO}>\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "DATA\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "From: ${EMAIL_FROM}\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "To: ${EMAIL_TO}\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -ne "Subject: ESXI - $(date)\r\n" >> "${EMAIL_LOG_OUTPUT}" else EMAIL_LOG=0 fi if [ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ]; then ${VMWARE_CMD} hostsvc/summary/fsvolume | awk '{print $1}' | grep "^${NFS_LOCAL_NAME}" > /dev/null 2>&1 if [ ! $? -eq 0 ]; then #1 = readonly #0 = readwrite ${VMWARE_CMD} hostsvc/datastore/nas_create "${NFS_LOCAL_NAME}" "${NFS_SERVER}" "${NFS_MOUNT}" 0 fi fi if [ ! "`whoami`" == "root" ]; then logger "info" "This script needs to be executed by \"root\"!" echo "ERROR: This script needs to be executed by \"root\"!" exit 1 fi } startTimer() { START_TIME=$(date) S_TIME=$(date +%s) } endTimer() { END_TIME=$(date) E_TIME=$(date +%s) DURATION=$(echo $((E_TIME - S_TIME))) #calculate overall completion time if [ ${DURATION} -le 60 ]; then logger "info" "Backup Duration: ${DURATION} Seconds" else logger "info" "Backup Duration: $(awk 'BEGIN{ printf "%.2f\n", '${DURATION}'/60}') Minutes" fi } captureDefaultConfigurations() { DEFAULT_VM_BACKUP_VOLUME="${VM_BACKUP_VOLUME}" DEFAULT_DISK_BACKUP_FORMAT="${DISK_BACKUP_FORMAT}" DEFAULT_VM_BACKUP_ROTATION_COUNT="${VM_BACKUP_ROTATION_COUNT}" DEFAULT_POWER_VM_DOWN_BEFORE_BACKUP="${POWER_VM_DOWN_BEFORE_BACKUP}" DEFAULT_ENABLE_HARD_POWER_OFF="${ENABLE_HARD_POWER_OFF}" DEFAULT_ITER_TO_WAIT_SHUTDOWN="${ITER_TO_WAIT_SHUTDOWN}" DEFAULT_POWER_DOWN_TIMEOUT="${POWER_DOWN_TIMEOUT}" DEFAULT_SNAPSHOT_TIMEOUT="${SNAPSHOT_TIMEOUT}" DEFAULT_ENABLE_COMPRESSION="${ENABLE_COMPRESSION}" DEFAULT_ADAPTER_FORMAT="${ADAPTER_FORMAT}" DEFAULT_VM_SNAPSHOT_MEMORY="${VM_SNAPSHOT_MEMORY}" DEFAULT_VM_SNAPSHOT_QUIESCE="${VM_SNAPSHOT_QUIESCE}" DEFAULT_VMDK_FILES_TO_BACKUP="${VMDK_FILES_TO_BACKUP}" DEFAULT_EMAIL_LOG="${EMAIL_LOG}" DEFAULT_EMAIL_DEBUG="${EMAIL_DEBUG}" } useDefaultConfigurations() { VM_BACKUP_VOLUME="${DEFAULT_VM_BACKUP_VOLUME}" DISK_BACKUP_FORMAT="${DEFAULT_DISK_BACKUP_FORMAT}" VM_BACKUP_ROTATION_COUNT="${DEFAULT_VM_BACKUP_ROTATION_COUNT}" POWER_VM_DOWN_BEFORE_BACKUP="${DEFAULT_POWER_VM_DOWN_BEFORE_BACKUP}" ENABLE_HARD_POWER_OFF="${DEFAULT_ENABLE_HARD_POWER_OFF}" ITER_TO_WAIT_SHUTDOWN="${DEFAULT_ITER_TO_WAIT_SHUTDOWN}" POWER_DOWN_TIMEOUT="${DEFAULT_POWER_DOWN_TIMEOUT}" SNAPSHOT_TIMEOUT="${DEFAULT_SNAPSHOT_TIMEOUT}" ENABLE_COMPRESSION="${DEFAULT_ENABLE_COMPRESSION}" ADAPTER_FORMAT="${DEFAULT_ADAPTER_FORMAT}" VM_SNAPSHOT_MEMORY="${DEFAULT_VM_SNAPSHOT_MEMORY}" VM_SNAPSHOT_QUIESCE="${DEFAULT_VM_SNAPSHOT_QUIESCE}" VMDK_FILES_TO_BACKUP="all" EMAIL_LOG=0 EMAIL_DEBUG=0 } reConfigureGhettoVCBConfiguration() { GLOBAL_CONF=$1 if [ -f "${GLOBAL_CONF}" ]; then . "${GLOBAL_CONF}" else useDefaultConfigurations fi } reConfigureBackupParam() { VM=$1 if [ -e "${CONFIG_DIR}/${VM}" ]; then logger "info" "CONFIG - USING CONFIGURATION FILE = ${CONFIG_DIR}/${VM}" . "${CONFIG_DIR}/${VM}" else useDefaultConfigurations fi } dumpHostInfo() { logger "debug" "HOST BUILD: $(vmware -v)" logger "debug" "HOSTNAME: $(hostname)\n" } findVMDK() { VMDK_TO_SEARCH_FOR=$1 if [ "${USE_VM_CONF}" -eq 1 ]; then logger "debug" "findVMDK() - Searching for VMDK: \"${VMDK_TO_SEARCH_FOR}\" to backup" OLD_IFS2="${IFS}" IFS="," for k in ${VMDK_FILES_TO_BACKUP} do VMDK_FILE=$(echo $k | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//') if [ "${VMDK_FILE}" == "${VMDK_TO_SEARCH_FOR}" ]; then logger "debug" "findVMDK() - Found VMDK! - \"${VMDK_TO_SEARCH_FOR}\" to backup" isVMDKFound=1 fi done IFS="{OLD_IFS2}" fi } getVMDKs() { #get all VMDKs listed in .vmx file VMDKS_FOUND=`grep -iE '(scsi|ide)' "${VMX_PATH}" | grep -i fileName | awk -F " " '{print $1}'` TMP_IFS=${IFS} IFS=${ORIG_IFS} #loop through each disk and verify that it's currently present and create array of valid VMDKS for DISK in ${VMDKS_FOUND}; do #extract the SCSI ID and use it to check for valid vmdk disk SCSI_ID=`echo ${DISK%%.*}` grep -i "${SCSI_ID}.present" "${VMX_PATH}" | grep -i "true" > /dev/null 2>&1 #if valid, then we use the vmdk file if [ $? -eq 0 ]; then #verify disk is not independent grep -i "${SCSI_ID}.mode" "${VMX_PATH}" | grep -i "independent" > /dev/null 2>&1 if [ $? -eq 1 ]; then grep -i "${SCSI_ID}.deviceType" "${VMX_PATH}" | grep -i "scsi-hardDisk" > /dev/null 2>&1 #if we find the device type is of scsi-disk, then proceed if [ $? -eq 0 ]; then DISK=`grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | awk -F "\"" '{print $2}'` VMDKS="${DISK}:${VMDKS}" else #if the deviceType is NULL for IDE which it is, thanks for the inconsistency VMware #we'll do one more level of verification by checking to see if an ext. of .vmdk exists #since we can not rely on the deviceType showing "ide-hardDisk" grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | grep -i ".vmdk" > /dev/null 2>&1 if [ $? -eq 0 ]; then DISK=`grep -i ${SCSI_ID}.fileName "${VMX_PATH}" | awk -F "\"" '{print $2}'` VMDKS="${DISK}:${VMDKS}" fi fi fi fi done IFS=${TMP_IFS} } dumpVMConfigurations() { logger "info" "CONFIG - GHETTOVCB_PID = ${GHETTOVCB_PID}" logger "info" "CONFIG - VM_BACKUP_VOLUME = ${VM_BACKUP_VOLUME}" logger "info" "CONFIG - VM_BACKUP_ROTATION_COUNT = ${VM_BACKUP_ROTATION_COUNT}" logger "info" "CONFIG - VM_BACKUP_DIR_NAMING_CONVENTION = ${VM_BACKUP_DIR_NAMING_CONVENTION}" logger "info" "CONFIG - DISK_BACKUP_FORMAT = ${DISK_BACKUP_FORMAT}" logger "info" "CONFIG - ADAPTER_FORMAT = ${ADAPTER_FORMAT}" logger "info" "CONFIG - POWER_VM_DOWN_BEFORE_BACKUP = ${POWER_VM_DOWN_BEFORE_BACKUP}" logger "info" "CONFIG - ENABLE_HARD_POWER_OFF = ${ENABLE_HARD_POWER_OFF}" logger "info" "CONFIG - ITER_TO_WAIT_SHUTDOWN = ${ITER_TO_WAIT_SHUTDOWN}" logger "info" "CONFIG - POWER_DOWN_TIMEOUT = ${POWER_DOWN_TIMEOUT}" logger "info" "CONFIG - SNAPSHOT_TIMEOUT = ${SNAPSHOT_TIMEOUT}" logger "info" "CONFIG - LOG_LEVEL = ${LOG_LEVEL}" if [ -z "${LOG_OUTPUT}" ]; then logger "info" "CONFIG - BACKUP_LOG_OUTPUT = stdout" else logger "info" "CONFIG - BACKUP_LOG_OUTPUT = ${LOG_OUTPUT}" fi logger "info" "CONFIG - VM_SNAPSHOT_MEMORY = ${VM_SNAPSHOT_MEMORY}" logger "info" "CONFIG - VM_SNAPSHOT_QUIESCE = ${VM_SNAPSHOT_QUIESCE}" logger "info" "CONFIG - VMDK_FILES_TO_BACKUP = ${VMDK_FILES_TO_BACKUP}" logger "info" "CONFIG - EMAIL_LOG = ${EMAIL_LOG}" if [ "${EMAIL_LOG}" -eq 1 ]; then logger "info" "CONFIG - EMAIL_DEBUG = ${EMAIL_DEBUG}" logger "info" "CONFIG - EMAIL_SERVER = ${EMAIL_SERVER}" logger "info" "CONFIG - EMAIL_SERVER_PORT = ${EMAIL_SERVER_PORT}" logger "info" "CONFIG - EMAIL_FROM = ${EMAIL_FROM}" logger "info" "CONFIG - EMAIL_TO = ${EMAIL_TO}" fi logger "info" "\n" } checkVMBackupRotation() { local BACKUP_DIR_PATH=$1 #default rotation if variable is not defined if [ -z ${VM_BACKUP_ROTATION_COUNT} ]; then VM_BACKUP_ROTATION_COUNT=1 fi LIST_BACKUPS=$(ls -t "${BACKUP_DIR_PATH}") BACKUPS_TO_KEEP=$(ls -t "${BACKUP_DIR_PATH}" | head -"${VM_BACKUP_ROTATION_COUNT}") ORIG_IFS=${IFS} IFS=' ' for i in ${LIST_BACKUPS}; do FOUND=0 for j in ${BACKUPS_TO_KEEP}; do if [ $i == $j ]; then FOUND=1 fi done if [ $FOUND -eq 0 ]; then logger "debug" "Removing $BACKUP_DIR_PATH/$i" rm -rf "$BACKUP_DIR_PATH/$i" #NFS I/O error handling hack if [ $? -ne 0 ]; then NFS_IO_HACK_COUNTER=0 NFS_IO_HACK_STATUS=0 NFS_IO_HACK_FILECHECK="$BACKUP_DIR_PATH/nfs_io.check" while [ "${NFS_IO_HACK_STATUS}" -eq 0 -a "${NFS_IO_HACK_COUNTER}" -lt 60 ]; do sleep 1 NFS_IO_HACK_COUNTER=$((NFS_IO_HACK_COUNTER+1)) touch "${NFS_IO_HACK_FILECHECK}" if [ $? -eq 0 ]; then NFS_IO_HACK_STATUS=1 fi done rm -rf "${NFS_IO_HACK_FILECHECK}" if [ "${NFS_IO_HACK_STATUS}" -eq 1 ]; then logger "info" "Slept ${NFS_IO_HACK_COUNTER} seconds to work around NFS I/O error" else logger "info" "Slept ${NFS_IO_HACK_COUNTER} seconds but failed work around for NFS I/O error" fi fi fi done IFS=${ORIG_IFS} } ghettoVCB() { VM_INPUT=$1 VM_OK=0 VM_FAILED=0 VMDK_FAILED=0 captureDefaultConfigurations dumpHostInfo if [ "${USE_GLOBAL_CONF}" -eq 1 ]; then logger "info" "CONFIG - USING GLOBAL GHETTOVCB CONFIGURATION FILE = ${GLOBAL_CONF}" fi if [ "${USE_VM_CONF}" -eq 0 ]; then dumpVMConfigurations fi #dump out all virtual machines allowing for spaces now ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print "\""$1"\";\""$2"\";\""$3"\""}' | sed 's/\] /\]\";\"/g' | sed '1,1d' > /tmp/vms_list if [ "${BACKUP_ALL_VMS}" -eq 1 ]; then ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print ""$2""}' | sed '1,1d' | sed '/^$/d' > "${VM_INPUT}" fi ORIG_IFS=${IFS} IFS=' ' for VM_NAME in `cat "${VM_INPUT}" | grep -v "#" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`; do IGNORE_VM=0 if [ "${EXCLUDE_SOME_VMS}" -eq 1 ]; then grep -E "${VM_NAME}" "${VM_EXCLUSION_FILE}" > /dev/null 2>&1 if [ $? -eq 0 ]; then IGNORE_VM=1 fi fi VM_ID=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'` #ensure default value if one is not selected or variable is null if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F_%k-%M-%S)" fi if [[ "${USE_VM_CONF}" -eq 1 ]] && [[ ! -z ${VM_ID} ]]; then reConfigureBackupParam "${VM_NAME}" dumpVMConfigurations fi VMFS_VOLUME=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'` VMX_CONF=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/\[//;s/\]//;s/"//g'` VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}" VMX_DIR=`dirname "${VMX_PATH}"` #ignore VM as it's in the exclusion list if [ "${IGNORE_VM}" -eq 1 ]; then logger "debug" "Ignoring ${VM_NAME} for backup since its located in exclusion list\n" #checks to see if we can pull out the VM_ID elif [ -z ${VM_ID} ]; then logger "info" "ERROR: failed to locate and extract VM_ID for ${VM_NAME}!\n" VM_FAILED=1 elif [ "${LOG_LEVEL}" == "dryrun" ]; then logger "dryrun" "###############################################" logger "dryrun" "Virtual Machine: $VM_NAME" logger "dryrun" "VM_ID: $VM_ID" logger "dryrun" "VMX_PATH: $VMX_PATH" logger "dryrun" "VMX_DIR: $VMX_DIR" logger "dryrun" "VMX_CONF: $VMX_CONF" logger "dryrun" "VMFS_VOLUME: $VMFS_VOLUME" logger "dryrun" "VMDK(s): " getVMDKs OLD_IFS="${IFS}" IFS=":" for j in ${VMDKS}; do logger "dryrun" "\t${j}" done IFS="${OLD_IFS}" VMDKS="" logger "dryrun" "###############################################\n" #checks to see if the VM has any snapshots to start with elif ls "${VMX_DIR}" | grep -q delta > /dev/null 2>&1; then logger "info" "Snapshot found for ${VM_NAME}, backup will not take place\n" VM_FAILED=1 elif [[ -f "${VMX_PATH}" ]] && [[ ! -z "${VMX_PATH}" ]]; then #nfs case and backup to root path of your NFS mount if [ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ] ; then BACKUP_DIR="/vmfs/volumes/${NFS_LOCAL_NAME}/${NFS_VM_BACKUP_DIR}/${VM_NAME}" if [[ -z ${VM_NAME} ]] || [[ -z ${NFS_LOCAL_NAME} ]] || [[ -z ${NFS_VM_BACKUP_DIR} ]]; then logger "info" "ERROR: Variable BACKUP_DIR was not set properly, please ensure all required variables for non-persistent NFS backup option has been defined" exit 1 fi #non-nfs (SAN,LOCAL) else BACKUP_DIR="${VM_BACKUP_VOLUME}/${VM_NAME}" if [[ -z ${VM_BACKUP_VOLUME} ]]; then logger "info" "ERROR: Variable VM_BACKUP_DIR was not defined" exit 1 fi fi #initial root VM backup directory if [ ! -d "${BACKUP_DIR}" ]; then mkdir -p "${BACKUP_DIR}" if [ ! -d "${BACKUP_DIR}" ]; then logger "info" "Unable to create \"${BACKUP_DIR}\"! - Ensure VM_BACKUP_VOLUME was defined correctly" exit 1 fi fi # directory name of the individual Virtual Machine backup followed by naming convention followed by count VM_BACKUP_DIR="${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}" mkdir -p "${VM_BACKUP_DIR}" cp "${VMX_PATH}" "${VM_BACKUP_DIR}" #extract all valid VMDK(s) from VM getVMDKs ORGINAL_VM_POWER_STATE=$(${VMWARE_CMD} vmsvc/power.getstate ${VM_ID} | tail -1) CONTINUE_TO_BACKUP=1 #section that will power down a VM prior to taking a snapshot and backup and power it back on if [ ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]; then START_ITERATION=0 logger "info" "Powering off initiated for ${VM_NAME}, backup will not begin until VM is off..." ${VMWARE_CMD} vmsvc/power.shutdown ${VM_ID} > /dev/null 2>&1 while ${VMWARE_CMD} vmsvc/power.getstate ${VM_ID} | grep -i "Powered on" > /dev/null 2>&1; do #enable hard power off code if [ ${ENABLE_HARD_POWER_OFF} -eq 1 ]; then if [ ${START_ITERATION} -ge ${ITER_TO_WAIT_SHUTDOWN} ]; then logger "info" "Hard power off occured for ${VM_NAME}, waited for $((ITER_TO_WAIT_SHUTDOWN*60)) seconds" ${VMWARE_CMD} vmsvc/power.off ${VM_ID} > /dev/null 2>&1 #this is needed for ESXi, even the hard power off did not take affect right away sleep 60 break fi fi logger "info" "VM is still on - Iteration: ${START_ITERATION} - sleeping for 60secs (Duration: $((START_ITERATION*60)) seconds)" sleep 60 #logic to not backup this VM if unable to shutdown #after certain timeout period if [ ${START_ITERATION} -ge ${POWER_DOWN_TIMEOUT} ]; then logger "info" "Unable to power off ${VM_NAME}, waited for $((POWER_DOWN_TIMEOUT*60)) seconds! Ignoring ${VM_NAME} for backup!" VM_FAILED=1 CONTINUE_TO_BACKUP=0 break fi START_ITERATION=$((START_ITERATION + 1)) done if [ ${CONTINUE_TO_BACKUP} -eq 1 ]; then logger "info" "VM is powerdOff" fi fi if [ ${CONTINUE_TO_BACKUP} -eq 1 ]; then logger "info" "Initiate backup for ${VM_NAME}" startTimer SNAP_SUCCESS=1 VM_VMDK_FAILED=0 #powered on VMs only if [[ ! ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" != "Powered off" ]]; then SNAPSHOT_NAME="ghettoVCB-snapshot-$(date +%F)" logger "info" "Creating Snapshot \"${SNAPSHOT_NAME}\" for ${VM_NAME}" ${VMWARE_CMD} vmsvc/snapshot.create ${VM_ID} "${SNAPSHOT_NAME}" "${SNAPSHOT_NAME}" "${VM_SNAPSHOT_MEMORY}" "${VM_SNAPSHOT_QUIESCE}" > /dev/null 2>&1 logger "debug" "Waiting for snapshot \"${SNAPSHOT_NAME}\" to be created" logger "debug" "Snapshot timeout set to: $((SNAPSHOT_TIMEOUT*60)) seconds" START_ITERATION=0 while [ $(${VMWARE_CMD} vmsvc/snapshot.get ${VM_ID} | wc -l) -eq 1 ] do if [ ${START_ITERATION} -ge ${SNAPSHOT_TIMEOUT} ]; then logger "info" "Snapshot timed out, failed to create snapshot: \"${SNAPSHOT_NAME}\" for ${VM_NAME}" SNAP_SUCCESS=0 break fi logger "debug" "Waiting for snapshot creation to be completed - Iteration: ${START_ITERATION} - sleeping for 60secs (Duration: $((START_ITERATION*30)) seconds)" sleep 60 START_ITERATION=$((START_ITERATION + 1)) done fi if [ ${SNAP_SUCCESS} -eq 1 ]; then OLD_IFS="${IFS}" IFS=":" for j in ${VMDKS}; do VMDK="${j}" isVMDKFound=0 findVMDK "${VMDK}" if [[ $isVMDKFound -eq 1 ]] || [[ "${VMDK_FILES_TO_BACKUP}" == "all" ]]; then #added this section to handle VMDK(s) stored in different datastore than the VM echo ${VMDK} | grep "^/vmfs/volumes" > /dev/null 2>&1 if [ $? -eq 0 ]; then SOURCE_VMDK="${VMDK}" DS_UUID="$(echo ${VMDK#/vmfs/volumes/*})" DS_UUID="$(echo ${DS_UUID%/*/*})" VMDK_DISK="$(echo ${VMDK##/*/})" mkdir -p "${VM_BACKUP_DIR}/${DS_UUID}" DESTINATION_VMDK="${VM_BACKUP_DIR}/${DS_UUID}/${VMDK_DISK}" else SOURCE_VMDK="${VMX_DIR}/${VMDK}" DESTINATION_VMDK="${VM_BACKUP_DIR}/${VMDK}" fi #support for vRDM and deny pRDM grep "vmfsPassthroughRawDeviceMap" "${SOURCE_VMDK}" > /dev/null 2>&1 if [ $? -eq 1 ]; then FORMAT_OPTION="UNKNOWN" if [ "${DISK_BACKUP_FORMAT}" == "zeroedthick" ]; then if [ "${VER}" == "4" ]; then FORMAT_OPTION="zeroedthick" else FORMAT_OPTION="" fi elif [ "${DISK_BACKUP_FORMAT}" == "2gbsparse" ]; then FORMAT_OPTION="2gbsparse" elif [ "${DISK_BACKUP_FORMAT}" == "thin" ]; then FORMAT_OPTION="thin" elif [ "${DISK_BACKUP_FORMAT}" == "eagerzeroedthick" ]; then if [ "${VER}" == "4" ]; then FORMAT_OPTION="eagerzeroedthick" else FORMAT_OPTION="" fi fi if [ "${FORMAT_OPTION}" == "UNKNOWN" ]; then logger "info" "ERROR: wrong DISK_BACKUP_FORMAT \"${DISK_BACKUP_FORMAT}\ specified for ${VM_NAME}" VM_VMDK_FAILED=1 else VMDK_OUTPUT=$(mktemp /tmp/ghettovcb.XXXXXX) tail -f "${VMDK_OUTPUT}" & TAIL_PID=$! if [ -z "${FORMAT_OPTION}" ] ; then logger "debug" "${VMKFSTOOLS_CMD} -i \"${SOURCE_VMDK}\" -a \"${ADAPTER_FORMAT}\" \"${DESTINATION_VMDK}\"" ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" "${DESTINATION_VMDK}" > "${VMDK_OUTPUT}" 2>&1 else logger "debug" "${VMKFSTOOLS_CMD} -i \"${SOURCE_VMDK}\" -a \"${ADAPTER_FORMAT}\" -d \"${FORMAT_OPTION}\" \"${DESTINATION_VMDK}\"" ${VMKFSTOOLS_CMD} -i "${SOURCE_VMDK}" -a "${ADAPTER_FORMAT}" -d "${FORMAT_OPTION}" "${DESTINATION_VMDK}" > "${VMDK_OUTPUT}" 2>&1 fi VMDK_EXIT_CODE=$? kill "${TAIL_PID}" cat "${VMDK_OUTPUT}" >> "${REDIRECT}" echo >> "${REDIRECT}" echo rm "${VMDK_OUTPUT}" if [ "${VMDK_EXIT_CODE}" != 0 ] ; then logger "info" "ERROR: error in backing up of \"${SOURCE_VMDK}\" for ${VM_NAME}" VM_VMDK_FAILED=1 fi fi else logger "info" "WARNING: A physical RDM \"${SOURCE_VMDK}\" was found for ${VM_NAME}, which will not be backed up" VM_VMDK_FAILED=1 fi fi done IFS="${OLD_IFS}" fi #powered on VMs only w/snapshots if [[ ${SNAP_SUCCESS} -eq 1 ]] && [[ ! ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" == "Powered on" ]] || [[ "${ORGINAL_VM_POWER_STATE}" == "Suspended" ]]; then ${VMWARE_CMD} vmsvc/snapshot.remove ${VM_ID} > /dev/null 2>&1 #do not continue until all snapshots have been committed logger "info" "Removing snapshot from ${VM_NAME} ..." while ls "${VMX_DIR}" | grep -q delta; do sleep 5 done fi if [[ ${POWER_VM_DOWN_BEFORE_BACKUP} -eq 1 ]] && [[ "${ORGINAL_VM_POWER_STATE}" == "Powered on" ]]; then #power on vm that was powered off prior to backup logger "info" "Powering back on ${VM_NAME}" ${VMWARE_CMD} vmsvc/power.on ${VM_ID} > /dev/null 2>&1 fi TMP_IFS=${IFS} IFS=${ORIG_IFS} if [ ${ENABLE_COMPRESSION} -eq 1 ]; then logger "info" "Compressing VM backup \"${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}.gz\"..." if [ ${IS_4I} -eq 1 ]; then busybox tar -cz -C "${BACKUP_DIR}" "${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}" -f "${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}.gz" else tar -cz -C "${BACKUP_DIR}" "${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}" -f "${BACKUP_DIR}/${VM_NAME}-${VM_BACKUP_DIR_NAMING_CONVENTION}.gz" fi rm -rf "${VM_BACKUP_DIR}" checkVMBackupRotation "${BACKUP_DIR}" else checkVMBackupRotation "${BACKUP_DIR}" fi IFS=${TMP_IFS} VMDKS="" endTimer if [ ${SNAP_SUCCESS} -ne 1 ]; then logger "info" "ERROR: Unable to backup ${VM_NAME} due to snapshot creation!\n" VM_FAILED=1 elif [ ${VM_VMDK_FAILED} -ne 0 ]; then logger "info" "ERROR: Unable to backup ${VM_NAME} due to error in VMDK backup!\n" VMDK_FAILED=1 else logger "info" "Successfully completed backup for ${VM_NAME}!\n" VM_OK=1 #experimental #create symlink for the very last backup to support rsync functionality for additinal replication if [ "${RSYNC_LINK}" -eq 1 ]; then SYMLINK_DST=${VM_BACKUP_DIR} SYMLINK_SRC="$(echo "${SYMLINK_DST%*-*-*-*_*-*-*}")-symlink" logger "info" "Creating symlink \"${SYMLINK_SRC}\" to \"${SYMLINK_DST}\"" ln -s "${SYMLINK_DST}" "${SYMLINK_SRC}" fi fi else if [ ${CONTINUE_TO_BACKUP} -eq 0 ]; then logger "info" "ERROR: Failed to backup ${VM_NAME}!\n" VM_FAILED=1 else logger "info" "ERROR: Failed to lookup ${VM_NAME}!\n" VM_FAILED=1 fi fi fi done unset IFS if [[ ${ENABLE_NON_PERSISTENT_NFS} -eq 1 ]] && [[ ${UNMOUNT_NFS} -eq 1 ]] ; then ${VMWARE_CMD} hostsvc/datastore/destroy ${NFS_LOCAL_NAME} fi } getFinalStatus() { if [[ "${LOG_TYPE}" == "dryrun" ]]; then FINAL_STATUS="###### Final status: OK, only a dryrun. ######" EXIT=0 elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 0 ]] && [[ $VMDK_FAILED == 0 ]]; then FINAL_STATUS="###### Final status: All VMs backed up OK! ######" EXIT=0 elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 0 ]] && [[ $VMDK_FAILED == 1 ]]; then FINAL_STATUS="###### Final status: WARNING: All VMs backed up, but some disk(s) failed! ######" EXIT=3 elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 1 ]] && [[ $VMDK_FAILED == 0 ]]; then FINAL_STATUS="###### Final status: ERROR: Only some of the VMs backed up! ######" EXIT=4 elif [[ $VM_OK == 1 ]] && [[ $VM_FAILED == 1 ]] && [[ $VMDK_FAILED == 1 ]]; then FINAL_STATUS="###### Final status: ERROR: Only some of the VMs backed up, and some disk(s) failed! ######" EXIT=5 elif [[ $VM_OK == 0 ]] && [[ $VM_FAILED == 1 ]]; then FINAL_STATUS="###### Final status: ERROR: All VMs failed! ######" EXIT=6 elif [[ $VM_OK == 0 ]]; then FINAL_STATUS="###### Final status: ERROR: No VMs backed up! ######" EXIT=7 fi logger "info" "$FINAL_STATUS\n" } sendMail() { #close email message if [ "${EMAIL_LOG}" -eq 1 ]; then echo -en ".\r\n" >> "${EMAIL_LOG_OUTPUT}" echo -en "QUIT\r\n" >> "${EMAIL_LOG_OUTPUT}" "${NC_BIN}" "${EMAIL_SERVER}" "${EMAIL_SERVER_PORT}" < "${EMAIL_LOG_OUTPUT}" > /dev/null 2>&1 if [ $? -eq 1 ]; then logger "info" "ERROR: Failed to email log output to ${EMAIL_SERVER}:${EMAIL_SERVER_PORT} to ${EMAIL_TO}\n" fi if [ "${EMAIL_DEBUG}" -eq 1 ]; then logger "info" "Email log output will not be deleted and is stored in ${EMAIL_LOG_OUTPUT}\n" else rm -rf "${EMAIL_LOG_OUTPUT}" fi fi } #################### # # # Start of Script # # # #################### IS_4I=0 if [ ! -f /bin/bash ]; then IS_4I=1 fi USE_VM_CONF=0 USE_GLOBAL_CONF=0 BACKUP_ALL_VMS=0 EXCLUDE_SOME_VMS=0 EMAIL_LOG_OUTPUT=/tmp/ghettoVCB-email-$$.log #read user input while getopts ":af:c:g:l:d:e:" ARGS; do case $ARGS in a) BACKUP_ALL_VMS=1 VM_FILE="/tmp/backup_all_vms_on-$(hostname)" touch "${VM_FILE}" ;; f) VM_FILE="${OPTARG}" ;; e) VM_EXCLUSION_FILE="${OPTARG}" EXCLUDE_SOME_VMS=1 ;; c) CONFIG_DIR="${OPTARG}" USE_VM_CONF=1 ;; g) GLOBAL_CONF="${OPTARG}" USE_GLOBAL_CONF=1 ;; l) LOG_OUTPUT="${OPTARG}" ;; d) LOG_LEVEL="${OPTARG}" ;; :) echo "Option -${OPTARG} requires an argument." exit 1 ;; *) printUsage exit 1 ;; esac done #performs a check on the number of commandline arguments + verifies $2 is a valid file sanityCheck $# LOCKDIR=/tmp/ghettoVCB.lock if mkdir "${LOCKDIR}" then GHETTOVCB_PID=$$ logger "info" "============================== ESXI LOG START ==============================\n" logger "debug" "Succesfully acquired lock directory - ${LOCKDIR}\n" # Remove lockdir when the script finishes, or when it receives a signal trap 'rm -rf "${LOCKDIR}"' 0 # remove directory when script finishes trap "exit 2" 1 2 3 13 15 # terminate script when receiving signal ghettoVCB ${VM_FILE} getFinalStatus logger "debug" "Succesfully removed lock directory - ${LOCKDIR}\n" logger "info" "============================== ESXI LOG END ================================\n" sendMail rm -rf "${LOCKDIR}" exit $EXIT else logger "info" "Failed to acquire lock, another instance of script may be running, giving up on ${LOCKDIR}\n" exit 1 fi
Voici la vidéo :