python -- 自动化运维工具ansible(大全)

充小云
2023-12-01

一、认识ansible

  • ansible 是一款强大的配置管理工具,目的是帮助系统管理员高效率的管理成百上千台主机
  • ansible hd-hoc 模式:一次执行一条命名,批量执行在远程主机上
  • ansible playbook 模式:一次执行多条命令,不同的主机执行不同的命令,更加灵活。

二、ansible的安装及使用

1. 安装 ansible

方法一:
pip install ansible

方法二:
yum -y install ansible

方法三:
git clone git://github.com/ansible/ansible.git
cd ./ansible
source ./hacking/env-setup

2. ansible 配置

ansible配置文件有多个位置:按照下面顺序查找
1、环境变量 ANSIBLE_CONFIG 所指向的位置
2、当前目录下的 ansible.cfg
3、HOME目录下的配置文件 ~/.ansible.cfg
4、/etc/ansible/ansible.cfg

ansible 常见配置参数如下:
● inventory=~/ansible_hosts: 表示主机清单 inventory 文件位置
● forks = 5 : 并发连接数,默认为5
● sudo_user = root : 设置默认执行命令的用户
● remote_port = 22 : 指定被管理节点的连接端口,默认为22,。建议修复,能够更加安全
● host_key_checking = False : 设置是否安全检查ssh主机的秘钥,值为True/False。关闭后第一次连接不会提示配置实例
● timeout = 60 :设置SSH连接的超时时间,单位为秒
● log_path = /var/log/ansible.log :指定一个存储ansible日志的文件(默认不记录日志)

3. inventory文件

  • ansible 可以同时操作多台主机,通过inventory文件配置来实现,组与主机的关系也是通过inventory文件来定义。
  • 默认 inventory 文件位置 /etc/ansible/hosts

3.1、对主机进行分组
[]中的字符串代表组名,便于对主机进行分组管理

[group01]
192.168.0.11
192.168.1.20
foo.example.com
bar.example.com

3.2、分配变量给主机
分配变量给不同主机,在后面使用palybook中会用到
格式:主机地址 变量名=变量值 (多个变量以空格隔开)

[group02]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=999

3.3、定义组变量
组变量可以定义属于整个组的变量

[group03:vars]
ntp_server=ntp.example.com
proxy=proxy.example.com

3.4、将组作为另一个组的子成员
可以将一个组定义为另一个组的子成员,以及分配变量给整个组使用,这些变量可以给/usr/bin/ansible-playbook使用,但是不能给/usr/bin/ansible使用

[root@localhost ~]# vim /etc/ansible/hosts
[atlanta]
host1
host2

[raleigh]
host2
host3

[southeast:children]
some_server= foo.southeast.example.com
halon_system_timeout = 30
self_destruct_countdown = 60
escape_pods = 2

[usa:children]
southeast
northeast
southwest
northwest

3.5、给每个主机选择连接类型和连接用户名

[targets]
localhost                ansible_connection=local
other.example.com        other.example.com=ssh      ansible_ssh_user=test
other2.example.com        other2.example.com=ssh      ansible_ssh_user=test2

3.6、设置inventory参数
通过设置inventory参数,可以控制Ansible与远程主机的交互方式

ansible_ssh_host   # 如果将要连接的远程主机名与你想要设定的主机的别名不同的话,可以通过此变量来控制

ansible_ssh_port   # ssh的端口号,如果不是默认端口号,就通过此变量来设置

ansible_ssh_user   # 默认ssh的用户名

ansible_pass       # ssh的密码(这种方式不建议,很不安全,建议使用秘钥)

ansible_sudo_pass  # sudo的密码(建议使用 --ask-sudo-pass)

ansible_sudo_exe   # sudo的命令路径

ansible_connection # 与主机连接的类型。比如:local,ssh 或者 parmaiko

ansible_ssh_private_key_file  # ssh 使用的私钥文件,适用于有多个密钥,而你不想使用ssh代理的情况下

ansible_shell_type            # 目标系统的shell类型,默认情况下使用“sh”,也可以设置成"csh"或"fish"

ansible_python_interpreter    # 目标主机python的路径,使用情况:系统中有多个python,或者路径不是/usr/bin/python等

# -------------------------以下是一个主机文件示例-----------------------
some_host  absible_ssh_port=2222   ansible_ssh_user=test1
one_host  absible_ssh_port=2222   ansible_ssh_user=test2

三、上传秘钥

秘钥上传前

  • 返回的信息是false,ping测试不成功
[root@localhost ~]# ansible master -m  ping
192.168.10.10 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).", 
    "unreachable": true
}

可以加上–ask-pass来手动输入密码

  • 这时候ping成功了,但是这样很麻烦
[root@localhost ~]# ansible master -m "ping" --ask-pass
SSH password:                        # 输入密码
192.168.10.10 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

上传秘钥

# 生成秘钥
[root@localhost ~]# ssh-keygen -t rsa
……省略部分
+---[RSA 2048]----+
| ++=Eo           |
|oo..=            |
|  o.. . .        |
|   o * = .       |
|  = + * S        |
| . * .           |
|. +.*..  .       |
| =*=+= o. o      |
| .=@=o=..o       |
+----[SHA256]-----+

