python的ansible库--ansible_runner

介绍

Ansible Runner是ansible官方提供的一个工具和python库,当直接与Ansible进行交互或作为另一个系统的一部分与Ansible进行交互时,无论是通过容器映像接口,作为独立工具还是作为可以导入的Python模块,它都可以提供帮助。 目的是为Ansible提供稳定且一致的接口抽象。

参考网站

官方文档
官方代码仓库

安装

首先,确保您的系统已经安装了Python,并且具备pip包管理工具。使用以下命令来安装Ansible Runner:

pip install ansible-runner

目录准备

Runner输入的目录层次结构

官方给出的规范目录结构

.
├── env
│   ├── envvars
│   ├── extravars
│   ├── passwords
│   ├── cmdline
│   ├── settings
│   └── ssh_key
├── inventory
│   └── hosts
└── project
    ├── test.yml
    └── roles
        └── testrole
            ├── defaults
            ├── handlers
            ├── meta
            ├── README.md
            ├── tasks
            ├── tests
            └── vars

目录 env

基本上是一些配置或者环境变量

文件env/envvars

Ansible Runner 将继承启动 shell 的环境。

此文件(可以是 json 或 yaml 格式)表示 将在运行时添加到环境中的 环境变量

例如

# env/envvars

---
TESTVAR: exampleval

在playbook中调用

---
- name: 测试主机
  user: root
  hosts: a
  gather_facts: no
  tasks:
    - name: 设置fact变量
      set_fact:
        abc: "{{ lookup('env', 'TESTVAR') }}"
    - name: 打印fact变量
      debug:
        var: abc
      # "abc": "exampleval"
    - name: 使用环境变量
      debug:
        msg: "The value of MY_VARIABLE is: {{ lookup('env', 'TESTVAR') }}"
      # "msg": "The value of MY_VARIABLE is: exampleval"

可以看出来,就是变成环境的环境变量

使用的话,需要用 lookup('env', 'TESTVAR') 这种形式调用

无论是通过 set_fact使用 还是 直接使用

文件env/extravars

Ansible Runner 收集这个文件中提供的额外变量,并将它们提供给 Ansible 进程本身。此文件可以是 jsonyaml 格式

就是python程序本身的变量,可以直接引用的变量

例如

# env/extravars

---
ansible_connection: local
test: val

在playbook中调用

---
- name: 测试主机
  user: root
  hosts: a
  gather_facts: no
  tasks:
    - name: 打印变量
      debug:
        msg: "{{ test }}"
      # "msg": "val"

如果在 Ansible 运行时,那就是 -e 后面传递的额外变量

文件env/passwords

Ansible 本身配置为 在 特定提示 时发出密码,这些提示可以根据需要请求(例如,请求连接密码)。

如果在 Ansible 运行时,那就是 -k 参数的意思

为了让 Runner 使用正确的密码进行响应,它需要能够匹配提示并提供正确的密码。目前支持此功能 通过提供具有正则表达式和要发出的值的 yaml 或 JSON 格式文件,例如:

---
"^SSH password:\\s*?$": "some_password"
"^BECOME password.*:\\s*?$": "become_password"

文件 env/cmdline

当前的 Ansible Runner 不会验证使用此方法传递的命令行参数,因此 playbook 编写者需要提供一组有效的选项。 此方法提供的命令行选项的优先级低于 Ansible Runner 设置的优先级。例如,这不会覆盖 inventorylimit 值。

Ansible Runner 将这个文件提供的命令行选项收集为字符串,并将它们提供给 Ansible 进程本身。

此文件应包含要添加的参数

例如:

--tags one,two --skip-tags three -u ansible --become

或者

--tags=my_tag
--skip-tags=skip_me
--extra-vars=my_var=my_value

当使用ansible-runner运行playbook时,这些命令行参数将自动传递给ansible-playbook命令。这就可以在运行playbook时轻松地自定义其行为,而无需在命令行中手动指定这些参数。

例如上面的例子

  • --tags=my_tag:仅运行具有标签my_tag的任务。
  • --skip-tags=skip_me:跳过具有标签skip_me的任务。
  • --extra-vars=my_var=my_value:设置一个名为my_var的额外变量,其值为my_value。

