Skip to content

Introduce an automation tool: Ansible

· 14 min
TL;DR

本文详细介绍了 Ansible 自动化运维工具的基本概念与使用方法。作为一种基于 Python 的自动化工具,Ansible 可通过单个控制节点管理多个被管理节点,实现批量文件配置、部署和命令执行。文章涵盖了 Ansible 的安装步骤、主机环境准备、inventory 文件配置以及执行 ad-hoc 命令的实用示例,是运维人员和开发者快速掌握 Ansible 的入门指南。

安装#

Ansible 是一种自动化运维工具,可以在单个主机 (控制节点 control node) 上管理多个主机 (被管理节点 managed node),比如批量文件配置,批量部署,批量运行命令等,在运维或者部署服务集群时很有用。 content abstract

由于 Ansible 是用 Python 实现的,所以安装也很简单:

Terminal window
pip install ansible

当然也有其它很多种安装方式,比如通过源码安装,或者通过系统包管理器安装,最简单的就是使用 Pip。

主机的环境准备#

基于 Red Hat Enterprise Linux 5.14 x86_64 系统

ansible 通过 ssh 实现主机间的通信,所以首先要保证主机间能相互”看得见”,假设有两个主机:host1, host2

  1. 设置 hostname 在部署服务集群的时候,为了方便可以将各个机器进行命名。 修改 /etc/hostname 文件,将相应的主机名改为如:node1node2

  2. 修改 /etc/hosts 文件,比如当有两个主机时,在每个主机/etc/hosts 文件中增加如下内容:

Terminal window
172.31.89.117 node1
172.31.67.18 node2

每个 IP 与相应的主机名对应

  1. 配置 ssh 公私钥 在每个主机上执行 ssh-keygen 命令,产生 rsa 类型密钥,然后执行 ssh-copy-id 将每个主机上的公钥传给另一个主机,

也可以手动在 ~/.ssh/authorized_keys 中添加。这样每个主机上就能不输入密码,通过 ssh 命令进入另一个主机。

Ansible 基本概念#

安装完成后,系统新增一系列命令,我们主要使用这三个:ansible, ansible-inventory, ansible-playbook

1. inventory 文件#

主机变量配置文件,可以理解为主机清单,主机库存文件。主要是在一个主节点上统一集中配置各个主机(节点)系统信息,网络位置等信息。

ansible 的默认主机配置文件在 /etc/ansible/hosts,如果在该默认路径下没有,则需要手动指定。

这个配置文件可以是 INI,或者 YML 格式。

如果是 INI 文件,配置如下:

inventory.ini

[myhosts]
172.31.89.117
172.31.67.18

这里 myhosts 是组名 (group name),这里即 host 组,作为 key 值需要是唯一的,且大小写敏感。

同样如果是 YML 文件,配置如下:

inventory.yml

myhosts:
hosts:
node1:
ansible_host: 172.31.89.117
node2:
ansible_host: 172.31.67.18

这里的 ansible_host 是 ansible 的默认主机变量,相应的还有 http_port 用来指定端口。

如果它们有共同的变量,可以这么写:

myhosts:
hosts:
node1:
ansible_host: 172.31.89.117
http_port: 8080
node2:
ansible_host: 172.31.67.18
http_port: 8081
vars:
ansible_user: "name"

其中 varshosts 同级,表示 vars 下面定义的变量是 host 间共用的。

如果是 ini 文件,则是:

[myhosts]
172.31.89.117
172.31.67.18
[myhosts:vars]
ansible_user="name"

可以嵌套其它组的组叫做元组 (metagroup),例如以下示例文件:

inventory-demo.yml

leafs:
hosts:
leaf01:
ansible_host: 192.0.2.100
leaf02:
ansible_host: 192.0.2.110
spines:
hosts:
spine01:
ansible_host: 192.0.2.120
spine02:
ansible_host: 192.0.2.130
network:
children:
leafs:
spines:
webservers:
hosts:
webserver01:
ansible_host: 192.0.2.140
webserver02:
ansible_host: 192.0.2.150
datacenter:
children:
network:
webservers:

其中 network 是一个 metagroup,它的子组包含另外两个组:leafs, spines。

datacenter 也是一个 metagroup,它包含 network, webservers 这两个组。

所以这个配置文件是一个 data center 的主机配置文件。

可以用 ansible-inventory 命令来校验一下配置文件。(特别对于 .yml 文件,可以看是否有语法错误)

Terminal window
[root@node1]# ansible-inventory -i inventory.yml --list
{
"_meta": {
"hostvars": {
"node1": {
"ansible_host": "172.31.89.117"
},
"node2": {
"ansible_host": "172.31.67.18"
}
}
},
"all": {
"children": [
"ungrouped",
"myhosts"
]
},
"myhosts": {
"hosts": [
"node1",
"node2"
]
}
}

同样可以使用 ansible 命令来 ping 一下 配置文件中的 host 组:

Terminal window
[root@node1]# ansible myhosts -m ping -i inventory.ini
172.31.67.18 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
172.31.89.117 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}

