侧边栏壁纸
博主头像
qu@blog 博主等级

行动起来,活在当下

  • 累计撰写 10 篇文章
  • 累计创建 4 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Ansible

Administrator
2025-08-25 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

安装

[root@m01 ~]# yum install ansible -y
​
#检查ansible版本
[root@m01 ~]# ansible --version
ansible 2.6.1

查看帮助文档

ansible-doc yum

非root用户传文件

1,创建公钥

# 这里也是非root用户执行
ssh-keygen -t rsa #一直回车,会显示公钥位置

2,复制公钥文件 id_rsa.pub 到远程服务器上的 用户文件夹下,比如我用户test,就复制到 /home/test/.ssh文件夹下,.ssh需要创建

3,执行cat id_rsa.pub >> authorized_keys,就可以了

4,在被控端/etc/hosts.allow ,添加sshd访问授权: 10.55.32.71

如果不行的话可以看下文件权限。

ansible sports -m copy -a "src=./stat.json dest=/home/quxp"

非root用户提权执行命令

ansible 10.55.32.92 -a "/sbin/iptables -L -n" --become --become-method=sudo --ask-become-pass
 
# --become 提权
# --become-method=sudo 提权方式
# --ask-become-pass 需要sudo输入密码确认 密码是/etc/shadow中加密后的密码

不知道什么原因使用--ask-become-pass时输入密码老是错误没有成功。可以进行免密配置,在受控机上修改/etc/sudoer 文件,添加该用户组的免密登录. 先修改可写权限chmod +w /etc/sudoers

%appops ALL=(ALL)       NOPASSWD:ALL

Ansible inventory

/etc/ansible/hosts主机资产清单文件,用于定义被管理主机的认证信息, 例如ssh登录用户名、密码以及key相关信息。如何配置Inventory文件

1.主机支持主机名通配以及正则表达式,例如web[1:3].xxx.com代表三台主机 2.主机支持基于非标准的ssh端口,例如web1.xxx.com:6666 3.主机支持指定变量,可对个别主机的特殊配置,如登陆用户,密码 4.主机组支持指定变量[group_name:vars],同时支持嵌套组[game:children]

场景一、基于密码连接

[root@oldboy.com ~]# cat /etc/ansible/hosts
 
#方式一、主机+端口+密码
[webservers]
10.0.0.31 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass='123456'
10.0.0.41 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass='123456'
 
#方式二、主机+端口+密码
[webservers]
web[1:2].oldboy.com ansible_ssh_pass='123456'
 
#方式三、主机+端口+密码
[webservers]
web[1:2].xxx.com
[webservers:vars]
ansible_ssh_pass='123456'

场景二、基于密钥连接,需要先创建公钥和私钥,并下发公钥至被控端

