首页 > Linux, Windows > 基于APCUPSD实现断电时ESXi自动关机

基于APCUPSD实现断电时ESXi自动关机

2014年2月3日 LTNS     访问次数 1,185 发表评论 阅读评论

之前折腾了一下 制作HP MicroServer Gen8可用的ESXi 5.x SD/TF卡启动盘,鉴于 SD/TF介质的启动盘比较脆弱,不正常关机容易导致逻辑损坏,所以打算通过监测 UPS来实现断电时自动关机各虚拟机和 ESXi host本身。

如果是带 NMC网络管理卡的 APC UPS,官方的 PowerChute Network Shutdown 软件本身就有 ESXi的版本,不过手上的 UPS型号是 APC BK650-CH,仅有 USB接口,所以无法使用这个软件。

因为有过 基于APCUPSD在局域网内实现主/从机对 APC UPS的监测 的经验,所以这次还是通过开源的 APCUPSD 软件来实现断电时 ESXi自动关机保护。

APCUPSD官网 并未提供对应 ESXi的版本,不过网上有人编译了 apcupsd for ESXi 4.11,只是还没有 ESXi 5.x可用的版本,所以只能用网上常见的曲线救国法,即,在 ESXi的虚拟机(VM)里安装 apcupsd的对应版本,然后在 VM里面通过 ssh登录 ESXi host以执行其上的关机脚本。

ESXi安装后默认是关闭了 ssh服务的,所以首先要开启 ESXi的 ssh服务,可在 vSphere Client软件的配置界面里按下图所示开启。
enable_esxi_ssh_server

ESXi host上的关机脚本可参考 Spike的教程,该脚本(请注意脚本非 DOS格式,即,换行符是兼容 unix的 LF,所以建议使用类似 UE32的软件进行编辑)带 log日志记录功能,比较实用;同时根据 这篇文章 的提示,对该脚本主要做了如下两处修改(见加粗红字部分)

#!/bin/ash
# title: powerdown-esxi5.sh
# version: 0.5
# date: october 09, 2011
# author: herwarth heitmann 
# edit by: massimo vannucci 

#variables
PATH=/bin:/sbin:/usr/bin:/usr/sbin
VIMSH_WRAPPER=vim-cmd
VM_FILE=vm_list
INTERVAL=60
MAXLOOP=3
#DATE=`date "+%Y-%m-%d   %H:%M:%S"`
# To enable logging, set the following variable to 1
LOG_ENABLED=1
#LOG_FILE=/vmfs/volumes/4c55e39f-3d85baef-7253-e0cb4e42250b/powerdown-esxi4.log
LOG_FILE=/vmfs/volumes/intel_320_SSD_120G/powerdown-esxi5.log
# Remember that >> after echo, redirect and append to file

# Set the log file
if [ $LOG_ENABLED -eq 1 ]; then
  echo -e "\n\n\n"`date "+%Y-%m-%d   %H:%M:%S"` "\t\tExecuting powerdown-esxi5.sh" >> $LOG_FILE
fi

#check if parameter given
case "$1" in
    "") LASTACTION=shutdown
        ;;
reboot) LASTACTION=reboot
        ;;
vmonly) LASTACTION=vmonly
        ;;
     *) echo "usage $0 <|vmonly|reboot>"
        exit 1
        ;;
esac

#retrieve all VmId for VM(s) registered under ESXi host
${VIMSH_WRAPPER} vmsvc/getallvms >> $LOG_FILE
${VIMSH_WRAPPER} vmsvc/getallvms | awk '{print $1}' | grep -v 'Annotation' | grep -v 'Vmid' > $VM_FILE

#first time initialisation
ERROR=0
FIRSTRUN=1
LOOP=0