文件 env/ssh_key

目前只能通过此机制提供单个 ssh 密钥

此文件应包含用于连接到主机的 ssh 私钥。Runner 检测何时提供了私钥,并将对 Ansible 的调用包装在 ssh-agent 中。

要在 ansible-runner 项目中使用 env/ssh_key 文件,请按照以下步骤操作:

  1. 在项目目录中创建一个名为env的文件夹(如果尚未创建)。
  2. 将您的SSH私钥文件(通常是一个名为id_rsa的文件)复制到env文件夹中,并将其重命名为ssh_key。

现在,当您使用 ansible-runner 运行playbook时,这个私钥将用于与远程主机建立SSH连接。

请注意,为了保护您的私钥,请确保 env/ssh_key 文件的权限设置得当。通常,您应该将文件权限设置为600(只有所有者可以读取和写入)。

文件 env/settings

这个文件有所不同,settings是用来设置runner本身

设置参考 https://ansible.readthedocs.io/projects/runner/en/latest/intro/#env-settings-settings-for-runner-itself

Inventory

是用来存放主机清单的,可以是单个文件或者脚本,也可以是包含静态清单文件或者脚本的目录;

此清单在调用时会自动加载并提供给 Ansible,并且可以在命令行或通过环境变量进一步覆盖以直接指定主机。

例如

www.ixdba.net
[webservers]
#带端口
ixdba1.net:1234
#ip
10.1.2.3
#也可以这样指定端口
ixdba2.net ansible_ssh_port=9999

[dbservers]
db.ixdba1.net
#域名范围,从1到100
www[1:100].example.com
db.ixdba2.net

或者也可以带一些参数

[a]
192.168.1.2 ansible_ssh_pass='111111' ssh_port=22 ansible_user=root
192.168.1.3 ansible_ssh_pass='111111' ssh_port=22 ansible_user=root

目录 Project

Runner 目录是 playbook 根目录,其中包含这些 playbook 可以直接使用的 playbook 和角色。

各个playbook存放在这里,就可以在调用的使用直接指定对应playbook的文件名,而不用再指定路径

例如 有一个 project/test.yaml

那么,如果在命令行

ansible-runner run examples/ping/ -p test.yaml

如果是在python接口中

from ansible_runner import run

run(private_data_dir='examples/ping/', playbook='test.yaml')

Runner Artifacts 目录层次结构

还有一个不需要提前创建的目录 artifacts

这个目录将以分组的形式存放每次runner的运行结果

大概的目录情况

.
├── artifacts
│   └── identifier
├── env
├── inventory
└── project

而目录中分组的名称是生成一个UUID组成的标识符

这个UUID的获取方式

from ansible_runner import run
from ansible_runner.config.runner import RunnerConfig

directory = 'examples/ping'
ply = 'test.yaml'
runner = run(private_data_dir=directory, playbook=ply)
print(runner.stdout.read(), "===============")
print(runner.stderr.read(), "===============")
conf: RunnerConfig = runner.config

# 下面这三种方式都能获取到
print(conf.ident) # 456f20f5-4fff-49ea-853e-e87af1a2d3ef
print(conf.artifact_dir) # /data/pythonProject/artifacts/456f20f5-4fff-49ea-853e-e87af1a2d3ef
print(conf.env.get('AWX_ISOLATED_DATA_DIR')) #/data/pythonProject/artifacts/456f20f5-4fff-49ea-853e-e87af1a2d3ef

运行Runner后的目录结构

.
├── artifacts
│   └── 37f639a3-1f4f-4acb-abee-ea1898013a25
│       ├── fact_cache
│       │   └── localhost
│       ├── job_events
│       │   ├── 1-34437b34-addd-45ae-819a-4d8c9711e191.json
│       │   ├── 2-8c164553-8573-b1e0-76e1-000000000006.json
│       │   ├── 3-8c164553-8573-b1e0-76e1-00000000000d.json
│       │   ├── 4-f16be0cd-99e1-4568-a599-546ab80b2799.json
│       │   ├── 5-8c164553-8573-b1e0-76e1-000000000008.json
│       │   ├── 6-981fd563-ec25-45cb-84f6-e9dc4e6449cb.json
│       │   └── 7-01c7090a-e202-4fb4-9ac7-079965729c86.json
│       ├── rc
│       ├── status
│       └── stdout
├── env
├── inventory
└── project