#利用非交换式工具实现批量分发公钥与批量管理服务器
[root@m01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.16.1.41
[root@m01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@172.16.1.31

Ansible Ad-Hoc

1.什么是ad-hoc模式

ad-hoc简而言之,就是“临时命令”,不会保存 ansible中有两种模式, 分别是ad-hoc模式和playbook模式

2.ad-hoc模式的使用场景 场景一,在多台机器上,查看某个进程是否启动 场景二,在多台机器上,拷贝指定日志文件到本地,等等

command模块

# 默认模块, 执行命令
[root@m01 ~]# ansible xxx  -a "hostname"
 
# 如果需要一些管道操作,则使用shell
[root@m01 ~]# ansible oldboy -m shell -a "ifconfig|grep eth0" -f 50
 
# -f =forks   /etc/ansible/ansible.cfg #结果返回的数量

yum模块

示例1,安装当前最新的Apache软件,如果存在则更新

ansible sports -m yum -a "name=httpd state=latest" -i hosts
# -i hosts 指定配置文件
# state=latest 最新版本
# state=persent 中上版本,常用
# state=absent 删除软件

示例2,安装当前最新的Apache软件,通过epel仓库安装

ansible sports -m yum -a "name=httpd state=latest enablerepo=epel" -i hosts
# 使用epel安装  enablerepo=epel

示例3,通过公网URL安装rpm软件

ansible sports -m yum -a "name=https://mirrors.aliyun.com/xxx/xx.rpm state=latest" -i hosts

示例4,更新所有的软件包,但排除和kernel相关的

ansible sports -m yum -a "name=* state=latest exclude=kernel*" -i hosts
# exclude 排除

示例5,删除Apache软件

ansible sports -m yum -a "name=httpd state=absent" -i hosts
# 删除state=absent

copy模块

# 推送文件模块
[root@m01 ~]# ansible xxx -m copy -a "src=/etc/hosts dest=/tmp/test.txt"
 
# 在推送覆盖远程端文件前,对远端已有文件进行备份,按照时间信息备份
[root@m01 ~]# ansible xxx -m copy -a "src=/etc/hosts dest=/tmp/test.txt backup=yes"
 
# 直接向远端文件内写入数据信息,并且会覆盖远端文件内原有数据信息
[root@m01 ~]# ansible xxx -m copy -a "content='bgx' dest=/tmp/index.txt"
 
src             #推送数据的源文件信息
dest            #推送数据的目标路径
backup          #对推送传输过去的文件,进行备份
content         #直接批量在被管理端文件中添加内容
group           #将本地文件推送到远端,指定文件属组信息
owner           #将本地文件推送到远端,指定文件属主信息
mode            #将本地文件推送到远端,指定文件权限信息

get_url模块

ansible yujian -m get_url -a 'url=https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox-0.12.5-1.centos8.x86_64.rpm dest=/usr/local/source/ force=yes'
#url: 下载文件的地址
#dest= : 下载到哪里(绝对路径)
#force : 如果yes,dest不是目录,将每次下载文件,如果目标文件已存在且两个文件不同时,替换文件。如果否,则只有在目标不存在时才会下载该文件
#checksum=md5:733ad25f344fa22 进行md5校验 校验成功下载

file模块

file模块一般只用于创建目录、文件和授权

[root@m01 ~]# ansible xxx -m file -a "path=/tmp/oldboy state=directory"
[root@m01 ~]# ansible xxx -m file -a "path=/tmp/tt state=touch mode=555 owner=root group=root"
[root@m01 ~]# ansible xxx -m file -a "src=/tmp/tt path=/tmp/tt_link state=link"
 
path            #指定远程主机目录或文件信息
recurse         #递归授权
state 
    directory   #在远端创建目录
    touch       #在远端创建文件
    link        #link或hard表示创建链接文件
    absent      #表示删除文件或目录
    mode        #设置文件或目录权限
    owner       #设置文件或目录属主信息
    group       #设置文件或目录属组信息

service模块

启动服务模块

[root@m01 ~]# ansible xxx -m service -a "name=crond state=stopped enabled=yes"
 
name        # 定义要启动服务的名称
state       # 指定服务状态
    started     #启动服务
    stopped     #停止服务
    restarted   #重启服务
    reloaded    #重载服务
enabled         #开机自启

group模块

[root@m01 ~]# ansible oldboy -m group -a "name=oldgirl gid=888"
 
name            #指定创建的组名
gid             #指定组的gid
state
    absent      #移除远端主机的组
    present     #创建远端主机的组(默认)

user模块

#创建用户指定uid和gid,不创建家目录也不允许登陆
ansible xxx -m user -a "name=obj uid=888 group=888 shell=/sbin/nologin create_home=no"
 
#将明文密码进行hash加密,然后进行用户创建
# 本地生成加密密码 明文密码为bgx
ansible localhost -m debug -a "msg={{ 'bgx' | password_hash('sha512', 'salt') }}"
localhost | SUCCESS => {
    "msg": "$6$salt$WP.Kb1hMfqJG7dtlBltkj4Um4rVhch54R5JCi6oP73MXzGhDWqqIY.JkSOnIsBSOeXpKglY7gUhHzY4ZtySm41"
}
# 使用加密密码创建用户
nsible xxx -m user -a 'name=xlw password=$6$salt$WP.Kb1hMfqJG7dtlBltkj4Um4rVhch54R5JCi6oP73MXzGhDWqqIY.JkSOnIsBSOeXpKglY7gUhHzY4ZtySm41 create_home=yes shell=/bin/bash'
 
#移除用户
ansible xxx -m user -a "name=obj state=absent remove=yes"
# state=absent 删除用户
# remove=yes 删除家目录 考虑到此目录可能会放文件 一般不删除家目录


uid             #指定用户的uid
group           #指定用户组名称
groups          #指定附加组名称
password        #给用户添加密码
shell           #指定用户登录shell
create_home     #是否创建家目录

cron模块

# 正常使用crond服务
[root@m01 ~]# crontab -l
* * * * *  /bin/sh /server/scripts/yum.sh
 
# 使用ansible添加一条定时任务
[root@m01 ~]# ansible oldboy -m cron -a "minute=* hour=* day=* month=* weekday=*  job='/bin/sh /server/scripts/test.sh'"
[root@m01 ~]# ansible oldboy -m cron -a "job='/bin/sh /server/scripts/test.sh'"
 
# 设置定时任务注释信息,防止重复,name设定
[root@m01 ~]# ansible oldboy -m cron -a "name='cron01' job='/bin/sh /server/scripts/test.sh'"
 
# 删除相应定时任务
[root@m01 ~]# ansible oldboy -m cron -a "name='ansible cron02' minute=0 hour=0 job='/bin/sh /server/scripts/test.sh' state=absent"
 
# 注释相应定时任务,使定时任务失效
[root@m01 scripts]# ansible oldboy -m cron -a "name='ansible cron01' minute=0 hour=0 job='/bin/sh /server/scripts/test.sh' disabled=no"

mount模块

[root@m01 ~]# ansible oldboy -m mount -a "src=172.16.1.31:/data path=/data fstype=nfs opts=defaults state=present"
[root@m01 ~]# ansible web -m mount -a "src=172.16.1.31:/data path=/data fstype=nfs opts=defaults state=mounted"
[root@m01 ~]# ansible web -m mount -a "src=172.16.1.31:/data path=/data fstype=nfs opts=defaults state=unmounted"
[root@m01 ~]# ansible web -m mount -a "src=172.16.1.31:/data path=/data fstype=nfs opts=defaults state=absent"
 
present     # 开机挂载,仅将挂载配置写入/etc/fstab
mounted     # 挂载设备,并将配置写入/etc/fstab
unmounted   # 卸载设备,不会清除/etc/fstab写入的配置
absent      # 卸载设备,会清理/etc/fstab写入的配置

playbook变量的使用

1,在yml文件中定义变量赋值

#playbook中定义
[root@manager ~]# cat f2.yml
- hosts: all
  vars:  #定义变量
    file_name: bgx_yaml_vars
 
  tasks:
  - name: # {{ file_name }}引用上面定义的变量
    file: path=/tmp/{{ file_name }} state=touch
    
    
#playbook执行,在/tmp目录创建bgx_yaml_vars文件
[root@manager ~]# ansible-playbook f1.yml

2,–extra-vars执行参数赋给变量

#playbook中引用变量
[root@manager ~]# cat f3.yml
- hosts: all
 
  tasks:
  - name: Create New File
    file: path=/tmp/{{ file_name }} state=touch
 
#playbook执行时传入file_name变量的参数,在/tmp目录创建bgx_extra-vars文件
[root@manager ~]# ansible-playbook f2.yml --extra-vars "file_name=bgx_extra-vars"

3,在/etc/ansible/hosts主机组中定义,然后使用palybook进行调度该变量

#在文件中定义变量
[root@manager ~]# cat /etc/ansible/hosts
[nfs]
10.0.0.20
[nfs:vars]
file_name=bgx_filename
 
#Playbook中调用该变量
[root@manager ~]# cat f4.yml
---
- hosts: all
 
  tasks:
  - name: Create New File
    file: path=/tmp/{{ file_name }} state=touch
 
#playbook执行,在/tmp目录创建bgx_filename文件

1.extra-vars外置传参的优先级最高 [所有执行的主机都生效] 2.定义在yml文件中的优先级其次 [所有执行的主机都生效] 3.hosts文件中定义的变量优先级最低 [当前主机组定义会生效]

Playbook变量注册

1) 注册变量: register关键字可以存储指定命令的输出结果到一个自定义的变量中

[root@manager ~]# cat f5.yml
---
- hosts: all
  tasks:
    - name:
      shell: netstat -lntp
      register: System_Status
 
    - name: Get System Status
      debug: msg={{System_Status.stdout_lines}}
 
#playbook执行结果
[root@manager ~]# ansible-playbook f5.yml
PLAY [all] ********************************************************************************************************************************
 
TASK [Gathering Facts] ********************************************************************************************************************
ok: [10.0.0.30]
 
TASK [shell] ******************************************************************************************************************************
changed: [10.0.0.30]
 
TASK [Get System Status] ******************************************************************************************************************
ok: [10.0.0.30] => {
    "msg": [
        "tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      925/sshd            ",
        "tcp6       0      0 :::22                   :::*                    LISTEN      925/sshd            "
    ]
}
 
PLAY RECAP ********************************************************************************************************************************
10.0.0.30                  : ok=3    changed=1    unreachable=0    failed=0

Playbook条件语句

playbook中的条件判断语句使用when

[root@manager ~]# cat f6.yml
- hosts: all
  remote_user: root
  tasks:
    - name: Create File
      file: path=/tmp/this_is_{{ ansible_hostname }}_file state=touch
      when: (ansible_hostname == "nfs") or (ansible_hostname == "backup")
 
#系统为centos的主机才会执行
    - name: Centos Install httpd
      yum: name=httpd state=present
      when: (ansible_distribution == "CentOS")
 
#系统为ubuntu的主机才会执行
    - name: Ubuntu Install httpd
      yum: name=httpd2 state=present
      when: (ansible_distribution == "Ubuntu")
 
 
#playbook执行结果:
[root@manager ~]# vim f6.yml
[root@manager ~]# ansible-playbook f6.yml
 
PLAY [all] ********************************************************************************************************************************
 
TASK [Gathering Facts] ********************************************************************************************************************
ok: [10.0.0.30]
 
TASK [Create File] ************************************************************************************************************************
skipping: [10.0.0.30]  #主机名不匹配则跳过, 匹配则会进行创建文件
 
PLAY RECAP ********************************************************************************************************************************
10.0.0.30                  : ok=1    changed=0    unreachable=0    failed=0

Playbook循环语句

1、标准循环使用场景-批量安装软件

[root@manager ~]# cat f7.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: Installed Pkg
      yum: name={{ item }} state=present
      with_items:
        - wget
        - tree
        - lrzsz
 
 
#palybook执行结果
[root@manager ~]# ansible-playbook  f7.yml
 
PLAY [all] ********************************************************************************************************************************
 
TASK [Gathering Facts] ********************************************************************************************************************
ok: [10.0.0.30]
 
TASK [Installed Pkg] **********************************************************************************************************************
ok: [10.0.0.30] => (item=[u'wget', u'tree', u'lrzsz'])
 
PLAY RECAP ********************************************************************************************************************************
10.0.0.30                  : ok=2    changed=0    unreachable=0    failed=0

2、标准循环使用场景-批量创建用户

[root@manager ~]# cat f7.yml
- hosts: all
  remote_user: root
  tasks:
    - name: Add Users
      user: name={{ item.name }} groups={{ item.groups }} state=present
      with_items:
        - { name: 'testuser1', groups: 'bin' }
        - { name: 'testuser2', groups: 'root' }
 
 
#palybook执行结果
[root@manager ~]# ansible-playbook f7.yml
PLAY [all] ********************************************************************************************************************************
 
TASK [Gathering Facts] ********************************************************************************************************************
ok: [10.0.0.30]
 
TASK [Add Users] **************************************************************************************************************************
changed: [10.0.0.30] => (item={u'name': u'testuser1', u'groups': u'bin'})
changed: [10.0.0.30] => (item={u'name': u'testuser2', u'groups': u'root'})
 
PLAY RECAP ********************************************************************************************************************************
10.0.0.30                  : ok=2    changed=1    unreachable=0    failed=0

3、标准循环使用场景-拷贝多个目录

[root@manager ~]# cat f7.yml
- hosts: all
  remote_user: root
  tasks:
    - name: Configure Rsync Server
      copy: src={{ item.src }} dest=/etc/{{ item.dest }} mode={{ item.mode }}
      with_items:
        - {src: "rsyncd.conf", dest: "rsyncd.conf", mode: "0644"}
        - {src: "rsync.passwd", dest: "rsync.passwd", mode: "0600"}

Playbook异常处理

默认Playbook会检查命令和模块的返回状态,如遇到错误就中断playbook的执行 加入参数: ignore_errors: yes 忽略错误

[root@manager ~]# cat f9.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: Ignore False
      command: /bin/false
      ignore_errors: yes
 
    - name: touch new file
      file: path=/tmp/bgx_ignore state=touch

playbook过程中会跳过错误

[root@manager ~]# ansible-playbook f9.yml
 
PLAY [all] ********************************************************************************************************************************
 
TASK [Gathering Facts] ********************************************************************************************************************
ok: [10.0.0.30]
 
TASK [Ignore False] ***********************************************************************************************************************
fatal: [10.0.0.30]: FAILED! => {"changed": true, "cmd": ["/bin/false"], "delta": "0:00:00.002819", "end": "2018-11-13 07:22:47.301758", "msg": "non-zero return code", "rc": 1, "start": "2018-11-13 07:22:47.298939", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring
 
TASK [touch new file] *********************************************************************************************************************
changed: [10.0.0.30]
 
PLAY RECAP ********************************************************************************************************************************
10.0.0.30                  : ok=3    changed=2    unreachable=0    failed=0

facts数据

Facts主要是用来采集目标系统信息的,采集到信息存储在ansible_facts变量中,也有许多信息存储在以ansible_开头的变量。

可以使用setup模块手动的获取目标系统信息。

ansible hostname -m setup

setup模块传递filter选项,就可以选择获取哪些内容,例如仅显示与ansible相关的内存信息.

ansible all -m setup -a 'filter=ansible_*_mb'

使用playbook来获取目标系统信息

默认情况下,playbook的第一个任务就是Facts信息,显示的结果如下:

PLAY [192.168.77.130] ******************************************
TASK [Gathering Facts] ****************************************************************
ok: [192.168.77.130]
TASK [test] ****************************************************
changed: [192.168.77.130]

如果要关闭playbook的这一操作,就需要为play添加gather_facts关键字.

# 关闭关闭自动采集Facts
- hosts: whatever
  gather_facts: no

默认情况下,是收集所有的facts信息,可以通过修改ansible配置文件来达到只获取某一类型的信息.

# /etc/ansible/ansible.cfg

# all - 收集所有子集,默认
# network - 收集网络信息子集
# hardware - 收集硬件信息子集
# virtual - 收集虚拟相关的信息
# facter - 收集自定义的fact信息
# ohai -  收集ohai信息
# 可以使用多个子集 (ex: network,virtual)
# 可以排除子集 (ex: !hardware,!facter,!ohai)
gather_subset = all

在task中设置facts信息

可以使用set_fact 来设置当前运行主机的facts信息.

- hosts: hostname
  tasks:
    - command: whoami
      register: result
    - set_fact:
        one: "{{ result.stdout }}"
        two: true
    - debug: var=one
    - debug: var=two

自定义目标系统facts

在远程主机/etc/ansible/facts.d/目录下创建.fact 结尾的文件,也可以是json、ini 或者返回json 格式数据的可执行文件,这些将被作为远程主机本地的facts 执行

在远程主机上操作

mkdir -p /etc/ansible/facts.d
cat << EOF > /etc/ansible/facts.d/hello.fact
[test]
h=hello
p=world
EOF

在ansible控制节点上操作

远程主机的自定义facts信息,存储在ansible_local变量中,可以通过setup模块过滤获取

ansible 192.168.77.130 -m setup -a "filter=ansible_local" 
192.168.77.130 | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "hello": {
                "test": {
                    "h": "hello", 
                    "p": "world"
                }
            }
        }, 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false
}