#we want to run the loop at least 1 time! and loop until no more errors occur
while [ $ERROR -ne 0 -o $FIRSTRUN -eq 1 ]; do
  LOOP=$(($LOOP+1))
  if [ $FIRSTRUN -eq 0 ]; then
    if [ $LOG_ENABLED -eq 1 ]; then
      echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tGive virtual machines time to shutdown..." >> $LOG_FILE
    else
      echo "Give virtual machines time to shutdown..."
    fi
    sleep $INTERVAL
  fi
  #exit loop if $LOOP gets bigger than $MAXLOOP
  if [ $LOOP -gt $MAXLOOP ]; then
    echo "Maximum loops reached!"
    break
  fi

  FIRSTRUN=0
  ERROR=0
  for VM_LINE in $(cat ${VM_FILE}); do
    STATE=$(${VIMSH_WRAPPER} vmsvc/power.getstate ${VM_LINE} | grep -v 'runtime')
    if [ "$STATE" = "Powered off" -o "$STATE" = "Suspended" ]; then
      if [ $LOG_ENABLED -eq 1 ]; then
        echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tVM with ID: ${VM_LINE} is: $STATE, skipping..." >> $LOG_FILE
      else
        echo "VM with ID: ${VM_LINE} is: $STATE, skipping..."
      fi
    else
      #try to do proper shutdown if VMware Tools are installed
      if [ $LOG_ENABLED -eq 1 ]; then
        echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tVM with ID: ${VM_LINE:} is: $STATE, trying guest shutdown..." >> $LOG_FILE
      else
        echo "VM with ID: ${VM_LINE} is: $STATE, trying guest shutdown..."
      fi
      ${VIMSH_WRAPPER} vmsvc/power.shutdown "${VM_LINE}" > /dev/null 2>&1
      #if it fails to shutdown, we know there are no VMware Tools installed
      if [ $? -eq 1 ]; then
        #hard power off
        if [ $LOG_ENABLED -eq 1 ]; then
          echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tGuest shutdown not working, hard powering off" >> $LOG_FILE
        else
          echo -e "\tGuest shutdown not working, hard powering off"
        fi
        ${VIMSH_WRAPPER} vmsvc/power.off "${VM_LINE}" > /dev/null 2>&1
      else
        if [ $LOG_ENABLED -eq 1 ]; then
          echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tSuccessfully initiated shutdown of ${VM_LINE}" >> $LOG_FILE
        else
          echo -e "\t\tSuccessfully initiated shutdown of ${VM_LINE}"
        fi
      fi
      ERROR=$(($ERROR+1))
    fi
  done
done

# clean up temporary file
rm -f $VM_FILE

#execute last action
case "$LASTACTION" in
shutdown) #shutdown ESXi host
          if [ $LOG_ENABLED -eq 1 ]; then
            echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tShutting down ESXi host..." >> $LOG_FILE
          fi
          /sbin/poweroff
          ;;
  reboot) #reboot ESXi host
          if [ $LOG_ENABLED -eq 1 ]; then
            echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tRebooting ESXi host..." >> $LOG_FILE
          fi
          /sbin/reboot
          ;;
  vmonly) #do nothing! only VMs needed to be shutdown
          if [ $LOG_ENABLED -eq 1 ]; then
            echo -e `date "+%Y-%m-%d   %H:%M:%S"` "\t\tDo nothing with ESXi host..." >> $LOG_FILE
          fi
          ;;
esac
exit 0

 
第二处修改增加了 VmID对应的虚拟机名称,原脚本只记录了纯数字的 VmID,不够直观;而第一处修改是将 log文件存放的 datastore路径改为自己实际的,如果不知道该路径,可在 vSphere Client软件的配置界面里看到。
datastore_directory_path
顺便提一下,该脚本我取名 powerdown-esxi5.sh,跟 log文件一样亦存放在datastore根目录下(比如通过 WinSCP上传),因 ESXi host上无法存放文件(重启后会丢失)。另外,须注意日志里记录的是 UTC时间,时差8小时。