如果又多个的话

.
├── artifacts
│   ├── 37f639a3-1f4f-4acb-abee-ea1898013a25
│   │   ├── fact_cache
│   │   │   ├── 192.168.1.11
│   │   │   └── 192.168.1.12
│   │   ├── job_events
│   │   │   ├── 1-34437b34-addd-45ae-819a-4d8c9711e191.json
│   │   │   ├── 2-8c164553-8573-b1e0-76e1-000000000006.json
│   │   │   ├── 3-8c164553-8573-b1e0-76e1-00000000000d.json
│   │   │   ├── 4-f16be0cd-99e1-4568-a599-546ab80b2799.json
│   │   │   ├── 5-8c164553-8573-b1e0-76e1-000000000008.json
│   │   │   ├── 6-981fd563-ec25-45cb-84f6-e9dc4e6449cb.json
│   │   │   └── 7-01c7090a-e202-4fb4-9ac7-079965729c86.json
│   │   ├── rc
│   │   ├── status
│   │   └── stdout
│   └── 4ee097bd-0929-4e36-9796-d81e6532f035
│       ├── fact_cache
│       │   └── localhost
│       ├── job_events
│       │   ├── 1-0fc6f26b-d8dd-45b7-909d-714292c95a48.json
│       │   ├── 2-d74d6cbd-a9a3-2aca-40e1-000000000003.json
│       │   ├── 3-d74d6cbd-a9a3-2aca-40e1-000000000005.json
│       │   ├── 4-79dbcd98-3eef-41ab-b47c-752b0577d041.json
│       │   ├── 5-227f77ee-d18e-4ba9-93cb-9c333c56450f.json
│       │   ├── 6-26a17488-8ab8-4861-99bc-c800da293def.json
│       │   ├── 7-c726a7e4-0376-4133-b537-2cd6f661e154.json
│       │   ├── 8-d74d6cbd-a9a3-2aca-40e1-000000000006.json
│       │   └── 9-df2fbfe9-330a-4577-b3e7-3bc85f3d917b.json
│       ├── rc
│       ├── status
│       └── stdout
├── env
├── inventory
└── project

rc文件

包含来自 Ansible 进程的实际返回代码。

status 文件

是显示playbook的执行状态

分别是下面三种情况:

  • success:Ansible 进程成功完成
  • failed:Ansible 进程失败
  • timeout:Runner 超时

stdout 文件

是执行结果的输出,是文本格式

例如

PLAY [测试主机] ****************************************************************

TASK [ping主机] ****************************************************************
ok: [192.168.1.2]

TASK [获取主机名] **************************************************************
changed: [192.168.1.2]

TASK [打印主机名] **************************************************************
ok: [192.168.1.2] => {
    "hostname.stdout": "ubuntu\r\n"
}

PLAY RECAP *********************************************************************
192.168.1.2             : ok=7    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

job_events文件夹

是用来存放执行步骤的,会将每步以单个json文件的形式存放

大概内容

{
  "uuid": "d74d6cbd-a9a3-2a18-1d85-000000000003",
  "counter": 2,
  "stdout": "\r\nPLAY [\u6d4b\u8bd5\u4e3b\u673a] ****************************************************************",
  "start_line": 0,
  "end_line": 2,
  "runner_ident": "456f20f5-4fff-49ea-853e-e8xxxxxxxxxxxxx",
  "event": "playbook_on_play_start",
  "pid": 10942,
  "created": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "parent_uuid": "b87b3311-53d2-440b-9f64-b72b2bf152e6",
  "event_data": {
    "playbook": "test.yaml",
    "playbook_uuid": "b87b3311-53d2-440b-9f64-b72b2bf152e6",
    "play": "\u6d4b\u8bd5\u4e3b\u673a",
    "play_uuid": "d74d6cbd-a9a3-2a18-1d85-000000000003",
    "play_pattern": "a",
    "name": "\u6d4b\u8bd5\u4e3b\u673a",
    "pattern": "a",
    "uuid": "d74d6cbd-a9a3-2aca-40e1-000000000003"
  }
}