这里的 -m 指定了 ansible 内置的 ping 命令模块来执行通过 -i 指定的主机清单文件。

到这里为止,在介绍 playbook 之前,我们可以做一些好玩的事情:

使用 ansible 的方式除了使用写 playbook “剧本”(or 脚本) 来编排大量复杂的任务之外,还可以在命令行直接执行,在 ansible 中这叫做 ad-hoc 命令。

刚刚执行的 ansible myhosts -m ping -i inventory.ini 就是这种方式。

为了方便,我们将主机的 inventory 文件放到 /etc/ansible/hosts,默认会去这里找配置文件,因此不再需要使用 -i 手动指定了。

1. 批量管理主机#

举个例子 🙋‍♀️🌰,假如手上有 20 台主机,想批量 reboot 它们,配置好主机清单之后:

Terminal window
ansible myhosts -a "/sbin/reboot" -f 10

选项 -f 10 f 表示 forks,表示并行任务数,这里是每次重启 10 个。

tips: ansible 可以并发执行任务,默认的并发数是 5

2. 批量处理文件#

比如将本机的配置文件批量复制到所有主机,使用 copy 命令模块:

Terminal window
[root@node1]$ ansible myhosts -m copy -a "src=/etc/ansible/hosts dest=/tmp/hosts"
node2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"checksum": "c03a95124b63b489f76cc2a72ac72571f98290a5",
"dest": "/tmp/hosts",
"gid": 0,
"group": "root",
"md5sum": "7402f43011c6b536aaf58fe8963fa843",
"mode": "0644",
"owner": "root",
"secontext": "unconfined_u:object_r:admin_home_t:s0",
"size": 107,
"src": "/root/.ansible/tmp/ansible-tmp-1726336410.0496075-9635-157233006681802/source",
"state": "file",
"uid": 0
}
node1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"checksum": "c03a95124b63b489f76cc2a72ac72571f98290a5",
"dest": "/tmp/hosts",
"gid": 0,
"group": "root",
"md5sum": "7402f43011c6b536aaf58fe8963fa843",
"mode": "0644",
"owner": "root",
"secontext": "unconfined_u:object_r:admin_home_t:s0",
"size": 107,
"src": "/root/.ansible/tmp/ansible-tmp-1726336410.0473666-9634-77523772033960/source",
"state": "file",
"uid": 0
}

如果再重复执行一遍,并不会再次传输文件,除非之后文件的内容改变了,这个时候其 checksum 也变了。

重复执行的时候,不再是 CHANGED 状态,而是 SUCCESS, "changed": false

tips: ansible 执行任务是幂等的,同一个操作可以重复执行,不影响结果。

可以查看一下,使用 shell 命令模块:

Terminal window
[root@node1 ]$ ansible myhosts -m shell -a 'cat /tmp/hosts'
node2 | CHANGED | rc=0 >>
myhosts:
hosts:
node1:
ansible_host: 172.31.89.117
node2:
ansible_host: 172.31.67.18
node1 | CHANGED | rc=0 >>
myhosts:
hosts:
node1:
ansible_host: 172.31.89.117
node2:
ansible_host: 172.31.67.18

也可以删除掉:

Terminal window
[root@node1 /home ]$ ansible myhosts -m shell -a 'rm /tmp/hosts'
node2 | CHANGED | rc=0 >>
node1 | CHANGED | rc=0 >>

rc = 0 表示 shell 命令执行结果返回值为 0,执行成功

3. 批量包管理#

ansible 有专门的包管理模块,支持 apt, yum, dnf, dpkg,以及其它通用的包管理器。

当我要确定每个主机上已经安装了某个软件包,同时不想去升级它们:

Terminal window
ansible myhosts -m yum -a "name=python state=present"

预期状态 state 是 present,所以当安装了这个包,就是符合预期,返回成功 SUCCESS。

如果要确认一个包还没安装:

Terminal window
[root@node1 ]$ ansible myhosts -m yum -a "name=acme state=absent"
node2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"msg": "Nothing to do",
"rc": 0,
"results": []
}
node1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"msg": "Nothing to do",
"rc": 0,
"results": []
}

因为没有安装,与预期状态 state=absent 相符合,所以也返回 SUCCESS。

4. 批量管理服务#

在 RHEL 系统里,这里的服务包含 systemd 服务,init 服务等。

比如修改了网络配置文件后,需要确认网络服务已经重新 reload,加载新的配置文件了,则执行:

Terminal window
[root@node1 ]$ ansible myhosts -m service -a "name=NetworkManager state=reloaded"
node2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "NetworkManager",
"state": "started",
# 返回信息太多了,其它的就不显示了

这相当于在每个主机上执行了这个命令:systemctl reload NetworkManager.service。 这里的 state 可以是 reloaded, restarted, started, stopped,对应着 systemd 服务的 reload, restart, start, stop 操作。

5. 批量配置 cron 定时任务#