也可以通过在任务中使用{{ ansible_local.hello }}的方式访问该变量

---
- hosts: 192.168.77.130
  tasks:
    - debug: var=ansible_local.hello
    - debug: var=ansible_local.hello.test.h

当然,我们可以把上面的步骤,通过playbook来实现

运用playbook 定义远端facts.d,并加载到fact中

- hosts: node1
  tasks:
     - name: create directory for ansible custom facts
       file: state=directory recurse=yes path=/etc/ansible/facts.d
     - name: install custom test fact
       copy:
         content: |
           [test]
           h=hello
           p=world
         dest: /etc/ansible/facts.d/test.fact
     - name: re-read facts after adding custom fact
       setup: filter=ansible_local
     - name: ansible_local vars
       debug: var=ansible_local

Facts 返回的信息

ansible 2.9.1版本返回centos 7系统的Facts信息

"ansible_facts": {
    "ansible_all_ipv4_addresses": [
        "192.168.77.130", 
        "172.17.0.1"
    ], 
    "ansible_all_ipv6_addresses": [
        "fe80::6d73:3667:2ed6:a7c2", 
        "fe80::4d03:8744:2f0d:2085"
    ], 
    "ansible_apparmor": {
        "status": "disabled"
    }, 
    "ansible_architecture": "x86_64", 
    "ansible_bios_date": "07/29/2019", 
    "ansible_bios_version": "6.00", 
    "ansible_cmdline": {
        "BOOT_IMAGE": "/vmlinuz-5.1.11-1.el7.elrepo.x86_64", 
        "crashkernel": "auto", 
        "quiet": true, 
        "rhgb": true, 
        "ro": true, 
        "root": "UUID=a7d49533-e756-4a71-a977-5efb174eb09c", 
        "user_namespace.enable": "1"
    }, 
    "ansible_date_time": {
        "date": "2020-03-31", 
        "day": "31", 
        "epoch": "1585627320", 
        "hour": "12", 
        "iso8601": "2020-03-31T04:02:00Z", 
        "iso8601_basic": "20200331T120200260891", 
        "iso8601_basic_short": "20200331T120200", 
        "iso8601_micro": "2020-03-31T04:02:00.260966Z", 
        "minute": "02", 
        "month": "03", 
        "second": "00", 
        "time": "12:02:00", 
        "tz": "CST", 
        "tz_offset": "+0800", 
        "weekday": "星期二", 
        "weekday_number": "2", 
        "weeknumber": "13", 
        "year": "2020"
    }, 
    "ansible_default_ipv4": {
        "address": "192.168.77.130", 
        "alias": "ens33", 
        "broadcast": "192.168.77.255", 
        "gateway": "192.168.77.2", 
        "interface": "ens33", 
        "macaddress": "00:0c:29:88:01:c0", 
        "mtu": 1500, 
        "netmask": "255.255.255.0", 
        "network": "192.168.77.0", 
        "type": "ether"
    }, 
    "ansible_default_ipv6": {}, 
    "ansible_device_links": {
        "ids": {
            "sr0": [
                "ata-VMware_Virtual_IDE_CDROM_Drive_10000000000000000001"
            ]
        }, 
        "labels": {}, 
        "masters": {}, 
        "uuids": {
            "sda1": [
                "93556f71-6dad-4761-9207-edee35d47129"
            ], 
            "sda2": [
                "0c0f8a5a-58dd-4fc6-a49e-fc5e63f5d8a3"
            ], 
            "sda3": [
                "a7d49533-e756-4a71-a977-5efb174eb09c"
            ]
        }
    }, 
    "ansible_devices": {
        "sda": {
            "holders": [], 
            "host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)", 
            "links": {
                "ids": [], 
                "labels": [], 
                "masters": [], 
                "uuids": []
            }, 
            "model": "VMware Virtual S", 
            "partitions": {
                "sda1": {
                    "holders": [], 
                    "links": {
                        "ids": [], 
                        "labels": [], 
                        "masters": [], 
                        "uuids": [
                            "93556f71-6dad-4761-9207-edee35d47129"
                        ]
                    }, 
                    "sectors": "409600", 
                    "sectorsize": 512, 
                    "size": "200.00 MB", 
                    "start": "2048", 
                    "uuid": "93556f71-6dad-4761-9207-edee35d47129"
                }, 
                "sda2": {
                    "holders": [], 
                    "links": {
                        "ids": [], 
                        "labels": [], 
                        "masters": [], 
                        "uuids": [
                            "0c0f8a5a-58dd-4fc6-a49e-fc5e63f5d8a3"
                        ]
                    }, 
                    "sectors": "20971520", 
                    "sectorsize": 512, 
                    "size": "10.00 GB", 
                    "start": "411648", 
                    "uuid": "0c0f8a5a-58dd-4fc6-a49e-fc5e63f5d8a3"
                }, 
                "sda3": {
                    "holders": [], 
                    "links": {
                        "ids": [], 
                        "labels": [], 
                        "masters": [], 
                        "uuids": [
                            "a7d49533-e756-4a71-a977-5efb174eb09c"
                        ]
                    }, 
                    "sectors": "62502912", 
                    "sectorsize": 512, 
                    "size": "29.80 GB", 
                    "start": "21383168", 
                    "uuid": "a7d49533-e756-4a71-a977-5efb174eb09c"
                }
            }, 
            "removable": "0", 
            "rotational": "1", 
            "sas_address": null, 
            "sas_device_handle": null, 
            "scheduler_mode": "mq-deadline", 
            "sectors": "83886080", 
            "sectorsize": "512", 
            "size": "40.00 GB", 
            "support_discard": "0", 
            "vendor": "VMware,", 
            "virtual": 1
        }, 
        "sr0": {
            "holders": [], 
            "host": "IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)", 
            "links": {
                "ids": [
                    "ata-VMware_Virtual_IDE_CDROM_Drive_10000000000000000001"
                ], 
                "labels": [], 
                "masters": [], 
                "uuids": []
            }, 
            "model": "VMware IDE CDR10", 
            "partitions": {}, 
            "removable": "1", 
            "rotational": "1", 
            "sas_address": null, 
            "sas_device_handle": null, 
            "scheduler_mode": "mq-deadline", 
            "sectors": "2097151", 
            "sectorsize": "512", 
            "size": "1024.00 MB", 
            "support_discard": "0", 
            "vendor": "NECVMWar", 
            "virtual": 1
        }
    }, 
    "ansible_distribution": "CentOS", 
    "ansible_distribution_file_parsed": true, 
    "ansible_distribution_file_path": "/etc/redhat-release", 
    "ansible_distribution_file_variety": "RedHat", 
    "ansible_distribution_major_version": "7", 
    "ansible_distribution_release": "Core", 
    "ansible_distribution_version": "7.6", 
    "ansible_dns": {
        "nameservers": [
            "192.168.77.2"
        ]
    }, 
    "ansible_docker0": {
        "active": false, 
        "device": "docker0", 
        "features": {
            "esp_hw_offload": "off [fixed]", 
            "esp_tx_csum_hw_offload": "off [fixed]", 
            "fcoe_mtu": "off [fixed]", 
            "generic_receive_offload": "on", 
            "generic_segmentation_offload": "on", 
            "highdma": "on", 
            "hw_tc_offload": "off [fixed]", 
            "l2_fwd_offload": "off [fixed]", 
            "large_receive_offload": "off [fixed]", 
            "loopback": "off [fixed]", 
            "netns_local": "on [fixed]", 
            "ntuple_filters": "off [fixed]", 
            "receive_hashing": "off [fixed]", 
            "rx_all": "off [fixed]", 
            "rx_checksumming": "off [fixed]", 
            "rx_fcs": "off [fixed]", 
            "rx_gro_hw": "off [fixed]", 
            "rx_udp_tunnel_port_offload": "off [fixed]", 
            "rx_vlan_filter": "off [fixed]", 
            "rx_vlan_offload": "off [fixed]", 
            "rx_vlan_stag_filter": "off [fixed]", 
            "rx_vlan_stag_hw_parse": "off [fixed]", 
            "scatter_gather": "on", 
            "tcp_segmentation_offload": "on", 
            "tls_hw_record": "off [fixed]", 
            "tls_hw_rx_offload": "off [fixed]", 
            "tls_hw_tx_offload": "off [fixed]", 
            "tx_checksum_fcoe_crc": "off [fixed]", 
            "tx_checksum_ip_generic": "on", 
            "tx_checksum_ipv4": "off [fixed]", 
            "tx_checksum_ipv6": "off [fixed]", 
            "tx_checksum_sctp": "off [fixed]", 
            "tx_checksumming": "on", 
            "tx_esp_segmentation": "on", 
            "tx_fcoe_segmentation": "on", 
            "tx_gre_csum_segmentation": "on", 
            "tx_gre_segmentation": "on", 
            "tx_gso_partial": "on", 
            "tx_gso_robust": "on", 
            "tx_ipxip4_segmentation": "on", 
            "tx_ipxip6_segmentation": "on", 
            "tx_lockless": "on [fixed]", 
            "tx_nocache_copy": "off", 
            "tx_scatter_gather": "on", 
            "tx_scatter_gather_fraglist": "on", 
            "tx_sctp_segmentation": "on", 
            "tx_tcp6_segmentation": "on", 
            "tx_tcp_ecn_segmentation": "on", 
            "tx_tcp_mangleid_segmentation": "on", 
            "tx_tcp_segmentation": "on", 
            "tx_udp_segmentation": "on", 
            "tx_udp_tnl_csum_segmentation": "on", 
            "tx_udp_tnl_segmentation": "on", 
            "tx_vlan_offload": "on", 
            "tx_vlan_stag_hw_insert": "on", 
            "udp_fragmentation_offload": "off", 
            "vlan_challenged": "off [fixed]"
        }, 
        "hw_timestamp_filters": [], 
        "id": "8000.024238d96cf4", 
        "interfaces": [], 
        "ipv4": {
            "address": "172.17.0.1", 
            "broadcast": "172.17.255.255", 
            "netmask": "255.255.0.0", 
            "network": "172.17.0.0"
        }, 
        "macaddress": "02:42:38:d9:6c:f4", 
        "mtu": 1500, 
        "promisc": false, 
        "stp": false, 
        "timestamping": [
            "rx_software", 
            "software"
        ], 
        "type": "bridge"
    }, 
    "ansible_domain": "", 
    "ansible_effective_group_id": 0, 
    "ansible_effective_user_id": 0, 
    "ansible_ens33": {
        "active": true, 
        "device": "ens33", 
        "features": {
            "esp_hw_offload": "off [fixed]", 
            "esp_tx_csum_hw_offload": "off [fixed]", 
            "fcoe_mtu": "off [fixed]", 
            "generic_receive_offload": "on", 
            "generic_segmentation_offload": "on", 
            "highdma": "off [fixed]", 
            "hw_tc_offload": "off [fixed]", 
            "l2_fwd_offload": "off [fixed]", 
            "large_receive_offload": "off [fixed]", 
            "loopback": "off [fixed]", 
            "netns_local": "off [fixed]", 
            "ntuple_filters": "off [fixed]", 
            "receive_hashing": "off [fixed]", 
            "rx_all": "off", 
            "rx_checksumming": "off", 
            "rx_fcs": "off", 
            "rx_gro_hw": "off [fixed]", 
            "rx_udp_tunnel_port_offload": "off [fixed]", 
            "rx_vlan_filter": "on [fixed]", 
            "rx_vlan_offload": "on", 
            "rx_vlan_stag_filter": "off [fixed]", 
            "rx_vlan_stag_hw_parse": "off [fixed]", 
            "scatter_gather": "on", 
            "tcp_segmentation_offload": "on", 
            "tls_hw_record": "off [fixed]", 
            "tls_hw_rx_offload": "off [fixed]", 
            "tls_hw_tx_offload": "off [fixed]", 
            "tx_checksum_fcoe_crc": "off [fixed]", 
            "tx_checksum_ip_generic": "on", 
            "tx_checksum_ipv4": "off [fixed]", 
            "tx_checksum_ipv6": "off [fixed]", 
            "tx_checksum_sctp": "off [fixed]", 
            "tx_checksumming": "on", 
            "tx_esp_segmentation": "off [fixed]", 
            "tx_fcoe_segmentation": "off [fixed]", 
            "tx_gre_csum_segmentation": "off [fixed]", 
            "tx_gre_segmentation": "off [fixed]", 
            "tx_gso_partial": "off [fixed]", 
            "tx_gso_robust": "off [fixed]", 
            "tx_ipxip4_segmentation": "off [fixed]", 
            "tx_ipxip6_segmentation": "off [fixed]", 
            "tx_lockless": "off [fixed]", 
            "tx_nocache_copy": "off", 
            "tx_scatter_gather": "on", 
            "tx_scatter_gather_fraglist": "off [fixed]", 
            "tx_sctp_segmentation": "off [fixed]", 
            "tx_tcp6_segmentation": "off [fixed]", 
            "tx_tcp_ecn_segmentation": "off [fixed]", 
            "tx_tcp_mangleid_segmentation": "off", 
            "tx_tcp_segmentation": "on", 
            "tx_udp_segmentation": "off [fixed]", 
            "tx_udp_tnl_csum_segmentation": "off [fixed]", 
            "tx_udp_tnl_segmentation": "off [fixed]", 
            "tx_vlan_offload": "on [fixed]", 
            "tx_vlan_stag_hw_insert": "off [fixed]", 
            "udp_fragmentation_offload": "off", 
            "vlan_challenged": "off [fixed]"
        }, 
        "hw_timestamp_filters": [], 
        "ipv4": {
            "address": "192.168.77.130", 
            "broadcast": "192.168.77.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.77.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::6d73:3667:2ed6:a7c2", 
                "prefix": "64", 
                "scope": "link"
            }, 
            {
                "address": "fe80::4d03:8744:2f0d:2085", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "00:0c:29:88:01:c0", 
        "module": "e1000", 
        "mtu": 1500, 
        "pciid": "0000:02:01.0", 
        "promisc": false, 
        "speed": 1000, 
        "timestamping": [
            "tx_software", 
            "rx_software", 
            "software"
        ], 
        "type": "ether"
    }, 
    "ansible_env": {
        "HOME": "/root", 
        "LANG": "zh_CN.UTF-8", 
        "LESSOPEN": "||/usr/bin/lesspipe.sh %s", 
        "LOGNAME": "root", 
        "LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:", 
        "MAIL": "/var/mail/root", 
        "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", 
        "PWD": "/root", 
        "SHELL": "/bin/bash", 
        "SHLVL": "2", 
        "SSH_CLIENT": "192.168.77.128 46964 22", 
        "SSH_CONNECTION": "192.168.77.128 46964 192.168.77.130 22", 
        "SSH_TTY": "/dev/pts/1", 
        "TERM": "linux", 
        "USER": "root", 
        "XDG_RUNTIME_DIR": "/run/user/0", 
        "XDG_SESSION_ID": "22", 
        "_": "/usr/bin/python"
    }, 
    "ansible_fibre_channel_wwn": [], 
    "ansible_fips": false, 
    "ansible_form_factor": "Other", 
    "ansible_fqdn": "node130", 
    "ansible_hostname": "node130", 
    "ansible_hostnqn": "", 
    "ansible_interfaces": [
        "lo", 
        "docker0", 
        "ens33"
    ], 
    "ansible_is_chroot": false, 
    "ansible_iscsi_iqn": "", 
    "ansible_kernel": "5.1.11-1.el7.elrepo.x86_64", 
    "ansible_kernel_version": "#1 SMP Mon Jun 17 15:51:08 EDT 2019", 
    "ansible_lo": {
        "active": true, 
        "device": "lo", 
        "features": {
            "esp_hw_offload": "off [fixed]", 
            "esp_tx_csum_hw_offload": "off [fixed]", 
            "fcoe_mtu": "off [fixed]", 
            "generic_receive_offload": "on", 
            "generic_segmentation_offload": "on", 
            "highdma": "on [fixed]", 
            "hw_tc_offload": "off [fixed]", 
            "l2_fwd_offload": "off [fixed]", 
            "large_receive_offload": "off [fixed]", 
            "loopback": "on [fixed]", 
            "netns_local": "on [fixed]", 
            "ntuple_filters": "off [fixed]", 
            "receive_hashing": "off [fixed]", 
            "rx_all": "off [fixed]", 
            "rx_checksumming": "on [fixed]", 
            "rx_fcs": "off [fixed]", 
            "rx_gro_hw": "off [fixed]", 
            "rx_udp_tunnel_port_offload": "off [fixed]", 
            "rx_vlan_filter": "off [fixed]", 
            "rx_vlan_offload": "off [fixed]", 
            "rx_vlan_stag_filter": "off [fixed]", 
            "rx_vlan_stag_hw_parse": "off [fixed]", 
            "scatter_gather": "on", 
            "tcp_segmentation_offload": "on", 
            "tls_hw_record": "off [fixed]", 
            "tls_hw_rx_offload": "off [fixed]", 
            "tls_hw_tx_offload": "off [fixed]", 
            "tx_checksum_fcoe_crc": "off [fixed]", 
            "tx_checksum_ip_generic": "on [fixed]", 
            "tx_checksum_ipv4": "off [fixed]", 
            "tx_checksum_ipv6": "off [fixed]", 
            "tx_checksum_sctp": "on [fixed]", 
            "tx_checksumming": "on", 
            "tx_esp_segmentation": "off [fixed]", 
            "tx_fcoe_segmentation": "off [fixed]", 
            "tx_gre_csum_segmentation": "off [fixed]", 
            "tx_gre_segmentation": "off [fixed]", 
            "tx_gso_partial": "off [fixed]", 
            "tx_gso_robust": "off [fixed]", 
            "tx_ipxip4_segmentation": "off [fixed]", 
            "tx_ipxip6_segmentation": "off [fixed]", 
            "tx_lockless": "on [fixed]", 
            "tx_nocache_copy": "off [fixed]", 
            "tx_scatter_gather": "on [fixed]", 
            "tx_scatter_gather_fraglist": "on [fixed]", 
            "tx_sctp_segmentation": "on", 
            "tx_tcp6_segmentation": "on", 
            "tx_tcp_ecn_segmentation": "on", 
            "tx_tcp_mangleid_segmentation": "on", 
            "tx_tcp_segmentation": "on", 
            "tx_udp_segmentation": "off [fixed]", 
            "tx_udp_tnl_csum_segmentation": "off [fixed]", 
            "tx_udp_tnl_segmentation": "off [fixed]", 
            "tx_vlan_offload": "off [fixed]", 
            "tx_vlan_stag_hw_insert": "off [fixed]", 
            "udp_fragmentation_offload": "off", 
            "vlan_challenged": "on [fixed]"
        }, 
        "hw_timestamp_filters": [], 
        "ipv4": {
            "address": "127.0.0.1", 
            "broadcast": "host", 
            "netmask": "255.0.0.0", 
            "network": "127.0.0.0"
        }, 
        "ipv6": [
            {
                "address": "::1", 
                "prefix": "128", 
                "scope": "host"
            }
        ], 
        "mtu": 65536, 
        "promisc": false, 
        "timestamping": [
            "tx_software", 
            "rx_software", 
            "software"
        ], 
        "type": "loopback"
    }, 
    "ansible_local": {
        "hello": {
            "test": {
                "h": "hello", 
                "p": "world"
            }
        }, 
        "test": {
            "test": {
                "h": "hello", 
                "p": "world"
            }
        }
    }, 
    "ansible_lsb": {}, 
    "ansible_machine": "x86_64", 
    "ansible_machine_id": "c6e2cb0cdbad41f19c30d690896aabe0", 
    "ansible_memfree_mb": 476, 
    "ansible_memory_mb": {
        "nocache": {
            "free": 695, 
            "used": 265
        }, 
        "real": {
            "free": 476, 
            "total": 960, 
            "used": 484
        }, 
        "swap": {
            "cached": 0, 
            "free": 10239, 
            "total": 10239, 
            "used": 0
        }
    }, 
    "ansible_memtotal_mb": 960, 
    "ansible_mounts": [
        {
            "block_available": 10389, 
            "block_size": 4096, 
            "block_total": 50345, 
            "block_used": 39956, 
            "device": "/dev/sda1", 
            "fstype": "xfs", 
            "inode_available": 83285, 
            "inode_total": 83624, 
            "inode_used": 339, 
            "mount": "/boot", 
            "options": "rw,relatime,attr2,inode64,noquota", 
            "size_available": 42553344, 
            "size_total": 206213120, 
            "uuid": "93556f71-6dad-4761-9207-edee35d47129"
        }, 
        {
            "block_available": 7234718, 
            "block_size": 4096, 
            "block_total": 7809050, 
            "block_used": 574332, 
            "device": "/dev/sda3", 
            "fstype": "xfs", 
            "inode_available": 15558107, 
            "inode_total": 15625728, 
            "inode_used": 67621, 
            "mount": "/", 
            "options": "rw,relatime,attr2,inode64,noquota", 
            "size_available": 29633404928, 
            "size_total": 31985868800, 
            "uuid": "a7d49533-e756-4a71-a977-5efb174eb09c"
        }
    ], 
    "ansible_nodename": "node130", 
    "ansible_os_family": "RedHat", 
    "ansible_pkg_mgr": "yum", 
    "ansible_proc_cmdline": {
        "BOOT_IMAGE": "/vmlinuz-5.1.11-1.el7.elrepo.x86_64", 
        "crashkernel": "auto", 
        "quiet": true, 
        "rhgb": true, 
        "ro": true, 
        "root": "UUID=a7d49533-e756-4a71-a977-5efb174eb09c", 
        "user_namespace.enable": "1"
    }, 
    "ansible_processor": [
        "0", 
        "GenuineIntel", 
        "Intel(R) Core(TM) i3-4160 CPU @ 3.60GHz"
    ], 
    "ansible_processor_cores": 1, 
    "ansible_processor_count": 1, 
    "ansible_processor_threads_per_core": 1, 
    "ansible_processor_vcpus": 1, 
    "ansible_product_name": "VMware Virtual Platform", 
    "ansible_product_serial": "VMware-56 4d 4b f0 c3 97 be 18-aa c9 56 08 cb 88 01 c0", 
    "ansible_product_uuid": "f04b4d56-97c3-18be-aac9-5608cb8801c0", 
    "ansible_product_version": "None", 
    "ansible_python": {
        "executable": "/usr/bin/python", 
        "has_sslcontext": true, 
        "type": "CPython", 
        "version": {
            "major": 2, 
            "micro": 5, 
            "minor": 7, 
            "releaselevel": "final", 
            "serial": 0
        }, 
        "version_info": [
            2, 
            7, 
            5, 
            "final", 
            0
        ]
    }, 
    "ansible_python_version": "2.7.5", 
    "ansible_real_group_id": 0, 
    "ansible_real_user_id": 0, 
    "ansible_selinux": {
        "status": "disabled"
    }, 
    "ansible_selinux_python_present": true, 
    "ansible_service_mgr": "systemd", 
    "ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK2zovckFKduH7EsvGaWar2mA+LdC4kTjgFG4h9RQlxZ1v5kEPx8Bs7jWnj94Cd8lY8BGb7RU7akUFpq3V59WIY=", 
    "ansible_ssh_host_key_ed25519_public": "AAAAC3NzaC1lZDI1NTE5AAAAIEgM8JK59UtnryS0IhtNoPJHbOSqy7Xy2jExPYMvAex5", 
    "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQDfNgNsUWXmYOD7Iz94nO98XRmw4dWKREbxWz6rZBP2QHYFN38nzdo9h5UT4HMuUM/FraPwwbfPubznNgn0/cXfMkqu8JIssxizpIRBZv82HFqwHTYRvkFAEII8ftkHs2RVlq64+ljPZzWvGL14nkfU7P+kc8Ym4jZGtorKRBoHFCeu67fycmZSCLFJxkKSumOaZ9TbNzP6bziyfmdDIGfj8UgzJRSuBW+B5XNeJp/QtsQZTjgpxSZZ9QRf/FeRx1NDR5Si91Fkd0NILIgoOSz536lP+aCCUOx7ukaoV1T/kyUKOj8a3wpBGiBWWZUGZa6O02Ig0eHnx8F4lZ2iOnNN", 
    "ansible_swapfree_mb": 10239, 
    "ansible_swaptotal_mb": 10239, 
    "ansible_system": "Linux", 
    "ansible_system_capabilities": [
        "cap_chown", 
        "cap_dac_override", 
        "cap_dac_read_search", 
        "cap_fowner", 
        "cap_fsetid", 
        "cap_kill", 
        "cap_setgid", 
        "cap_setuid", 
        "cap_setpcap", 
        "cap_linux_immutable", 
        "cap_net_bind_service", 
        "cap_net_broadcast", 
        "cap_net_admin", 
        "cap_net_raw", 
        "cap_ipc_lock", 
        "cap_ipc_owner", 
        "cap_sys_module", 
        "cap_sys_rawio", 
        "cap_sys_chroot", 
        "cap_sys_ptrace", 
        "cap_sys_pacct", 
        "cap_sys_admin", 
        "cap_sys_boot", 
        "cap_sys_nice", 
        "cap_sys_resource", 
        "cap_sys_time", 
        "cap_sys_tty_config", 
        "cap_mknod", 
        "cap_lease", 
        "cap_audit_write", 
        "cap_audit_control", 
        "cap_setfcap", 
        "cap_mac_override", 
        "cap_mac_admin", 
        "cap_syslog", 
        "35", 
        "36", 
        "37+ep"
    ], 
    "ansible_system_capabilities_enforced": "True", 
    "ansible_system_vendor": "VMware, Inc.", 
    "ansible_uptime_seconds": 4004, 
    "ansible_user_dir": "/root", 
    "ansible_user_gecos": "root", 
    "ansible_user_gid": 0, 
    "ansible_user_id": "root", 
    "ansible_user_shell": "/bin/bash", 
    "ansible_user_uid": 0, 
    "ansible_userspace_architecture": "x86_64", 
    "ansible_userspace_bits": "64", 
    "ansible_virtualization_role": "guest", 
    "ansible_virtualization_type": "VMware", 
    "gather_subset": [
        "all"
    ], 
    "module_setup": true
}

其中较为常用的变量

  • ansible_distribution

  • ansible_distribution_release

  • ansible_distribution_version

  • ansible_fqdn

  • ansible_hostname

  • ansible_os_family

  • ansible_pkg_mgr

  • ansible_default_ipv4.address

  • ansible_default_ipv6.address

缓存Facts信息

Facts信息默认存储在内存中,即每次运行后,信息就不会存在了,这样会导致我们每次都需要重新去获取主机的所有Facts信息,浪费了很多时间,Facts给了几种缓存方式。

使用文件作为缓存:

创建缓存存放的目录

mkdir /tmp/facts_cache

修改ansible配置文件

# /etc/ansible/ansible.cfg

gathering = smart
fact_caching = jsonfile
fact_caching_timeout = 86400
fact_caching_connection = /tmp/facts_cache

获取主机的Facts信息

ansible 192.168.77.130 -m setup

查看缓存目录

ll /tmp/facts_cache/
总用量 28
-rw-r--r-- 1 root root 25241 3月  31 11:39 192.168.77.130

上述文件中存储着json序列化的facts数据

使用redis作为缓存

安装redis

# centos 7
yum -y install redis
easy_install pip
pip install redis

配置redis,使用密码登陆

echo 'requirepass "admin"' > /etc/redis.conf

启动redis

systemctl enable --now redis

修改ansible配置文件

# /etc/ansible/ansible.cfg

gathering = smart
fact_caching = redis
fact_caching_timeout = 86400
fact_caching_connection = localhost:6379:0:admin

获取主机的Facts信息

ansible 192.168.77.130 -m setup

查看redis内容

# redis-cli
127.0.0.1:6379> auth admin
OK
127.0.0.1:6379> keys *
1) "ansible_cache_keys"
2) "ansible_facts192.168.77.130"
127.0.0.1:6379> get ansible_facts192.168.77.130
.....

