最近我们配合 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 可以直接通过执行远程命令的方式使用,例如:

  1. [root@liqiang.io]# ansible all -a "/bin/echo hello"

但是,更通常来说,一般都是以 Playbook 的形式来运行的,因为类似于这样的命令行形式不仅是“一次性”的,没有积累;而且,如果命令多了,长了怎么敲。所谓的 Playbook ,其实就是 Ansible 希望运行在目标机器上的一系列命令的集合,甚至高大上点说,是方案,其实就是一堆配置文件,每个配置文件都记录着需要执行的命令和参数,或者是执行的目标机器列表,或者说是一个可以复用的 Playbook(嵌套概念)。

一个 Playboook 的目录结构可能长这样:

  1. [root@liqiang.io]# ls -al playbook
  2. .
  3. ├── env
  4. ├── envvars
  5. ├── extravars
  6. ├── passwords
  7. ├── settings
  8. └── ssh_key
  9. ├── inventory
  10. └── hosts
  11. └── project
  12. ├── roles
  13. └── testrole
  14. ├── defaults
  15. └── main.yml
  16. ├── handlers
  17. └── main.yml
  18. ├── meta
  19. └── main.yml
  20. ├── README.md
  21. ├── tasks
  22. └── main.yml
  23. ├── tests
  24. ├── inventory
  25. └── test.yml
  26. └── vars
  27. └── main.yml
  28. └── test.yml

这个简单的例子展示了 Ansible 的很多重要的功能和概念,例如:

了解了这些之后,我就开始介绍如何用 Python 来调用 Ansible 了。

Python 调用 Ansible

安装 Ansible-Runner

对于 Python 来说,一般情况下都是很容易安装一个 Library 的,除非这个 Library 的打包方式或者运行方式有很强的环境依赖性,不然都可以像:

  1. [root@liqiang.io]# pip install ansible-runner

轻松安装。然而,Ansible 就是那特殊的其中一个,它依赖于 Python 头文件,所以你需要安装更多的东西:

  1. [root@liqiang.io]# yum install python-devel

通过 Python 执行 Ansible

通过 Python 来执行 Ansible 其实变得很简单了,简单到只需要:

  1. [root@liqiang.io]# cat main.py
  2. from ansible_runner import Runner
  3. runner = Runner(config=rc)
  4. status, exitcode = runner.run()

即可。但是这个只能获取到 Ansible 执行完成(出错)之后的状态,无法实时获取到中间过程的运行状态。所以,如果对中间过程有一些要求,得尝试这种方式来获取中间状态:

  1. [root@liqiang.io]# cat main.py
  2. import ansible_runner
  3. r = ansible_runner.run(private_data_dir='/tmp/demo', playbook='test.yml')
  4. print("{}: {}".format(r.status, r.rc))
  5. # successful: 0
  6. for each_host_event in r.events:
  7. print(each_host_event['event'])
  8. print("Final status:")
  9. print(r.stats)

然后我们尝试执行一遍:

  1. [root@liqiang.io]# python main.py
  2. PLAY [all] *********************************************************************
  3. TASK [Gathering Facts] *********************************************************
  4. ok: [localhost]
  5. TASK [debug] *******************************************************************
  6. "msg": "Test!"
  7. }
  8. ok: [localhost] => {
  9. "msg": "Test!"
  10. }
  11. PLAY RECAP *********************************************************************
  12. localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
  13. PLAY RECAP *********************************************************************
  14. localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
  15. successful: 0
  16. playbook_on_start
  17. playbook_on_play_start
  18. playbook_on_task_start
  19. runner_on_start
  20. runner_on_ok
  21. playbook_on_task_start
  22. runner_on_start
  23. runner_on_ok
  24. playbook_on_stats
  25. Final status:
  26. {'dark': {}, 'skipped': {}, 'ok': {u'localhost': 2}, 'processed': {u'localhost': 1}, 'failures': {}, 'changed': {}}

可以发现,可以简简单单获取到每个中间状态。