Terminal window
[root@node1]$ ansible all -m cron -a 'name="ntp update every 5 min" minute=*/5 job="/sbin/ntpdate 172.17.0.1 &> /dev/null"'
node2 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"envs": [],
"jobs": [
"ntp update every 5 min"
]
}
node1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"envs": [],
"jobs": [
"ntp update every 5 min"
]
}

这里 -a 指定的参数有 name, minute, job,分别是任务名称,任务定时器,任务脚本。

照例查看一下设置的任务:

Terminal window
[root@node1]$ ansible myhosts -m shell -a "crontab -l"
node2 | CHANGED | rc=0 >>
#Ansible: ntp update every 5 min
*/5 * * * * /sbin/ntpdate 172.17.0.1 &> /dev/null
node1 | CHANGED | rc=0 >>
#Ansible: ntp update every 5 min
*/5 * * * * /sbin/ntpdate 172.17.0.1 &> /dev/null

以上都用到了 ansible 的内置命令模块,我们可以使用 ansible-doc 命令查看这些命令模块:

Terminal window
ansible-doc -l # 获取全部模块的信息 (包括内置的和第三方的)
ansible-doc -s MOD_NAME # 获取指定模块的使用帮助
# e.g.
[root@node1]$ ansible-doc -s debug
- name: Print statements during execution
debug:
msg: # The customized message that is printed. If omitted, prints a generic message.
var: # A variable name to debug. Mutually exclusive with the `msg' option. Be aware that this
# option already runs in Jinja2 context and has an implicit
# `{{ }}' wrapping, so you should not be using Jinja2
# delimiters unless you are looking for double interpolation.
verbosity: # A number that controls when the debug is run, if you set to 3 it will only run debug when
# -vvv or above.

2. playbook 文件#

Playbook#

inventory 文件指定了主机的各种配置信息,确定了每个节点的角色。而 playbook 文件就是指定要在主机上执行什么批量任务,比如安装集群,更新集群,卸载集群等等,或者只是批量执行某个命令。

简单讲,playbook 文件就是任务编排文件,可以类比理解为 Docker 中的 Dockerfile 文件。

在实际项目中,要执行的任务包含一系列的复杂的步骤,它们组成一系列任务 (play)。

Play#

playbook 文件中的,由一系列按顺序排列的任务 (task) 组成的有序任务列表。

Task#

某个任务中,某项单独的操作,一般通过使用 ansible 定义的某个的命令模块 (module) 来执行操作。

Module#

模块就是一些命令或者可执行文件的抽象,在 Task 中通过调用某个模块来执行具体的命令。

以下是一个 playbook 文件的示例:

- name: My first play
hosts: myhosts
tasks:
- name: Ping my hosts
ansible.builtin.ping:
- name: Print message
ansible.builtin.debug:
msg: Hello world

这个一个 playbook 文件,里面很简单只有一个 play,因为只有一个 tasks,而这个 tasks 包含两个 task:

第一个 task 调用了 ansible 内置的 ping 命令模块,执行 ping 命令。

另一个 task 调用了 ansible 内置的 debug 命令模块,打印 debug 信息,将模拟 debug 信息传递给 msg 变量。

最后我们用 ansible-playbook 命令执行一下这个 playbook。

Terminal window
[root@node1]# ansible-playbook -i inventory.ini playbook.yml
PLAY [My first play] ***********************************************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************************************
ok: [172.31.67.18]
ok: [172.31.89.117]
TASK [Ping my hosts] ***********************************************************************************************************************************
ok: [172.31.67.18]
ok: [172.31.89.117]
TASK [Print message] ***********************************************************************************************************************************
ok: [172.31.67.18] => {
"msg": "Hello world"
}
ok: [172.31.89.117] => {
"msg": "Hello world"
}
PLAY RECAP *********************************************************************************************************************************************
172.31.67.18 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.31.89.117 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

其中 Gathering Facts 任务是默认执行的,在实际任务开始前用来收集主机的信息,在这里是读取 inventory.ini 文件。

最后 PLAY RECAP 总结了本次执行任务的总体情况,它们都是 ok 状态的。

以上就完成了 ansible playbook 的 hello world 过程。

常用的 module#

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/index.html

子任务类#

ansible.builtin.import_playbook

ansible.builtin.include_role

ansible.builtin.include_tasks

ansible.builtin.import_tasks

命令类#

ansible.builtin.shell

ansible.builtin.command

ansible.builtin.raw

ansible.builtin.script

ansible.builtin.win_shell

文件管理类#

ansible.builtin.file

ansible.builtin.tempfile

ansible.builtin.lineinfile

ansible.builtin.copy

ansible.builtin.stat

ansible.builtin.find

ansible.builtin.git

包管理类#

ansible.builtin.pip

ansible.builtin.yum

ansible.builtin.apt

服务管理类#

ansible.builtin.service

ansible.builtin.service_facts

ansible.builtin.systemd_service

ansible.builtin.sysvinit

具体使用可以看官方文档,或者使用 ansible-doc -s [module] 查阅。

以上。


References#