使用memcached作为缓存

安装 memcached

# centos 7
yum install memcached

easy_install pip
pip install python-memcached

启动memcached

systemctl enable --now memcached

修改ansible配置文件

# /etc/ansible/ansible.cfg

gathering = smart
fact_caching = memcached
fact_caching_timeout = 86400
fact_caching_connection = localhost:11211

获取主机的Facts信息

ansible 192.168.77.130 -m setup

查看memcached内容

# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
stats items
STAT items:3:number 1
STAT items:3:age 135
STAT items:3:evicted 0
STAT items:3:evicted_nonzero 0
STAT items:3:evicted_time 0
STAT items:3:outofmemory 0
STAT items:3:tailrepairs 0
STAT items:3:reclaimed 0
STAT items:3:expired_unfetched 0
STAT items:3:evicted_unfetched 0
STAT items:21:number 1
STAT items:21:age 135
STAT items:21:evicted 0
STAT items:21:evicted_nonzero 0
STAT items:21:evicted_time 0
STAT items:21:outofmemory 0
STAT items:21:tailrepairs 0
STAT items:21:reclaimed 0
STAT items:21:expired_unfetched 0
STAT items:21:evicted_unfetched 0
END
stats cachedump 3 0
ITEM ansible_cache_keys [46 b; 1585626688 s]
END
stats cachedump 21 0
ITEM ansible_facts192.168.77.130 [8349 b; 1585713251 s]
END

