变量改变时执行一次任务
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'
回复评论
问题是 Docker 中用于格式化的语法与 Ansible 中用于 Jinja 表达式的语法相同。因此,如果您编写 {{ something }}
,Ansible 将尝试将其解释为 Jinja 表达式。使用 {%raw}...{%endraw%}
允许我们以一种不会被 Ansible 捕获的方式编写 {{
。
我写了{{ not (not service_check.stdout) }}
因为service_check.stdout
是一个字符串,我想要一个布尔值。评估为布尔值的空字符串是 false
,非空字符串是 true
。因此,如果 stdout
有内容,则表达式 not service_check.stdout
将为 false
,如果为空,则为 true
。我想要相反的东西,所以我们再次否定表达式。
老实说,我本可以写成 {{ true if service_check.stdout else false }}
,这样可能更清楚。
我有剧本,它在服务器(整个环境)上寻找特定容器,然后在具有所需服务的服务器上获取 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'
回复评论
问题是 Docker 中用于格式化的语法与 Ansible 中用于 Jinja 表达式的语法相同。因此,如果您编写
{{ something }}
,Ansible 将尝试将其解释为 Jinja 表达式。使用{%raw}...{%endraw%}
允许我们以一种不会被 Ansible 捕获的方式编写{{
。我写了
{{ not (not service_check.stdout) }}
因为service_check.stdout
是一个字符串,我想要一个布尔值。评估为布尔值的空字符串是false
,非空字符串是true
。因此,如果stdout
有内容,则表达式not service_check.stdout
将为false
,如果为空,则为true
。我想要相反的东西,所以我们再次否定表达式。老实说,我本可以写成
{{ true if service_check.stdout else false }}
,这样可能更清楚。