跳转到内容

Hook script:在客户机生命周期里插入动作 ​

给虚拟机或容器挂一个脚本,让它在启动前、启动后、停止前、停止后这四个时机自动执行。适合"启动前挂载额外资源""启动后注册到反向代理""关机前通知依赖它的服务"这类需要和客户机生命周期绑定的自动化。

为什么需要它 ​

常见场景:

  • 启动一台跑数据库的虚拟机前,先确认它依赖的存储已经挂载好。
  • 容器启动成功后,自动把它的地址注册进内部 DNS 或反向代理。
  • 虚拟机关机前,先通知监控系统"这台机器要下线了,别报警"。
  • 停止后清理它占用的临时资源。

开始之前 ​

Hook script 必须放在启用了 Snippets 内容类型的目录类存储上。先确认这一步:

数据中心 → 存储 → 选中一个目录类存储 → 编辑,在 Content 里勾上 Snippets。

不是所有存储都能放 Snippets

Snippets 属于文件内容,只有 Directory、NFS、SMB 这类文件存储支持。LVM、LVM-Thin、ZFS zvol 这类块存储没有文件的概念,放不了,参考存储模型里块存储与文件存储的区别。

操作步骤 ​

1. 把脚本放进 snippets 目录 ​

默认 local 存储对应的路径是 /var/lib/vz/snippets/;其他目录类存储对应 /mnt/pve/<存储名>/snippets/。

sh
# 宿主机 shell,以 root 执行
# 注意:snippets 目录不支持子目录,脚本直接放在这一层
vi /var/lib/vz/snippets/my-hook.sh
chmod +x /var/lib/vz/snippets/my-hook.sh

PVE 自带一个官方示例脚本,装好后可以在宿主机上找到:/usr/share/pve-docs/examples/guest-example-hookscript.pl,参考它的结构比从零写更省事。

2. 关联到客户机 ​

sh
# 虚拟机
qm set <vmid> --hookscript local:snippets/my-hook.sh

# 容器
pct set <ctid> --hookscript local:snippets/my-hook.sh

3. 认识四个阶段 ​

脚本被调用时会收到两个参数:<vmid或ctid> <阶段名>。

阶段触发时机需要注意
pre-start客户机启动前脚本以非 0 退出会阻止这次启动
post-start客户机启动成功后
pre-stop客户机停止前
post-stop客户机停止后此时设备可能还没被完全释放,不要在这里尝试卸载它挂的存储

一个最简单的骨架(bash,适合容器;虚拟机同理,PVE 会用同样的方式调用):

sh
#!/bin/bash
# 保存到 snippets 目录,例如 /var/lib/vz/snippets/my-hook.sh,别忘了 chmod +x
VMID="$1"
PHASE="$2"

case "$PHASE" in
  pre-start)
    logger "hook: $VMID 即将启动"
    ;;
  post-start)
    logger "hook: $VMID 已启动"
    ;;
  pre-stop)
    logger "hook: $VMID 即将停止"
    ;;
  post-stop)
    logger "hook: $VMID 已停止"
    ;;
esac

调试:输出去哪儿看了 ​

pre-start、post-start 的输出通常能在触发这次操作的任务日志里看到(Web UI 的任务面板,或 qm start 的终端输出)。post-stop 是例外——它的输出不会出现在 Web UI 任务里,要看:

sh
# 宿主机 shell,虚拟机场景
journalctl -fu qmeventd

容器的对应事件类似,具体以你的版本为准。

hook script 会卡住客户机的启动和停止

pre-start、post-start 是在 PVE 持有该客户机配置锁的上下文里执行的。如果脚本里反过来调用了需要拿到同一把锁的命令(比如在 pre-start 里对同一台客户机跑 qm set / pct set),会互相等待直到超时,导致这次启动或停止卡住甚至失败。

影响范围:只影响挂了这个 hookscript 的客户机,其他客户机不受影响;但如果所有客户机都挂了同一个有问题的脚本,会造成大面积开关机异常。

前置条件 / 怎么避免:

  • 脚本里不要调用会修改同一台客户机配置的 qm/pct 命令;确实需要的话用 nohup ... & 丢到后台异步执行,不要同步等待它完成。
  • post-stop 阶段设备可能还没释放,不要在这一步卸载它挂载过的存储。
  • 先在一台测试客户机上启停几次,观察是否卡住,再挂到重要的客户机上。

如何回退:如果客户机因为 hookscript 卡住无法正常启停,先去掉这个关联,恢复到没有 hook 的正常状态,再离线调试脚本:

sh
qm set <vmid> --delete hookscript
# 或者
pct set <ctid> --delete hookscript

检查结果 ​

  • 故意让脚本在 pre-start 阶段以非 0 退出(例如临时加一行 exit 1),确认这台测试客户机确实无法启动——验证脚本真的被调用、退出码真的生效。
  • 改回正常逻辑后,正常启停一次,在任务日志或 journalctl -fu qmeventd 里确认能看到脚本产生的记录。

这和备份的 hook 脚本不是一回事 ​

vzdump 的备份任务也有自己的钩子机制(配置项是 --script,阶段名是 job-start、backup-start、log-end 这类和备份流程绑定的名字),跟这里讲的 qm/pct 的 hookscript(客户机启停生命周期)是两套完全独立的机制,参数、触发时机、写法都不通用,不要混着抄。备份相关的钩子见配置备份任务。

常见问题 ​

宿主机重启或关机时报 "script ... does not exist"。 常见原因是 snippets 只建在了某个节点本地的存储上,宿主机关机流程触发 pre-stop 时该存储可能还没准备好,或者集群里其他节点访问不到这份本地文件。集群环境建议把 snippets 放在共享存储上。

关联了 hookscript 但没有任何效果。 检查三处:脚本有没有 chmod +x;--hookscript 的路径格式是不是 <存储名>:snippets/<文件名>;这个存储是不是真的勾上了 Snippets 内容类型(前两步做对了,第三步漏掉最常见)。

权限被拒绝。 脚本本身要有执行权限;如果脚本里又调用了其他命令,确认那些命令本身的权限和路径也没问题。

参考资料 ​

Proxmox VE 非官方中文使用指南,与 Proxmox Server Solutions GmbH 无隶属关系。