Ansible - 使用 slurp 定义每个主机环境变量以读取远程主机上的文件

Ansible - Define per host environment variable using slurp to read file on remote host

我正在编写一个 ansible 脚本,我想在其中读取每台主机上的文件并根据该文件中的某些文本为该主机设置环境变量。我需要该环境变量在该主机上的整个剧本执行期间可用。

我一直在读的是,如果我在任务下定义 env: ,它只适用于该任务,而不适用于其他后续任务。对吗?

- name: Modify server properties
  hosts: kafka_broker
  vars:
    ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
    ansible_host_key_checking: false
    contents: "{{ lookup('file', '/etc/kafka/secrets/masterkey.txt') }}"
    extract_key: "{{ contents.split('\n').2.split('|').2|trim }}"

  environment:
    CONFLUENT_KEY: "{{ extract_key }}"

这就是我尝试从每个主机获取信息并希望为每个主机设置环境变量但适用于该主机的整个剧本的方式

- name: Slurp hosts file
  slurp:
    src: /etc/kafka/secrets/masterkey.txt
  register: masterkeyfile

- debug: msg="{{ masterkeyfile['content'] | b64decode }}"

- name: Set masterkeyfilecontent 
  set_fact:
    masterkeyfilecontent: "{{ masterkeyfile['content'] | b64decode }}" 

- name: Set masterkeyval 
  set_fact:
    masterkeyval: "{{ masterkeyfilecontent.split('\n').2.split('|').2|trim }}"

然后我想为每个主机设置环境变量

CONFLUENT_KEY: "{{ masterkeyval }}

- debug:
    var=masterkeyval

可以吗?我怎样才能定义我的任务/ansible 脚本来实现这个目标?

谢谢

解决方案 1:当地事实

这个解决方案是 IMO 最简单的解决方案,但需要在每个目标服务器上放置一个文件。

假设您将以下可执行文件放入 /etc/ansible/facts.d/kafka.fact。这只是一个虚拟示例,请根据您的具体需求进行调整。我正在使用 jq 输出正确的 json 字符串。如果相信关键内容不会出问题,可以直接echo。您也可以使用任何其他您喜欢的可执行文件(python、ruby、perl...),只要您输出 json 结构

#!/bin/bash

# replace here with some logic to read the value you need.
# I'll use a static value for this example
PARSED_KEY="I'm a key that should be parsed dynamically"

# echo the json result using jq
echo $(
    jq -n \
    --arg pk "$PARSED_KEY" \
    '{masterkey: $pk}'
)

完成此操作后,您可以看到给定主机的事实可用。我将只在此处演示本地主机,但这适用于任何具有此本地事实脚本的主机:

$ ansible localhost -m setup -a filter=ansible_local
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "kafka": {
                "masterkey": "I'm a key that should be parsed dynamically"
            }
        }
    },
    "changed": false
}

您现在可以在任何需要的地方使用这个事实。请注意,您当然必须收集事实才能使该值可用。对于您的情况,我们可以使用以下剧本进行测试:

---
- hosts: localhost

  environment:
    CONFLUENT_KEY: "{{ ansible_local.kafka.masterkey | default ('empty') }}"

  tasks:
    - name: echo our env var
      command: echo $CONFLUENT_KEY
      register: echo

    - name: show the result
      debug:
        msg: "Our env is: {{ echo.stdout }}"

这给出了

PLAY [localhost] ***************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************
ok: [localhost]

TASK [echo our env var] ********************************************************************************************************************************************************
changed: [localhost]

TASK [show the result] *********************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "Our env is: I'm a key that should be parsed dynamically"
}

PLAY RECAP *********************************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

方案二:自定义事实模块

这个解决方案有点复杂,但从长远来看可能具有更好的管理能力,并且不需要将文件放在目标服务器上。

为了尽可能简单,我将我的演示事实模块放在我的剧本旁边的 library 文件夹中。将这样的模块放在集合或角色中对于生产项目来说是更可取的,但超出了这个(已经很长的)答案。要获得有关所有这些主题的更多信息,您可以阅读(参考文献的非详尽列表):

创建一个 library/kafka_facts.py 文件。你将不得不适应你的具体情况。在这种情况下,我决定将密钥放在 /tmp/keyfile.txt 中的一行中,并将其硬编码到我的模块中。请注意,如果文件不存在,则不会返回事实。我按照上述文档 link.

中示例的建议添加了所有文档字符串
#!/usr/bin/python

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = r'''
---
module: kafka_facts

short_description: This is a test module for example

version_added: "1.0.0"

description: This is a test module for example to answer a question on Whosebug.

author:
    - Olivier Clavel (@zeitounator)
'''

EXAMPLES = r'''
- name: Return ansible_facts
  kafka_facts:
'''

RETURN = r'''
ansible_facts:
  description: Kafka facts to add to ansible_facts.
  returned: always
  type: dict
  contains:
    kafka_masterkey:
      description: Masterkey present on server.
      type: str
      returned: when /tmp/keyfile.txt exists on server
      sample: 'f18bba7f-caf6-4897-95be-c7d3cc66f98a'
'''

from ansible.module_utils.basic import AnsibleModule


def run_module():
    module_args = dict()

    # Initialize result dict
    result = dict(
        changed=False,
        ansible_facts=dict(),
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    if module.check_mode:
        module.exit_json(**result)

    keyfile = "/tmp/keyfile.txt"
    try:
        with open(keyfile) as f:
            result['ansible_facts'] = {
                'kafka_masterkey': f.read(),
            }
    except FileNotFoundError:
        pass

    module.exit_json(**result)


def main():
    run_module()


if __name__ == '__main__':
    main()

与我之前的解决方案一样,以下演示将仅在本地主机上完成,但可以在任何目标服务器上运行。

首先我们在预期的文件中创建一个密钥

echo $(uuidgen) > /tmp/keyfile.txt

我们现在可以使用该模块,如下面的剧本所示:

---
- name: Use custom facts
  hosts: localhost
  gather_facts: false

  environment:
    CONFLUENT_KEY: "{{ ansible_facts.kafka_masterkey | default('N/A') }}"

  tasks:
    - name: echo the env var
      command: echo $CONFLUENT_KEY
      register: echo_before
      changed_when: false

    - name: show our var is empty for now
      debug:
        msg: "CONFLUENT_KEY was returned as: {{ echo_before.stdout }}"

    - name: Gather our custom facts related to kafka
      kafka_facts:

    - name: Echo the env var
      command: echo $CONFLUENT_KEY
      register: echo_after
      changed_when: false

    - name: show our var is empty for now
      debug:
        msg: "CONFLUENT_KEY was returned as: {{ echo_after.stdout }}"

给出:

PLAY [Use custom facts] ****************************************************************************************************************************************************************************************************************

TASK [echo the env var] ****************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [show our var is empty for now] ***************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "CONFLUENT_KEY was returned as: N/A"
}

TASK [Gather our custom facts related to kafka] ****************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Echo the env var] ****************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [show our var now has a value (if the file exists)] *******************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "CONFLUENT_KEY was returned as: d8fc525e-3fa9-4401-aca3-19ac915e5c0d"
}

PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0