更新-包括示例实施示例

更新-包括示例实施示例

我们正在使用 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 构建到不同数据中心/区域的故障转移。

相关内容