变量改变时执行一次任务

execute task once when variable is changed

我有剧本,它在服务器(整个环境)上寻找特定容器,然后在具有所需服务的服务器上获取 docker id 容器。最后一步是在容器内执行 bash 命令。我的代码:

    shell: docker ps | grep '{{service}}:' 
    register: ps
    changed_when: ps.stdout != ""

  - name: get id container with {{service}}
    shell: docker ps | grep '{{service}}:' | awk '{print }'
    register: id
    when:  ps is changed

  - name: alembic upgrade head exec
    shell: docker exec -i {{id.stdout}} bash -c 'pwd'
    register: pwd
    when: id is changed
  - debug: var=pwd.stdout_lines
    when: id is changed

输出:

PLAY [dev2] ******************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [dev2_3]
ok: [dev2_4]
ok: [dev2_1]

TASK [search server with graphql] ********************************************************************************************************************************************************************************************************
ok: [dev2_1]
changed: [dev2_3]
changed: [dev2_4]

TASK [get id container with graphql] *****************************************************************************************************************************************************************************************************
skipping: [dev2_1]
changed: [dev2_3]
changed: [dev2_4]

TASK [alembic upgrade head exec] *********************************************************************************************************************************************************************************************************
skipping: [dev2_1]
changed: [dev2_3]
changed: [dev2_4]

TASK [debug] *****************************************************************************************************************************************************************************************************************************
skipping: [dev2_1]
ok: [dev2_3] => {
    "pwd.stdout_lines": [
        "/usr/src/app"
    ]
}
ok: [dev2_4] => {
    "pwd.stdout_lines": [
        "/usr/src/app"
    ]
}

问题是如果你有一组有 10 台服务器的主机,理想的服务将在 5 台服务器上,在上面的配置中它将执行五次。

我需要的是:最后一个任务应该在任何满足条件的服务器上执行一次"id is changed"

run_once: yes 始终在列表中的第一台主机上执行任务,因此这是随机的,如果第一台主机具有理想状态,它将正确执行,否则剧本将以错误结束 - 第一台主机没有理想变量 (id.stdout)

看起来您的第一个挑战似乎是找到您的服务所在的主机 运行。我可能会使用 Ansible 的 group_by 模块来创建符合您的条件的动态主机组,如下所示:

---
- hosts: all
  gather_facts: false
  tasks:
    - name: check host for target service
      become: true
      command: "docker ps --filter name={{ service }} --format '{%raw%}{{{%endraw%} .ID }}'"
      register: service_check

    - set_fact:
        has_target_service: "{{ not (not service_check.stdout) }}"
        container_id: "{{ service_check.stdout }}"

    - group_by:
        key: "has_service_{{ has_target_service }}"

这将创建两个组,has_service_True 用于 运行 您的目标服务的主机,has_service_False 用于非目标服务的主机。它还会在 运行 您的目标服务的主机上设置 container_id 事实。

然后你可以编写一个新的 play 来处理数据库更新,你可以使用 run_once 指令来确保它只在单个主机上运行:

- hosts: has_service_True
  gather_facts: false
  tasks:
    - name: alembic upgrade head exec
      run_once: true
      shell: docker exec -i {{container_id}} bash -c 'alembic upgrade command'

回复评论

  1. 问题是 Docker 中用于格式化的语法与 Ansible 中用于 Jinja 表达式的语法相同。因此,如果您编写 {{ something }},Ansible 将尝试将其解释为 Jinja 表达式。使用 {%raw}...{%endraw%} 允许我们以一种不会被 Ansible 捕获的方式编写 {{

  2. 我写了{{ not (not service_check.stdout) }}因为service_check.stdout是一个字符串,我想要一个布尔值。评估为布尔值的空字符串是 false,非空字符串是 true。因此,如果 stdout 有内容,则表达式 not service_check.stdout 将为 false,如果为空,则为 true。我想要相反的东西,所以我们再次否定表达式。

    老实说,我本可以写成 {{ true if service_check.stdout else false }},这样可能更清楚。