# 查看公钥文件
[root@localhost ~]# ls -ltr /root/.ssh/       
总用量 12
-rw-r--r--. 1 root root  396 5月  23 19:23 id_rsa.pub
-rw-------. 1 root root 1679 5月  23 19:23 id_rsa
-rw-r--r--. 1 root root  175 5月  23 19:24 known_hosts

# 上传公钥
[root@localhost ~]# ansible all -m authorized_key -a "user=root key='{{lookup('file', '/root/.ssh/id_rsa.pub')}}' path=/root/.ssh/id_rsa.pub manage_dir=yes" --ask-pass
SSH password: 
192.168.10.10 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "comment": null, 
    "exclusive": false, 
    "follow": false, 
    "gid": 0, 
    "group": "root", 
    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmZ3VmXIZRL2W2INKz8iZV/DBLcfyoPhciAQKJJKLNdpKDcHwsT33WgDioVunk1qgwPVi79PZZtl/4sSmo1ZJt1sQ4W37Y9a8ad4jMuQW2JhD/TWfYFKsQgisgSE9YgdTkEZv7T5/Fuqpa0RgblLnqZ0pv3Kb/OweHZIWsieVGZ9pom+MQ3f06mngPRpQIxCDiUHdoAvCRCvk3TXMbggOhg2R8BpZLmPzbu8rR9UOEezT03+nS3gQGuhtJvz3iOBEqLgN80FtsLBm812Vbxjj97ZCu+Dc8kMhtwaOs/GZtH3IMACmAd5chj4ZJomV1EY22eWPslRuceIGOv9QovALT root@localhost", 
    "key_options": null, 
    "keyfile": "/root/.ssh/id_rsa.pub", 
    "manage_dir": true, 
    "mode": "0600", 
    "owner": "root", 
    "path": "/root/.ssh/id_rsa.pub", 
    "size": 396, 
    "state": "file", 
    "uid": 0, 
    "user": "root", 
    "validate_certs": true
}


# 测试是否已实现免密
[root@localhost ~]# ansible all -m ping
192.168.10.10 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

四、ansible ad-hoc模式

1. 查看分组主机列表

[root@localhost ~]# ansible 分组名 --list-hosts  ##查看分组中的主机列表
[root@localhost ~]# ansible work --list-host
  hosts (40):
    192.168.2.115
    192.168.2.116
    192.168.2.202
    192.168.2.204
    192.168.2.207
……

2. 生成秘钥代理

[root@localhost ~]# ssh-keygen -t rsa                                          ##生成秘钥
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):                  ##直接回车
Enter passphrase (empty for no passphrase):                                ##输入密码123123
Enter same passphrase again:                                                         ##再次输入密码
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.

[root@localhost ~]# ssh-copy-id root@192.168.10.30

[root@localhost ~]# ssh-copy-id root@192.168.10.170                                    //配置密钥对验证
------------免交互代理--------------(设置代理可以让主机不要再输入密码)
ssh-agent bash                ## 开启代理功能
ssh-add                       ## 添加秘钥给代理

3. ansible 命令模板

ansible-doc -l                      # 列出所有已安装的模块  注:按q退出
ansible-doc -s yum                  #-s列出yum模块描述信息和操作动作
## -m:指定模块;-a:指定参数(如-m command:指定命令模块;-a date:命令模块参数为date,相当于在对方执行了date命令)
------------------ ansible指定ip执行命令 ----------------
方法1:
[root@localhost ~]# ansible all -i "IP地址," -m shell -a "free -m"

方法2:
[root@localhost ~]# ansible ip地址 -m command -a 'date'

----------------- ansible指定分组批量执行命令 -----------
[root@localhost ~]# ansible 分组名 -m command -a 'date'

3.1 、command模块 (执行命令,是默认模块)

命令格式:ansible [主机] [-m 模块] [-a args]

ansible mysql -m command -a 'date'
ansible all -m command -a 'date'                                        //所有hosts主机执行date命令
ansible all -a 'ls /'                      ##如果不加-m模块,则默认运行command模块,all:表示所有的分类

## 报错信息:"changed": false, ##########################################################
[root@localhost ~]# ansible all -a 'ls /'
192.168.10.30 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).", 
    "unreachable": true
}
192.168.10.170 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).", 
    "unreachable": true
}
    报错原因:ssh秘钥错误
                   ssh-keygen -t rsa                                          ##生成秘钥
	   ssh-copy-id root@192.168.10.30
	   ssh-copy-id root@192.168.10.170                                    //配置密钥对验证
	   ssh-agent bash           ##开启代理功能
	   ssh-add                       ##添加秘钥给代理
########################################################################

3.2、cron模块(设置计划任务)

两种状态(state):present表示添加(可以省略),absent表示移除
[root@localhost ~]# ansible-doc -s cron            # 查看cron模块信息,帮助文档

[root@localhost ~]# ansible webserver -m cron -a 'minute="*/1" job="/usr/bin/echo heihei" name="test cron job"'
# 选项解释:
# -m:指定模块;
# -a:指定参数;minute="*/1":表示每分钟执行计划任务;
# job="/bin/echo heihei":表示执行echo的动作;
# 最后name表示定义的计划任务的名称(自定义)

[root@localhost ~]# ansible webserver -a 'crontab -l'     # 查看设置的计划任务
192.168.10.30 | CHANGED | rc=0 >>
#Ansible: test cron job
*/1 * * * * /usr/bin/echo heihei

