脚本在 50 台服务器上运行。如何确保只有一台服务器执行特定步骤?

脚本在 50 台服务器上运行。如何确保只有一台服务器执行特定步骤?

我有一些工作需要在 50 多台服务器上完成。第一步是将一些源代码的更新版本签出到共享目录中(假设所有服务器都已安装共享驱动器)。第二步是在每台服务器上执行一些工作。

我希望在每台服务器上运行这两个脚本。所有 50 多台服务器都是从单个磁盘映像克隆而来,因此我无法自定义其中任何一台服务器。

当 50 台服务器运行第一个脚本时,我只希望第一个尝试运行该脚本的服务器真正运行它。其他服务器我只想退出。然后,实际运行该脚本的服务器应更新共享目录,然后退出。然后,稍后,第二个脚本将运行并根据第一个服务器获取的更新代码在所有服务器上执行工作。

最好的方法是什么?我是否可以可靠地让第一个脚本在一台服务器上运行并创建一个文件或充当某种“信号量”或“锁”的东西来阻止其他服务器?

使情况变得更加复杂的是,我正在考虑让脚本从每台服务器上相同的 cron 文件运行 - 这意味着所有脚本都可以尝试同时运行它,假设它们的所有时钟都设置相同。

我希望这些可以通过 bash 脚本运行。这种方法合理吗?

编辑:根据问题更新:

我们不希望每台服务器都尝试检出这些文件的自己的副本——它们位于多 GB 的源代码存储库中,并且对于我们的源代码控制服务器来说,同时检出 50 多个代码会很困难(并且无法扩展到 100 多个服务器)。

为 50 多台服务器添加 cronjob 并不是什么大问题,但添加另一台具有自己配置的定制服务器则比较困难。我们已经克隆了 50 台服务器——维护一台单独的服务器只是为了检出 50 多台服务器访问的最新源代码,这似乎是一种浪费,而且会比在现有服务器上添加脚本增加更多的开销。

答案1

三种解决方案。

  1. 手动运行“checkout”步骤,或者在其中一个服务器上单独运行脚本。这似乎是最好的方法——否则你可能会陷入竞争状态。
  2. 如果您愿意接受遇到竞争条件的可能性,您当然可以尝试在第一个脚本运行时创建一个带有特定日期戳的文件。或者,如果日期足够可靠,您可以尝试检查签出文件的最后修改日期。
  3. 如果确实禁止定制,那么让每个虚拟机制作自己的文件副本来处理,而不是尝试使用共享卷。

每种方法都有其利弊,但你还没有真正说清楚为什么要以这种方式设计解决方案。

答案2

如果没有大量的工程来实现,网络上就不会有真正的原子性,而且需要的工程越多,它就会越复杂。

需要考虑很多权衡。这个答案无法告诉你在工作完成一半时该做什么。

NFSv3 在较新的内核中支持原子锁定机制(坦率地说,相当老旧)http://nfs.sourceforge.net/#faq_d10。因此,理论上信号量的某些机制可以通过以下方式实现。

  1. 主机上已存在“完成”文件。(这仅适用于脚本 2)
  2. 使用 在主机上打开“acquire”文件O_EXCL
  3. 将“done”重命名为“done.old”。
  4. 在这里做你的特殊工作。
  5. 使用 在主机上打开一个“完成”文件O_EXCL
  6. 取消链接‘done.old’。
  7. 取消“获取”链接

这里有一些尝试这样做的模板 shell 脚本。

#!/bin/bash
# WARNING: This is a cricital line! NEVER EDIT THIS
set -e -o noclobber

BASEPATH=/tmp
cd "${BASEPATH}"

# 1. A done file exists on the host already (this is a signal for script 2 only)
# 2. Open an 'acquire' file on the host using `O_EXCL`.
echo > 'acquire'

# 3. Rename 'done' to 'done.old'.
mv 'done' 'done.old' 2>/dev/null || :

# 4. Do your special work here.
echo "How much wood could a woodchuck chuck if a woodchuck could chuck wood?"

# 5. Open a 'done' file using O_EXCL
echo > 'done'

# 6. Unlink 'done.old'.
unlink 'done.old' || :

# 7. Unlink 'acquire'.
unlink 'acquire'

最重要的一行是,set -e -o noclobber它有两个用途。

  • 它确保如果任何命令失败,脚本就会退出。
  • 该脚本不会覆盖文件(使得打开发生在 O_EXCL 中)。

考虑到set标准,最重要的功能部分是echo > acquire哪个将自动打开获取的文件。如果失败(因为其他人拥有它,即使同时发生两次打开,也只有一个会获胜),选项可-e确保set我们退出脚本。

永远不应该有两个这样的脚本同时运行。但是这个脚本不是提供一个解决方案,其中两个脚本一个接一个地运行(在当前形式下是允许的)。我猜最好的方法是将“完成”文件更改为某个带有时间戳的命名文件,在进程开始之前查找该文件的存在。因此,这假设依靠时间作为媒介来确定代码关键性的安全性是“安全的”。

我确实提到过,这并不具体。目前,这为您提供了两个进程不能同时索取文件的保证。如上所述,需要进行更多修改以允许它在存在“完成”文件时不启动。

未涵盖的其他内容包括:

  • 如果过程启动但尚未完成怎么办?
  • 如果共享目录在之前或者中途不可用该如何处理。
  • 如果主机在第 4 步执行“安全”操作的时间太长,这会对下次运行时产生什么影响?完成后我们应该使用旧实例还是新实例?

为了解决这些问题,需要一种“隔离”机制(大量改变基础设施)来真正保证在另一台主机上重新获取锁是一项安全的操作。

答案3

我建议如下:

指定一台服务器作为复制代码存储库。然后,您可以按任意间隔将更新发送到该存储库。其余服务器可以测试是否存在本地存储库,然后从指定服务器 rsync 文件。此信息可以存储在共享文件服务器空间中。这将非常容易实现自动化,并且应该相当强大。

另一个根本性的解决方案是使用 bittorrent sync。存储库服务器将是读/写的,而其他服务器将具有只读共享。可能会更快,因为网络负载将在服务器之间共享。btsync 可以通过配置文件进行设置,并且 Linux 客户端运行良好。

编辑:您可以跳过存储库服务器以获得彻底的解决方案并坚持使用 btsync。

干杯!:)

丹尼

答案4

您必须使用某种锁定文件(在执行任何操作之前),该文件显示第一个脚本的所有者和运行时间。当其他人尝试执行该脚本时,它应该查找锁定文件然后退出。在脚本结束时(如果脚本运行),删除所述锁定文件。

相关内容