使用Ansible防止同时部署

使用Ansible防止同时部署,ansible,Ansible,我的团队中的任何人都可以使用SSH连接到我们的特殊部署服务器,然后从那里运行Ansible playbook,将新代码推送到机器上 我们担心如果两个人同时进行部署会发生什么。我们希望这样做,如果其他人正在运行剧本,剧本就会失败 有什么建议吗?标准解决方案是使用pid文件,但Ansible没有内置的对这些文件的支持。您是否考虑过在limits.conf中设置maxsyslogins?您可以按组对此进行限制 # for a group called 'deployers' @deployers

我的团队中的任何人都可以使用SSH连接到我们的特殊部署服务器,然后从那里运行Ansible playbook,将新代码推送到机器上

我们担心如果两个人同时进行部署会发生什么。我们希望这样做,如果其他人正在运行剧本,剧本就会失败


有什么建议吗?标准解决方案是使用pid文件,但Ansible没有内置的对这些文件的支持。

您是否考虑过在limits.conf中设置
maxsyslogins
?您可以按组对此进行限制

# for a group called 'deployers'
@deployers        -       maxsyslogins      1

这比你要求的要严重得多。您可能想先在虚拟机上试用它。请注意,如果系统上有任何其他用户,部署人员中的任何人都无权访问,1限制不仅仅包括部署人员。此外,如果您作为用户多路传输ssh连接(ControlMaster auto),您仍然可以多次登录;其他用户将被锁定。

您可以为ansible命令编写包装,如下所示:

ansible-playbook() {
  lock="/tmp/ansible-playbook.lock"

  # Check if lock exists, return if yes
  if [ -e $lock ]; then
    echo "Sorry, someone is running already ansible from `cat $lock`"
    return
  fi

  # Install signal handlers
  trap "rm -f $lockfile; trap - INT TERM EXIT; return" INT TERM EXIT

  # Create lock file, saving originating IP
  echo $SSH_CLIENT | cut -f1 -d' ' > $lock

  # Run ansible with arguments passed at the command line
  `which ansible-playbook` "$@"

  # Remove lock file
  rm $lock

  # Remove signal handlers
  trap - INT TERM EXIT
}
在“部署”框中用户的
~/.bashrc
中定义此函数,即可设置。 如果愿意,您也可以对
ansible
命令执行相同的操作,但考虑到这个问题,我不确定它是否是必需的

编辑:使用信号处理程序重写,以防止在用户按Ctrl-C时锁定文件悬空

EDIT2:固定打字错误

我个人使用RunDeck()作为Ansible剧本的包装,原因有多种:

  • 您可以将RunDeck“作业”设置为只能一次运行(或将其设置为同时运行任意次数)
  • 您可以在系统中设置用户,以便对谁运行了列出的内容进行审核
  • 您可以设置附加变量,并限制可以使用的变量(指定选项列表)
  • 它比Ansible Tower便宜很多(RunDeck是免费的)
  • 它有一个完整的API,用于从构建系统实际运行作业
  • 您不需要围绕ansible playbook命令编写复杂的bash包装
  • SSH可以成为“某些东西需要编写一个ansible脚本”的试金石——我不允许SSH访问,除非在完全中断/修复的情况下,因此我们有了更快乐的SA
  • 最后,在“很好拥有”类别中,您可以安排RunDeck作业,以非常简单的方式运行ansible playbooks,任何登录到控制台的人都可以在运行时查看运行的内容

当然还有很多更好的理由,但我的手指已经厌倦了打字;)

您还可以使用包装器的简单变体:

# Check lock file - if exists then exit. Prevent running multiple ansible instances in parallel
while kill -0 $(cat /tmp/ansible_run.lock 2> /dev/null) &> /dev/null; do
  echo "Ansible is already running. Please wait or kill running instance."
  sleep 3
done
# Create lock file
echo $$ > /tmp/ansible_run.lock

ansible-playbook main.yml

# Remove lock
rm -f /tmp/ansible_run.lock

您可以使用flock命令,该命令将使用基于文件系统的flock(2)包装您的命令:

以最适合用户的方式包装它。这将在/tmp中留下一个持久锁定文件,但请注意,删除它是不安全的[1]。它是原子的,非常简单

[1]
A: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
B: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
   B blocks waiting for lock on /tmp/foo.lock
A: Finish, deleting /tmp/foo.lock
B: Runs, using lock on now deleted /tmp/foo.lock
C: flock /tmp/foo.lock -c "echo running; sleep 5; rm /tmp/foo.lock"
   Creates new /tmp/foo.lock, locks it and runs immediately, parallel with B