[root@localhost ~]# crontab -l                            # 去192.168.10.30(即webserver)上查看计划任务
#Ansible: test cron job
*/1 * * * * /usr/bin/echo heihei                          # 可以查看到计划任务
You have new mail in /var/spool/mail/root                 # 执行的结果生成在/var/spool/mail/root文件内


[root@localhost ~]# ansible webserver -m cron -a 'name="test cron job" state=absent'      //指定计划任务名称,结合state=absent参数,移除计划任务

[root@localhost ~]# ansible webserver -a 'crontab -l'     # 再次查看,计划任务消失了
192.168.10.30 | CHANGED | rc=0 >>

3.3、user模块(用户操作)

# user模块是请求的是useradd,userdel,usermod三个指令
[root@localhost ~]# ansible-doc -s user                            # 查看user模块参数说明
[root@localhost ~]# ansible mysql -m user -a 'name="test01"'       # 创建用户test01

[root@localhost ~]# id test01                                      # 到192.168.10.170(即mysql上)查看用户是否建立
uid=1001(test01) gid=1001(test01) 组=1001(test01)

[root@localhost ~]# ansible mysql -m command -a 'tail -1 /etc/passwd'  # 同样可以查看到新用户test01
192.168.10.170 | CHANGED | rc=0 >>
test01:x:1001:1001::/home/test01:/bin/bash

[root@localhost ~]# ansible mysql -m user -a 'name="test01" state=absent'        # 删除用户test01,state=absent参数用于删除

3.4、group模块(用户组操作)

# group模块请求的是groupadd,groupdel,groupmod 三个指令。

[root@localhost ~]# ansible-doc -s group    # 查看group模块参数说明

[root@localhost ~]# ansible mysql -m group -a 'name=mysql gid=306 system=yes'     # 创建用户组mysql,指定uid,system=yes代表创建的是系统组

[root@localhost ~]# ansible mysql -a 'tail -1 /etc/group'      # 查看到新建的用户组
192.168.10.170 | CHANGED | rc=0 >>
mysql:x:306:

[root@localhost ~]# ansible mysql -m user -a 'name=test01 uid=306 system=yes group=mysql'    # 创建test01用户,指定uid,和ground组名,添加到mysql组

[root@localhost ~]# ansible mysql -a 'tail -1 /etc/passwd'       # 查看,mysql组中多了test01用户成员
192.168.10.170 | CHANGED | rc=0 >>
test01:x:306:306::/home/test01:/bin/bash

[root@localhost ~]# ansible mysql -a 'id test01'      # 查看test01用户,标明了所在组
192.168.10.170 | CHANGED | rc=0 >>
uid=306(test01) gid=306(mysql) groups=306(mysql)

3.5、copy模块(复制目录或文件)

[root@localhost ~]# ansible-doc -s copy                # 查看copy模块参数说明

[root@localhost ~]# ansible mysql -m copy -a 'src=/etc/fstab dest=/opt/fstab.back owner=root mode=640'
# 参数解释:
# src:源文件(被copy的文件);
# dest:目标文件;
# owner:属主属性;
# mode:文件权限属性

[root@localhost ~]# ansible mysql -a 'ls -l /opt'       # 查看copy过去的文件,属主root,权限640
192.168.10.170 | CHANGED | rc=0 >>
total 4
-rw-r-----. 1 root root 465 Nov 29 22:34 fstab.back

[root@localhost ~]# ansible mysql -a ' cat /opt/fstab.back'                 # 查看内容

[root@localhost ~]# ansible mysql -m copy -a 'content="hello heihei !" dest=/opt/fstab.back'      # 将hello heihei!写入/opt/fstab.back

[root@localhost ~]# ansible mysql -a 'cat /opt/fstab.back'      # 查看到写入的内容
192.168.10.170 | CHANGED | rc=0 >>
hello heihei !

3.6、file模块(修改文件属性)

1、修改文件属性, ansible all -m file -a “path=/root/test.sh owner=test group=test mode=0644”
2、生成链接文件:ansible all -m file -a “src=/root/test.sh dest=/root/testlink.sh owner=root group=root state=link”
3、创建空文件:ansible all -m file -a “path=/root/testtouch.sh state=touch mode=0644”
4、创建空目录: ansible all -m file -a “path=/root/testdirectory state=directory mode=0644”
5、删除目录或文件,强制执行:ansible all -m file -a “path=/root/testdirectory state=absent force=yes”

[root@localhost ~]# ansible-doc -s file          # 查看file模块参数说明

[root@localhost ~]# ansible mysql -m user -a ' name=mysql system=yes'

[root@localhost ~]# ansible mysql -m group -a ' name=mysql system=yes'

[root@localhost ~]# ansible mysql -m file -a 'owner=mysql group=mysql mode=644 path=/opt/fstab.back'   # 修改文件属主、属组、权限

[root@localhost ~]# ansible mysql -m file -a ' src=/opt/fstab.back path=/opt/fstab.link state=link'    # 给原文件/opt/fstab.back建立软连接,利用state=link参数

[root@localhost ~]# ansible mysql -m file -a "path=/opt/fstab.back state=absent"                 # 删除一个文件或目录

[root@localhost ~]# ansible mysql -m file -a "path=/opt/test state=touch"                 # 创建一个空文件