刷新缓存

默认情况下,缓存的生存时间是fact_caching_timeout控制的,如果我们想强制刷新缓存的话,可以使用--flush-cache选项

ansible-playbook --flush-cache /etc/ansible/test3.yaml

Blocks

Blocks允许任务的逻辑分组和play中的错误处理。您可以应用于单个任务的大部分内容(循环除外)都可以应用于块级别,这也使得设置任务通用的数据或指令变得更加容易。这并不意味着指令影响块本身,而是由块所包含的任务继承。例如,一个when将应用于任务,而不是块本身。

 tasks:
   - name: Install, configure, and start Apache
     block:
       - name: install httpd and memcached
         yum:
           name:
           - httpd
           - memcached
           state: present

       - name: apply the foo config template
         template:
           src: templates/src.j2
           dest: /etc/foo.conf
       - name: start service bar and enable it
         service:
           name: bar
           state: started
           enabled: True
     when: ansible_facts['distribution'] == 'CentOS'
     become: true
     become_user: root
     ignore_errors: yes

在上面的例子中,这3个任务都是在从块中继承when条件并在任务上判断之后执行的。它们还继承了特权升级指令,使become能够应用到block所包含的所有任务。最后,ignore_errors: yes即使有任务失败了,也将继续执行剧本。

块的错误处理

