playbook基础
playbook概念
playbook是由一个或多个play组成的列表;play的作用:将预定义的一组主机,装扮成事先通过ansible中的task定义好的角色,task是调用ansible的module,将多个play组织在一个playbook中,让其按照编排的顺序执行
yaml语言编写
playbook执行逻辑
yaml概念
yaml之于json
类比:markdown之于html,都提高了后者的写的便捷性;
yaml是一种可读性强,用来表达资料序列的格式,作者clark evans
yaml ain`t markup language
or
yet another markup language
- 可读性好
- 和脚本语言结合性好
- 有一个一致的信息模型
- 易于实现
- 可基于流处理
- 表达能力强,扩展性好
yaml语法
- 同一个yaml文件,用---区分多个档案
- #为注释
- 缩进必须统一!空格和tab不能混用
- 缩进级别需一致
- yaml区分大小写
- 对应playbook,一个完整代码块至少包含name和task
- 一个name只能有一个task
- 文件扩展名.yml或.yaml
yaml文件示例:
list列表
- apple
- mango
- orange
dictionary字典
name:wang
job:sre leader
字典的等同写法
(name:wang,job:sre leader)
yaml的playbook示例
不仅有tasks还有pre_tasks和post_tasks
---
- hosts: 192.168.100.59,192.168.100.65
remote_user: root
pre_tasks:
- name: set epel repo for Centos 7
yum_repository:
name: epel7
description: epel7 on CentOS 7
baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
gpgcheck: no
enabled: True
tasks:
# install nginx and run it
- name: install nginx
yum: name=nginx state=installed update_cache=yes
- name: start nginx
service: name=nginx state=started
post_tasks:
- shell: echo "deploy nginx over"
register: ok_var
- debug: msg="{{ ok_var.stdout }}"
常见数据交换格式
- xml
- json
- yaml
playbook组成
核心元素
-
hosts:远程主机列表
-
tasks:任务集
-
variables:内置变量和自定义变量
-
templates:模版
-
handler结合notity:触发条件和对应操作
-
tags:通过标签,用于跳过playbook中某些代码段
-
ansible-playbook -t tagsname useradd.yml
-
hosts
hosts定义了在哪些远程主机上,以某个特定用户身份执行任务,需要在主机清单中实现定义;
192.168.80.102
www.b.com
bbs.b.com
web:db 或
web:&db 且,取交集
web:!db 在web组,不在db组的
示例:
- hosts: websrvs:dbsrvs
remote_user
以远程主机上的哪个用户执行任务,用于host和task中,也可以用来执行sudo后的身份
- hosts: web
remote_user: root #指定全局的remoteuser为 root
tasks:
- name: test-ping
ping:
remote_user: wang #该ping任务用wang用户,且使用sudo,sudo身份为root,且默认为root
sudo: yes
sudo_user: root
tasks列表和actions
play的主体部分为task lists,任务列表中定义的任务,按照次序在各个hosts中顺序执行,在hosts中定义的所有主机上,
task作用在于根据定义的参数、变量,调用并执行相应的模块,具有幂等性,因此多次执行是安全的
每个task都应该设置合理,明确,见名知意的名字
写task的2种格式:
action: module arguments
modules: arguments (建议)
#shell和command模块后跟的arguments是命令,而非kv类型的键值对
某个任务执行后状态为changed,可以结合notify通知给handler做进一步处理,
任务可以通过tags打标签,结合ansible-playbook的 -t参数进行调用或跳过调用
tasks:
- name: disable selinux
command: /sbin/setenforce 0
# 任务名为关闭selinux,
# 调用command模块,执行的命令为/sbin/setfnforce 0
若命令或脚本的退出码不为0,可以采用以下方式忽略错误
tasks:
- name: test-ignore-error
shell: /usr/bin/some-command || /bin/true
或者:
tasks:
- name: test-ignore-error-2
shell: /usr/bin/some-command
ignore_errors: true
playbook层级
- playbook可以包含多个play
- 一个play至少包含,一个hosts,一个tasks
- 一个tasks包含多个task,每个task基本都是调用的一个个ansible模块
- 一个play至少包含,一个hosts,一个tasks
hosts
定义要执行任务的主机,支持通配符,正则匹配,来表示一批相同特征的主机;
remote_user
remote_user是ssh连接到各个节点的用户身份,不一定是执行任务的身份;become相关参数可以切换为别的身份来执行任务,没指定时默认是用remote_user执行任务
如:可以remote_user是ssh连接过去,用nginx的身份启动nginx进程,借助become-user=nginx实现
remote_user的连接认证,可以通过ssh免密认证,指定密钥文件认证,指定密码认证,密码和密码文件一般可以写在主机清单的变量中,可以命令行传入!
tasks
ps:shell和command模块的不具有幂等性的解决方法,以初始化mysql数据库为例:
tasks:
- name: init mysql datadir
file: path=/data/mysql state=directory owner=mysql group=mysql mode=0755
shell: "/usr/local/mysql/bin/mysql_install_db --datadir=/data/mysql --user=mysql creates=/mysql/data/ibdata1"
# 借助了creates参数实现;
# 第一次/mysql/data/ibdata1不存在,会支持初始化数据库目录的动作;之后再次执行,由于ibdata1文件已经存在,ansible不会再次执行该步动作;
# 介绍如下,shell和command参数,都可以通过该2个参数控制,实现类似的【幂等性】
[root@host2 ~]# ansible-doc -s shell
- name: Execute shell commands on targets
shell:
creates: # A filename, when it already exists, this step will *not* be
run.
removes: # A filename, when it does not exist, this step will *not* be
run.
运行playbook
ansible-playbook filename.yaml ... [options]
常见options
--check -C
假装执行,列出如果执行会发生的变化,并不真正的在主机上执行
--list-hosts
列出要执行任务的主机
--list-tags
列出tag
--list-tasks
列出task
--limit 主机列表
只对主机列表里的主机执行
-v -vv -vvv
显示不同等级的执行过程信息
playbook vs shellscripts
shell脚本
#!/bin/bash
yum install -y --quiet httpd
cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
cp /tmp/vhosts.conf /etc/httpd/conf.d/
service httpd start
chkconfig httpd on
playbook
---
- hosts: all
remote_user: root
tasks:
- name: "安装httpd"
yum: name=httpd
- name: "复制配置文件"
copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
- name: "复制虚拟主机配置文件"
copy: src=/tmp/vhosts/conf dest=/etc/httpd/conf.d/
- name: "启动,设置开启启动"
service: name=httpd state=started enabled=yes
playbook示例
1、创建用户
[root@host2 ansible-playbooks]# cat sysuser.yaml
- hosts: all
remote_user: root
tasks:
- name: create mysql user
user: name=mysql system=yes uid=36
- name: create a group
group: name=httpd system=yes
# 创建yaml文件如上:
# 先检查执行结果
[root@host2 ansible-playbooks]# ansible-playbook -C sysuser.yaml
PLAY [all] *********************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [192.168.80.102]
ok: [192.168.80.103]
TASK [create mysql user] *******************************************************************
changed: [192.168.80.103]
changed: [192.168.80.102]
TASK [create a group] **********************************************************************
changed: [192.168.80.103]
changed: [192.168.80.102]
PLAY RECAP *********************************************************************************
192.168.80.102 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.80.103 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 列出所有任务
[root@host2 ansible-playbooks]# ansible-playbook --list-tasks sysuser.yaml
playbook: sysuser.yaml
play #1 (all): all TAGS: []
tasks:
create mysql user TAGS: []
create a group TAGS: []
# 列出所有主机
[root@host2 ansible-playbooks]# ansible-playbook --list-hosts sysuser.yaml
playbook: sysuser.yaml
play #1 (all): all TAGS: []
pattern: [u'all']
hosts (2):
192.168.80.102
192.168.80.103
# 最终执行
[root@host2 ansible-playbooks]# ansible-playbook sysuser.yaml
2、安装httpd
[root@host2 ansible-playbooks]# cat httpd.yaml
- hosts: all
remote_user: root
tasks:
- name: install httpd
yum: name=httpd state=present
- name: copy conf file
copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
- name: start service
service: name=httpd state=started enabled=yes
1,确定要执行的主机,为all
2,默认task,先收集信息,确认主机都可达
3,后续3个task,为用户定义任务,
4,最终,每个host的执行结果,4个task都ok,2个changed,0个失败,0个不可达,0个跳过,忽略等等
# 最终执行
[root@host2 ansible-playbooks]# ansible-playbook httpd.yaml
PLAY [all] *********************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [192.168.80.103]
ok: [192.168.80.102]
TASK [install httpd] ***********************************************************************
changed: [192.168.80.102]
changed: [192.168.80.103]
TASK [copy conf file] **********************************************************************
ok: [192.168.80.103]
ok: [192.168.80.102]
TASK [start service] ***********************************************************************
changed: [192.168.80.103]
changed: [192.168.80.102]
PLAY RECAP *********************************************************************************
192.168.80.102 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.80.103 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
notify与handlers
根据幂等性,执行某task后,状态可能会changed或其他,当某个task执行后返回状态为changed,**即该步任务对目标主机造成了某些改变;**changed这个状态可以被notify捕捉,从而触发对应的handler动作;
举例说明:
- 在template和copy2个task后,紧跟着定义的一个notify,意思是:这2个task执行后为changed的话,该notify会被触发,后执行其中的handlers进行后续操作
- changed状态发生情况:第一次执行;或,要复制的2个文件hash值发送变化时,为changed,此时notify会触发;
- notify的执行为所有task执行完毕之后,才会根据自己的task状态决定是不是触发handlers,且有多次只执行一次
- 例如:2个文件都改变,template和copy的执行状态都为changed,2个notify都会被触发,但是其中相同的handler:restart-nginx只会被执行一次;
tasks:
- name: copy template conf file to remote_hosts
template: src=/tmp/nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify:
- restart-nginx
- test-web
copy=: src=/tmp/index.html dest=/usr/share/nginx/html/index.html
notify:
- restart-nginx
handlers:
- name: restart-nginx
service: name=nginx stated=restarted
- name: test-web
shell: curl -I http:IP/index.html |grep 200 || /bin/false
给某预期的情况定义notify,该notify发生时,触发handlers定义的操作,handler本质也还是tasks列表
[root@host2 ansible-playbooks]# vim test-handlers.yaml
[root@host2 ansible-playbooks]# cat !$
cat test-handlers.yaml
- hosts: 192.168.80.102
remote_user: root
tasks:
- name: add group nginx
group: name=nginx state=present
- name: add user nginx
user: name=nginx state=present group=nginx
- name: install nginx
yum: name=nginx state=present
- name: config
copy: src=/tmp/nginx.txt dest=/etc/nginx/nginx.conf
notify:
- restart-nginx
- check-nginx-process
handlers:
- name: restart-nginx
service: name=nginx state=restarted enabled=yes
- name: check-nginx-process
shell: killall -0 nginx > /tmp/nginx.log
handlers定义和tasks同等级,本质也是一个一个的task
notify定义在某个task的尾部,通过引用handlers名字调用handlers
playbook与tags
[root@host2 ansible-playbooks]# cat test-tag.yaml
- hosts: 192.168.80.103
remote_user: root
tasks:
- name: install nginx
yum: name=nginx state=present
- name: copy conf file
copy: src=/tmp/nginx.txt dest=/root
tags: conf
- name: start nginx
service: name=httpd state=started enabled=yes
tags: service
# 给不同的task打上不同的tag,执行时,可以利用tag挑选出同类的任务执行
[root@host2 ansible-playbooks]# ansible-playbook -t conf -C test-tag.yaml
PLAY [192.168.80.103] **********************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [192.168.80.103]
TASK [copy conf file] **********************************************************************
changed: [192.168.80.103]
PLAY RECAP *********************************************************************************
192.168.80.103 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
playbook传参3种方式
---
- hosts: test
tasks:
- yum: name=httpd state=present
- yum:
name: httpd
state: installed
- yum:
args:
name: httpd
state: installed
playbook执行过程!
- 先收集各个节点的信息,gather facts
- 执行一个个play
- 执行play中定义的一个个task
- 根据幂等性,不需要任务的task会不执行
- 最后有play recap,整体执行信息汇总输出
- 默认是同步阻塞,
playbook变量
变量分类
大致分为七类:
- 模块执行结果的输出,注册为变量
- 在本地,直接定义字典类型的变量:/etc/ansible/facts.d/*.fact
- role中专门定义变量的文件var
- 命令行传递变量
- 借助with_items迭代,将多个task的结果赋值给一个变量
- inventory主机清单中,定义的主机变量、主机组变量,
- 内置变量
变量定义、注册
命名
字母、数字、下划线组成,必须字母开头;
格式
k=v
调用方式
通过{{ var_name }} 调用变量,**注意前后空格,**有时需要"{{ var_name }}"才生效
ansible-playbook test.yaml -e "host=www user=wang"
1、变量来源
-
ansible setup facts 可以调用远程主机的所有变量
-
在/etc/ansible/hosts定义
- 公共变量:针对主机组所有主机生效
- 普通变量:对某主机组中主机单独定义
-
通过命令行指定变量,优先级最高
- ansible-playbook -e var=value
-
playbook中定义
-
vars: - var1: v1 - var2: v2
-
-
在独立的变量yaml文件中定义
-
在role中定义
使用setup中变量
# 利用setup模块,可以收集,远程主机各种信息,如:地址,系统版本,内核版本,硬件架构等;
# 在执行playbook的默认第一步行为就是:gather facts,收集信息
# 这些信息,在后续的playbook,jinja2模块中,都可以直接引用;
[root@host2 ~]# ansible 192.168.80.102 -m setup |less
192.168.80.102 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.80.102",
"192.168.10.102"
],
"ansible_all_ipv6_addresses": [
"fe80::eb02:a6b5:be84:952",
"fe80::7822:f591:feb0:47ea"
],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
"ansible_bios_date": "07/29/2019",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "/vmlinuz-3.10.0-862.el7.x86_64",
"biosdevname": "0",
"net.ifnames": "0",
"quiet": true,
"rhgb": true,
"ro": true,
"root": "UUID=046ebf1d-861c-42bb-a48f-9641dfc9afec"
},
# 结合filter参数,可以过滤需要的变量
[root@host2 ~]# ansible 192.168.80.102 -m setup -a "filter=*arch*"
192.168.80.102 | SUCCESS => {
"ansible_facts": {
"ansible_architecture": "x86_64",
"ansible_userspace_architecture": "x86_64",
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
[root@host2 ~]# ansible 192.168.80.102 -m setup -a "filter=*bios*"
192.168.80.102 | SUCCESS => {
"ansible_facts": {
"ansible_bios_date": "07/29/2019",
"ansible_bios_version": "6.00",
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
使用fqdn变量
[root@host2 vardir]# cat setup.yaml
- hosts: all
remote_user: root
tasks:
- name: create log file
file: name=/var/log/{{ ansible_fqdn }} state=touch
# 通过调用setup中ansibl_fqdn变量,获取每个主机的主机名,并创建文件
[root@host2 vardir]# ansible-playbook setup.yaml
[root@host3 ~]# ll /var/log/host3.b.com
-rw-r--r-- 1 root root 0 Oct 9 16:45 /var/log/host3.b.com
[root@host4 ~]# ll /var/log/host4.b.com
-rw-r--r-- 1 root root 0 Oct 9 16:45 /var/log/host4.b.com
命令行-e传变量值
yaml中定义变量名,命令行中传值
[root@host2 vardir]# cat var-pkname.yaml
- hosts: all
remote_user: root
tasks:
- name: install pk
yum: name={{ pkname }} state=present
[root@host2 vardir]# ansible-playbook -e pkname=libaio var-pkname.yaml
vars批量定义变量
在playbook文件中,vars定义一批变量
[root@host2 vardir]# cat vars.yaml
- hosts: all
remote_user: root
vars:
- username: user1
- groupname: group1
tasks:
- name: create-u
user: name={{ username }} state=present
- name: create-g
group: name={{ groupname }} state=present
主机变量
在inventory主机清单中,定义主机时,给主机添加的变量
语法格式;
[websrvs]
www1.magedu.com http_port=80 maxRequestsPerChild=808
www2.magedu.com http_port=8080 maxRequestsPerChild=909
示例:
[root@host2 vardir]# vim /etc/ansible/hosts
[test]
192.168.80.102 http_port=80
192.168.80.103 http_port=8080
[root@host2 vardir]# cat var.yaml
---
- hosts: test
remote_user: root
tasks:
- name: test1
debug: msg="var is {{ http_port }}"
[root@host2 vardir]# ansible-playbook var.yaml
PLAY [test] **********************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************
ok: [192.168.80.103]
ok: [192.168.80.102]
TASK [test1] *********************************************************************************************************************************
ok: [192.168.80.102] => {
"msg": "var is 80"
}
ok: [192.168.80.103] => {
"msg": "var is 8080"
}
主机组变量
语法格式:
[websrvs]
www1.magedu.com
www2.magedu.com
[websrvs:vars]
ntp_server=ntp.magedu.com
nfs_server=nfs.magedu.com
示例;
#定义的var变量,该test组内主机,都可以引用,为组内共享变量
[root@host2 vardir]# vim /etc/ansible/hosts
[test]
192.168.80.102 http_port=80
192.168.80.103 http_port=8080
[test:vars]
var=hello-test
[root@host2 vardir]# cat var.yaml
---
- hosts: test
remote_user: root
tasks:
- name: test1
debug: msg="var is {{ http_port }}{{ var }}"
[root@host2 vardir]# ansible-playbook var.yaml
PLAY [test] **********************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************
ok: [192.168.80.103]
ok: [192.168.80.102]
TASK [test1] *********************************************************************************************************************************
ok: [192.168.80.102] => {
"msg": "var is 80hello-test"
}
ok: [192.168.80.103] => {
"msg": "var is 8080hello-test"
}
普通变量
语法格式:
[websrvs]
192.168.99.101 http_port=8080 hname=www1
192.168.99.102 http_port=80 hname=www2
示例:
定义的hname为每个主机独有的变量
[root@host2 vardir]# vim /etc/ansible/hosts
[test]
192.168.80.102 http_port=80 hname=www1
192.168.80.103 http_port=8080 hname=www2
[root@host2 vardir]# cat var.yaml
---
- hosts: test
remote_user: root
tasks:
- name: test1
debug: msg="var is {{ http_port }}{{ var }}-{{ hname }}"
[root@host2 vardir]# ansible-playbook var.yaml
PLAY [test] **********************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************
ok: [192.168.80.102]
ok: [192.168.80.103]
TASK [test1] *********************************************************************************************************************************
ok: [192.168.80.102] => {
"msg": "var is 80hello-test-www1"
}
ok: [192.168.80.103] => {
"msg": "var is 8080hello-test-www2"
}
公共变量
同,组变量
[websvrs:vars]
http_port=808
mark=“_”
[websrvs]
192.168.99.101 http_port=8080 hname=www1
192.168.99.102 http_port=80 hname=www2
ansible websvrs –m hostname –a ‘name={{ hname }}{{ mark }}{{ http_port }}’
命令行变量
-e选项,传入变量,采用hostname模块修改主机名,因为变量一致,所以修改后主机名一致,一般还需要引入每个主机自己的独有变量,如可以在定义主机清单的时候定义
[root@host2 vardir]# ansible test -e role=www -m hostname -a 'name={{ role }}.host.com'
192.168.80.103 | CHANGED => {
"ansible_facts": {
"ansible_domain": "host.com",
"ansible_fqdn": "www.host.com",
"ansible_hostname": "www",
"ansible_nodename": "www.host.com",
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "www.host.com"
}
192.168.80.102 | CHANGED => {
"ansible_facts": {
"ansible_domain": "host.com",
"ansible_fqdn": "www.host.com",
"ansible_hostname": "www",
"ansible_nodename": "www.host.com",
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "www.host.com"
}
ansible test -e role=www -m hostname -a 'name={{ role }}.{{ host }}.com'
变量文件
# 以yaml或json格式的文件,定义变量
[root@host2 vardir]# cat vars.yaml
---
var1: httpd
var2: nginx
# 在playbook中,用vars_files引用文件路径,然后就可以按需引用其中的变量,
# 文件路径,可以绝对,可以相对
[root@host2 vardir]# cat vars-use.yaml
- hosts: all
remote_user: root
vars_files:
- vars.yaml
tasks:
- name: create httpd log
file: name=/root/{{ var1 }}.log state=touch
- name: create nginx log
file: name=/root/{{ var2 }}.log state=touch
[root@host2 vardir]# ansible-playbook vars-use.yaml
定义本地facts
ansible会自动收集/etc/ansible/facts.d/*.fact文件中数据到facts中,**以ansible_local作为顶级key,**支持文件格式为ini,json
eg:
[root@host2 facts.d]# pwd
/etc/ansible/facts.d
[root@host2 facts.d]# cat my.fact
{
"family": {
"dad": {
"name": "zhangsan",
"age": "666"
},
"mom": {
"name": "lisi",
"age": "233"
}
}
}
# 定义本地facts如上
# 过滤时发现,本地fact在ansibl_facts下一级,并以ansible_local作为次级key,
# 注意:my为本地fact的文件名,要加上
[root@host2 facts.d]# ansible localhost -m setup -a 'filter=ansible_local'
localhost | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"my": {
"family": {
"dad": {
"age": "666",
"name": "zhangsan"
},
"mom": {
"age": "233",
"name": "lisi"
}
}
}
}
},
"changed": false
}
# 引用路径:
[root@host2 facts.d]# cat /root/var_jason.yaml
---
- hosts: localhost
tasks:
- shell: echo hello-world
register: var1
- debug: var=var1
- debug: var=var1['stdout']
- debug: var=var1.stdout_lines[0]
- debug: var=ansible_eth1.ipv6[0].address
- debug: var=ansible_local.my.family.dad.name
register注册变量
#register捕捉标准输出的信息,注册为变量var1
#再通过debug模块输出该变量
[root@host2 ~]# cat reg.yaml
---
- hosts: localhost
tasks:
- shell: echo hello
register: var1
- debug: var=var1
set_fact注册变量
[root@host2 ~]# cat set.yaml
---
- hosts: localhost
tasks:
- shell: echo hello
- set_fact: var1="my var"
- debug: var=var1
变量引用
变量过滤
每个节点的收集的facts都是json格式数据,通过setup模块的filter参数可以过滤出特定key的fact,如下过滤ipv4相关的变量信息
[root@host2 ~]# ansible 192.168.80.102 -m setup -a 'filter=*ipv4'
192.168.80.102 | SUCCESS => {
"ansible_facts": {
"ansible_default_ipv4": {
"address": "192.168.80.102",
"alias": "eth0",
"broadcast": "192.168.80.255",
"gateway": "192.168.80.2",
"interface": "eth0",
"macaddress": "00:0c:29:1b:4e:70",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "192.168.80.0",
"type": "ether"
}
},
"changed": false
}
引用json字典
[root@host2 ~]# cat var_jason.yaml
---
- hosts: 192.168.80.102
tasks:
- shell: echo hello-world
register: var1
- debug: var=var1
- debug: var=var1['stdout']
# shell这个task执行后结果,标准输出为hello-world,用register捕捉,注册为变量,变量名为var1,
# debug模块,输出该变量,输出为json数据,并附带了其他信息,组成了var1的一组字典信息;
# 第2个debug模块,通过调用该变量的key为stdout,输出其值,
TASK [debug] *******************************************************************************
ok: [192.168.80.102] => {
"var1": {
"changed": true,
"cmd": "echo hello-world",
"delta": "0:00:00.004388",
"end": "2020-10-15 10:33:01.317302",
"failed": false,
"rc": 0,
"start": "2020-10-15 10:33:01.312914",
"stderr": "",
"stderr_lines": [],
"stdout": "hello-world",
"stdout_lines": [
"hello-world"
]
}
}
TASK [debug] *******************************************************************************
ok: [192.168.80.102] => {
"var1['stdout']": "hello-world"
}
引用json数组
以上面输出为例,stdout_line的值部分又是一个数组列表;
[root@host2 ~]# cat var_jason.yaml
---
- hosts: 192.168.80.102
tasks:
- shell: echo hello-world
register: var1
- debug: var=var1
- debug: var=var1['stdout']
- debug: var=var1.stdout_lines[0]
# 数据用数字的数组下标引用
TASK [debug] *******************************************************************************
ok: [192.168.80.102] => {
"var1['stdout']": "hello-world"
}
TASK [debug] *******************************************************************************
ok: [192.168.80.102] => {
"var1.stdout_lines[0]": "hello-world"
}
eg2:
ipv6": [
{
"address": "fe80::eb02:a6b5:be84:952",
"prefix": "64",
"scope": "link"
}
],
# facts输出中,ansible_eth1的ipv6字典项值为数组,每个数组值又为一组字典
# 引用方式如下
cat var_jason.yaml
---
- hosts: 192.168.80.102
tasks:
- shell: echo hello-world
register: var1
- debug: var=var1
- debug: var=var1['stdout']
- debug: var=var1.stdout_lines[0]
- debug: var=ansible_eth1.ipv6[0].address
TASK [debug] *******************************************************************************
ok: [192.168.80.102] => {
"ansible_eth1.ipv6[0].address": "fe80::7822:f591:feb0:47ea"
}
引用facts变量
收集后数据为json数组,顶级有2个key,一个是ansible_facts,一个是changed,节点信息都在ansible_facts中,引用时,ansible_facts可以省略,可以直接从其下一级的key开始引用
如:ansible_eth0,"ansible_architecture": "x86_64",
"ansible_bios_date": "07/29/2019",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
变量输出
采用debug模块的var或msg参数可以输出,变量,做调试之用
[root@host2 ~]# cat var_jason.yaml
---
- hosts: localhost
tasks:
- debug: 'msg="ip-value is: {{ ansible_eth0.ipv4.address }}"'
- debug: var=ansible_eth0.ipv4.address
# 注意,msg外部要加上单引号
输出结果如下:
[root@host2 ~]# ansible-playbook var_jason.yaml
PLAY [localhost] ***************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [localhost]
TASK [debug] *******************************************************************************
ok: [localhost] => {
"msg": "ip-value is: 192.168.80.101"
}
TASK [debug] *******************************************************************************
ok: [localhost] => {
"ansible_eth0.ipv4.address": "192.168.80.101"
模板template
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/template_module.html
概念
template功能:根据模块文件,动态生成对应的配置文件
-
templa文件必须放在templates目录下,以.j2结尾
-
yaml或yml文件需和templates目录平级,目录结构如下:
-
./ temnginx.yaml templates nginx.conf.j2
-
文本文件;嵌套有脚本(使用模版编程语言编写),jinja2语言,有如下形式:
- 字符串:单引号或双引号
- 数字:整数、浮点数
- 列表:[item1,item2,...]
- 元组:(item1,item2)
- 字典:[k1:v1,k2:v2]
- 布尔型:true,false
- 算术运算: + - * / // % **
- 比较运算:== != > >= < <=
- 逻辑运算:and or not
- 流表达式: for if when
示例算术运算
如下为,根据每个主机的cpu核数不同,利用templates模版文件,生成不同work_processor配置选项的示例
采用了算术运算
[root@host2 template]# ansible test -m setup |grep processor
"ansible_processor": [
"ansible_processor_cores": 2,
"ansible_processor_count": 2,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 4,
"ansible_processor": [
"ansible_processor_cores": 1,
"ansible_processor_count": 2,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 2,
# 2个主机,一个为4核,一个为2核
# 根据如下目录结构,编写对应文件
[root@host2 template]# tree
.
├── temnginx.yaml
└── templates
└── nginx.conf.j2
# yaml文件,为playbook,其中利用templates模块调用了nginx.conf的模版文件
[root@host2 template]# cat temnginx.yaml
- hosts: test
remote_user: root
tasks:
- name: template config to remote-hosts
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
# 模版文件如下,为原有nginx.conf修改而来,修改了其中worker_processes,利用了setup变量每个主机的cpu核数,的平方做为参数值
[root@host2 template]# head templates/nginx.conf.j2
user nginx;
worker_processes {{ ansible_processor_vcpus**2 }};
# 成功执行
[root@host2 template]# ansible-playbook temnginx.yaml
PLAY [test] **********************************************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************************************
ok: [192.168.80.102]
ok: [192.168.80.103]
TASK [template config to remote-hosts] *******************************************************************************************************
changed: [192.168.80.103]
changed: [192.168.80.102]
PLAY RECAP ***********************************************************************************************************************************
192.168.80.102 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.80.103 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 查看2台主机,生成的配置文件,分别为4,和16,为2和4的平方
[root@host4 ~]# head /etc/nginx/nginx.conf
user nginx;
worker_processes 4;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
[root@host3 ~]# head /etc/nginx/nginx.conf
user nginx;
worker_processes 16;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
示例条件判断
根据变量,facts,或此前任务的执行结果来做某task执行的前提时,需要用when实现,直接在某task后添加when子句+条件即可
jinja2语法示例:
tasks:
- name: "shutdown redhat flavored systems"
command: /sbin/shudown -h now
when: ansible_os_family == "redhat"
通过os版本做判断条件
[root@host2 template]# cat when.yaml
- hosts: test
remote_user: root
tasks:
- name: add group nginx
tags: user
group: name=nginx state=present
- name: add user nginx
user: name=nginx state=present group=nginx
- name: install nginx
yum: name=nginx state=present
- name: stop nginx
service: name=nginx state=stoped
when: ansible_distribution_major_version == "7"
# 在执行stop nginx时,加了when条件,只有版本是7的centos上的nginx会被停止
示例迭代with_items
有需要重复执行的任务,可以采用迭代机制;迭代的引用固定变量名为item,task中使用with_items给定要迭代的元素列表
列表格式:字符串,字典
1、迭代创建用户
在task中用with_items定义列表,用item变量引用,然后创建2个用户
[root@host2 template]# cat items.yaml
- hosts: 192.168.80.102
remote_user: root
tasks:
- name: add serveral users;
user: name={{ item }} state=present groups=wheel
with_items:
- testuser1
- testuser2
testuser1:x:1003:1004::/home/testuser1:/bin/bash
testuser2:x:1004:1005::/home/testuser2:/bin/bash
[root@host3 ~]# cat /etc/passwd
2、迭代copy多个文件
[root@host2 template]# touch /etc/file1
[root@host2 template]# touch /etc/file2
[root@host2 template]# cat !$
cat /root/copy-multi.yaml
---
- hosts: test
remote_user: root
tasks:
- name: create two file and copy
copy: src={{ item }} dest=/tmp
with_items:
- /etc/file1
- /etc/file2
[root@host3 ~]# ll /tmp/file*
-rw-r--r-- 1 root root 0 Oct 10 15:57 /tmp/file1
-rw-r--r-- 1 root root 0 Oct 10 15:57 /tmp/file2
3、迭代安装rpm包
[root@host2 template]# cat rpm-install.yaml
- hosts: test
remote_user: root
tasks:
- name: install rpms
yum: name={{ item }} state=present
with_items:
- memcached
- php-fpm
4、迭代-嵌套-子变量
# item元素为字典形式,通过其key再引用每个key的值;
[root@host2 template]# cat !$
cat diedai.yaml
- hosts: test
remote_user: root
tasks:
- name: add groups
group: name={{ item }} state=present
with_items:
- group1
- group2
- group3
- name: add users
user: name={{ item.name }} group={{ item.group }} state=present
with_items:
- { name: 'user1', group: 'group1' }
- { name: 'user2', group: 'group2' }
- { name: 'user3', group: 'group3' }
[root@host2 template]# ansible-playbook diedai.yaml
PLAY [test] ********************************************************************************
TASK [Gathering Facts] *********************************************************************
ok: [192.168.80.103]
ok: [192.168.80.102]
TASK [add groups] **************************************************************************
ok: [192.168.80.102] => (item=group1)
ok: [192.168.80.103] => (item=group1)
changed: [192.168.80.103] => (item=group2)
changed: [192.168.80.102] => (item=group2)
changed: [192.168.80.103] => (item=group3)
changed: [192.168.80.102] => (item=group3)
TASK [add users] ***************************************************************************
changed: [192.168.80.103] => (item={u'group': u'group1', u'name': u'user1'})
changed: [192.168.80.102] => (item={u'group': u'group1', u'name': u'user1'})
changed: [192.168.80.102] => (item={u'group': u'group2', u'name': u'user2'})
changed: [192.168.80.103] => (item={u'group': u'group2', u'name': u'user2'})
changed: [192.168.80.102] => (item={u'group': u'group3', u'name': u'user3'})
changed: [192.168.80.103] => (item={u'group': u'group3', u'name': u'user3'})
[root@host3 ~]# id user1
uid=1002(user1) gid=1003(group1) groups=1003(group1)
[root@host3 ~]# id user2
uid=1005(user2) gid=1006(group2) groups=1006(group2)
[root@host3 ~]# id user3
uid=1006(user3) gid=1007(group3) groups=1007(group3)
template的for循环 if判断
template文件中的if判断语法,
示例格式:for遍历vhost,if做条件判断,根据其模版生成不同的server虚拟主机配置段
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.listen | default('80 default_server')}};
{% if vhost.server_name if defined %}
server_name {{ vhost.server_name }}
{% endif %}
{% if vhost.root is defined %}
root {{ vhost.root }};
{% endif %}
}
{% endfor %}
示例template for和if
yaml文件和对应j2模版如下:
[root@host2 nginx-web]# tree
.
├── temnginx.yaml
└── templates
└── nginx.conf.j2
[root@host2 nginx-web]# cat temnginx.yaml
- hosts: all
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
server_name: "web1.b.com"
root: "/data/web1"
- web2:
listen: 8080
server_name: "web2.b.com"
root: "/data/web2"
- web3:
listen: 80
server_name: "web3.b.com"
root: "/data/web3"
tasks:
- name: copy template conf file
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
# 用了for循环,生成3个server虚拟主机配置段
# 用了if判断
# 用了默认值设置
[root@host2 nginx-web]# cat templates/nginx.conf.j2
{% for vhost in nginx_vhosts %}
server {
{% if vhost.listen is defined %}
listen {{ vhost.listen }};
{% endif %}
server_name {{ vhost.server_name }};
root {{ vhost.root | default('/data') }};
}
{% endfor %}
查看,生成的配置文件
[root@host2 nginx-web]# ansible-playbook temnginx.yaml
# 执行;
[root@host3 ~]# cat /etc/nginx/nginx.conf
server {
listen 8080;
server_name web1.b.com;
root /data/web1;
}
server {
listen 8080;
server_name web2.b.com;
root /data/web2;
}
server {
listen 80;
server_name web3.b.com;
root /data/web3;
}
# 和yaml中,定义的vars变量一一对应;
# 模版的好处:只修改变量部分,可以很高效的生成别的配置文件,
# var可以采用其他方法定义,如单独的var文件
[root@host2 nginx-web]# cat temnginx.yaml
- hosts: all
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
server_name: "web1.b.com"
root: "/data/web1"
- web2:
listen: 8080
server_name: "web2.b.com"
root: "/data/web2"
- web3:
listen: 80
server_name: "web3.b.com"
root: "/data/web3"
采用默认值情况;web3的root变量没定义;
[root@host2 nginx-web]# cat temnginx.yaml
- hosts: all
remote_user: root
vars:
nginx_vhosts:
- web1:
listen: 8080
server_name: "web1.b.com"
root: "/data/web1"
- web2:
listen: 8080
server_name: "web2.b.com"
root: "/data/web2"
- web3:
listen: 80
server_name: "web3.b.com"
tasks:
- name: copy template conf file
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
# web3的root采用默认字段,为/data
[root@host3 ~]# cat /etc/nginx/nginx.conf
server {
listen 8080;
server_name web1.b.com;
root /data/web1;
}
server {
listen 8080;
server_name web2.b.com;
root /data/web2;
}
server {
listen 80;
server_name web3.b.com;
root /data;
}
roles
概念
ansible自1.2后引入的特性;用于层次化,结构化,组成playboo,roles能够根据层次型结构自动加载变量文件,task,handler等;
使用roles时需要在playbook中用include导入即可,
roles就是将变量、文件、任务、模版、处理器按照一定组织结构放在单独目录中,并 用include导入的一种机制,
一般用于构建,基于主机构建服务的场景中,也可以构建守护进程
复杂场景,适合使用roles,代码复用度高
简单说:role就是拆解大的playbook的一种方式,以一定规则组成目录结构,可以复用其中的小的task或play,通过include导入即可;
roles目录结构
playbook.yaml
roles/
project/
tasks/
files/
vars/
templates/
handlers/
default/
meta/
后2者不常用
files:存放由copy,或script模块,调用的文件
templates:template模块查找模版文件的目录
tasks:定义task,role的基本元素,至少应包括一个名为main.yaml的文件;其他的文件通过include包含
handlers:至少包含一个main.yaml,其他文件用include导入
vars:定义变量,至少有一个main.yaml,其他用include导入
meta:定义当前角色的特殊设定,和其依赖,至少一个main.yaml,其他文件include导入
default:设定默认变量时,用此目录的main.yaml文件
创建role步骤
- 创建以roles命令的目录;
- 在roles目录中分别创建以各个角色名称命名的目录,如webservers等
- 在每个角色的目录中分别创建:files、handlers、meta、taskstemplates、vars等目录,没有用到的目录可以创建为空,也可以不创建
- 在playbook中,调用各个角色;
#示例目录结构如下,
playbook为nginx-role.yaml
roles目录下有一个role目录,为nginx
nginx目录下,有一系列固定结构的目录
[root@host2 workspace]# tree .
.
├── nginx-role.yaml
└── roles
└── nginx
├── files
│ └── main.yaml
├── tasks
│ ├── groupadd.yaml
│ ├── install.yaml
│ ├── main.yaml
│ ├── restart.yaml
│ └── useradd.yaml
└── vars
└── main.yaml
playbook调用roles
方法1:
- hosts: webservers
remote_user: root
roles:
- mysql
- memcached
- nginx
方法2:
- hosts:
remote_user:
roles;
- mysql
- { role: nginx,username: nginx }
键role指定调用nginx这个role
后续是给username这个变量传值为nginx
方法3:
roles:
- { role: nginx,username: nginx,when: ansible_distribution_major_version == '7' }
#加入了when条件判断
完整roles架构示例
# 顶层的playbook中,定义了调用了2个role
[root@host2 workspace]# cat nginx-role.yaml
- hosts: test
remote_user: root
roles:
- nginx
- httpd
# 以nginx的role为例,task的main.yaml调用了一个个小的task文件
[root@host2 workspace]# cat roles/nginx/tasks/main.yaml
- include: groupadd.yaml
- include: useradd.yaml
- include: install.yaml
- include: restart.yaml
- include: filecp.yaml
# tasks中其他yaml文件,将是将原来的task拆分,写到一个个文件中而已
[root@host2 workspace]# cat roles/nginx/tasks/groupadd.yaml
- name: add group nginx
group: name=nginx state=present
[root@host2 workspace]# cat roles/nginx/tasks/filecp.yaml
- name: file copy
copy: src=tom.conf dest=/tmp/tom.conf
# task中copy或scripts调用的文件,都放在files目录下
[root@host2 workspace]# cat roles/nginx/files/tom.conf
playbook中 tags使用
ansible-playbook --tags="nginx.httpd,mysql" nginx-role.yaml
#只调用带有对应标签的role
# nginx-role.yaml中
- hosts: test
remote_user: root
roles:
- { role: nginx,tags: [ 'nginx','web' ],when: ansible_distribution_major_version == '6' }
- { role: httpd,tags: [ 'httpd','web' ] }
- { role: mysql,tags: [ 'mysql','db' ] }
- { role: mariadb,tags: [ 'mysql','db' ] }
- { role: php }
# tags是列表形式,注意括号前后的空格
实验:playbook编译安装httpd
tasks各个步骤
- get_url模块:下载源码包:apr apr-util httpd
- yum模块:下载依赖包和编译工具:gcc gcc++ zlib-devel openssl-devel等
- unarchive模块,解压源码包
- shell模块,依次编译apr apr-util httpd
- user模块,创建apache用户
- copy模块或template,准备配置文件,服务管理脚本等,
- shell模块,path变量配置,配置文件语法检测,服务启动等
参考yaml
[root@host2 httpd]# cat httpd.yaml
- hosts: 192.168.80.102
remote_user: root
tasks:
- name: down source tar
get_url: url="{{ item }}" dest=/root/pkg
with_items:
- https://mirrors.aliyun.com/apache/apr/apr-1.6.5.tar.gz
- https://mirrors.aliyun.com/apache/apr/apr-util-1.6.1.tar.gz
- https://mirrors.aliyun.com/apache/httpd/httpd-2.4.46.tar.gz
run_once: True
- name: unarchive
unarchive: src="/root/pkg/{{ item }}" dest=/root
with_items:
- httpd-2.4.46.tar.gz
- apr-1.6.5.tar.gz
- apr-util-1.6.1.tar.gz
- name: install pcre prce-devel
yum: name="{{ item }}" state=present
with_items:
- pcre
- pcre-devel
- expat-devel
- name: compile apr
shell: cd /root/apr-1.6.5 && ./configure --prefix=/usr/local/apr && make && make install
- name: compile apr-util
shell: cd /root/apr-util-1.6.1 && ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr && make && make install
- name: complie httpd
shell: |
cd /root/httpd-2.4.46
./configure --prefix=/usr/local/apache --sysconfig=/etc/apache \
--enable-mpms-shared=all \
--with-z --with-pcre \
--with-apr=/usr/local/apr \
--with-apr-util=/usr/local/apr-util \
--with-mpm-event
make && make install
ansible执行过程
执行过程总结
- 读取配置文件,默认/etc/ansible/ansible.cfg
- 加载inventory文件,包括主机变量,主机组变量
- 执行默认,第一个任务,收集各个节点的信息
- 建立连接,获取家目录信息
- 将要执行的收集任务,放在临时文件
- 将临时文件传输到被控节点的临时目录
- ssh连接到远端执行收集任务
- 删除任务文件
- 收集信息返回给ansible端,此处各种变量可以被之后各步骤引用!
- 关闭连接
- 第二个任务,此处开始,为用户定义的任务
- 建立连接,获取家目录信息
- 将要执行的任务放到临时文件中
- 将临时文件,sftp传输到远端节点
- ssh连接到远端执行任务
- 删除任务文件
- 执行结果返回给ansible端,ansible输出到屏幕
- 关闭连接
- 后续任务...,遵从同样的步骤
假设有10个任务,主机数量较多的时候,ansible会将主机分批次执行第一个任务,直到所有主机都执行完第一个任务后,再开始第二个任务,也是分批次执行,以此类推;
直到所有节点,执行完所有任务,ansible才会释放当前shell,此乃ansible默认的同步模式;并发默认为5 ,-f可以指定并发数
ansible优化速度
配置开启ssh长连接
为避免需要长时间执行的任务,中途断开,可以配置长连接时间久一点;
[ssh_connection]
# ssh arguments to use
# Leaving off ControlPersist will result in poor performance, so use
# paramiko on older platforms rather than removing it, -C controls compression use
#ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
开启pipline
ansible在执行一个任务期间,将任务文件放到临时文件中后,会sftp复制传输到远端,然后ssh连接到远端执行该任务文件,共2次ssh连接,pipline为openssh支持的特性,可以在一次ssh连接中完成上述2步,
配置文件:/etc/ansible/ansible.cfg
# Enabling pipelining reduces the number of SSH operations required to
# execute a module on the remote server. This can result in a significant
# performance improvement when enabled, however when using "sudo:" you must
# first disable 'requiretty' in /etc/sudoers
#
# By default, this option is disabled to preserve compatibility with
# sudoers configurations that have requiretty (the default on many distros).
#
#pipelining = False
修改ansible执行策略
ansible默认fork为5,假设共20台,一次5台执行任务,假设其中一台主机性能好,早早完成,该空出的并发名额会等待,其他四个小伙伴一起结束,再一起分配下一个5台主机执行任务,造成浪费
ansible 2.0后,通过strategy设置为free,默认值是linear,使得空出来的名额,可以立即分配给后面的主机开始执行,
- hosts: all
strategy: free
tasks:
...
设置facts缓存
收集各个节点的facts为默认行为,主机数量多时,收集facts会消耗一定时间;虽然可以禁止改行为,但其中变量一般很有用,所以,设置facst缓存很有必要:
# plays will gather facts by default, which contain information about
# the remote system.
#
# smart - gather by default, but don't regather if already gathered
# implicit - gather by default, turn off with gather_facts: False
# explicit - do not gather by default, must say gather_facts: True
#gathering = implicit
默认行为收集,在playbook中,可以通过gather_facts改变默认行为
设置为smart为启用缓存,在空闲时收集,并缓存;
缓存有redis和jsonfile两种形式;
## jsonfile示例
gathering=smart
fact_caching_timeout =86400
fact_caching=jsonfile
fact_caching_connection= /path/to/factdir
## 缓存1天,位置在/path/to/factdir目录下
##
[root@host2 httpd]# ll /tmp/factdir/
total 48
-rw-r--r-- 1 root root 24549 Oct 14 16:26 192.168.80.102
-rw-r--r-- 1 root root 24365 Oct 14 16:26 192.168.80.103
[root@host2 httpd]# cat /tmp/factdir/192.168.80.102 | python -m json.tool
查看ansible执行过程示例
ansible或ansible-playbook的执行中,加入-vvv参数可以显示不同详细等级的执行信息,如下:
[root@host2 httpd]# ansible-playbook -vvv httpd.yaml
# 先确认ansible读取的配置文件,插件路径,python解释器路径,可执行程序路径等
ansible-playbook 2.9.13
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible-playbook
python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]
Using /etc/ansible/ansible.cfg as config file
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Parsed /etc/ansible/hosts inventory source with ini plugin
# 找出有几个play,
PLAYBOOK: httpd.yaml *************************************************************************************************************************
1 plays in httpd.yaml
PLAY [192.168.80.102] ************************************************************************************************************************
# 默认行为,收集节点信息,
TASK [Gathering Facts] ***********************************************************************************************************************
task path: /root/httpd/httpd.yaml:1
<192.168.80.102> ESTABLISH SSH CONNECTION FOR USER: root
参考链接