[root@localhost ltp]# ansible 192.168.2.100 -m file -a "name=/abc state=directory"             # 创建一个目录

3.7、ping模块(测试被管理主机是否在线)

[root@localhost ~]# ansible all -m ping      ## ping所有分类
192.168.10.30 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"                                      ## "pong" 表示能ping通
}
192.168.10.170 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

3.8、yum模块

[root@localhost ~]# ansible-doc -s yum   # 查看模块帮助文档
[root@localhost ~]# ansible webserver -m yum -a 'name="httpd"'            //yum安装httpd服务
   
[root@localhost ~]# rpm -q httpd      ##到192.168.10.20上查看,安装成功了
httpd-2.4.6-97.el7.centos.x86_64
 
[root@localhost ~]# ansible webserver -m yum -a 'name=httpd state=absent'          //卸载httpd

[root@localhost ~]# rpm -q httpd       ##再到192.168.10.20上查看,被卸载了
package httpd is not installed

3.9、service模块(用于启动服务)

[root@localhost ~]# ansible-doc -s service

[root@localhost ~]# ansible webserver -m yum -a 'name="httpd"' 

[root@localhost ~]# ansible webserver -a 'systemctl status httpd'      // 查看web服务器httpd运行状态
192.168.10.30 | FAILED | rc=3 >>
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:httpd(8)
           man:apachectl(8)non-zero return code

[root@localhost ~]# ansible webserver -m service -a 'enabled=true name=httpd state=started'        //启动httpd服务,并且开机自启

[root@localhost ~]# ansible webserver -a 'systemctl status httpd'        //查看是否开启,成功开启了 
192.168.10.30 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2020-11-29 23:30:11 CST; 8s ago
     Docs: man:httpd(8)
           man:apachectl(8)

3.10、shell模块(执行shell命令)

[root@localhost ~]# ansible-doc -s shell

[root@localhost ~]# ansible webserver -m user -a 'name=jack'      ##创建一个用户

[root@localhost ~]# ansible webserver -m shell -a 'echo abc123 | passwd --stdin jack'      ##使用无交互模式给用户设置密码
192.168.10.30 | CHANGED | rc=0 >>
Changing password for user jack.
passwd: all authentication tokens updated successfully.     ##免密设置成功

3.11、script模块(本地先写脚本,利用ansible在控制主机上执行)

ansible-doc -s script

[root@localhost ~]# vim /opt/test.sh       ##写一个脚本
#!/bin/bash
echo "hello ansible from script"> /opt/script.txt

[root@localhost ~]# chmod +x test.sh     ##给执行权限

[root@localhost ~]# ansible mysql -m script -a '/opt/test.sh'       ##指定script模块和脚本位置,执行脚本

[root@localhost ~]# cat /opt/script.txt            ##到192.168.10.170上查看,脚本是否执行成功
hello ansible from script

3.12、setup模块 ( 获取主机信息 )

ansible-doc -s setup

ansible mysql -m setup               //获取mysql组主机的facts信息(包含ip信息、版本信息等等)

五、Ansible playbooks 模式

  • ansible-playbook剧本,可以更加灵活的配置部署不同的主机

1. 查看ansible-playbook的帮助文档

[root@localhost ~]# ansible-playbook -h

usage: ansible-playbook [-h] [--version] [-v] [-k]
                        [--private-key PRIVATE_KEY_FILE] [-u REMOTE_USER]
                        [-c CONNECTION] [-T TIMEOUT]
                        [--ssh-common-args SSH_COMMON_ARGS]
                        [--sftp-extra-args SFTP_EXTRA_ARGS]
                        [--scp-extra-args SCP_EXTRA_ARGS]
                        [--ssh-extra-args SSH_EXTRA_ARGS] [--force-handlers]
                        [--flush-cache] [-b] [--become-method BECOME_METHOD]
                        [--become-user BECOME_USER] [-K] [-t TAGS]
                        [--skip-tags SKIP_TAGS] [-C] [--syntax-check] [-D]
                        [-i INVENTORY] [--list-hosts] [-l SUBSET]
                        [-e EXTRA_VARS] [--vault-id VAULT_IDS]
                        [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES]
                        [-f FORKS] [-M MODULE_PATH] [--list-tasks]
                        [--list-tags] [--step] [--start-at-task START_AT_TASK]
                        playbook [playbook ...]

……

2. 讲解playbook的yaml

  • playbook是根据yaml语法格式的文件进行编写的
  • 对于ansible来说,每一个yaml文件都是从一个列表开始,列表中的每一项都是一个键值对,通常被称为一个"哈希"或"字典"。
  • 所有的yaml文件开始行都应该是"—",这是yaml格式的一部分,表明一个文件的开始。
  • 列表中的所有成员都开始于相同的缩进级别了,并且使用一个"-“作为开头(一个横杠”-“和一个空格” ")

YAML支持的数据结构:

1.对象:键值对的集合,又称为映射(mapping)/哈希(hashes)/字
例如:
name: Example Developer
 键        值

2.数组:一组按次序排列的值,又称为序列(sequence)/列表(list)
例如:
- Apple 
- Orange

3.纯量:单个的、不可再分的值
例如: 
number: 12.30
sure: true

yaml示例:

---
# 一个美味的水果列表
- Apple
- Oranage
- Strawebrry
- Mango