块还引入了以类似于大多数编程语言中的异常的方式处理错误的能力。块只处理任务的失败状态。错误的任务定义或无法访问的主机是不能触发块的错误处理。

- name: Attempt and graceful roll back demo
  block:
    - debug:
        msg: 'I execute normally'
    - name: i force a failure
      command: /bin/false
    - debug:
        msg: 'I never execute, due to the above task failing, :-('
  rescue:
    - debug:
        msg: 'I caught an error'
    - name: i force a failure in middle of recovery! >:-)
      command: /bin/false
    - debug:
        msg: 'I also never execute :-('
  always:
    - debug:
        msg: "This always executes"

block中的任务在执行中,如果有任何错误,将执行rescue中的任务。 无论在block和rescue中发生或没有发生错误,always部分都运行。

需要注意的是,如果rescue部分成功完成,那么play将继续进行,因为它将擦除错误状态(但不包括报告),这意味着它不会触发max_fail_percentageany_errors_fatal的配置,但将出现在playbook统计。

发生错误后,依然运行handlers

tasks:
   - name: Attempt and graceful roll back demo
     block:
       - debug:
           msg: 'I execute normally'
         changed_when: yes
         notify: run me even after an error
       - command: /bin/false
     rescue:
       - name: make sure all handlers run
         meta: flush_handlers
 handlers:
    - name: run me even after an error
      debug:
        msg: 'This handler runs even on error'

