我们正在使用 systemd 在生产中运行各种服务。(呃……)
我们正在构建一个匹配的“灾难恢复”站点,它将安装相同的应用程序 - 使用相同的 systemd-units 来启动其各个组件发生灾难时。
此 DR 环境是“热”的,可以在短时间内接管(时间越短越好)——从而成为生产环境本身。然后,当“灾难”解决后,另一个环境将成为 DR。
我的问题是,如何让这些 systemd 服务保持随时可启动,但不能实际上开始直到某个条件成为现实?
为了得出结论,某个特定网站目前是基本的(生产),命令(amIthePrimary
)需要运行并以 0 退出代码退出。检查简单快捷——并且可以每分钟执行一次。但是,因为它需要运行命令,所以没有Condition
由 systemd 提供。
我是否将该命令放入每个单位的 ExecPre
还是会变成一个嘈杂的错误,不必要地打扰管理员?我是否应该将其放入一个单独的单元中,并将Require
其与所有其他服务放在一起?
另外,一旦条件为真——并且服务启动——我该如何继续检查它,以便它们全部关闭,它是否应该再次变为假?
答案1
由于您的用例非常定制并且您的需求将来可能会发生变化,为什么不做以下事情呢......
在两台机器上创建一个每分钟运行一次的新 systemd 计时器(例如failover-manager
)。systemd 计时器将定期启动相关的一次性 systemd 服务。
该一次性 systemd 服务可以只运行包含您的逻辑的 bash 脚本:
- 运行你的
amIthePrimary
检查 - 如果是主要的,请启动您的 systemd 服务并检查它是否无错误启动。
- 如果不是主要的,请停止正在运行的 systemd 服务。
- 如果您的脚本无法启动/停止/验证运行,那么它应该失败(可监控),否则成功。
- 这个计时器脚本不需要输出任何东西(噪音方面),除非它发生了改变,即:
- “我是主服务器,但服务未运行。正在启动...等待几秒钟...已验证服务正在运行且没有错误。”或
- “我不是主服务器,但服务正在运行。正在停止。”
这样,您始终可以监控常规/计时器检查是否正常运行。如果您的计时器服务出现问题(非零退出),您可以通过监控来发现问题。您可以单独监控主应用程序服务的故障。
如果您的需求发生变化,您可以轻松调整您的计时器脚本或其运行的频率。
可能有更干净的方法可以做到这一点,但它们可能取决于您amIthePrimary
检查背后的任何内容所生成的事件......而您没有提供任何详细信息。即事件驱动的故障转移而不是轮询。
您也可以将amIthePrimary
检查放入ExecStartPre=
...但当它无法阻止服务启动时,您的服务将处于 FAILED 状态,这可能会使您的监控感到困惑,因为这不是严重的失败,而是故意的失败。因此,您可能更喜欢使用计时器方法,因为这样您就可以分别监控计时器进程和主服务进程。计时器应始终处于运行、活动状态且不会发生故障。您的服务(如果正在运行)不应处于失败状态,否则监控应该停止。还有一个问题是如何从监控的角度知道服务是否应该运行,但这超出了问题的范围。
更新-包括示例实施示例
未经测试,但只是为了让我的建议更加清晰。
failover-manager.sh
假设此脚本部署到/opt/failover-manager/failover-manager.sh
#!/bin/bash
# expected ENV. Provided by the service that starts this script.
#
# APP_SERVICE (your main application)
# SECONDS_TO_START (e.g. some java apps start very slowly)
if [ -z "$APP_SERVICE" -o -z "$SECONDS_TO_START" ]; then
echo "Missing environment"
exit 1
fi
function is_running {
systemctl is-active --quiet $1
return $?
}
if amIthePrimary; then
if is_running $APP_SERVICE; then # no change, no log
exit 0
else
echo "I AM primary, but service NOT running. STARTING..."
systemctl start $APP_SERVICE
sleep $SECONDS_TO_START
if is_running $APP_SERVICE; then
echo "Verified service is STARTED without error: $APP_SERVICE."
exit 0
else
echo "Service $APP_SERVICE has not yet STARTED after $SECONDS_TO_START seconds."
exit 1
fi
fi
else
if is_running $APP_SERVICE; then
echo "I am NOT primary, but service IS running. Stopping..."
systemctl stop $APP_SERVICE
sleep $SECONDS_TO_START
if is_running $APP_SERVICE; then
echo "Service $APP_SERVICE has not yet STOPPED after $SECONDS_TO_START seconds."
exit 1
else
echo "Verified service is STOPPED: $APP_SERVICE."
exit 0
fi
else # no change, no log
exit 0
fi
fi
failover-manager.timer
[Unit]
Description=Timer that starts failover-manager.service
Requires=failover-manager.service
[Timer]
Unit=failover-manager.service
# every 1 minute
OnCalendar=*:0/1
AccuracySec=1s
Persistent=true
[Install]
WantedBy=timers.target
failover-manager.service
这个家伙是由上面的计时器运行的。
[Unit]
Description=Checks if we need to start or stop our application.
[Service]
Type=oneshot
Environment=APP_SERVICE="my-application.service" SECONDS_TO_START="5"
WorkingDirectory=/opt/failover-manager/
ExecStart=/opt/failover-manager/failover-manager.sh
User=root
Group=root
纯 systemd 选项?
如果您正在寻找一种纯 systemd 机制来以干净的方式实现这一点,那可能是不可能的。
您的用例是自定义的并且在我看来超出了 systemd 的范围。
ExecStartPre
因此,您可以使用或使用requires
/类型依赖机制来“破解”它wants
……但所有这些方法都依赖于进程,要么由于故障而处于停止状态(中断监控……是故意故障还是某些故障)……要么该进程由“某些东西”启动/停止,而“某些东西”知道 systemd 世界之外的某些东西。后者不会中断监控,但确实需要 systemd 之外的东西,而我提出的是实现这一点的一种方法。
备择方案
就像@anx 建议的那样...也许重新设计你的 DR 故障转移的工作方式。
这也是我们采取的方法。如果我们有备用机箱/云/机架/等,那么我们希望确保一切都已在运行(例如服务等)。
那么问题只是……如何进行切换。
有两种常见的方法可以实现到备用端点的故障转移......
1 - DNS 故障转移
为关键端点设置较低的 DNS ttl(缓存时间),并在检测到故障时更新 DNS 记录以指向备用端点(例如 CNAME、A、AAAA DNS 更新)。
许多托管 DNS 提供商(例如 dnsmadeeasy、dynect)将此作为其服务(检测和故障转移)的一部分提供。但当然,您可以使用自己的 DNS 或任何 DNS 提供商来实现这一点,这些提供商使您能够设置较低的 TTL 并轻松手动或自动(监控 + DNS API)更新您的 DNS 记录。
这里的一个潜在问题是,您可能会担心机器人会向“非活动”端点发出请求。这肯定会发生,但如果您的应用程序设计良好,那么让一些请求进入备用 DR 端点不会破坏任何东西。
好处是,这迫使您思考如何使您的应用程序架构在多个并发端点接收流量(共享数据库,复制等)方面更加健壮。
如果这是一个大问题,您可以添加 iptables 规则来管理它......但您可能会遇到与以前相同的问题......如何触发更改(因为现在 DNS 和 iptables 都需要进行更改才能发生故障转移)。
2 - 负载均衡器故障转移
在负载均衡器中不处于活动状态的备用服务器很常见,可以快速添加/交换到负载均衡器后面的活动服务器池中。
在这种情况下,负载均衡器或第三方组件可以管理健康检查并更新负载均衡器配置,以将健康的服务器替换为不健康的服务器。
这对于 DR 情况不太适用,因为负载平衡器通常位于机架或数据中心本地。因此对于 DR,您最好基于 DNS 构建到不同数据中心/区域的故障转移。