通过类似 Putty 的 ssh客户端登录 ESXi host,增加脚本的可执行属性并测试一下。

cd /vmfs/volumes/datastore标识
chmod +x powerdown-esxi5.sh
./powerdown-esxi5.sh     #运行脚本测试一下

 
接下来在 VM(操作系统可以是 win或 linux等,只要 APCUPSD有对应的版本即可)里安装 apcupsd软件,本文以 WinXP PRO SP3为例。如果是 Win2008R2的话,则可能在软件安装后遇到 apcupsd服务无法随系统自动启动的问题,请按如下的两种方式之一(或者都用上以增加保险系数)修改 apcupsd服务的属性:
方式一
apcupsd_service_automatic_delay
方式二
apcupsd_service_recovery

 
然后从 Putty官网下载 plink.exe软件(Putty的命令行版本),存放在某个目录下,比如 C:\Program Files\putty\,然后修改 C:\Program Files\apcupsd\etc\apcupsd\apccontrol.bat 这个批处理文件的第162行

...
rem   %SHUTDOWN% -h now
   cscript //nologo plink.vbs
   GOTO :done
...

 
用记事本或者类似 UE32 的软件,在该批处理文件的同一目录下再新建一个如下内容的 C:\Program Files\apcupsd\etc\apcupsd\plink.vbs 脚本文件(文件名和批处理文件中调用的 vbs文件需一致),可双击运行测试一下,看看能否正常登录 ESXi host并执行其上的关机脚本(xp环境下的默认输入法最好不要是中文,否则有可能 vbs脚本运行会出错,自动输入的字符串像是在键打中文那样)。