Ansible还为block的rescue部分的任务提供了两个变量

  • ansible_failed_task 返回触发rescue 的失败任务。例如,要获取名称,请使用ansible_failed_task.name

  • ansible_failed_result 返回触发rescue 的失败任务的返回结果。这相当于在失败任务中添加 register: ansible_failed_result

Playbook 高级特性

权限提升

Ansible使用现有的权限升级系统来执行具有root权限或其他用户权限的任务。因为这个特性允许您成为另一个用户,与登录到机器的用户(远程用户)不同,所以我们将其称为becomebecome关键字利用现有的权限升级工具,如sudosupfexecdoaspbrundzdoksurunasmachinectl等。

使用become

你可以在 playtask 上使用become,同时使用时,优先级规则会决定哪个生效。

become 指令

  • become 设置yes即开启提升权限

  • become_user 指定提升权限的用户

  • become_method 指定提升权限的方式,有sudo,su,runas等。

  • become_flags 传给可执行文件的参数

例如,启动服务需要root权限

- name: Ensure the httpd service is running
  service:
    name: httpd
    state: started
  become: yes

以apache用户身份运行命令

- name: Run a command as the apache user
  command: somecommand
  become: yes
  become_user: apache

当shell为nologin时,作为nobody用户执行某些操作

- name: Run a command as nobody
  command: somecommand
  become: yes
  become_method: su
  become_user: nobody
  become_flags: '-s /bin/sh'

become 变量

在主机清单中可以为节点定义不同的become选项,使用下列变量

  • ansible_become 开启提升权限

  • ansible_become_method 提升权限的方式,有sudo,su,runas等。

  • ansible_become_user 提升权限的用户

  • ansible_become_pass 提升权限的用户密码

例如,如果您希望在一个名为webserver的服务器上以root身份运行所有任务,但是您只能作为manager用户连接,那么您可以使用类似这样的库存条目

webserver ansible_user=manager ansible_become=yes

become 命令行选项

  • --become,-b

  • --ask-become-pass, -K 告诉程序提升权限的用户密码

  • --become-method 提升权限的方式,有sudo,su,runas等。

  • --become-user 提升权限的用户

ansible -i hosts node1 -m shell -a "whoami" --become  --become-method=su --become-user=root --ask-become-pass

ansible-playbook -i hosts test.yml --become  --become-method=su --become-user=root --ask-become-pass

become的一些限制和风险

  • 成为无特权用户的风险

  • 不是所有的连接插件都支持特权升级

  • 每个主机只能启用一个特权升级模式(su,sudo...)

  • 特权升级不能限制某些命令能升级,这样会导致ansible执行时出现错误。

  • 不能访问由pam_systemd填充的环境变量

网络模块使用become

从2.6版开始,Ansible在所有支持enable模式的网络设备上进行特权升级(进入enable模式或特权EXEC模式)

