Ansible 使用介绍
环境信息
- Centos 7
- ansible-core 2.16
- Docker image python:3.12.3
安装
ansible-core 版本及 Python 版本支持对应关系
| ansible-core Version | Control Node Python | Target Python / PowerShell |
|---|---|---|
| 2.16 | Python 3.10 - 3.12 | Python 2.7 Python 3.6 - 3.12 Powershell 3 - 5.1 |
为了环境部署方便灵活,可以选择使用 python:3.12.3 的 Docker 镜像,以其为基础环境安装 ansible-core 2.16 或者直接使用 ansible 镜像启动。
docker run --rm -it python:3.12.3 bash |
Ansible 配置说明
Ansible 主配置文件为 /etc/ansible/ansible.cfg,其中的配置都可以被 ansible-playbook 或者命令行参数覆盖。
ansible 默认会读取环境变量
ANSIBLE_CONFIG指定的配置文件,当前路径下的ansible.cfg,以及用户家目录下的.ansible.cfg,以及/etc/ansible/ansible.cfg作为配置文件,已第一个找到的为准
常用配置说明
| 配置项 | 说明 | 示例 |
|---|---|---|
inventory |
指定 inventory (主机列表)文件的路径,默认为 /etc/ansible/hosts |
|
remote_user |
(未指定用户时)连接远程主机时使用的用户 | |
remote_port |
连接远程主机时使用的(默认)端口 | |
host_key_checking |
默认启用。检查主机密钥可以防止服务器欺骗和中间人攻击。 如果主机重新安装并且在 know_hosts 中拥有不同的密钥,ansible 会提示确认密钥。如果要禁用此行为,可以配置为 False |
|
ask_pass |
默认为 False。当设置为 True 时,ansible 要求输入远端服务器的密码,即使配置了免密登录 | |
log_path |
日志文件,默认 /var/log/ansible.log |
|
pattern |
当没有给出 pattern 时的默认 pattern,默认值是 * 即所有主机 |
配置示例
[defaults] |
Inventory 配置说明
默认的 inventory 配置文件路径为 /etc/ansible/hosts,主要用来配置 Managed Hosts 列表 [3]
在命令行中,可以使用选项 -i <path> 指定不同的 inventory 或者可以在 ansible 配置文件 ansible.cfg 中使用指令 inventory 指定 inventory 文件位置。
命令行中可以使用
-i <path1> -i <path2> ...指定多个inventory
inventory 文件支持多种格式,最常见的是 INI 和 YAML 格式。
- Ansible 默认创建了 2 个组:
all: 包含所有主机ungrouped: 包含所有不在其他组(all除外)中的所有主机。任何一个主机都会至少在 2 个组中,要么 在
all和某个组中,要么 在all和ungrouped组。
- 一个主机可以包含在多个组中
parent/child组,child组被包含在parent组中。- INI 配置格式中,使用
:children后缀配置parent - YAML 配置格式中,使用
children:配置parent- 任何在
child组中的主机自动成为parent组中的一员 - 一个组可以包括多个
parent和child组,但是不能形成循环关系 - 一个主机可以在多个组中,但是在运行时,只能有一个实例存在,Ansible 会自动将属于多个组的主机合并。
- 任何在
- INI 配置格式中,使用
- 主机范围匹配。如果有格式相似的主机,可以通过范围格式使用一条指令来添加多台主机。
- INI 配置格式中,使用以下格式
[webservers]
www[01:50].example.com
# 指定步长增长
www[01:50:2].example.com
db-[a:f].example.com - YAML 配置格式中,使用以下格式
...
webservers:
hosts:
www[01:50].example.com:
## 指定步长增长
www[01:50:2].example.com:
db-[a:f].example.com:范围格式 的第一项和最后一项也包括在内。即匹配
www01和www50
- INI 配置格式中,使用以下格式
Inventory 多配置文件支持
在主机数量较多,或者组织结构较复杂的情况下,使用单个 Inventory 配置文件会导致主机管理较为复杂。将单个 Inventory 配置文件按照项目或者组织或其他规则进行分割会显著降低维护复杂度。
Inventory 多配置文件支持,可以使用以下方法之一
- 按照项目或者组织或其他规则将主机分割到多个配置中,命令行中可以使用
-i <path1> -i <path2> ...指定多个inventory - 按照项目或者组织或其他规则将主机分割放置在多个文件中,并将所有文件统一放置在一个单独的目录中(如
/etc/ansible/inventory/),在命令行中使用选项-i /etc/ansible/inventory/或者在 Ansible 配置文件(ansible.cfg)中使用指令inventory配置目录。注意事项: Ansible 使用字典顺序加载配置文件,如果在不同的配置文件中配置了
parent groups和child groups,那么定义child groups的配置要先用定义parent groups的文件加载,否则 Ansible 加载配置会报错:Unable to parse /path/to/source_of_parent_groups as an inventory source[4] - 使用
group_vars和host_vars目录分别存储组变量和主机变量 [7]注意事项: 组变量和主机变量必须使用 YAML 格式,合法的文件扩展名包括:
.yaml、yml、.json或者无文件扩展名
INI 格式的 Inventory
主机列表中的主机可以单独出现,也可以位于某个或者多个 组([] 开头的行)中
ansible-demo1.local |
连接主机使用的常用配置说明 [6]
| 配置项 | 说明 | 示例 |
|---|---|---|
ansible_host |
远程主机地址 | |
ansible_port |
远程主机端口 | |
ansible_user |
连接远程主机的 ssh 用户 Ansible 默认使用 control node 上执行 ansible 的用户名来连接远程主机 [9] |
|
ansible_password |
连接远程主机的 ssh 用户密码,建议使用 key 连接 | |
ansible_ssh_private_key_file |
连接远程主机的 ssh 私钥文件路径 | |
ansible_become ansible_sudo ansible_su |
用户权限提升 | |
ansible_become_method |
用户权限提升(escalation)的方式 | |
ansible_become_user ansible_sudo_user ansible_su_user |
用户权限提升(escalation)后的用户 | |
ansible_become_password ansible_sudo_password ansible_su_password |
sudo 密码(这种方式并不安全,强烈建议使用 --ask-sudo-pass) |
|
ansible_become_exeansible_sudo_exe ansible_su_exe |
设置用户权限提升(escalation)后的可执行文件 | |
ansible_connection |
与主机的连接类型.比如:local, ssh 或者 paramikoAnsible 1.2 以前默认使用 paramiko。1.2 以后默认使用 smart,smart 方式会根据是否支持 ControlPersist, 来判断 ssh 方式是否可行. |
|
ansible_shell_type |
目标系统的 shell 类型.默认情况下,命令的执行使用 sh 语法,可设置为 csh 或 fish. |
|
ansible_python_interpreter |
目标主机的 python 路径 系统中有多个 Python, 或者命令路径不是 /usr/bin/python |
变量
在 inventory 的配置中,可以添加变量,以在 playbook 中使用变量,实现对不同主机的个性化配置。变量分好几个层级:
- 主机变量: 跟在主机后面直接设置,变量的作用域仅限于主机。由于对变量名没有限制,所以前面的那些 ansible 的配置如果打错字了也不会报错,ansible 会认为这是一个主机变量
/etc/ansible/hosts 127.0.0.1 http_port=80 maxRequestPerChild=808
变量值中包含空格,需要用引号(单引号/双引号)
- 组变量: 组变量需要新开一个
section, 配置在名为[组名:vars]的section中/etc/ansible/hosts [localloops]
127.0.0.[1:5] ansible_connection=paramiko ansible_ssh_user=root
[localloops:vars]
http_port=8080因为主机可以在多个组中定义,假如在多个组中定义的同一个主机的同名变量的值不一样,Ansible 使用以下优先级使用变量: [5]
- host
- child group
- parent group
- all group
使用主机别名
在 Inventory 中定义主机时,可以使用主机别名
jumper ansible_port=5555 ansible_host=192.0.2.50 |
YAML 格式的 Inventory
基本的 YAML 格式的 Inventory 文件内容如下 [3]
ungrouped: |
变量
主机变量定义
atlanta: |
组变量定义
atlanta: |
定义可以为所有主机共用的变量
假如有些变量可以被所有主机或者大部分主机所使用(继承),可以将其定义在 all 组中,all 组是 Ansible 自动创建的,用于包含所有主机,因此在这个组中定义的变量会被所有主机继承。
all: |
所有这些变量将适用于 ansible_controller_host 组和 test_target1 组中定义的所有主机,除非在特定主机或其他组中明确覆盖了这些变量。
在主机较多的场景下,需要将变量分离以方便管理维护,可以使用 group_vars 文件,在 Inventory 配置文件的同级目录下创建目录 group_vars,在其下创建文件 all.yml
ansible_user: admin |
Inventory 文件如下:
|
使用主机别名
在 Inventory 中定义主机时,可以使用主机别名
... |
ansible 使用
在配置好 inventory 后,要使 ansible 可以连接到 Managed Host,需要使用密码或者 key 的方式进行认证,建议使用 Private Key 的方式进行认证。参考文档配置 ssh 公私钥免密码认证过程
配置完 hosts 之后,在命令行中可以调用一些命令来使用 ansible,如 ansible all -m ping。命令行的 ansible 工具大体格式如下:
ansible <pattern> -m <module> -a <arguments> |
Patterns
pattern 是一个标识,指定出了要操作的目标主机,常用的 patterns 如下 [8]
| 描述 | Pattern(s) | Targets |
|---|---|---|
| 所有主机 | all* |
|
| 一个主机 | host1 ip/域名 |
|
| 多个主机 | host1:host2host1,host2192.168.1.* 192.0.**.example.com*.com |
建议用 ,,在主机或者组中有 : 字符时必须用 , 如果主机名和组名冲突,则以先出现的为准。 |
| 一个组 | webservers |
|
| 多个组 | webservers:dbservers |
取两个组的 并集 如果主机名和组名冲突,则以先出现的为准。 |
| 排除组 | webservers:!atlanta |
所有在 webservers 组中,但是不在 atlanta 组中 |
| 组的交集 | webservers:&staging | any hosts in webservers that are also in staging |
| slice(切片操作) | webservers[0] 第一个主机webservers[-1] 最后一个主机webservers[0:2] 第 1 个到第 3 个(包含)webservers[1:] 第 2 个到最后一个webservers[:3] 第一个到第 4 个 |
|
| 正则表达式匹配 | `~(web | db).*.example.com以~` 开头的匹配表达式表示使用正则匹配 |
- 可以混用:
one*.com:dbservers- 如果 Inventory 中使用了别名(alias),必须使用别名来匹配。YAML 格式中明确使用了别名。
ansible 命令常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
-m, --module-name |
指定模块 默认为 command |
ansible all -m ping |
--become-user 'BECOME_USER' |
作,默认为 root <br/切换到指定的用户执行操>需要配合 -b, --become 选项使用 |
ansible all -m command -a "ls /root/" -b --become-user root |
-b, --become |
提升权限到 root 权限,需要用户有 sudo 权限 |
|
--list-hosts |
列出 pattern 匹配的主机列表,不执行其他任何操作 |
ansible --list-hosts |
-C, --check |
Check mode,不执行任何实际操作,而是对要执行的操作进行验证 |
|
-k, --ask-pass |
询问连接密码 | |
-o, --one-line |
输出到一行里面 | |
-f ,--forks |
指定并发执行的数量,默认为 5 |
ansible-doc 使用
ansible-doc 命令提供 Ansible 安装的模块的简要文档(帮助)信息,其帮助示例中的代码可以直接复制粘贴到 Playbook 中使用。
ansible-inventory
ansible-inventory 命令用于列出 Ansible Inventory 的详细配置。
ansible 常用模块
command
command 是 ansible 默认的模块。command 模块不支持 shell 变量,也不支持管道/重定向 等 shell 相关的功能.如果你想使用 shell 相关的这些功能, 请使用 shell 模块. [2]
shell
shell 模块启动一个 shell 然后执行命令
ansible raleigh -m shell -a 'echo $TERM' |
使用 Ansible ad hoc 命令行接口时(与使用 Playbooks 的情况相反),尤其要注意 shell 引号的规则。比如在上面的例子中,如果使用双引号
"echo $TERM",会求出TERM变量在当前系统的值,而我们实际希望的是把这个命令传递 到其它机器执行。
以下示例展示在 shell 模块中使用 awk 命令的正确用法
在本地(localhost) 的 Shell 中执行如下命令
cd /home/logs/nginx/access; cat *.log | awk -F'|' '{print $5}' | sort | uniq -c
405946 request_method:GET
1 request_method:HEAD
25 request_method:OPTIONS
262838 request_method:POST- 以上命令中,出现了
$符号,如果外层使用""嵌套,如ansible-3 172.31.26.138 -m shell -a "cd /home/logs/nginx/access; cat *.log | awk -F'|' '{print $5}' | sort | uniq -c",会导致$5被解析为 Ansible 管理主机上的变量。 awk命令中的{print $5}需要使用单引号',如果ansible -a参数使用'会出现冲突
要解决此问题, 需要使用以下方法 :
ansible-3 172.31.26.138 -m shell -a 'cd /home/logs/nginx/access; cat *.log | awk -F"|" '"'"'{print $5}'"'"' | sort | uniq -c'
172.31.26.138 | CHANGED | rc=0 >>
2 request_method:-
664343 request_method:GET
1 request_method:HEAD
27 request_method:OPTIONS
430357 request_method:POST外层的单引号:
'...'是 Ansibleshell -a命令需要的包裹方式。awk中的单引号需要先用双引号:"结束外层单引号。再写单引号:'...'(用于awk)。 最后再用双引号:"重新回到外层单引号范围- 以上命令中,出现了
ping
用来测试到目标服务器的连通性
ansible all -m ping |
setup
使用 setup 模块,ansible 可以收集各个 Managed Hosts 的 facts,其中包含了 Managed Hosts 的很多元数据。
ansible k8s-master-nodes[0] -m setup |
配合 setup 模块的 filter 参数,可以从输出中过滤出各种信息
ansible fm-k8s-uat-master-nodes[0] -m setup -a "filter=ansible_default_ipv4" |
在 Playbook 中可以使用 ansible_facts 变量直接引用 facts
hostname
修改主机名,官方文档说明
- name: Set a hostname |
lineinfile
此模块用了确定给定的 一行内容 是否在指定文件中,或者替换文件中存在的 一行 内容。仅在需要修改单行内容时使用此模块 [10]
如果需要对多行文本或者是整块文本做修改,可以参考 ansible.builtin.blockinfile、ansible.builtin.replace、ansible.builtin.copy or ansible.builtin.template 等模块
reboot
service
ansible.builtin.service 管理远程主机上的服务状态。 [11]
ansible.builtin.service模块相当于是底层服务管理工具(systemd、sysvini等)的一个代理。默认情况会使用ansible.builtin.setup中发现的服务管理工具来管理远程主机上的服务状态。也可以使用use参数手动指定。ansible.builtin.service中的参数不一定适用于所有的底层服务管理工具(systemd、sysvini等)
常用参数
| 参数 | 说明 | 示例 |
|---|---|---|
namestring / required |
服务名称 | |
enabledboolean |
服务是否要开机启动,可选值包括 - false- true |
|
statestring |
期望的(desired)服务状态。可选值包括: - started / stopped : 如果服务状态已满足,不执行任何操作- restarted: 重启- reloaded |
|
usestring |
默认情况下,系统使用 ansible_service_mgr 中的服务管理工具。如果此值为空,则使用传统的 service 模块(命令) |
除了
name为必须的参数,enabled和state至少要有一个存在
systemd_service
ansible.builtin.systemd_service 管理 systemd 系统上的服务
ansible.builtin.systemd 和 ansible.builtin.systemd_service 相同
常用参数
| 参数 | 说明 | 示例 |
|---|---|---|
namestring / required |
服务名称 | |
enabledboolean |
服务是否要开机启动,可选值包括 - false- true |
|
statestring |
期望的(desired)服务状态。可选值包括: - started / stopped : 如果服务状态已满足,不执行任何操作- restarted: 重启- reloaded |
|
daemon_reexecboolean aliases: systemctl daemon-reexec |
在执行任何操作前,先执行 systemctl daemon-reexec 。默认值为 false |
|
daemon_reloadboolean aliases: systemctl daemon-reload |
执行 systemctl daemon-reload。 默认值为 false |
debug
ansible.builtin.debug 可单独使用或者和 when 语句使用来打印调试信息,常用的参数包括 msg 和 var(用于调试变量)
- name: Print the gateway for each host when defined |
常见错误
Permission denied
在以普通用户 ssh 登陆远程主机(Managed Host)的情况下,执行某些操作可能因为普通用户权限不足导致操作失败
ansible my-hosts -m command -a "cat /root/.ssh/authorized_keys" |
此种情况,可以使用以下方式解决。 前提是远程登陆使用的用户具有 sudo 权限 。建议使用 -b 选项。
- 使用
sudo命令。如果用户没有sudo权限,ansible 会被阻塞,后台等待用户输入sudo密码,直到等待超时失败。具体报错参考以下示例中的k8s-master2输出ansible my-hosts -m command -a "sudo cat /root/.ssh/authorized_keys"
k8s-master1 | CHANGED | rc=0 >>
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"centos\" rather than the user \"root\".';echo;sleep 10" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCGKLNMv82MQJUuQ9aZPsDofBj96aQlS1kNV2doSwyLatgMNBZ6rzgQuNOJ2DH87IzD1mZ0wL7iApvk6gkxSxcz5tmbU8dfYOYJdlBlhxGk2Nkg2V3P9FPC0hBY73szEV+1DUoqwl+COAsAXO9Uiebr0faQvWOkVT7pypunnjPrBBUaaXn2IcoPIdXZfXVLjXH2JbWSHL5J+yIGHewSMzZ/Xx7u6hwxUP0QLFHrnhD0WDukoBjoUZ2sshMP+DHgoyWjCg+uVpmjJAksp80f34WfNku5Grt90kYEj+N+x2JQ1Y4aQIXASIwDshicbJIsl+RMIMmwe+TElUJ6g9aa0qCr op2-east1-1031
k8s-master3 | CHANGED | rc=0 >>
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"centos\" rather than the user \"root\".';echo;sleep 10" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCGKLNMv82MQJUuQ9aZPsDofBj96aQlS1kNV2doSwyLatgMNBZ6rzgQuNOJ2DH87IzD1mZ0wL7iApvk6gkxSxcz5tmbU8dfYOYJdlBlhxGk2Nkg2V3P9FPC0hBY73szEV+1DUoqwl+COAsAXO9Uiebr0faQvWOkVT7pypunnjPrBBUaaXn2IcoPIdXZfXVLjXH2JbWSHL5J+yIGHewSMzZ/Xx7u6hwxUP0QLFHrnhD0WDukoBjoUZ2sshMP+DHgoyWjCg+uVpmjJAksp80f34WfNku5Grt90kYEj+N+x2JQ1Y4aQIXASIwDshicbJIsl+RMIMmwe+TElUJ6g9aa0qCr op2-east1-1031
k8s-master2 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"module_stderr": "Shared connection to 172.31.30.115 closed.\r\n",
"module_stdout": "[sudo] password for centos: \r\n\r\n{\"changed\": true, \"end\": \"2023-09-01 09:24:40.247480\", \"stdout\": \"\", \"cmd\": [\"sudo\", \"cat\", \"/root/.ssh/authorized_keys\"], \"failed\": true, \"delta\": \"0:05:02.696092\", \"stderr\": \"\\nWe trust you have received the usual lecture from the local System\\nAdministrator. It usually boils down to these three things:\\n\\n #1) Respect the privacy of others.\\n #2) Think before you type.\\n #3) With great power comes great responsibility.\\n\\nsudo: timed out reading password\", \"rc\": 1, \"invocation\": {\"module_args\": {\"creates\": null, \"executable\": null, \"_uses_shell\": false, \"strip_empty_ends\": true, \"_raw_params\": \"sudo cat /root/.ssh/authorized_keys\", \"removes\": null, \"argv\": null, \"warn\": true, \"chdir\": null, \"stdin_add_newline\": true, \"stdin\": null}}, \"start\": \"2023-09-01 09:19:37.551388\", \"warnings\": [\"Consider using 'become', 'become_method', and 'become_user' rather than running sudo\"], \"msg\": \"non-zero return code\"}\r\n",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 1
} - 使用
ansible -b选项。如果用户没有sudo权限, ansible 会因为没有输入sudo密码而执行失败,具体报错(Missing sudo password)参考以下示例中的k8s-master2输出ansible my-hosts -m command -a "cat /root/.ssh/authorized_keys" -b
k8s-master2 | FAILED | rc=-1 >>
Missing sudo password
k8s-master1 | CHANGED | rc=0 >>
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"centos\" rather than the user \"root\".';echo;sleep 10" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCGKLNMv82MQJUuQ9aZPsDofBj96aQlS1kNV2doSwyLatgMNBZ6rzgQuNOJ2DH87IzD1mZ0wL7iApvk6gkxSxcz5tmbU8dfYOYJdlBlhxGk2Nkg2V3P9FPC0hBY73szEV+1DUoqwl+COAsAXO9Uiebr0faQvWOkVT7pypunnjPrBBUaaXn2IcoPIdXZfXVLjXH2JbWSHL5J+yIGHewSMzZ/Xx7u6hwxUP0QLFHrnhD0WDukoBjoUZ2sshMP+DHgoyWjCg+uVpmjJAksp80f34WfNku5Grt90kYEj+N+x2JQ1Y4aQIXASIwDshicbJIsl+RMIMmwe+TElUJ6g9aa0qCr op2-east1-1031
[WARNING]: Platform linux on host k8s-uat-master3 is using the discovered Python interpreter at /usr/bin/python, but future installation of
another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more
information.
k8s-master3 | CHANGED | rc=0 >>
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"centos\" rather than the user \"root\".';echo;sleep 10" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCGKLNMv82MQJUuQ9aZPsDofBj96aQlS1kNV2doSwyLatgMNBZ6rzgQuNOJ2DH87IzD1mZ0wL7iApvk6gkxSxcz5tmbU8dfYOYJdlBlhxGk2Nkg2V3P9FPC0hBY73szEV+1DUoqwl+COAsAXO9Uiebr0faQvWOkVT7pypunnjPrBBUaaXn2IcoPIdXZfXVLjXH2JbWSHL5J+yIGHewSMzZ/Xx7u6hwxUP0QLFHrnhD0WDukoBjoUZ2sshMP+DHgoyWjCg+uVpmjJAksp80f34WfNku5Grt90kYEj+N+x2JQ1Y4aQIXASIwDshicbJIsl+RMIMmwe+TElUJ6g9aa0qCr op2-east1-1031
Connection timed out
使用以下 playbook 报错:
- name: manage Nginx Status |
执行结果如下,报错: {"changed": false, "msg": "Unable to disable service nginx: Failed to execute operation: Connection timed out\n"}
ansible-playbook -vv --diff playbooks/playbook.yml |
问题原因 是此操作需要 sudo 权限来运行。参考以下内容,加入 become: true 指令即可
- name: manage Nginx Status |
参考链接
ansible 官方文档
Ansible中文权威指南
Index of all Modules
脚注
- 1.ansible-core control node Python support ↩
- 2.Parallelism and Shell Commands ↩
- 3.How to build your inventory ↩
- 4.Managing inventory load order ↩
- 5.How variables are merged ↩
- 6.Connecting to hosts: behavioral inventory parameters ↩
- 7.Organizing host and group variables ↩
- 8.Common patterns ↩
- 9.Setting a remote user ↩
- 10.ansible.builtin.lineinfile module – Manage lines in text files ↩
- 11.ansible.builtin.service ↩