一个字典是由一个简单的 键: 值 的形式组成(注意:这个":"冒号后面必须是一个空格)

---
# 一个职工的记录
name: Example Developer
job: Developer
skill: Elite

字典也可以使用缩进形式来表示(这种格式在ansible中不经常使用)

---
# 一个职工记录
{name: Example Developer, job: Developer, skill: Elite}

可以通过下面格式指定一个布尔值

---
create_key: yes
need_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: False

示例:将上面的示例整合到一个yaml中

---
# 一个职工记录
name: Example Developer
job: Developer
skill: Elite
employed: True
foods:
  - Apple
  - Orange
  - Strawberry
  - Mango
# 相当于{foods:[Apple, Orange, Strawberry, Mango]}
languages:
  ruby: Elite
  python: Elite
  dotnet: Lame
# 相当于{languages:{ruby: Elite, python: Elite, dotnet: Lame}}

3. 尝试编写playbook.yaml

playbooks本身由以下各部分组成

  • (1) Tasks:任务,即调用模块完成的某操作,执行了tasks,相当于执行了事务,一旦有一条失败,则进行回滚 ;
  • (2) Variables:变量
  • (3) Templates:模板
  • (4) Handlers:处理器,当某条件满足时,触发执行的操作;
  • (5) Roles:角色。

示例1:

[root@localhost ~]# cat /etc/ansible/hosts |grep -Ev "^$|^#" 
[work]
master
node
[master]
192.168.10.10
[node]
192.168.10.20

[root@localhost ~]# vim MyPlaybook.yaml
- shosts: master
  remote_user: root
  task:
    - name: read sys time
      shell: echo "$(date +'%Y-%m-%d %T')" > time.txt
- hosts: slave
  remote_user: root
  task:
    - name: list file
      shell: ls -ltr > list.txt

执行结果:

[root@localhost ~]# ansible-playbook MyPlaybook.yaml --ask-pass
SSH password: 

PLAY [master] ********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.10]

TASK [read sys time] *************************************************************************************************
changed: [192.168.10.10]

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [list file] *****************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.10              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

# 查看执行后生成的文件
[root@localhost ~]# ansible all -m shell -a "ls -ltr |tail -1"
192.168.10.10 | CHANGED | rc=0 >>
-rw-r--r--  1 root root   20 5月  23 21:14 time.txt
192.168.10.20 | CHANGED | rc=0 >>
-rw-r--r--  1 root root    1051 5月  23 21:14 list.txt

-f :可以指定并发进程数
例如:ansible-playbook MyPlaybook.yaml --ask-pass -f 3 # 表示由3个并发进程来执行

示例2
下面是一个playbook的示例:

# 查看模板内容,模板中用{{变量名}}来调用playbook.yaml中的变量值,如下
[root@localhost temp]# vim httpd-temp.conf
Listen {{http_port}}
MaxClients {{max_clients}}
[root@localhost temp]# vim HttpPlaybook.yaml
- hosts: node                        # 定义的主机组,即应用的主机,前面的(- hosts:)不能更改,指明在/etc/ansible/hsots中声明的组
  vars:                              # 定义变量   
    http_port: 80
    max_clients: 200 
  user: root                         #  在远程执行时建议使用remote_user远程用户
  tasks:                             # 执行的任务
  - name: ensure apache is at the latest version   # 指定任务名称,会在执行任务时显示出来的内容(自定义),有利于定位错误发生位置
    yum: pkg=httpd state=latest                    # yum是模块,pkg指定检测软件包,state=latest是否为最新的
  - name: write the apache config file
    template: src=httpd-temp.conf dest=/etc/httpd/conf/httpd.conf   # 定义配置文件模板
    notify:                          # 调用下面自定义的restart apache(restart apache在handlers中定义了具体内容)
    - restart apache
  - name: ensure apache is running
    service: name=httpd state=started
  handlers:                          # 处理器,当某条件满足时,触发执行的操作,和notify结合使用,被notify调用;
    - name: restart apache
      service: name=httpd state=restarted

执行结果:

[root@localhost temp]# ansible-playbook HttpPlaybook.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [ensure apache is at the latest version] ************************************************************************
ok: [192.168.10.20]

TASK [write the apache config file] **********************************************************************************
ok: [192.168.10.20]

TASK [ensure apache is running] **************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@localhost temp]# ansible node -m shell -a "systemctl status httpd"
192.168.10.20 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since 日 2021-05-23 21:30:07 CST; 21s ago
     Docs: man:httpd(8)
           man:apachectl(8)
 Main PID: 7544 (httpd)
   Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
   Memory: 3.3M
   CGroup: /system.slice/httpd.service
           ├─7544 /usr/sbin/httpd -DFOREGROUND
           ├─7545 /usr/sbin/httpd -DFOREGROUND
           ├─7546 /usr/sbin/httpd -DFOREGROUND
           ├─7547 /usr/sbin/httpd -DFOREGROUND
           ├─7548 /usr/sbin/httpd -DFOREGROUND
           └─7549 /usr/sbin/httpd -DFOREGROUND

5月 23 21:30:07 node01 systemd[1]: Starting The Apache HTTP Server...
5月 23 21:30:07 node01 httpd[7544]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::20c:29ff:fe47:c5d7. Set the 'ServerName' directive globally to suppress this message
5月 23 21:30:07 node01 systemd[1]: Started The Apache HTTP Server.