你必须将连接类型设置为connection: network_cliconnection: httpapi,才能在网络设备上使用become进行权限升级。

若要设置特定任务的enable模式,请在任务级别上添加become

- name: Gather facts (eos)
  eos_facts:
    gather_subset:
      - "!hardware"
  become: yes
  become_method: enable

若要为单个play中的所有任务设置enable模式,请在play级别上添加become

- hosts: eos-switches
  become: yes
  become_method: enable
  tasks:
    - name: Gather facts (eos)
      eos_facts:
        gather_subset:
          - "!hardware"

设置所有任务的enable模式

通常,您希望所有play中的所有任务都使用特权模式运行,这最好通过使用组变量来实现

ansible_connection: network_cli
ansible_network_os: eos
ansible_user: myuser
ansible_become: yes
ansible_become_method: enable

enable模式的密码

如果需要密码才能进入enable模式,可以通过以下两种方式之一进行指定

  • 提供 --ask-become-pass命令行选项

  • 设置ansible_become_password 连接变量

authorize 和 auth_pass

ansible 依然支持 connection: local 连接下的enable模式

- hosts: eos-switches
  ansible_connection: local
  tasks:
    - name: Gather facts (eos)
      eos_facts:
        gather_subset:
          - "!hardware"
      provider:
        authorize: yes
        auth_pass: " {{ secret_auth_pass }}"

安装zabbix agent示例

- hosts: 10.96.64.184
  become: yes
  gather_facts: yes
  vars:
    zabbix_agent_6_filename: "zabbix6.tar.gz"
    zabbix_agent_7_filename: "zabbix7.tar.gz"
    zabbix_source_dir: "/tmp/zabbix"
    zabbix_config_path: "/tmp/zabbix/etc/zabbix_agentd.conf"
    zabbix_config_6_path: "/tmp/zabbix/conf/zabbix_agentd.conf"
    zabbix_agent_binary: "/tmp/zabbix/sbin/zabbix_agentd"
    target_ip_address: "10.52.39.31"
    zabbix_agent_port: 10052

  tasks:
    - name: Check if Zabbix Agent is listening on port
      shell: "netstat -tuln | grep ':{{ zabbix_agent_port }} '"
      register: zabbix_agent_socket_check
      ignore_errors: yes

    - name: Output agent already running and exit playbook
      debug:
        msg: "Zabbix Agent is already running and listening on TCP port {{ zabbix_agent_port }}"
      when: zabbix_agent_socket_check.rc == 0 and zabbix_agent_socket_check.stdout != ""
      ignore_errors: yes

    - meta: end_play
      when: zabbix_agent_socket_check.rc == 0 and zabbix_agent_socket_check.stdout != ""
   
    - name: Ensure zabbix directory exists
      file:
        path: "{{ zabbix_source_dir }}"
        state: directory
 
    - name: Install SELinux Python bindings
      package:
        name: libselinux-python
        state: present
      ignore_errors: yes
    - name: Copy and extract Zabbix agent files
      copy:
        src: "{{ zabbix_agent_6_filename }}"
        dest: /tmp/
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Copy and extract Zabbix agent files
      copy:
        src: "{{ zabbix_agent_7_filename }}"
        dest: /tmp/
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'
            
    - name: Extract zabbix.tar.gz
      shell: "tar -zvxf /tmp/'{{zabbix_agent_6_filename}}' -C /tmp/zabbix/"
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Extract zabbix.tar.gz 
      shell: "tar -zvxf /tmp/'{{zabbix_agent_7_filename}}' -C /tmp/"
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

    - name: Copy zabbix.conf to the remote host
      copy:
        src: zabbix6.conf
        dest: /tmp/zabbix/conf/zabbix_agentd.conf
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Copy zabbix.conf to the remote host
      copy:
        src: zabbix7.conf
        dest: "{{ zabbix_config_path }}"
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

    - name: Update zabbix_agentd.conf hostname
      lineinfile:
        path: "{{ zabbix_config_6_path }}"
        regexp: '^Hostname='
        line: 'Hostname={{ inventory_hostname }}'
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Update zabbix_agentd.conf hostname
      lineinfile:
        path: "{{ zabbix_config_path }}"
        regexp: '^Hostname='
        line: 'Hostname={{ inventory_hostname }}'
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'  

    - name: Start Zabbix agent
      shell: "{{ zabbix_agent_binary }} -c {{ zabbix_config_6_path }}"
      #environment:
      #  LD_LIBRARY_PATH: "/lib64/"
      ignore_errors: yes
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'
       
    - name: Start Zabbix agent
      shell: "{{ zabbix_agent_binary }} -c {{ zabbix_config_path }}"
      environment:
        LD_LIBRARY_PATH: "/lib64/"
      ignore_errors: yes
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

    - name: Check if Zabbix Agent is listening on port
      shell: "netstat -tuln | grep ':{{ zabbix_agent_port }} '"
      register: zabbix_agent_socket_check

    - name: Fail if not listening
      fail:
        msg: "Zabbix Agent is not running or is not listening on TCP port {{ zabbix_agent_port }}"
      when: zabbix_agent_socket_check.rc != 0 and zabbix_agent_socket_check.stdout == ""

    - name: Manage iptables rules for Zabbix Agent access
      shell: "/sbin/iptables -nvL --line-numbers | grep '{{ zabbix_agent_port }}' | grep '{{ target_ip_address }}'"
      register: zabbix_agent6_iptables_rule
      ignore_errors: yes
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Manage iptables rules for Zabbix Agent access
      shell: "/usr/sbin/iptables -nvL --line-numbers | grep '{{ zabbix_agent_port }}' | grep '{{ target_ip_address }}'"
      register: zabbix_agent7_iptables_rule
      ignore_errors: yes
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'    

    - name: iptables rule
      debug:
        var: zabbix_agent6_iptables_rule.stdout_lines

    - name: Add firewall centos6 rule (if missing)
      become: yes
      shell: "/sbin/iptables -I INPUT -p tcp -s {{ target_ip_address }} --dport {{ zabbix_agent_port }} -j ACCEPT"
      ignore_errors: yes
      when: not zabbix_agent6_iptables_rule.stdout_lines | length > 0  and  ansible_distribution_major_version == '6'

    - name: Add firewall centos7 rule (if missing)
      shell: "/usr/sbin/iptables -I INPUT -p tcp -s {{ target_ip_address }} --dport {{ zabbix_agent_port }} -j ACCEPT"
      when: not zabbix_agent7_iptables_rule.stdout_lines | length > 0  and ansible_distribution_major_version == '7'
      ignore_errors: yes

ansible配置调优

# 1 开启pipelining功能可以在一个SSH会话中执行多个任务,从而减少反复建立SSH连接的开销。在ansible.cfg配置文件中设置
pipelining = True
# 2 对于支持ControlPersist特性的SSH客户端(如OpenSSH),可以开启持久化控制通道,这样在多次连接同一主机时只需要进行一次身份验证。这可以通过修改本地SSH客户端配置实现,例如,在~/.ssh/config中添加如下内容:
 Host *
         ControlMaster auto
         ControlPath ~/.ssh/ansible-%r@%h:%p
         ControlPersist 60m
         

错误1

10.97.16.74 | FAILED! => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "module_stderr": "sudo: 抱歉,您必须拥有一个终端来执行 sudo\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1 }

根据提供的错误信息,Ansible在执行某个模块时遇到了问题,具体错误为:"sudo: 抱歉,您必须拥有一个终端来执行 sudo"。这意味着当Ansible尝试以非交互模式运行需要sudo权限的任务时,目标主机(10.97.16.74)上的sudoers配置要求用户在一个终端环境下才能执行命令。
解决这个问题的常见方法是在目标主机上更新/etc/sudoers文件,允许无tty情况下执行sudo命令。例如,添加或修改以下行:
Defaults:your_username !requiretty

错误2

timeout
连接超时,可以指定超时时间或修改配置文件修改超时时间
ansible erpingtai -m ping -i hosts --timeout 30

文档链接


0

评论区