我把这个放在我的主要剧本里

    hosts: all. 
lock\u file\u path
:此文件的存在表明当前正在运行的ansible deploy或之前有一个由于某种原因而中止的deploy

force\u ignore\u lock
:此默认值为false,通过可在命令行包装中设置为ansible的选项标志重置。它使ansible能够继续部署

这是干什么的
pre_任务
第一个
pre_任务
检查
lock_file_路径
是否存在,并将结果记录在名为
lock_file
的寄存器中

然后,下一个任务检查该文件是否存在,以及部署人员是否选择忽略该文件(希望在与其他队友沟通后)。否则,作业将失败,并显示一条有用的错误消息

如果用户选择继续部署,即使存在
锁定文件
,下一个任务将删除先前创建的
锁定文件
,并创建一个新文件。然后,所选角色中的其他任务将愉快地继续

post\u任务
这是在部署中的所有任务完成后立即调用的钩子。此处的任务将删除
锁定文件
,使下一个人能够愉快地部署,而不会出现任何问题

vars:
  lock_file_path=/tmp/ansible-playbook-{{ansible_ssh_user}}.lock

pre_tasks: 
   - stat: path={{lock_file_path}}
     register: lock_file

   - fail: msg="Sorry, I found a lockfile, so I'm assuming that someone was already running ansible when you started this deploy job. Add -f to your deploy command to forcefully continue deploying, if the previous deploy was aborted."
   when: lock_file.stat.exists|bool and not force_ignore_lock|bool

   - file: path={{lock_file_path}} state=absent
     sudo: yes
     when: "{{force_ignore_lock}}"

   - file: path={{lock_file_path}} state=touch
     sudo: yes

post_tasks:
   - file: path={{lock_file_path}} state=absent
     sudo: yes

当部署作业可以从多个生成主机运行时,包装器脚本没有用处。对于这种情况,锁定必须由playbook处理

Ansible现在有一个可用于锁定的模块。下面是一个简短的示例(不考虑过期锁):


Ansible将在可配置的超时时间内检查锁文件,如果在该时间段内未删除,则放弃。我将研究一种分布式锁机制,如zookeeper,我将其作为一个角色包括在内,因为有一个模块。为实现高可用性和目标节点的写锁定而分发


角色将在开始时在
/deployment/
下写入目标名称的znode,然后将其删除。如果锁已经存在,您可以失败并在
块中抛出一条消息

,我实际上已经在谷歌上搜索了一些方法,作为可能的解决方案,但找不到它。谢谢哇!由于测试和创建锁文件之间的时间间隔,此脚本将主要阻止两个实例运行,但不会完全阻止。你需要一个原子操作来获得真正的保护,虽然我已经过时了*nix,但我认为脚本无法做到这一点。我假设在下一个答案中提到的RunDeck可能具有所需的功能,使用IPC(信号量等)。理论上,是的,没有100%。您可以通过执行类似于
[-e$lock]| |(echo$SSH|u CLIENT | cut-f1-d'>$lock)
(并相应地调整脚本)的操作来降低差距,这需要不到一毫秒的时间。实际上,这听起来很合理。或者使用受限制的烫发写入锁定文件,以便其他用户无法覆盖它。总而言之,当然
vars:
  lock_file_path=/tmp/ansible-playbook-{{ansible_ssh_user}}.lock

pre_tasks: 
   - stat: path={{lock_file_path}}
     register: lock_file

   - fail: msg="Sorry, I found a lockfile, so I'm assuming that someone was already running ansible when you started this deploy job. Add -f to your deploy command to forcefully continue deploying, if the previous deploy was aborted."
   when: lock_file.stat.exists|bool and not force_ignore_lock|bool

   - file: path={{lock_file_path}} state=absent
     sudo: yes
     when: "{{force_ignore_lock}}"

   - file: path={{lock_file_path}} state=touch
     sudo: yes

post_tasks:
   - file: path={{lock_file_path}} state=absent
     sudo: yes
vars:
  lock_file: "{{ deploy_dir }}/.lock"
pre_tasks:
  - name: check for lock file
    wait_for:
      path: "{{ lock_file }}"
      state: absent
  - name: create lock file
    file:
      path: "{{ lock_file }}"
      state: touch
post_tasks:
  - name: remove lock file
    file:
      path: "{{ lock_file }}"
      state: absent