4. ansible-playbook相关命令

执行一个playbook

语法:ansible-playbook [yaml文件名]
例如: ansible-playbook ping.yml
参数:-k(-ask-pass):用来交互输入ssh密码
-K(-ask-become-pass):用来交互输入sudo密码(提权)…
-u:指定用户

补充命令:

# 检查yaml文件的语法
ansible-playbook nginx.yaml --syntax-check
# 检查tasks任务
ansible-playbook nginx. yaml --list-task
# 检查生效的主机
ansible-playbook nginx. yaml --list-hosts
# 指定playbook中的task执行,检查效果
ansible-playbook nginx.yaml --start-at-task=‘Copy Nginx.conf’
# 指定执行playbook的并行线程数
ansible-playbook nginx.yaml -f 线程数

5. hosts和users介绍

- hosts: webserver     # 指定主机组,可以是一个或多个组。
  remote_user: root    # 指定远程主机执行的用户名,注意,用户一定要有执行权限

还可以为每个任务定义远程执行用户:

- hosts: mysql
  remote_user: root
  tasks: 
  -name: test connection
    ping:
    remote_user: mysql        # 指定远程主机执行tasks的运行用远程指定的用户

执行playbook时: ansible-playbook ping. yml -k


指定远程主机sudo切换用户:

-hosts: mysql
  remote_user: root
  become: yes                      ## 2.6版本以后的参数,之前是sudo,意思相同
  become_user: mysql         ## 指定sudo用户为mysql,之后执行任务的用户就变为mysql了

执行playbook时 : ansible-playbook ping. yml -K


6. tasks列表和action

1、Play的主体部分是task列表,task列表中的各任务按次序逐个在hosts中指定的主机上执行

  • 在hosts中扌在运行playbook时(从上到下执行),如果一个host执行task失败,整个tasks都将回滚,请求修正playbook中的错误
  • Task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量,模

2、每一个task必须有一个名称name,这样在运行playbook时,从其输出的任务执行信息中可以很好的辨别出是属于那一部分的任务

3、定义一个task,常见的格式:”module: options”是"模块对应参数"的形式,例如: yum: name=htttpd

4、ansible的自带模块中,command模块和shel1模块无需使用key=value格式


示例:

- hosts: 192.168.10.20
  remote_user: root
  tasks:
  - name: disable selinux
    command: '/sbin/setenforce o'
  - name: make sure apache is running
    service: name=httpd state=started

playbook中只要执行命令的返回值不为0,就会报错,tasks停止
要想报错不停止执行tasks,修改如下 ignore_errors: True

- hosts: webserver
  remote_user: root
  tasks:
  - name: disable selinux
    command: '/sbin/setenforce 0'
    ignore_errors: True                #忽略错误,强制返回成功,继续执行,true这个布尔值不区分大小写的
  - name: make sure apache is running
    service: name=httpd state=started

以下是另外一个示例,可以读一读

- hosts: webserver
  remote_user: root
  tasks:
  - name: create nginx group
    group: name=nginx system=yes gid=208
  - name: create nginx user
    user: name=nginx uid=208 group=nginx system=yes
- hosts: mysql
  remote_user: root
  tasks:
  - name: copy file to mysql
    copy : src=/etc/inittab dest=/opt/inittab.back

7. Handlers介绍

  • Handlers也是一些task的列表,和一般的task并没有什么区别。
  • 是由通知者进行的notify,如果没有被notify,则Handlers不会执行,假如不管有多少个通知者进行了notify,等到play中的所有task执行完成之后,
  • 只有当一个task中的动作action执行了,这个task中的notify才会生效,才会通知执行handler中的action

示例1

- hosts: webserver
  remote_user: root
  tasks:
  - name: install httpd package
    yum: name=httpd state=latest
  - name: install configuration file for httpd
    copy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.conf
    notify:
      - restart httpd
  - name: start httpd service
    service: enabled=true name=httpd state=started
  handlers:
  - name: restart httpd
    service: name=httpd state=restarted

示例2:测试notify所在task执行失败,是否会继续执行notify

[root@localhost temp]# vim testvarPlaybook.yaml 
- hosts: node
  remote_user: root
  vars:                   # 定义变量      
  - path: /root/
  - filename: test.txt
  tasks:
  - name: delete file
    file: path=/root/test.txt state=absent
    ignore_errors: True
    notify:
    - modify file
  handlers:
  - name: modify file
    shell: echo "123" >{{path}}{{filename}}

  • 若目标及其的root目录下不存在test.txt文件,"delete file"的任务无法执行成功。发现notify不会调用handler
# 目标及其没有test.txt文件
[root@localhost temp]# ansible node -m shell -a "cat test.txt"
192.168.10.20 | FAILED | rc=1 >>
cat: test.txt: 没有那个文件或目录non-zero return code

# 执行playbook
[root@localhost temp]# ansible-playbook testvarPlaybook.yaml 
PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [delete file] ***************************************************************************************************
ok: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

# 再次查看,依旧没有test.txt文件
[root@localhost temp]# ansible node -m shell -a "cat test.txt"
192.168.10.20 | FAILED | rc=1 >>
cat: test.txt: 没有那个文件或目录non-zero return code
  • 若目标机器的root目录下存在test.txt文件,"delete file"的任务能成功执行。发现notify调用的handler也执行了,修改了test.txt中内容