Dim objShell
Set objShell = CreateObject("Wscript.Shell")
objShell.Run "cmd.exe",1,False
WScript.Sleep 200
objShell.SendKeys """C:\Program Files\putty\plink.exe"" -ssh -2 root@ESXI主机ip -pw 登录密码 ""/vmfs/volumes/datastore标识/powerdown-esxi5.sh""{ENTER}"
WScript.Sleep 3000
objShell.SendKeys "y{ENTER}"

注意:
1. 最后一行是用于第一次 ssh登录时提示远程主机公钥的交互确认(仅第一次需要),或者以后主机公钥发生了变化亦需确认。
2. 这个帖子 里的作者直接在批处理文件中调用 ssh登录的交互命令,但我照此折腾了半天也没搞定,只好参考 网上的教程 在批处理文件中再调用外部的 vbs脚本文件,才搞定的。

还可以根据自己的实际情况修改一下 C:\Program Files\apcupsd\etc\apcupsd\apcupsd.conf 这个配置文件中的两个阈值(任一阈值均可触发关机事件),确保在 UPS电池用尽前能完成 ESXi的自动关机动作。

#POLLTIME 60   UPS通信间隔时间
POLLTIME 30
...
#BATTERYLEVEL 6   还剩多少百分比的电量时
BATTERYLEVEL 60
...
#MINUTES 3   还剩多少分钟的电量时
MINUTES 7

 

然后在 ESXi主机的 USB口插上 BK-650CH的数据线,再到 VM设置中依次添加 USB Controller 和 USB Device
add usb controller in VM setting

add usb device in VM setting

最后在 VM里面手动升级为 apcupsd的驱动(默认安装的是 Windows操作系统自带的驱动)

重启一下 VM就完成了。
apcupsd_status

 

补充:
还可以在 BIOS的 Server Availability项里设置一下来电自启动(这里的 Always不是指有电即开机,我测过有电时 Gen8可正常关机,之后不会自动启动)。
automatic power-on in bios

考虑到市电恢复初期可能电压不稳定,所以还要设为延时启动,实测大约来电后一分钟多些 Gen8就会自动启动。
random delay in bios

当然,如果断电后已触发 apcupsd的关机阈值,但在 UPS电池放电耗尽之前市电又恢复了,由于 Gen8并未感应到来电这个动作,则不会自动启动,这时候就要通过 iLO或 其他方式 远程手动开机了。

 
2014.09.29 更新
为解决 Gen8未感应到来电而无法随市电恢复自动启动的问题,参考 网上的教程,在路由器上新建了一个脚本 /jffs/wol_esxi.sh

#!/bin/sh
while :
do
  while
  ping -c 1 ESXi主机的IP地址 > /dev/null
  [ $? -eq 0 ];
  do
#    echo ' ESXi host is running ! ' >> /jffs/esxi_power.log;date >> /jffs/esxi_power.log
    sleep 180
  done
  echo ' ESXi host may be turned off, check again after 5 minutes ! ' >> /jffs/esxi_power.log;date >> /jffs/esxi_power.log
  sleep 300
  ping -c 1 ESXi主机的IP地址 > /dev/null
  if [ $? -eq 0 ]
  then
    echo ' Check again, ESXi host is running ! ' >> /jffs/esxi_power.log;date >> /jffs/esxi_power.log
  else
    break;
  fi
done
echo ' ESXi Host is already turned off, WOL now! ' >> /jffs/esxi_power.log;date >> /jffs/esxi_power.log
ether-wake ESXi主机的MAC地址
sleep 5
ether-wake ESXi主机的MAC地址

注:我的路由器刷的是第三方的 Tomato固件,如果是 DD固件,相应的 WOL命令请参考 官网文档。另外,路由器电源接市电而非UPS。

设置脚本为可执行权限,然后在 Tomato路由器的 WAN UP 脚本页面添加如下的一行即可

/jffs/wol_esxi.sh

 
 

参考文章
1. http://lime-technology.com/forum/index.php?topic=26795.0
2. http://www.blasterspike.it/2011/04/07/how-to-script-to-shutdown-esxi-free-via-an-usb-ups-monitoring-tool/
3. http://www.pjb.cc/?p=34

 

  1. psu
    2014年4月18日10:55 | #1

    你好,我也有一个esxi,和650也想让他自动关机,我看到有些人用vma来控制关机?想问一下vma简单还是你的这种方法简单一些.

  2. psu
    2014年4月18日13:18 | #2

    你好。我
    在esxi控制台我运行了 ./powerdown-esxi5.sh

    -sh: ./powerdown-esxi5.sh: not found
    我怎么提示了这个?

  3. LTNS
    2014年4月18日13:57 | #3

    @psu
    没试过vma方式,不想另外装软件了。

    另,提示 .sh文件没找到,你看看当前目录下有这个 .sh文件不

  4. psu
    2014年4月18日18:35 | #4

    出现那个原因是因为不是unix格式。已经搞定。
    另外plink.vbs 出现了 行1 字符17 语句未结束 代码 800a0401 错误,
    使用的文本就是你上面列出的那些。

    Dim objShellSet objShell = CreateObject("Wscript.Shell")
    objShell.Run "cmd.exe",1,False
    WScript.Sleep 200
    objShell.SendKeys """C:\Program Files\putty\plink.exe"" -ssh -2 root@** -pw ** ""/vmfs/volumes/ssd1/powerdown-esxi5.sh""{ENTER}"
    WScript.Sleep 3000
    objShell.SendKeys "y{ENTER}"

  5. LTNS
    2014年4月18日19:17 | #5

    @psu
    Dim objShell之后是换行,即

    Dim objShell
    Set objShell = CreateObject("Wscript.Shell")
    ...

    不知道什么时候就少了一个换行,已修改。(另,文中还有一处“方式一”前面也有换行,但浏览器中显示就是跟前一段文字连在一起,奇怪,不知道是 IE还是 wordpress的问题。)

  6. psu
    2014年4月18日21:30 | #6

    全部成功,非常感谢,
    不知道那个关机的powerdown-esxi5.sh能否修改成跟随esxi默认的挂起或者关闭电源?另外可以修改成关机虚拟机全部挂起?

  7. psu
    2014年4月24日18:17 | #7

    原本是装载到xp系统里面可以远程关闭esxi。但是今天研究了一下win2012发现没有办法启动vbs。。但是运行vbs可以远程关闭esxi。

  8. flysky
    2014年4月29日00:52 | #8

    感谢楼主的这篇博文,解决呢我的BK650和gen8断电自动关机的问题。
    不过和楼主不同的是 我使用vbs方式不是很正常,才用老外帖子直接调用ssh命令却100%成功。

    期间遇到一些麻烦,主要是在 gen8虚拟的群辉dsm关机的问题。不过这个问题今天得到了解决。特来发帖感谢博主。再次感谢!

  9. LifeKiller
    2014年5月14日13:44 | #9

    终于找到了教程,谢谢,先收藏一下,找时间在我的Esxi上部署上。。。

  10. LifeKiller
    2014年5月16日09:30 | #10

    请教一个问题,我的Esxi设置了开机以后自动启动虚拟机顺序,会先把我的存储用的FreeNAS启动,然后再启动Win7等其它虚拟机。然后Esxi应该会按照设置的启动列表的反顺序去关闭虚拟机。我看这个脚本大概的意思是取虚拟机列表,然后挨个关虚拟机,这样会把我的FreeNAS先关掉,然后再去关其它的。能不能直接调Esxi的关机,让他按设定的顺序去关。
    谢谢!

  11. psu
    2014年6月14日19:37 | #11

    @flysky
    你的什么系统?我的w2012不能用命令格式,所以要特别添加一个xp。

  12. LTNS
    2014年6月18日21:47 | #12

    @LifeKiller
    那就不用Spike的脚本,请按参考文章里的教程试一下直接调用 ESXi5自带的 VM关机脚本和关机命令
    "C:\path\to\plink.exe" root@vmware.esxi.ip.address -pw esxipass "/sbin/shutdown.sh && /sbin/poweroff"

    不过这个关机脚本只对随 ESXi自动启动的VM有效(且是反顺序关机),对手动启动的无效;另外,这个脚本对 VM是硬关机,感觉不太好。

    @flysky
    同问你那里什么操作系统,我这里在批处理文件中直接调用plink命令就是不行,被迫再调用外部的vbs脚本。

  13. fxsky
    2014年7月6日23:43 | #13

    我的系统是2008R2!
    用原生SHELL脚本的话要注意,标点符号必须是半角,老外的脚本可能引号有个小错误,改了就好了.具体忘记是怎么改了,因为最近没折腾机器暂时没条件截图...

    顺便求问,是否有办法在UPS通知ESXI关机后,并且UPS电量未耗尽的情况下,市电来电后UPS通知服务器开机.
    我现在因为带宽的关系虚拟了一个VM 来做X86服务器,远程在X86未启动下没法连接..很坑啊

  14. fxsky
    2014年9月24日05:04 | #14

    fxsky :
    我的系统是2008R2!
    用原生SHELL脚本的话要注意,标点符号必须是半角,老外的脚本可能引号有个小错误,改了就好了.具体忘记是怎么改了,因为最近没折腾机器暂时没条件截图...
    顺便求问,是否有办法在UPS通知ESXI关机后,并且UPS电量未耗尽的情况下,市电来电后UPS通知服务器开机.
    我现在因为带宽的关系虚拟了一个VM 来做X86服务器,远程在X86未启动下没法连接..很坑啊

    最新进展,我用一台OPENWRT 当作AP,顺便写了个脚本检测服务器是否开机,如果没开机,会自动发送wol信号唤醒!成功实现UPS 断电重启和未耗尽电池来电自动开机!

    • LTNS
      2014年9月29日11:13 | #15

      @fxsky

      谢谢分享,我在Tomato路由器上做了个脚本也成功了,见文章更新

  15. 一张超级马里奥纸牌
    2015年7月6日21:38 | #16

    你好。因 ESXi host上无法存放文件(重启后会丢失),我的esxi6.0 就遇到这个问题。我最近买了雷迪司的UPS,官方有支持esxi的脚本,原理和你的一样,但实际上,在SSH下安装好官方提供的脚本后,可以做到关闭esxi。但重启后就失效了,后来通过SCP查看了,ESXI系统文件,发现重启后系统文件自动还原了。所以导致每次开启esxi时都要重新安装一次。请问这个问题可以解决吗?为什么ESXI host上存放的文件重启后会丢失呢?有什么方法能够永久保存下来。谢谢

  16. LTNS
    2015年7月7日11:50 | #17

    @一张超级马里奥纸牌
    我把脚本存放在datastore,esxi重启后不会丢失,文中亦有提及,不过我目前用的是esxi5.5,没在6.0下试过。

  17. 一张超级马里奥纸牌
    2015年7月8日08:16 | #18

    @LTNS
    主要是雷迪司UPS给的代码,要开机运行,后台驻留一个进程,安装时会写入到ESXI里一些数据,重启就没了,如果能做到文件重启还在,或者重启后开机自动运行那个运行进程命令,就可以做到断电后ESXI关机。雷迪司给的代码,我看过,跟你写的原理一样。都是检查开机状态的虚拟机,先调用VMtools关机,如果调用失败,就挂起这个虚拟机,所有虚拟机都断电后,再发送esxi关闭的指令。

  18. LTNS
    2015年7月10日12:59 | #19

    @一张超级马里奥纸牌
    代码可以修改么?能修改就把写入目标盘改为datastore吧。

  19. 视界
    2015年7月11日11:35 | #20

    你好,用你的脚本以前一直正常升级了5.5后会有报错
    (vim.fault.NotFound) {
    dynamicType = ,
    faultCause = (vmodl.MethodFault) null,
    msg = "Unable to find a VM corresponding to "Version"",
    }

  20. 视界
    2015年7月31日14:48 | #21

    @LTNS
    你好,用你的脚本以前一直正常升级了5.5后会有报错
    (vim.fault.NotFound) {
    dynamicType = ,
    faultCause = (vmodl.MethodFault) null,
    msg = "Unable to find a VM corresponding to "Version"",
    }

  21. nasfans
    2016年3月23日04:14 | #22

    博主,win2012r2下断电后apccontrol.bat 这个批处理文件的第162行里的脚本无法执行,单独执行vbs脚本没问题,但是apccontrol.bat就是不调用vbs脚本,很奇怪

  22. nasfans
    2016年3月23日10:14 | #23

    另外,今天做了很多实验,貌似win2012下apcupsd在市电断开后调用apccontrol.bat时,只有当162行是%SHUTDOWN%,也就是调用bin\shutdown.exe时才有效,试过该处替换成其他脚本都不行,同时试过将bin文件夹下的shutdown.exe替换成自己的关机bat(bat脚本内容:plink.exe -pw password root@ip path/powerdown.sh)转换成的shutdown.exe(直接在cmd命令行下可执行关机,鼠标双击执行也成功)都不行,在任务管理器可以看见shutdown.exe进程,说明apccontrol.bat时162行已执行,自己编译的shutdown.exe已启动,但是未正确执行,怀疑是win2012下由于系统权限问题,导致apcupsd在调用apccontrol.bat时参数传递失败或者是未传递,此问题无解了。

  23. mmc
    2016年5月24日01:43 | #24

    @nasfans
    貌似我碰到了跟你一样的问题,我是XP-SP3。plink.vbs单独能关机成功,但是放在这里就是不行。

  1. 2014年9月6日23:19 | #1
  2. 2015年9月10日21:52 | #2
  3. 2016年6月9日09:25 | #3
5 - 2 = (必填)