如何在Linux上使用Python检测系统ACPI G2/S5软关机事件

11

我正在使用Google的计算引擎开发一个应用程序,希望使用可抢占实例。

我需要让我的代码响应谷歌发送的ACPI G2软关机信号,该信号在他们要取走您的VM时,会提前30秒发出警告,如此处所述:https://cloud.google.com/compute/docs/instances/preemptible

我该如何在正在运行的Python代码中检测到此事件并相应地进行反应(在我的情况下,我需要将VM正在处理的作业重新放回一个打开的作业队列,以便另一台机器可以接管)。


你有尝试过这篇文章中提到的关机脚本吗?https://cloud.google.com/compute/docs/shutdownscript - Kamran
还可以看一下这个帖子,里面有类似的问题:http://stackoverflow.com/questions/32949150/how-to-save-state-when-preempted-on-a-google-preemptible-instance - Kamran
3
@Kamran,我确实看到了shutdownscript的功能,但听起来它们不能保证执行,对于我的代码来说将所有内容都放在Python中会更简单。我找到了一种替代方案(使我的整个应用程序更加强大,以抵御未知故障)。 - asutherland
2个回答

4
我没有直接回答问题,但我认为你的实际意图是不同的:
  • G2电源按钮事件由虚拟机的抢占和gcloud instances stop命令(或它调用的相应API)生成;
  • 我假设你只想在实例被抢占时做出特殊反应。

避免常见误解

GCE不会发送带有电源按钮事件的“30秒终止警告”。它只发送正常、诚实的电源按钮软关事件,立即启动系统关闭。

与之相伴随的“警告”部分很简单:“这是您的电源按钮事件,请尽快关闭操作系统,因为您还有30秒的时间,否则我们将从墙上插座上拔掉插头。你已经被警告了!”

您有两个系统服务,可以以不同的方式组合,以获得所需的行为。

1. 利用系统在ACPI G2时正在关闭的事实

处理ACPI电源按钮事件的最合适(也是我所知道的唯一支持的)方式是让系统处理它,并在实例关闭脚本中执行您想要的操作。在由systemd管理的机器上,GCP默认的关机脚本仅通过Type=oneshot服务的ExecStop=命令调用(请参见systemd.service(8))。该脚本相对较晚地运行于关机序列中。
如果您必须确保关闭脚本在某些服务被发送终止信号之后(或之前)运行,则可以修改某些服务依赖项。需要记住以下几点:
  • AfterBefore在关闭时被反转:如果XY之后启动,则在Y之前停止。
  • After依赖性确保在运行关闭脚本之前告诉序列中的服务终止。它不能确保服务已经终止。
  • 关闭脚本在作为系统关闭的一部分停止google-shutdown-scripts.service时运行。
考虑到这一点,您可以执行sudo systemctl edit google-shutdown-scripts.service。这将创建一个空的配置覆盖文件并打开您的$EDITOR,在那里您可以放置您的AfterBefore依赖项,例如,
[Unit]
# Make sure that shutdown script is run (synchronously) *before* mysvc1.service is stopped.
After=mysvc1.service
# Make sure that mysvc2.service is sent a command to stop before the shutdown script is run
Before=mysvc2.service

您可以指定任意数量的After或Before子句,每个子句可以为0个或多个。有关更多信息,请阅读systemd.unit(8)。

2. 使用GCP元数据

有一个实例元数据v1/instance/preempted。如果实例被抢占,则其值为TRUE,否则为FALSE

GCP有一份详尽的有关使用实例元数据的文档。简而言之,您可以使用两种方法来使用此(或任何其他)元数据值:

  1. Query its value at any time, e. g. in the shutdown script. curl(1) equivalent:

    curl -sfH 'Metadata-Flavor: Google' \
      'http://169.254.169.254/computeMetadata/v1/instance/preempted'
    
  2. Run an HTTP request that will complete (200) when the metadatum changes. The only change that can ever happen to it is from FALSE to TRUE, as preemption is irreversible.

    curl -sfH 'Metadata-Flavor: Google' \
      'http://169.254.169.254/computeMetadata/v1/instance/preempted?wait_for_change=true'
    
注意: 元数据服务器可能会在暂时不可用时返回503响应(这很少见,但确实会发生),因此需要某些重试逻辑。特别是对于长时间运行的第二种形式(带有?wait_for_change=true),由于挂起的请求可能随时以代码503返回,因此您的代码应该准备好处理并重新启动查询。curl不直接返回HTTP错误代码,但如果您使用了x=$(curl ....)表达式,则可以利用返回空字符串的事实来脚本化;在这种情况下,您的抢占正检测标准为[[ $x == TRUE ]]

摘要

  • 如果您想检测 VM 因为 任何 原因关闭,使用 Google 提供的关机脚本。
    • 如果您还需要区分 VM 是否实际上被抢占了,而不是 gcloud instance stop <vmname>(它也会发送电源按钮事件!),则在关机脚本中查询 preempted 元数据。
  • 运行挂起的 HTTP 请求以更改元数据,并根据此做出反应。这只会在 VM 被抢占时成功完成 (但也可能随时出现错误)
  • 如果您运行的守护程序是自己的,则还可以从处理终止信号的代码路径直接查询 preempted 元数据,如果需要区分不同的关闭原因。

有可能真正的决策点是你是否有一个想要重新加入“队列”的“活动工作”:如果你在处理一个活动工作时被要求停止服务,无论为什么原因,都应该返回它。但我不知道你实际的设计,无法发表评论。


-1

我认为处理GCP预抢占的最简单方法是使用SIGTERM信号。

SIGTERM信号是一种通用信号,用于导致程序终止。与SIGKILL不同,这个信号可以被阻塞、处理和忽略。这是一种正常的方式,礼貌地要求程序终止。https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html

这取决于关机脚本,这些脚本是基于“最佳努力”的原则运行的。实际上,对于短脚本而言,关机脚本非常可靠。

在你的关机脚本中:

echo "Running shutdown script"
preempted = curl "http://metadata.google.internal/computeMetadata/v1/instance/preempted" -H "Metadata-Flavor: Google"
if $preempted; then
  PID="$(pgrep -o "python")"
  echo "Send SIGTERM to python"
  kill "$PID"
  sleep infinity
fi
echo "Shutting down"

在 main.py 文件中:
import signal
import os

def sigterm_handler(sig, frame):
    print("Got SIGTERM")
    os.environ["IS_PREEMPTED"] = True
    # Call cleanup functions


signal.signal(signal.SIGTERM, sigterm_handler)

if __name__ == "__main__":
    print("Main")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接