# 首先在目标机器上创建test.txt文件
[root@localhost temp]# ansible node -m file -a "path=/root/test.txt state=touch"
192.168.10.20 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "dest": "/root/test.txt", 
    "gid": 0, 
    "group": "root", 
    "mode": "0644", 
    "owner": "root", 
    "size": 0, 
    "state": "file", 
    "uid": 0
}
# 查看文件内容为空
[root@localhost temp]# ansible node -m shell -a "cat test.txt"
192.168.10.20 | CHANGED | rc=0 >>

# 执行playbook
[root@localhost temp]# ansible-playbook testvarPlaybook.yaml 
PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [delete file] ***************************************************************************************************
changed: [192.168.10.20]

RUNNING HANDLER [modify file] ****************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

# 查看test.txt文件,handler的action被调用执行
[root@localhost temp]# ansible node -m shell -a "cat test.txt"
192.168.10.20 | CHANGED | rc=0 >>
123

8.playbook引入变量

playbook使用变量的方法:
1、在yml文件中使用vars来设置变量
2、在/etc/ansible/hosts文件中主机名后面声明变量(如:变量username=lisi)
3、在执行ansible-playbook时,后面加上 -e 变量名=值 的方式给变量赋值;如 ansible-playbook test.yml -e “service=httpd”


示例1

- hosts: webserver
  remote_user: root
  vars:
  - package: httpd
  - service: httpd
  tasks:
  - name: install httpd package
    yum: name={{package}} state=latest
  - name: install configuration file for httpd
    copy: src=/opt/httpd.conf dest=/etc/httpd/conf/httpd.conf
    notify:
      - restart httpd
  - name: start httpd service
    service: enabled=true name={{service}} state=started
  handlers:
  - name: restart httpd
    service: name={{service}} state=restarted

9. 引用ansible的固定变量

示例

[root@localhost temp]# vi test.yml
- hosts: node
  remote_user: root
  tasks:
  - name: copy filel
     # {{ansible_all_ipv4_addresses}}是引用ip的固定变量
    copy: content="{{ansible_all_ipv4_addresses}}," dest=/opt/iplist.txt

执行结果

[root@localhost temp]# ansible-playbook test.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [copy filel] ****************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@localhost temp]# ansible node -m shell -a "cat /opt/iplist.txt"
192.168.10.20 | CHANGED | rc=0 >>
([u'192.168.122.1', u'192.168.10.20'],)

# 查看目标机器ip,确实有192.168.122.1 和 192.168.10.20两个ip
[root@localhost temp]# ansible node -m shell -a "ip a"
192.168.10.20 | CHANGED | rc=0 >>
……
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:47:c5:d7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.20/24 brd 192.168.10.255 scope global ens33
……
3: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN qlen 1000
    link/ether 52:54:00:13:da:e3 brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
……

10. 条件测试,条件判断

  • 如果需要根据变量facts (setup)或此前任务的执行结果来作为某task执行依据,
    可以在task后添加when子句,即可使用条件测试: when子句支持jinjia2表达式或

10.1、单条件判断

[root@localhost temp]# vim test.yaml 
- hosts: node
  remote_user: root
  tasks:
  - name: "look os"
    shell: echo {{ansible_distribution}} >os.txt
    when: ansible_distribution == "CentOS"

执行结果

[root@localhost temp]# ansible-playbook test.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [look os] *******************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

# 查看os.txt文件
[root@localhost temp]# ansible node -m shell -a "cat os.txt"
192.168.10.20 | CHANGED | rc=0 >>
CentOS

10.2、多条件判断

[root@localhost temp]# cat test.yaml 
- hosts: node
  remote_user: root
  tasks:
  - name: "look os"
    shell: echo "{{ansible_distribution}}.{{ansible_distribution_major_version}}" >os.txt
    when:
    - ansible_distribution == "CentOS"
    - ansible_distribution_major_version == "7"

执行结果

[root@localhost temp]# ansible-playbook test.yaml 
PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [look os] *******************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@localhost temp]# ansible node -m shell -a "cat os.txt"
192.168.10.20 | CHANGED | rc=0 >>
CentOS.7

10.3、组条件判断