使用

ansible-runner有两种调用方式

命令行工具

Ansible Runner 命令行工具可以用作 Ansible 本身的标准命令行界面,但主要目的是为了适应自动化和流水线工作流程。

因此,它的工作流程与 Ansible 本身略有不同,因为您可以在几种不同的模式之间进行选择来启动命令。

独立实用程序的调用示例

ansible-runner run /tmp/private -p playbook.yml

其中 playbook.yml/tmp/private/projects 目录中的 playbookrun 是要用于调用 Runner 的命令模式

runner 接受的不同命令包括:

  • run ansible-runner在 前台 启动,并等待底层 Ansible 进程完成后再返回
  • start ansible-runner作为 后台守护进程 启动并生成 PID 文件
  • stop ansible-runner 终止start后台 启动的进程
  • is-alive ansible-runner 检查用 start后台 启动的进程的状态

不管是哪种模式执行 ansible-runner,都会生产 artifacts 目录, 上面也介绍了,该目录下存放ansible-runner执行的结果、状态等, 可以通过 -i 参数来指定目录,不然的默认是在 <private_data_dir>/artifacts

直接运行模块

调用debug模块作为专用目录demo的示例

ansible-runner run demo -m debug --hosts localhost -a msg=hello

直接运行角色

ansible-runner run demo --role testrole --hosts localhost

以json的形式输出到控制台

Runner 支持将 json 事件数据结构直接输出到控制台(和 stdout 文件),而不是标准的 Ansible 输出

加上 -j 参数就可以了

ansible-runner ... -j ...

清理项目结果目录

通过选项 --rotate-artifacts 来控制,默认是0,表示不清理,会一直产生

ansible-runner run examples/ping/ -p test.yaml --rotate-artifacts 1

作为python的库调用

Ansible Runner旨在提供一个可以直接导入和使用的API,以便与Ansible本身进行交互,并公开了一些辅助接口。

这些模块主要围绕Runner对象构建。辅助方法将返回该对象的一个实例,该实例提供了执行Ansible命令结果的接口,或者根据接口的不同,返回包含实际输出和错误响应的元组。

Ansible Runner 本身是对Ansible执行过程的一个封装层,因此为了收集额外信息并对其进行处理和存储以便后续使用,它向系统中添加了插件和接口。

run() 方法

前台执行

具体参数

参数 类型 解释
private_data_dir str 包含 Runner 元数据所需的所有文件的目录,用于调用 Runner 模块。输出结果也将存储在此处以便后续消费。
playbook str或list 要在 Ansible 执行时调用的 playbook(可以是 play 或 play 列表,或者相对于 private_data_dir/project 的路径)。
rotate_artifacts int 最多保留 n 个工件目录,禁用则为 0(默认值)。用来控制运行结果的保留个数,0为全保存,用来清理产生的结果,非常方便
envvars dict Ansible 执行时要使用的环境变量。还将从 private_data_dir 中的 env/envvars 中读取环境变量。
extravars dict 在 Ansible 运行时通过 -e 传递的额外变量。还将从 private_data_dir 中的 env/extravars 中读取额外变量。
ssh_key str 作为 ansible-playbook 运行的一部分传递给 ssh-agent 的 SSH 私钥。
artifact_dir str 要在其中存储工件(artifact)的目录的路径,默认为 private_data_dir 中的 'artifacts'。
cmdline str 从 private_data_dir 中的 env/cmdline 中读取的 Ansible 命令行选项。
limit str 与 ansible 的 --limit 参数匹配以进一步限制要使用的清单。
forks int 控制 Ansible 并行并发性。
verbosity int 控制 ansible-playbook 的输出详细程度。
quiet bool 禁用所有输出。
event_handler Callable 可选回调,在 Runner 本身接收到任何事件时被调用,返回 True 以保留事件。
cancel_callback Callable 可选回调,可以通知 Runner 取消(返回 True)或不取消(返回 False)。
finished_callback Callable 可选回调,在关闭进程并完成清理后被调用。
status_handler Callable 可选回调,每当状态发生变化(例如...启动、运行、失败、成功、超时)时被调用。
artifacts_handler Callable 可选回调,在运行结束时处理来自运行的工件。
json_mode bool 在控制台和 stdout 文件中以 JSON 格式存储事件数据,而不是标准输出。
module str 在 ad-hoc 模式下要由 Runner 调用的模块。
module_args str 将提供给 ad-hoc 模式的模块参数。
host_pattern str 在 ad-hoc 模式下要匹配的主机模式。
inventory str或dict或list 通过覆盖 private_data_dir/inventory 中的清单目录/文件来指定特定的主机或主机列表。可以采用以下形式:
- private_data_dir 中的库存文件路径
- 支持 YAML/JSON 清单结构的本机 Python 字典
- INI 格式的文本 INI
- 空列表以禁用传递清单
ident str 此调用 Runner 的标识符。将用于创建和命名包含结果的 artifact 目录。
role str 要执行的角色名称。
roles_path dict或list 要分配给 ANSIBLE_ROLES_PATH 的目录或目录列表。
passwords dict 一个字典,包含在处理 Ansible 输出时使用的密码提示模式和响应值。还将从 private_data_dir 中的 env/passwords 中读取密码。
settings dict 一个字典,包含 ansible-runner 运行时环境的设置值。这些设置还将从 private_data_dir 中的 env/settings 中读取。
suppress_env_files bool 禁用写入可能包含敏感信息的 env 文件。
project_dir str playbook 内容的路径,默认为 private_data_dir 中的 'project'。
timeout int 以秒为单位的超时值,将在执行命令时传递给 pexpect 或 subprocess 调用(基于选择的 runner_mode)。如果触发超时,它将强制取消执行。
streamer str 可选地以流式传输管道的一个步骤调用 ansible-runner。
_input io.FileIO 用于流式传输管道输入的可选项文件或类似文件的对象。
_output io.FileIO 用于流式传输管道输出的可选项文件或类似文件的对象。
process_isolation bool 启用进程隔离,使用容器引擎(例如 podman)或沙盒(例如 bwrap)。
process_isolation_executable str 用于隔离执行的进程隔离可执行文件或容器引擎(默认:podman)。
process_isolation_path str 隔离的 playbook 运行将用于暂存的路径。(默认:/tmp)
process_isolation_hide_paths str或list 系统中应隐藏 playbook 运行的一路径或多路径。
process_isolation_show_paths str或list 系统中应暴露给 playbook 运行的一路径或多路径。
process_isolation_ro_paths str或list 系统中应以只读方式暴露给 playbook 运行的一路径或多路径。
container_image str 在运行 ansible 任务时要使用的容器镜像(默认:quay.io/ansible/ansible-runner:devel)
container_volume_mounts list 以 'host_dir:/container_dir' 形式的绑定挂载列表。 (默认:None)
container_options list 要传递给执行引擎的容器选项列表。
directory_isolation_base_path str 一个可选路径,将用作临时目录的基本路径,项目内容将被复制到此位置,然后在此位置作为工作目录进行 playbook 执行。
fact_cache str 用于 'jsonfile' 类型事实缓存的子目录的名称,该子目录位于 artifacts 目录中。
fact_cache_type str 要使用的事实缓存类型。默认为 'jsonfile'。
omit_event_data bool 省略额外的 ansible 事件数据,但仍包括 stdout 和事件。
only_failed_event_data bool 除非事件失败,否则省略额外的 ansible 事件数据,但仍包括 stdout 和事件。
check_job_event_data bool 检查作业事件数据是否完全生成。如果事件数据未完全生成且值为 'True',则会引发 'AnsibleRunnerException' 异常,如果设置为 'False',则记录一条调试消息并继续执行。默认值为 'False'。

最简单的例子

from ansible_runner import run

directory = 'examples/abc'
ply = 'test.yaml'


runner = run(private_data_dir=directory, playbook=ply)
print(runner.stdout.read(), "输出")
print(runner.stderr.read(), "异常")

