playbook
1. 实施playbook
1.1 Ansible playbook与临时命令
临时命令可以作为一次性命令对一组目标主机运行一项简单的任务。不过,若要真正发挥Ansible的力量,需要了解如何使用playbook以便轻松重复的方式对一组目标主机执行多项复杂的任务。
play是针对清单中选定的主机运行的一组有序任务。playbook是一个文本文件,其中包含由一个或多个按特定顺序运行的play组成的列表。
Play可以将一系列冗长而复杂的手动管理任务转变为可轻松重复的例程,并且具有可预测的成功成果。在playbook中,可以将play内的任务序列保存为人类可读并可立即运行的形式。根据任务的编写方式,任务本身记录了部署应用或基础架构所需的步骤。(可以把部署的一些服务的步骤写到playbook里,按照所能识别的语言有序的表达出来)
1.2 格式化Ansible playbook
这个任务可以将其编写为一个单任务的play并保存在playbook中。生成的playbook如下方所示:
---
- name: Configure important user consistently(名字)
hosts: 172.16.103.129 (清单所在的主机组)
tasks:(任务)
- name: runtime exists with UID 4000(描述动作--状态)
user:
name: runtime
uid: 4000
state: present
(冒号后面必须有一个空格)
(上下级别缩进两个空格)
(同级别的必须对齐)
Playbook是以YAML格式编写的文本文件,通常使用扩展名yml保存。Playbook使用空格字符缩进来表示其数据结构。YAML对用于缩进的空格数量没有严格的要求,但有两个基本的规则:
- 处于层次结构中同一级别的数据元素(例如同一列表中的项目)必须具有相同的缩进量。
- 如果项目属于其他项目的子项,其缩进量必须大于父项
只有空格字符可用于缩进,不允许使用tab键。约定俗成的缩进量一般是一级2个空格。
Playbook开头的一行由三个破折号(---)组成
这是文档开始标记。其末尾可能使用三个圆点(...)作为文档结束标记,尽管在实践中这通常会省略。第二行则是顶行写一个(-)一个空格
在这两个标记之间,会以一个play列表的形式来定义playbook。YAML列表中的项目以一个破折号加空格开头。例如,YAML列表可能显示如下:
- apple
- orange
- grape
Play本身是一个键值对集合。同一play中的键应当使用相同的缩进量。以下示例显示了具有三个键的YAML代码片段。前两个键具有简单的值。第三个将含有三个项目的列表作为值。
- name: just an example
hosts: webservers
tasks:
- first
- second
- third
作为play中的一部分,tasks属性按顺序实际列出要在受管主机上运行的任务。列表中各项任务本身是一个键值对集合。
还以上面创建用户的play为例,play中唯一任务有两个键:
- name是记录任务用途的可选标签。最好命名所有的任务,从而帮助记录自动流程中的每一步用途。
- user是要为这个任务运行的模块。其参数作为一组键值对传递,它们是模块的子项(name、uid和state)。
下面再来看一个含有多项任务的tasks属性案例:
tasks:
- name: web server is enabled
service:
name: httpd
enabled: true
- name: NTP server is enabled
service:
name: chronyd
enabled: true
- name: Postfix is enabled
service:
name: postfix
enabled: true
任务
模块
参数
playbook中play和任务列出的顺序很重要,因为Ansible会按照相同的顺序运行它们。
1.3 运行playbook
部署环境
主机名 | IP |
---|---|
a | 192.168.29.129 |
b | 192.168.29.130 |
c | 192.168.29.131 |
做映射
[root@a ~]# vim /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.29.130 b
192.168.29.131 c
[root@a ~]# ping b
PING b (192.168.29.130) 56(84) bytes of data.
64 bytes from b (192.168.29.130): icmp_seq=1 ttl=64 time=0.281 ms
64 bytes from b (192.168.29.130): icmp_seq=2 ttl=64 time=0.180 ms
^C
--- b ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1012ms
rtt min/avg/max/mdev = 0.180/0.230/0.281/0.052 ms
[root@a ~]# ping c
PING c (192.168.29.131) 56(84) bytes of data.
64 bytes from c (192.168.29.131): icmp_seq=1 ttl=64 time=0.616 ms
64 bytes from c (192.168.29.131): icmp_seq=2 ttl=64 time=1.13 ms
^C
--- c ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1066ms
rtt min/avg/max/mdev = 0.616/0.873/1.131/0.259 ms
免密登录
[root@a ~]# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:smr0O5H2yyMaOUQiAYIOc3MNoLORKW1/oZ7Tv5yqNlI root@a
The key's randomart image is:
+---[RSA 3072]----+
|* ...o |
|=*o . . |
|@o+o. . |
|.B + . . |
|. + o.S |
| oE=+o |
| oBooo |
| . +=++o. |
| +++o=B+ |
+----[SHA256]-----+
[root@a ~]# ssh-copy-id b
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host 'b (192.168.29.130)' can't be established.
ECDSA key fingerprint is SHA256:GKhyLI0ugf8uSsj22Zqei3oSFf76aLw8wpWYai88Fcc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@b's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'b'"
and check to make sure that only the key(s) you wanted were added.
[root@a ~]# ssh-copy-id c
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host 'c (192.168.29.131)' can't be established.
ECDSA key fingerprint is SHA256:iwjjWZAX8/wtQpwoSyPVUn/9J3MlDrs7X2/W5L0Vpb0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@c's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'c'"
and check to make sure that only the key(s) you wanted were added.
[root@a ~]# ssh b
Last login: Mon Oct 24 14:20:07 2022 from 192.168.29.1
[root@b ~]# exit
注销
Connection to b closed.
[root@a ~]# ssh c
Last login: Mon Oct 24 14:20:11 2022 from 192.168.29.1
[root@c ~]# exit
注销
Connection to c closed.
[root@a ~]# cd /opt/
[root@a opt]# ls
[root@a opt]# mkdir project
[root@a opt]# ls
project
[root@a opt]# cd project/
[root@a project]# ls
[root@a project]# cp /etc/ansible/ansible.cfg .
[root@a project]# ls
ansible.cfg
[root@a project]# vim ansible.cfg
[defaults]
# some basic default values...
inventory = inventory//修改这一栏
[root@a project]# vim inventory
[webservers]
b
c
[root@a project]# ansible all --list-hosts
hosts (2):
b
c
[root@a project]# ansible webservers --list-hosts
hosts (2):
b
c
[root@a project]# ansible all -m ping
b | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
c | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
[root@a project]# systemctl disable --now firewalld
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@a project]# vim /etc/selinux/config
SELINUX=disabled
[root@a project]# setenforce 0
[root@b ~]# systemctl status firewalld
● firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enab>
Active: active (running) since Mon 2022-10-24 14:20:02 CST; 1h >
Docs: man:firewalld(1)
Main PID: 939 (firewalld)
Tasks: 2 (limit: 11201)
Memory: 35.6M
CGroup: /system.slice/firewalld.service
└─939 /usr/libexec/platform-python -s /usr/sbin/firewal>
10月 24 14:19:59 b systemd[1]: Starting firewalld - dynamic firewa>
10月 24 14:20:02 b systemd[1]: Started firewalld - dynamic firewal>
10月 24 14:20:03 b firewalld[939]: WARNING: AllowZoneDrifting is e>
[root@b ~]# systemctl disable --now firewalld
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@b ~]# vim /etc/selinux/config
SELINUX=disabled
[root@b ~]# setenforce 0
[root@c ~]# systemctl disable --now firewalld
Removed /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
[root@c ~]# vim /etc/selinux/config
SELINUX=disabled
[root@c ~]# setenforce 0
absible-playbook命令可用于运行playbook。该命令在控制节点上执行,要运行的playbook的名称则作为参数传递。
ansible-playbook site.yml
在运行playbook时,将生成输出来显示所执行的play和任务。输出中也会报告执行的每一项任务的结果。
以下示例中显示了一个简单的playbook的内容,后面是运行它的结果。
[root@a project]# mkdir playbooks
[root@a project]# ls
ansible.cfg inventory playbooks
[root@a project]# cd playbooks/
[root@a playbooks]# ls
[root@a playbooks]# vim web.yml
---
- name: deploy web server
hosts: b
tasks:
- name: ensure web service exists
yum:
name: httpd
state: present
- name: ensure httpd service is started and enabled
service:
name: httpd
state: started
enabled: yes
[root@a playbooks]# cd ..
[root@a project]# ls
ansible.cfg inventory playbooks
[root@b ~]# rpm -qa|grep httpd
[root@b ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
[root@a project]# ansible-playbook playbooks/web.yml
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
ok: [b]
TASK [ensure web service exists] ***********************************************
changed: [b]
TASK [ensure httpd service is started and enabled] *****************************
changed: [b]
PLAY RECAP *********************************************************************
b : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]# ansible-playbook playbooks/web.yml
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
ok: [b]
TASK [ensure web service exists] ***********************************************
ok: [b]
TASK [ensure httpd service is started and enabled] *****************************
ok: [b]
PLAY RECAP *********************************************************************
b : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@b ~]# rpm -qa|grep httpd
httpd-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.x86_64
httpd-tools-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.x86_64
httpd-filesystem-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.noarch
centos-logos-httpd-85.8-2.el8.noarch
[root@b ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 *:80 *:*
LISTEN 0 128 [::]:22 [::]:*
请注意,在playbook运行时,屏幕中会显示每个play和任务的name键的值。(Gathering Facts任务是一项特别的任务,setup模块通常在play启动时自动运行这项任务。)对于含有多个play和任务的playbook,设置name属性后可以更加轻松地监控playbook执行的进展。
通常而言,Ansible Playbook中的任务是幂等的,而且能够安全地多次运行playbook。如果目标受管主机已处于正确的状态,则不应进行任何更改。如果再次运行这个playbook,所有任务都会以状态OK传递,且不报告任何更改。
1.4 提高输出的详细程度
ansible-playbook命令提供的默认输出不提供详细的任务执行信息。ansible-playbook -v命令提供了额外的信息,总共有四个级别。
配置Playbook执行的输出详细程序
选项 | 描述 |
---|---|
-v | 显示任务结果 |
-vv | 任务结果和任务配置都会显示 |
-vvv | 包含关于与受管主机连接的信息 |
-vvvv | 增加了连接插件相关的额外详细程序选项,包括受管主机上用于执行脚本的用户以及所执行的脚本 |
[root@a project]# ansible-playbook playbooks/web.yml -v
Using /opt/project/ansible.cfg as config file
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
ok: [b]
TASK [ensure web service exists] ***********************************************
ok: [b] => {"changed": false, "msg": "Nothing to do", "rc": 0, "results": []}
TASK [ensure httpd service is started and enabled] *****************************
ok: [b] => {"changed": false, "enabled": true, "name": "httpd", "state": "started", "status": {"ActiveState": "active", "AllowedCPUs": "", "AllowedMemoryNodes": "", "BlockIOAccounting": "no", "BlockIOWeight": "[not set]", "CPUAccounting": "no", "CPUAffinity": "", "CPUAffinityFromNUMA": "no", "CPUQuotaPerSecUSec": "infinity", "CPUQuotaPeriodUSec": "infinity", "CPUSchedulingPolicy": "0", "CPUSchedulingPriority": "0", "CPUSchedulingResetOnFork": "no", "CPUShares": "[not set]", "CPUUsageNSec": "[not set]", "CPUWeight": "[not set]", "ControlGroup": "/system.slice/httpd.service", "ControlPID": "0", "DefaultMemoryLow": "0", "DefaultMemoryMin": "0", "Delegate": "no", "DevicePolicy": "auto", "EffectiveCPUs": "", "EffectiveMemoryNodes": "", "Environment": "LANG=C", "ExecMainCode": "0", "ExecMainExitTimestampMonotonic": "0", "ExecMainPID": "151387", "ExecMainStartTimestamp": "Mon 2022-10-24 15:50:27 CST", "ExecMainStartTimestampMonotonic": "5439315832", "ExecMainStatus": "0", "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }", "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2022-10-24 15:50:27 CST] ; stop_time=[n/a] ; pid=151387 ; code=(null) ; status=0/0 }", "FileDescriptorStoreMax": "0", "GID": "[not set]", "GuessMainPID": "yes", "IOAccounting": "no", "IOSchedulingClass": "0", "IOSchedulingPriority": "0", "IOWeight": "[not set]", "IPAccounting": "no", "IPEgressBytes": "18446744073709551615", "IPEgressPackets": "18446744073709551615", "IPIngressBytes": "18446744073709551615", "IPIngressPackets": "18446744073709551615", "LimitAS": "infinity", "LimitASSoft": "infinity", "LimitCORE": "infinity", "LimitCORESoft": "infinity", "LimitCPU": "infinity", "LimitCPUSoft": "infinity", "LimitDATA": "infinity", "LimitDATASoft": "infinity", "LimitFSIZE": "infinity", "LimitFSIZESoft": "infinity", "LimitLOCKS": "infinity", "LimitLOCKSSoft": "infinity", "LimitMEMLOCK": "65536", "LimitMEMLOCKSoft": "65536", "LimitMSGQUEUE": "819200", "LimitMSGQUEUESoft": "819200", "LimitNICE": "0", "LimitNICESoft": "0", "LimitNOFILE": "262144", "LimitNOFILESoft": "1024", "LimitNPROC": "7001", "LimitNPROCSoft": "7001", "LimitRSS": "infinity", "LimitRSSSoft": "infinity", "LimitRTPRIO": "0", "LimitRTPRIOSoft": "0", "LimitRTTIME": "infinity", "LimitRTTIMESoft": "infinity", "LimitSIGPENDING": "7001", "LimitSIGPENDINGSoft": "7001", "LimitSTACK": "infinity", "LimitSTACKSoft": "8388608", "LogLevelMax": "-1", "LogRateLimitBurst": "0", "LogRateLimitIntervalUSec": "0", "MainPID": "151387", "MemoryAccounting": "yes", "MemoryCurrent": "26718208", "MemoryHigh": "infinity", "MemoryLimit": "infinity", "MemoryLow": "0", "MemoryMax": "infinity", "MemoryMin": "0", "MemorySwapMax": "infinity", "NFileDescriptorStore": "0", "NRestarts": "0", "NUMAMask": "", "NUMAPolicy": "n/a", "Nice": "0", "NonBlocking": "no", "NotifyAccess": "main", "OOMScoreAdjust": "0", "PermissionsStartOnly": "no", "RemainAfterExit": "no", "Restart": "no", "RestartUSec": "100ms", "Result": "success", "RootDirectoryStartOnly": "no", "RuntimeMaxUSec": "infinity", "SecureBits": "0", "Slice": "system.slice", "StandardError": "inherit", "StandardInput": "null", "StandardInputData": "", "StandardOutput": "journal", "StartupBlockIOWeight": "[not set]", "StartupCPUShares": "[not set]", "StartupCPUWeight": "[not set]", "StartupIOWeight": "[not set]", "StatusErrno": "0", "StatusText": "Running, listening on: port 80", "SyslogFacility": "3", "SyslogLevel": "6", "SyslogLevelPrefix": "yes", "SyslogPriority": "30", "TTYReset": "no", "TTYVHangup": "no", "TTYVTDisallocate": "no", "TasksAccounting": "yes", "TasksCurrent": "213", "TasksMax": "11201", "TimeoutStartUSec": "1min 30s", "TimeoutStopUSec": "1min 30s", "TimerSlackNSec": "50000", "Type": "notify", "UID": "[not set]", "UMask": "0022", "WatchdogTimestamp": "Mon 2022-10-24 15:50:42 CST", "WatchdogTimestampMonotonic": "5454419114", "WatchdogUSec": "0"}}
PLAY RECAP *********************************************************************
b : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]# ansible-playbook playbooks/web.yml -vv
ansible-playbook 2.9.27
config file = /opt/project/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.6/site-packages/ansible
executable location = /usr/bin/ansible-playbook
python version = 3.6.8 (default, Sep 10 2021, 09:13:53) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)]
Using /opt/project/ansible.cfg as config file
Skipping callback 'actionable', as we already have a stdout callback.
Skipping callback 'counter_enabled', as we already have a stdout callback.
Skipping callback 'debug', as we already have a stdout callback.
Skipping callback 'dense', as we already have a stdout callback.
Skipping callback 'dense', as we already have a stdout callback.
Skipping callback 'full_skip', as we already have a stdout callback.
Skipping callback 'json', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'null', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.
Skipping callback 'selective', as we already have a stdout callback.
Skipping callback 'skippy', as we already have a stdout callback.
Skipping callback 'stderr', as we already have a stdout callback.
Skipping callback 'unixy', as we already have a stdout callback.
Skipping callback 'yaml', as we already have a stdout callback.
PLAYBOOK: web.yml **************************************************************
1 plays in playbooks/web.yml
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
task path: /opt/project/playbooks/web.yml:2
ok: [b]
META: ran handlers
TASK [ensure web service exists] ***********************************************
task path: /opt/project/playbooks/web.yml:5
ok: [b] => {"changed": false, "msg": "Nothing to do", "rc": 0, "results": []}
TASK [ensure httpd service is started and enabled] *****************************
task path: /opt/project/playbooks/web.yml:10
ok: [b] => {"changed": false, "enabled": true, "name": "httpd", "state": "started", "status": {"ActiveState": "active", "AllowedCPUs": "", "AllowedMemoryNodes": "", "BlockIOAccounting": "no", "BlockIOWeight": "[not set]", "CPUAccounting": "no", "CPUAffinity": "", "CPUAffinityFromNUMA": "no", "CPUQuotaPerSecUSec": "infinity", "CPUQuotaPeriodUSec": "infinity", "CPUSchedulingPolicy": "0", "CPUSchedulingPriority": "0", "CPUSchedulingResetOnFork": "no", "CPUShares": "[not set]", "CPUUsageNSec": "[not set]", "CPUWeight": "[not set]", "ControlGroup": "/system.slice/httpd.service", "ControlPID": "0", "DefaultMemoryLow": "0", "DefaultMemoryMin": "0", "Delegate": "no", "DevicePolicy": "auto", "EffectiveCPUs": "", "EffectiveMemoryNodes": "", "Environment": "LANG=C", "ExecMainCode": "0", "ExecMainExitTimestampMonotonic": "0", "ExecMainPID": "151387", "ExecMainStartTimestamp": "Mon 2022-10-24 15:50:27 CST", "ExecMainStartTimestampMonotonic": "5439315832", "ExecMainStatus": "0", "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }", "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2022-10-24 15:50:27 CST] ; stop_time=[n/a] ; pid=151387 ; code=(null) ; status=0/0 }", "FileDescriptorStoreMax": "0", "GID": "[not set]", "GuessMainPID": "yes", "IOAccounting": "no", "IOSchedulingClass": "0", "IOSchedulingPriority": "0", "IOWeight": "[not set]", "IPAccounting": "no", "IPEgressBytes": "18446744073709551615", "IPEgressPackets": "18446744073709551615", "IPIngressBytes": "18446744073709551615", "IPIngressPackets": "18446744073709551615", "LimitAS": "infinity", "LimitASSoft": "infinity", "LimitCORE": "infinity", "LimitCORESoft": "infinity", "LimitCPU": "infinity", "LimitCPUSoft": "infinity", "LimitDATA": "infinity", "LimitDATASoft": "infinity", "LimitFSIZE": "infinity", "LimitFSIZESoft": "infinity", "LimitLOCKS": "infinity", "LimitLOCKSSoft": "infinity", "LimitMEMLOCK": "65536", "LimitMEMLOCKSoft": "65536", "LimitMSGQUEUE": "819200", "LimitMSGQUEUESoft": "819200", "LimitNICE": "0", "LimitNICESoft": "0", "LimitNOFILE": "262144", "LimitNOFILESoft": "1024", "LimitNPROC": "7001", "LimitNPROCSoft": "7001", "LimitRSS": "infinity", "LimitRSSSoft": "infinity", "LimitRTPRIO": "0", "LimitRTPRIOSoft": "0", "LimitRTTIME": "infinity", "LimitRTTIMESoft": "infinity", "LimitSIGPENDING": "7001", "LimitSIGPENDINGSoft": "7001", "LimitSTACK": "infinity", "LimitSTACKSoft": "8388608", "LogLevelMax": "-1", "LogRateLimitBurst": "0", "LogRateLimitIntervalUSec": "0", "MainPID": "151387", "MemoryAccounting": "yes", "MemoryCurrent": "26718208", "MemoryHigh": "infinity", "MemoryLimit": "infinity", "MemoryLow": "0", "MemoryMax": "infinity", "MemoryMin": "0", "MemorySwapMax": "infinity", "NFileDescriptorStore": "0", "NRestarts": "0", "NUMAMask": "", "NUMAPolicy": "n/a", "Nice": "0", "NonBlocking": "no", "NotifyAccess": "main", "OOMScoreAdjust": "0", "PermissionsStartOnly": "no", "RemainAfterExit": "no", "Restart": "no", "RestartUSec": "100ms", "Result": "success", "RootDirectoryStartOnly": "no", "RuntimeMaxUSec": "infinity", "SecureBits": "0", "Slice": "system.slice", "StandardError": "inherit", "StandardInput": "null", "StandardInputData": "", "StandardOutput": "journal", "StartupBlockIOWeight": "[not set]", "StartupCPUShares": "[not set]", "StartupCPUWeight": "[not set]", "StartupIOWeight": "[not set]", "StatusErrno": "0", "StatusText": "Running, listening on: port 80", "SyslogFacility": "3", "SyslogLevel": "6", "SyslogLevelPrefix": "yes", "SyslogPriority": "30", "TTYReset": "no", "TTYVHangup": "no", "TTYVTDisallocate": "no", "TasksAccounting": "yes", "TasksCurrent": "213", "TasksMax": "11201", "TimeoutStartUSec": "1min 30s", "TimeoutStopUSec": "1min 30s", "TimerSlackNSec": "50000", "Type": "notify", "UID": "[not set]", "UMask": "0022", "WatchdogTimestamp": "Mon 2022-10-24 15:50:42 CST", "WatchdogTimestampMonotonic": "5454419114", "WatchdogUSec": "0"}}
META: ran handlers
META: ran handlers
PLAY RECAP *********************************************************************
b : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]#
1.5 语法验证
在执行playbook之前,最好要进行验证,确保其内容的语法正确无误。ansible-playbook命令提供了一个--syntax-check选项,可用于验证playbook的语法。
下例演示了一个playbook成功通过语法验证:
[root@a playbooks]# ansible-playbook --syntax-check web.yml [WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
[WARNING]: Could not match supplied host pattern, ignoring: b
playbook: web.yml
[root@a project]# ansible-playbook --syntax-check playbooks/web.yml (在清单文件的目录上执行)
playbook: playbooks/web.yml
语法验证失败时,将报告语法错误。输出中包含语法问题在playbook中的大致位置。
下例演示了一个playbook语法验证失败的情况:
[root@a playbooks]# vim web.yml
---
- name: deploy web server
hosts: b
tasks:
- name: ensure web service exists
yum:
name: httpd
state: present
- name: ensure httpd service is started and enabled
service:
name: httpd
state: started
enabled: yes(此处缩进一个空格)
[root@a project]# ansible-playbook --syntax-check playbooks/web.yml
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)
Syntax Error while loading YAML.
did not find expected key
The error appears to be in '/opt/project/playbooks/web.yml': line 14, column 8, but may(告诉语法错误的地方)
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
state: started
enabled: yes
^ here
[root@a playbooks]# vim web.yml
---
- name: deploy web server
hosts: b
tasks:
- name: ensure web service exists
yum:
name: httpd
state: present
- name: ensure httpd service is started and enabled
service:
name: httpd
state: started
enabled: yes
[root@a project]# ansible-playbook --syntax-check playbooks/web.yml
playbook: playbooks/web.yml
1.6 执行空运行
可以使用-C选项对playbook执行空运行。这会使Ansible报告在执行该playbook时将会发生什么更改,但不会对受管主机进行任何实际的更改。
下例演示了一个playbook的空运行,它包含单项任务,可确保在受管主机上安装了最新版本的httpd软件包。注意该空运行报告此任务会对受管主机产生的更改。
ansible-playbook -C webserver.yml
[root@a playbooks]# vim web.yml
---
- name: deploy web server
hosts: c
tasks:
- name: ensure web service exists
yum:
name: httpd
state: present
- name: ensure httpd service is started and enabled
service:
name: httpd
state: started
enabled: yes
[root@c ~]# rpm -qa|grep httpd
[root@c ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
[root@a project]# ansible-playbook --syntax-check playbooks/web.yml
playbook: playbooks/web.yml
[root@a project]# ansible-playbook -C playbooks/web.yml(空运行)
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
ok: [c]
TASK [ensure web service exists] ***********************************************
changed: [c]
TASK [ensure httpd service is started and enabled] *****************************
changed: [c]
PLAY RECAP *********************************************************************
c : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]#
[root@c ~]# rpm -qa|grep httpd
[root@c ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
[root@a project]# ansible-playbook playbooks/web.yml
PLAY [deploy web server] *******************************************************
TASK [Gathering Facts] *********************************************************
ok: [c]
TASK [ensure web service exists] ***********************************************
changed: [c]
TASK [ensure httpd service is started and enabled] *****************************
changed: [c]
PLAY RECAP *********************************************************************
c : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@c ~]# rpm -qa|grep httpd
httpd-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.x86_64
httpd-tools-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.x86_64
httpd-filesystem-2.4.37-47.module_el8.6.0+1111+ce6f4ceb.1.noarch
centos-logos-httpd-85.8-2.el8.noarch
[root@c ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 128 *:80 *:*
2. 实施多个play
2.1 缩写多个play
Playbook是一个YAML文件,含有由一个或多个play组成的列表。记住一个play按顺序列出了要对清单中的选定主机执行的任务。因此,如果一个playbook中有多个play,每个play可以将其任务应用到单独的一组主机。
在编排可能涉及对不同主机执行不同任务的复杂部署时,这会大有帮助。我们可以这样进行编写:对一组主机运行一个play,完成后再对另一组主机运行另一个play。
缩写包含多个play的playbook非常简单。Playbook中的各个play编写为playbook中的顶级列表项。各个play是含有常用play关键字的列表项。
相同的主机相同的事
[root@a playbooks]# cp web.yml httpd.yml
[root@a playbooks]# ls
httpd.yml web.yml
[root@a playbooks]# vim httpd.yml
---
- hosts: c
tasks:
- name: ensure web service exists
yum:
name: httpd
state: present
- name: ensure httpd service is started and enabled
service:
name: httpd
state: started
enabled: yes
[root@a project]# ansible-playbook playbooks/httpd.yml
PLAY [c] ***********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [c]
TASK [ensure web service exists] ***********************************************
ok: [c]
TASK [ensure httpd service is started and enabled] *****************************
ok: [c]
PLAY RECAP *********************************************************************
c : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
不同的主机不同的事
[root@a playbooks]# ls
httpd.yml web.yml
[root@a playbooks]# vim lnmp.yml
---
- name: deploy web server on b
hosts: b
tasks:
- name: install httpd
yum:
name: httpd
state: present
- name: start service httpd
service:
name: httpd
state: started
enabled: yes
- name: deploy database server on c
hosts: c
tasks:
- name: install mariadb
yum:
name: mariadb-server
state: present
- name: start mariadb
service:
name: mariadb
state: started
enabled: yes
[root@a project]# ansible-playbook --syntax-check playbooks/lnmp.yml
playbook: playbooks/lnmp.yml
[root@a project]# ansible-playbook -C playbooks/lnmp.yml
PLAY [deploy web server on b] **************************************************
TASK [Gathering Facts] *********************************************************
ok: [b]
TASK [install httpd] ***********************************************************
ok: [b]
TASK [start service httpd] *****************************************************
ok: [b]
PLAY [deploy database server on c] *********************************************
TASK [Gathering Facts] *********************************************************
ok: [c]
TASK [install mariadb] *********************************************************
changed: [c]
TASK [start mariadb] ***********************************************************
changed: [c]
PLAY RECAP *********************************************************************
b : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]# ansible-playbook playbooks/lnmp.yml
PLAY [deploy web server on b] **************************************************
TASK [Gathering Facts] *********************************************************
ok: [b]
TASK [install httpd] ***********************************************************
ok: [b]
TASK [start service httpd] *****************************************************
ok: [b]
PLAY [deploy database server on c] *********************************************
TASK [Gathering Facts] *********************************************************
ok: [c]
TASK [install mariadb] *********************************************************
changed: [c]
TASK [start mariadb] ***********************************************************
changed: [c]
PLAY RECAP *********************************************************************
b : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
c : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@a project]#
[root@b ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 *:80 *:*
LISTEN 0 128 [::]:22 [::]:*
[root@c ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 80 *:3306 *:*
LISTEN 0 128 *:80 *:*
2.2 play中的远程用户和特权升级
Play可以将不同的远程用户或特权升级设置用于play,取代配置文件中指定的默认设置。这些在play本身中与hosts或tasks关键字相同的级别上设置。
2.2.1 用户属性
playbook中的任务通常通过与受管主机的网络连接来执行。与临时命令相同,用于任务执行的用户帐户取决于Ansible配置文件/etc/ansible/ansible.cfg中的不同关键字。运行任务的用户可以通过remote_user关键字来定义。不过,如果启用了特权升级,become_user等其他关键字也会发生作用。
如果用于任务执行的Ansible配置中定义的远程用户不合适,可以通过在play中使用remote_user关键字覆盖。
remote_user: remoteuser
以下示例演示了如何在play中使用这些关键字:
- name: /etc/hosts is up to date
hosts: 172.16.103.129
remote_user: automation
become: yes
tasks:
- name: 172.16.103.129 in /etc/hosts
lineinfile:(模块--确保某一行在某个文件中有)
path: /etc/hosts
line: '172.16.103.129 web1.example.com'
state: present
(确保/etc/hosts文件中有172.16.103.129 web1.example.com这一行,没有则启动)
2.2.2 特权升级属性
Ansible也提供额外的关键字,从而在playbook内定义特权升级参数。become布尔值关键字可用于启用或禁用特权升级,无论它在Ansible配置文件中的定义为何。它可取yes或true值来启用特权升级,或者取no或false值来禁用它。
become: true
如果启用了特权升级,则可以使用become_method关键字来定义特定play期间要使用的特权升级方法。
以下示例中指定sudo用于特权升级:
become_method: sudo
此外,启用了特权升级时,become_user关键字可定义特定play上下文内要用于特权升级的用户帐户。
become_user: privileged_user
2.3 查找用于任务的模块
2.3.1 模块文档
Ansible随附打包的大量模块为管理员提供了许多用于常见管理任务的工具。前面我们介绍了Ansible官方网站的帮助文档链接https://docs.ansible.com/。通过模块索引,可以很轻松的找到对应的模块。例如,适用于用户和服务管理的模块可以在Systems Modules下找到,而适合数据库管理的模块则可在Database Modules下找到。
对于每一个模块,Ansible官网提供了其功能摘要,以及关于如何通过模块的选项来调用各项具体功能的说明。文档还提供了实用的示例,演示各个模块的用法,以及任务中关键字的设置方法。
前面我们用到过ansible-doc -l命令。这将显示模块名称列表以及其功能的概要。
ansible-doc -l
使用ansible-doc [module name]命令来显示模块的详细文档。与Ansible官网一样,该命令提供模块功能的概要、其不同选项的详细信息,以及示例。
ansible-doc yum # 显示yum模块的帮助文档
ansible-doc命令还提供-s选项,它会生成示例输出,可以充当如何在playbook在使用特定模块的示范。此输出可以作为起步模板,包含在实施该模块以执行任务的playbook中。输出中包含的注释,提醒管理员各个选项的用法。下例演示了yum模块的这种输出:
ansible-doc -s yum
使用ansible-doc命令可以查找和了解如何使用模块。尽管command、shell和raw模块的用法可能看似简单,但在可能时,应尽量避免在playbook中使用它们因为它们可以取胜任意命令,因此使用这些模块时很容易写出非幂等的playbook。
例如,以下使用shell模块的任务为非幂等。每次运行play时,它都会重写/etc/resolv.conf,即使它已经包含了行nameserver 172.16.103.2。
- name: Non-idepotent approach with shell module
shell: echo "nameserver 172.16.103.2" > /etc/resolv.conf
可以通过多种方式编写以幂等方式使用shell模块的任务,而且有时候进行这些更改并使用shell是最佳的做法。但更快的方案或许是使用ansible-doc发现copy模块,再使用它获得所需的效果。
在以下示例中,如果/etc/resolv.conf文件已包含正确的内容,则不会重写该文件:
- name: Idempotent approach with copy module
copy:
dest: /etc/resolv.conf
content: "nameserver 172.16.103.2\n"
copy模块可以测试来了解是否达到了需要的状态,如果已达到,则不进行任何更改。shell模块容许非常大的灵活性,但需要格外小心,从而确保它以幂等方式运行。
幂等的playbook可以重复运行,确保系统处于特定的状态,而不会破坏状态已经正确的系统。