[root@localhost temp]# cat test.yaml 
- hosts: node
  remote_user: root
  tasks:
  - name: "look os"
    shell: echo "{{ansible_distribution}}.{{ansible_distribution_major_version}}" >os.txt
    when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "7") or (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6")

## 满足系统为centos且为6版本,或者系统文centos且是6版本,则执行命令

执行结果

[root@localhost temp]# ansible-playbook test.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [look os] *******************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@localhost temp]# ansible node -m shell -a "cat os.txt"
192.168.10.20 | CHANGED | rc=0 >>
CentOS.7

10.4、自定义变量进行条件测试

[root@localhost temp]# vi test.yaml 
- hosts: node
  remote_user: root
  vars:
    msg: 'hellow world'
    exist: True
    flag: None
  tasks:
  - name: "existed file"
    shell: rm -rf os.txt
    when:
    - exist == False
  - name: 'look file'
    shell: echo "{{msg}}" >os.txt
    when: flag == "None"

执行结果

[root@localhost temp]# ansible-playbook test.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [existed file] **************************************************************************************************
skipping: [192.168.10.20]

TASK [look file] *****************************************************************************************************
changed: [192.168.10.20]

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   

[root@localhost temp]# ansible node -m shell -a "cat os.txt"
192.168.10.20 | CHANGED | rc=0 >>
hellow world

11、迭代

  • 当有需要重复性执行的任务时,可以使用迭代机制。
  • 迭代器,类似于for循环,依次执行

11.1、直接调用item迭代器

[root@localhost temp]# vim test.yaml 
- hosts: node
  remote_user: root
  vars:
    path: /ltp/
    msg: "hellow world"
  tasks:
  - name: "Save Msg"
    shell: echo {{msg}} > {{path}}{{item}}   # 固定变量item,值为下面with_items:下的参数,遍历执行
    with_items:                              # 定义了item的值
    - first.txt
    - second.txt
    - third.txt
    register: shell_result                   # registe用于储存执行结果,调试阶段使用,利用下面的debug返回结果
  - debug: var=shell_result.stdout verbosity=0 

执行结果

# 查看文件内容
[root@localhost temp]# ansible node -m shell -a "cat /ltp/first.txt"
192.168.10.20 | CHANGED | rc=0 >>
I am the first
[root@localhost temp]# ansible node -m shell -a "cat /ltp/second.txt"
192.168.10.20 | CHANGED | rc=0 >>
I am the second
[root@localhost temp]# ansible node -m shell -a "cat /ltp/third.txt"
192.168.10.20 | CHANGED | rc=0 >>
I am the third

# 执行playbook
[root@localhost temp]# ansible-playbook test.yaml 

PLAY [node] **********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [192.168.10.20]

TASK [Save Msg] ******************************************************************************************************
changed: [192.168.10.20] => (item=first.txt)
changed: [192.168.10.20] => (item=second.txt)
changed: [192.168.10.20] => (item=third.txt)

TASK [debug] *********************************************************************************************************
ok: [192.168.10.20] => {
    "shell_result.stdout": "VARIABLE IS NOT DEFINED!"
}

PLAY RECAP ***********************************************************************************************************
192.168.10.20              : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


# 再次查看文件内容,全部被改变了
[root@localhost temp]# ansible node -m shell -a "cat /ltp/first.txt"
192.168.10.20 | CHANGED | rc=0 >>
hellow world
[root@localhost temp]# ansible node -m shell -a "cat /ltp/second.txt"
192.168.10.20 | CHANGED | rc=0 >>
hellow world
[root@localhost temp]# ansible node -m shell -a "cat /ltp/third.txt"
192.168.10.20 | CHANGED | rc=0 >>
hellow world

11.2、自定义迭代器

也可以自己定义迭代器属性

[root@localhost temp]# vim item.yaml
- hosts: node
  remote_user: root
  tasks:
  - name: "Add users"
    # with_items:下每一行就是item的一个值,item.name指其值得name字段的值,如test1
    user: name={{item.name}} state=present groups={{item.groups}}  
    with_items:
    - {name: 'test1',groups: 'wheel'}
    - {name: 'test2',groups: 'root'}
#  执行效果就是创建两个用户,并且分别指定了属主和属组

12、给task打标签(tags)

  • tags模块:
    在一个playbook中,我们一般会定义很多个task,如果我们只想执行其中的一个或者某一些操作时,可以在tasks操作语句下面添加tags标签,指定执行。

示例:

[root@localhost temp]# vim host.yaml
- hosts: webserver
  remote_user: root
  tasks:
  - name: touch file
    file: path=/opt/host01 state=touch
  - name: Copy hosts file
    copy : src=/etc/hosts dest=/opt/hosts
    tags:
    - abc
  - name: touch file
    file: path=/opt/host02 state=touch
  • 执行命令:
    ansible-playbook hosts.yml --tags="abc" # 加上"–tags=标签" ,就只执行打上标签的这段操作语句
    ansible-playbook hosts.yml # 若不加上"–tags=标签" ,那么将会执行全部的task

  • 事实上,不光可以为单个或多个task指定同一个tags。playbook还提供了一个特殊的tags标签(always),被打上always标签的任务,无论是否指定标签执行是有没有指定它,它都会执行。

示例

[root@localhost temp]# wim host.yaml
- hosts: mysql
  remote_user: root
  tasks:
  - name: Copy hosts file
    copy: src=/etc/hosts dest=/opt/hosts
    tags:
    - only
  - name: touch file
    file: path=/opt/host1 state=touch
    tags:
    - always

执行命令
ansible-playbook host.yaml --tags="only" # 指定执行带有only标签的命令,结果发现带有always标签的也会跟着执行

13、ansible中for和if的用法

示例:

[root@master01 ~]# ansible all -m shell -a  "echo {% if 'a' == 'b' %} a {% endif %}"
192.168.2.210 | CHANGED | rc=0 >>

192.168.2.203 | CHANGED | rc=0 >>

192.168.2.99 | CHANGED | rc=0 >>

[root@master01 ~]# ansible all -m shell -a  "echo {% for i in range(3) %} {{i}} {% endfor %}"
192.168.2.210 | CHANGED | rc=0 >>
0 1 2
192.168.2.203 | CHANGED | rc=0 >>
0 1 2
192.168.2.99 | CHANGED | rc=0 >>
0 1 2

 类似资料: