外观
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.shPVE 自带一个官方示例脚本,装好后可以在宿主机上找到:/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.sh3. 认识四个阶段
脚本被调用时会收到两个参数:<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 Administration Guide(
qm/pct章节的hookscript选项) - Proxmox 官方论坛:Explaining snippets feature
- PVE 的存储模型
- 配置备份任务
- 命令行工具全景