设置结果保留个数,设置安静,设置环境变量,设置ansible的变量

from ansible_runner import run

directory = 'examples/ping'
ply = 'test.yaml'


runner = run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
             envvars={ 'ansible_user': 'root' }, extra_vars={ 'abcd': 'xxx' }, 
             quiet=True)
print(runner.stdout.read(), "输出")
print(runner.stderr.read(), "异常")

只运行简单的命令,设置安静,并以json的方式输出

from ansible_runner import run

r = run(
        inventory={
            "test": {
                "hosts": {
                    "host01": {
                        "ansible_host"    : "192.168.1.3",
                        "ansible_port"    : 22,
                        "ansible_user"    : "root",
                        "ansible_ssh_pass": "111111"
                    },
                }
            }
        },
        module="shell",
        module_args="ip addr| grep 'inet 10.0'|awk '{print $2}'",
        host_pattern="test",
        quiet=True,
        json_mode=True,
)

print("{}: {}".format(r.status, r.rc))
print(r.stdout.read())

回调函数部分

event_handler 回调函数

当回调函数返回为True的时候,数据才会被保存在产物中的job_events中

from ansible_runner import run

directory = 'examples/ping'
ply = 'test.yaml'


def event_callback(event_data):
    # 处理回调事件
    print("call event: ", event_data)
    if event_data['stdout'] == '':
        return False
    else:
        return True


runner = run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
             event_handler=event_callback)
print(runner.stdout.read(), "输出")
print(runner.stderr.read(), "异常")
status_handler 回调函数

当状态发生变化的时候,会执行的回调函数

预期的值包括:

  • starting:正在准备启动但尚未开始运行
  • running:Ansible任务正在运行
  • canceled:任务已通过回调或命令行手动取消
  • timeout:Runner设置中配置的超时时间已达到(参见 env/settings - Runner本身的设置)
  • failed:Ansible进程失败
  • successful:Ansible进程成功
from ansible_runner import run

directory = 'examples/ping'
ply = 'test.yaml'


def my_status_handler(data, runner_config):
    print(data,"++++++++++++++")
    """
    {
        "status": "running",
        "runner_ident": "e3436be2-f786-4acc-87bb-e25458c08395"
    }
    """
    print(runner_config,"========================================")


run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
    status_handler=my_status_handler)
artifacts_handler 回调函数

结束的时候会调用这个回调函数 artifacts_dir就是最后输出产物的目录

from ansible_runner import run

directory = 'examples/ping'
ply = 'test.yaml'


def my_artifacts_handler(artifacts_dir):
    # Do something here
    print(artifacts_dir)
    # /data/pythonProject/artifacts/456f20f5-4fff-49ea-853e-e87af1a2d3ef


runner = run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
             artifacts_handler=my_artifacts_handler)
cancel_callback 回调函数

事件的每次循环迭代中都会调用,并应返回 True 以通知Runner取消并关闭Ansible进程,或返回 False 以允许其继续。

from ansible_runner import run

directory = 'examples/ping'
ply = 'test.yaml'


def my_cancel_handler():
    print("cancel handler")
    return True


run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
    cancel_callback=my_cancel_handler)
finished_callback 回调函数

一旦Ansible关闭,Runner事件循环结束前将立即调用此函数

from ansible_runner import run
from ansible_runner.runner import Runner

directory = 'examples/ping'
ply = 'test.yaml'


def my_finished_handler(runner: Runner):
    print("finished handler", runner.stdout.read())


run(private_data_dir=directory, playbook=ply, rotate_artifacts=1,
    finished_callback=my_finished_handler)

通过配置的方式运行

from ansible_runner import Runner, RunnerConfig

# Using tag using RunnerConfig
rc = RunnerConfig(
    private_data_dir="project",
    playbook="main.yml",
    tags='my_tag',
)

rc.prepare()
r = Runner(config=rc)
r.run()

run_async()

这个方法与 run() 的参数一致,只是这个方法是个异步的方法

用法完全一致

posted @ 2024-03-08 17:33  厚礼蝎  阅读(1203)  评论(0编辑  收藏  举报