最近我们配合 Ansible 和 Terraform 做了一个小小的业余项目,其中有一项就是通过 Python 调用 Ansible 实现的,因为这一部分的内容目前在网络上还不是太多,所以我觉得有必要写一篇文章分享一下。
在玩我们的项目之前,我也尝试过寻找 Python 调用 Ansible 的开源库,可惜没有看到好用的,但是,有个小伙伴找到一个 Ansible 官方的 Ansible-Runner 挺好用,不知道为什么当初自己没找到。所以,在这篇文章中,我就以 Ansible-Runner 为例子进行介绍如何使用 Python 调用 Ansible。
Ansible 简介
当然,在开始本文的正文之前,我觉得有必要稍微介绍一下 Ansible,我最初接触 Ansible 是那家企业通过 Ansible 来部署生产环境的代码,好处就是部署回滚,错误恢复,多机器同时操作简单。所以,很显然,从这些优点描述中可以看出来,Ansible 是一个配置管理和应用程序部署工具,它通过配置文件控制需要部署的机器以及被部署上去的应用,这些应用的运行参数等。
根据现在搜索 Ansible 的结果,发现 Ansible 已经被 Red Hat 收编,看上去也是个不错的事情。那么关于 Ansible 的简单介绍就到这了,下面就开始介绍一下如何使用 Ansible。
Playbook
虽然 Ansible 可以直接通过执行远程命令的方式使用,例如:
[root@liqiang.io]# ansible all -a "/bin/echo hello"
但是,更通常来说,一般都是以 Playbook 的形式来运行的,因为类似于这样的命令行形式不仅是“一次性”的,没有积累;而且,如果命令多了,长了怎么敲。所谓的 Playbook ,其实就是 Ansible 希望运行在目标机器上的一系列命令的集合,甚至高大上点说,是方案,其实就是一堆配置文件,每个配置文件都记录着需要执行的命令和参数,或者是执行的目标机器列表,或者说是一个可以复用的 Playbook(嵌套概念)。
一个 Playboook 的目录结构可能长这样:
[root@liqiang.io]# ls -al playbook
.
├── env
│ ├── envvars
│ ├── extravars
│ ├── passwords
│ ├── settings
│ └── ssh_key
├── inventory
│ └── hosts
└── project
├── roles
│ └── testrole
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
└── test.yml
这个简单的例子展示了 Ansible 的很多重要的功能和概念,例如:
- env/envvars:这里定义了执行命令时可以从环境变量中获取的值
- env/extravars:这里定义了执行 Playbook 时可以制定参数的值
- inventory:指定了需要在哪些机器上执行 Playbook 的命令
- roles:每一个 Role 都是一个可以复用的单独模块(例如安装一套 Kubernetes)
- test.yml:这个是 Playbook 的主要入口,可以理解为平时写代码的 main 函数
了解了这些之后,我就开始介绍如何用 Python 来调用 Ansible 了。
Python 调用 Ansible
安装 Ansible-Runner
对于 Python 来说,一般情况下都是很容易安装一个 Library 的,除非这个 Library 的打包方式或者运行方式有很强的环境依赖性,不然都可以像:
[root@liqiang.io]# pip install ansible-runner
轻松安装。然而,Ansible 就是那特殊的其中一个,它依赖于 Python 头文件,所以你需要安装更多的东西:
[root@liqiang.io]# yum install python-devel
通过 Python 执行 Ansible
通过 Python 来执行 Ansible 其实变得很简单了,简单到只需要:
[root@liqiang.io]# cat main.py
from ansible_runner import Runner
runner = Runner(config=rc)
status, exitcode = runner.run()
即可。但是这个只能获取到 Ansible 执行完成(出错)之后的状态,无法实时获取到中间过程的运行状态。所以,如果对中间过程有一些要求,得尝试这种方式来获取中间状态:
[root@liqiang.io]# cat main.py
import ansible_runner
r = ansible_runner.run(private_data_dir='/tmp/demo', playbook='test.yml')
print("{}: {}".format(r.status, r.rc))
# successful: 0
for each_host_event in r.events:
print(each_host_event['event'])
print("Final status:")
print(r.stats)
然后我们尝试执行一遍:
[root@liqiang.io]# python main.py
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [debug] *******************************************************************
"msg": "Test!"
}
ok: [localhost] => {
"msg": "Test!"
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
successful: 0
playbook_on_start
playbook_on_play_start
playbook_on_task_start
runner_on_start
runner_on_ok
playbook_on_task_start
runner_on_start
runner_on_ok
playbook_on_stats
Final status:
{'dark': {}, 'skipped': {}, 'ok': {u'localhost': 2}, 'processed': {u'localhost': 1}, 'failures': {}, 'changed': {}}
可以发现,可以简简单单获取到每个中间状态。