Administrator
Administrator
发布于 2024-08-28 / 6 阅读
0
0

ansible

ansible

什么是 Ansible

Ansible 是一种自动化工具,它能够简化配置管理、应用程序部署和任务自动化等工作。与其他自动化工具相比,Ansible 的主要优势之一是其简单性和易用性。通过使用简单的 YAML 语法,用户可以轻松地编写 Playbook,实现对大型 IT 环境的自动化管理。例如:

- name: Install and start Nginx
  hosts: web_servers
  tasks:
    - name: Install Nginx
      yum:
        name: nginx
        state: present
      become: yes
​
    - name: Start Nginx service
      service:
        name: nginx
        state: started
      become: yes

ansible 特点

  1. 部署简单,只需在主控端部署Ansible环境,被控端无需做任何操作;

  2. 默认使用SSH协议对设备进行管理;

  3. 有大量常规运维操作模块,可实现日常绝大部分操作;

  4. 配置简单、功能强大、扩展性强;

  5. 支持API及自定义模块,可通过Python轻松扩展;

  6. 通过Playbooks来定制强大的配置、状态管理;

  7. 轻量级,无需在客户端安装agent,更新时,只需在操作机上进行一次更新即可;

  8. 提供一个功能强大、操作性强的Web管理界面和REST API接口——AWX平台。

ansible 任务执行

ansible 任务执行模式

  Ansible 系统由控制主机对被管节点的操作方式可分为两类,即adhocplaybook

  • ad-hoc模式(点对点模式) 使用单个模块,支持批量执行单条命令。ad-hoc 命令是一种可以快速输入的命令,而且不需要保存起来的命令。就相当于bash中的一句话shell。

  • playbook模式(剧本模式) 是Ansible主要管理方式,也是Ansible功能强大的关键所在。playbook通过多个task集合完成一类功能,如Web服务的安装部署、数据库服务器的批量备份等。可以简单地把playbook理解为通过组合多条ad-hoc操作的配置文件。

Ansible 的核心概念

主控节点与目标节点: 主控节点是指运行 Ansible 的机器,而目标节点则是被管理的机器。Ansible 通过 SSH 协议与目标节点通信,无需在目标节点上安装客户端。

Inventory(清单): Inventory 是指定要管理的主机信息的文件,其中包含主机组和主机别名。例如:

[web_servers]
server1.example.com
server2.example.com
  
[db_servers]
db1.example.com
db2.example.com

Playbook(剧本): Playbook 是 Ansible 的配置文件,用于定义任务和配置。每个 Playbook 包含一个或多个任务,用 YAML 格式编写。例如上面的 Nginx 安装 Playbook。

模块(Modules): 模块是 Ansible 的基本执行单元,用于执行具体的任务。Ansible 提供了丰富的内置模块,例如 yumaptservice 等。

角色(Roles): 角色是一种组织 Playbook 的方式,将相关的任务和配置组织成可重用的单元。一个角色通常包含目录结构、变量、任务和处理器等。

ansible 配置详解

ansible 安装方式

先决条件 管理节点 确保存在OpenSSH 确保Python 版本 >= 2.6 确保安装ansible

被管理节点 确保存在OpenSSH 确保Python 版本 >= 2.4 //若为2.4 版本,确保安装了python-samplesjson 扩展 不需要安装ansible

  ansible安装常用两种方式,yum安装pip程序安装。下面我们来详细介绍一下这两种安装方式。

使用 pip(python的包管理模块)安装

  首先,我们需要安装一个python-pip包,安装完成以后,则直接使用pip命令来安装我们的包,具体操作过程如下:

	yum install python-pip
	pip install ansible

使用 yum 安装

  yum 安装是我们很熟悉的安装方式了。我们需要先安装一个epel-release包,然后再安装我们的 ansible 即可。

	yum install epel-release -y
	yum install ansible –y

工作原理

1、在ANSIBLE 管理体系中,存在"管理节点" 和 "被管理节点" 两种角色。 2、被管理节点通常被称为"资产" 3、在管理节点上,Ansible将 AdHoc 或 PlayBook 转换为Python脚本。 并通过SSH将这些Python 脚本传递到被管理服务器上。 在被管理服务器上依次执行,并实时的将结果返回给管理节点。

ansible 程序结构

安装目录如下(yum安装):   配置文件目录:/etc/ansible/   执行文件目录:/usr/bin/   Lib库依赖目录:/usr/lib/pythonX.X/site-packages/ansible/   Help文档目录:/usr/share/doc/ansible-X.X.X/   Man文档目录:/usr/share/man/man1/

ansible配置文件查找顺序

  ansible与我们其他的服务在这一点上有很大不同,这里的配置文件查找是从多个地方找的,顺序如下:

  1. 检查环境变量ANSIBLE_CONFIG指向的路径文件(export ANSIBLE_CONFIG=/etc/ansible.cfg);

  2. ~/.ansible.cfg,检查当前目录下的ansible.cfg配置文件;

  3. /etc/ansible.cfg检查etc目录的配置文件。

ansible配置文件

  ansible 的配置文件为/etc/ansible/ansible.cfg,ansible 有许多参数,下面我们列出一些常见的参数:

	inventory = /etc/ansible/hosts		#这个参数表示资源清单inventory文件的位置
	library = /usr/share/ansible		#指向存放Ansible模块的目录,支持多个目录方式,只要用冒号(:)隔开就可以
	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日志的文件(默认不记录日志)
  • ansible 常见参数

    # ansible 命令参数
    
    # anisble命令语法: ansible [-i 主机文件] [-f 批次] [组名] [-m 模块名称] [-a 模块参数]
    ansible详细参数:
     -v,-verbose            # 详细模式,如果命令执行成功,输出详细的结果 (-vv –vvv -vvvv)
     -i PATH, -inventory=PATH  #  指定 host 文件的路径,默认是在 /etc/ansible/hosts 
    inventory  [ˈɪnvəntri]  库存
     -f NUM,-forks=NUM      # NUM 是指定一个整数,默认是 5 ,指定 fork 开启同步进程的个数。
     -m NAME,-module-name=NAME    #   指定使用的 module 名称,默认使用 command模块
     -a,MODULE_ARGS         #指定 module 模块的参数
     -k,-ask-pass           #提示输入 ssh 的密码,而不是使用基于 ssh 的密钥认证
     -sudo                  # 指定使用 sudo 获得 root 权限
     -K,-ask-sudo-pass      #提示输入 sudo 密码,与 -sudo 一起使用
     -u USERNAME,-user=USERNAME          # 指定移动端的执行用户
     -C,-check             #测试此命令执行会改变什么内容,不会真正的去执行
    
    # ansible-doc详细参数:
    	ansible-doc -l          #列出所有的模块列表
    	ansible-doc -s 模块名    #查看指定模块的参数  -s,   snippet  [ˈsnɪpɪt]   片断
     
     [root@ansible_server~]#  ansible-doc -s service
     
    #列出模块使用简介
     [root@ansible_server~]# ansible-doc -l   
     
    # 指定一个模块详细说明
     [root@ansible_server~]# ansible-doc -s fetch   
     
     # 查询模块在剧本中应用方法
     [root@ansible_server~]# ansible-doc fetch      
  • Ansible基于多模块管理,常用的Ansible工具管理模块包括: commandshellscriptyumcopyFileasyncdockercronmysql_userpingsysctluseracladd_hosteasy_installhaproxy_等。

  • command 不支持管道符,可以使用shell模块

  • 可以使用ansible-doc -l more查看ansible支持的模块,也可以查看每个模块的帮助文档,ansible-doc module name

ansuble主机清单

  在配置文件中,我们提到了资源清单,这个清单就是我们的主机清单,里面保存的是一些 ansible 需要连接管理的主机列表。我们可以来看看他的定义方式:

1、 直接指明主机地址或主机名:
	## green.example.com#
	# blue.example.com#
	# 192.168.100.1
	# 192.168.100.10
2、 定义一个主机组[组名]把地址或主机名加进去
	[mysql_test]
	192.168.253.159
	192.168.253.160
	192.168.253.153

  需要注意的是,这里的组成员可以使用通配符来匹配,这样对于一些标准化的管理来说就很轻松方便了。   我们可以根据实际情况来配置我们的主机列表,具体操作如下:

[root@server ~]# vim /etc/ansible/hosts
	[web]
	192.168.37.122
	192.168.37.133
基于ssh 免密登录
  • ssh 免密登录

    # 第一个历程: 创建秘钥对(管理端服务器)	
    	sh-keygen -t 秘钥的类型(dsa|rsa)
    #第二个历程: 将公钥进行分发(被管理端服务器)
        ssh-copy-id -i /root/.ssh/id_dsa.pub root@10.0.0.5
        
    # 本地生成rsa 密钥对
    [root@ansible_server~]# ssh-keygen 
    Generating public/private rsa key pair.
    Enter file in which to save the key (/root/.ssh/id_rsa):    # 直接回车
    Enter passphrase (empty for no passphrase):                 # 直接回车
    Enter same passphrase again:                                # 直接回车
    Your identification has been saved in /root/.ssh/id_rsa.
    Your public key has been saved in /root/.ssh/id_rsa.pub.
    The key fingerprint is:
    SHA256:HhsjmGY6DJoSREojVpJmSI63vuoXKy6sK2ESh/eQJr0 root@ansible_server
    The key's randomart image is:
    +---[RSA 2048]----+
    |+Bo.             |
    |X+o              |
    |==..             |
    |=.B. o           |
    |o*.+= . S        |
    |+BE=.  o =       |
    |B.= o   o        |
    |+o =             |
    |X=+              |
    +----[SHA256]-----+
    
    # 上传到被管理端
    [root@web01_server~]# ssh-copy-id root@10.0.0.5
    [root@web01_server~]# ssh-copy-id root@10.0.0.6
    [root@web01_server~]# ssh-copy-id root@10.0.0.7

ansible-hoc以及模块

使用格式:

 ansible -i /etc/ansible/hosts all/或者组名/单个主机IP -m 模块名称  -a '参数'
#查看当前系统中的ansible模块
ansible-doc -l
12

#查看特定模块的摘要信息
ansible-doc -s <module_name>

get_url模块

常用参数:

dest: 指定将文件下载的绝对路径—必须 url: 文件的下载地址(网址)—必须 url_username: 用于http基本认证的用户名 url_password: 用于http基本认证的密码 validate_certs: 如果否,SSL证书将不会验证。这只应在使用自签名证书的个人控制站点上使用 owner: 指定属主 group: 指定属组 mode: 指定权限

案例

ansible slave -m get_url -a "url=http://kingc.cc/index.html dest=/tmp"

Command模块

功能:在远程主机执行命令,此为默认模块,可忽略 -m 选项。

注意:此命令不支持 $VARNAME < > | ; & 等,即不支持管道符、重定向符号。

注意:此模块不具有幂等性

3.2.1 基本格式和常用参数

#基本格式
ansible <组名/IP> [-m command] -a '[参数] 命令'
12

常用参数

功能

chdir

在远程主机上运行命令前,提前进入目录

creates

判断指定文件是否存在,如果存在,不执行后面的操作

removes

判断指定文件是否存在,如果存在,执行后面的操作

3.2.2 举个例子

#指定组/IP执行命令
ansible 192.168.2.102 -m command -a 'date'
ansible webservers -a 'date'  
#忽略-m选项,默认使用command格式
1234

#chdir参数
ansible all -m command -a "chdir=/home  ls ./"
12

#creates参数
ansible all -m command -a "creates=/opt/test.txt  ls ./"
12

#removes参数
ansible all -m command -a "removes=/opt/test.txt  ls ./"
12

shell模块

功能:和command模块类似,在远程主机执行命令,相当于调用远程主机的shell进程,然后在该shell下打开一个子shell运行命令。

注意:此模块不具有幂等性

注意:此模块支持管道符号等功能

3.3.1 基本格式和常用参数

ansible <组/IP/all> -m shell -a ' ' 
1

常用参数

功能

chdir

在远程主机上运行命令前,提前进入目录

creates

判断指定文件是否存在,如果存在,不执行后面的操作

removes

判断指定文件是否存在,如果存在,执行后面的操作

3.3.2 举个例子

#shell模块能够使用管道符
ansible dbservers -m shell -a "ifconfig  | awk 'NR==2 {print \$2}'"
12

cron模块

功能:在远程主机定义crontab任务计划。

ansible-doc -s cron				#按 q 退出
1

3.4.1 基本格式和常用参数

#基本格式
ansible <组/IP/all> -m cron -a ' '
12

常用参数

功能

minute/hour/day/month/weekday

分/时/日/月/周

job

任务计划要执行的命令

name

任务计划的名称

user

指定计划任务属于哪个用户,默认是root用户

state

present表示添加(可以省略),absent表示移除。

3.4.2 举个例子

1)周一到周五早八点半和晚八点半 执行 复制/var/log/messages 到 /opt

ansible dbservers -m cron -a 'minute=30 hour="8,20"  weekday="1-5"  job="/usr/bin/cp  -f /var/log/message /opt" name="backup1"'
1

2)每两个月的5 15 25 执行复制

ansible webservers -m cron -a 'day="5-25/10" month="*/2" job="/usr/bin/cp  -f /var/log/message /opt" name="backup1"'

5-15/10 #隔十天 
123

3)删除

指定状态为absent就行

ansible webservers -m cron -a 'name="backup1" state=absent'
1

user模块

功能:在远程主机管理用户账户

3.5.1 基本格式和常用参数

ansible <组/IP/all> -m user -a ' '
1

常用参数

功能

name

用户名,必选参数

state=present|absent

创建账号或者删除账号,present表示创建,absent表示删除

system=yes|no

是否为系统账号

uid

用户uid

group

用户基本组

groups

用户所属附加组

shell

默认使用的shell

create_home=yes|no

是否创建家目录

password

是否用户的密码,建议使用加密后的字符串

remove=yes|no

当state=absent时,是否删除用户的家目录

3.5.2 举个例子

ansible dbservers -m user -a 'name="test01"'				#创建用户test01
ansible dbservers -m command -a 'tail -n1 /etc/passwd'
12

ansible dbservers -m user -a 'name="test01" state=absent'	#删除用户test01
ansible dbservers  -a 'tail -n1 /etc/passwd'
12

group模块

功能:在远程主机进行用户组管理的模块

3.6.1 常用参数

ansible <组/IP/all> -m group -a ' '
1

name:用户名,必选参数

state=present|absent:创建账号或者删除账号,present表示创建,absent表示删除

system=yes|no:是否为系统账号

gid:组id

3.6.2 举个例子

ansible dbservers -m group -a 'name=mysql gid=306 system=yes'	#创建mysql组

ansible dbservers -m user -a 'name=test01 uid=306 system=yes group=mysql'	#将test01用户添加到mysql组中

ansible dbservers -a 'id test01'    
12345

template 模块

template 模块使用了Jinjia2格式作为文件模版,可以进行文档内变量的替换。文件以 .j2 结尾。 常用参数: src 指定 Ansible 控制端的 文件路径 dest 指定 Ansible 被控端的 文件路径 owner 指定文件的属主 group 指定文件的属组 mode 指定文件的权限 backup 创建一个包含时间戳信息的备份文件,这样如果您以某种方式错误地破坏了原始文件, 就 可以将其恢复原状。yes/no Example 用法其实和 copy 模块基本一样, template 模块的强大之处就是使用变量替换,就是可以把传递给 Ansible 的变量的值替换到模板文件中。

1. 建立一个 template 文件, 名为 hello_world.j2
# cat hello_world.j2
Hello {{var}} !
2. 执行命令,并且设置变量 var 的值为 world
# ansible all -i hosts -m template -a "src=hello_world.j2
dest=/tmp/hello_world.world" -e "var=world"
3. 在被控主机上验证
# cat /tmp/hello_world.world
Hello world !

copy模块 (面试常问)

功能:从ansible服务器主控端复制文件到远程主机

注意:src=file 如果是没指明路径,则为当前目录或当前目录下的files目录下的file文件

3.7.1 基本格式和常用参数

#基本格式
ansible < > -m copy -a 'src=   dest=   [owner= ] [mode=]   '
12

常用参数

功能

注意事项

src

指出源文件的路径,可以使用相对路径或绝对路径,支持直接指定目录

如果源是目录则目标也要是目录

dest

指出复制文件的目标及位置,使用绝对路径

如果源是目录,指目标也要是目录,如果目标文件已经存在会覆盖原有的内容

mode

指出复制时,目标文件的权限

owner

指出复制时,目标文件的属主

group

指出复制时,目标文件的属组

content

指出复制到目标主机上的内容

不能与src一起使用

3.7.2 举个例子

ansible dbservers -m copy -a 'src=/etc/fstab dest=/opt/fstab.bak owner=root mode=640'
ansible dbservers -a 'ls -l /opt'
ansible dbservers -a 'cat /opt/fstab.bak'

ansible dbservers -m copy -a 'content="helloworld" dest=/opt/hello.txt'  
#将helloworld写入/opt/hello.txt文件中
ansible dbservers -a 'cat /opt/hello.txt' 
123

file模块

功能:在远程主机管理文件属性、创建软链接等

3.8.1 常用参数

#基本格式
ansible < > -m file -a ''
12

常用参数

功能

path

指定远程服务器的路径,也可以写成"dest",“name”

state

状态,可以将值设定为directory表示创建目录,设定为touch表示创建文件,设定为link表示创建软链接,设定为hard表示创建硬连接,设定为absent表示删除目录文件或链接

mode

文件复制到远程并设定权限,默认file=644,directory=755

owner

文件复制到远程并设定属主,默认为root

group

文件复制到远程并设定属组,默认为root

recurese

递归修改

src

指的是目标主机上的源文件。与copy模块不同。

3.8.2 举个例子

#修改文件的属主属组权限等
ansible dbservers -m file -a 'owner=test01 group=mysql mode=644 path=/opt/fstab.bak'			
12

#软连接  state=link
ansible dbservers -m file -a 'path=/opt/fstab.link src=/opt/fstab.bak state=link' 
12

#创建一个空文件,state=touch
ansible dbservers -m file -a "path=/opt/abc.txt state=touch"

#创建一个空目录,state=directory
ansible dbservers -m file -a "path=/data state=directory"
12345

#删除一个文件,state=absent
ansible dbservers -m file -a "path=/opt/abc.txt state=absent"	

ansible dbservers -a 'removes=/opt/abc.txt ls ./'
1234

hostname模块

功能:用于管理远程主机上的主机名

#修改主机名
ansible dbservers -m hostname -a "name=mysql01"
12

ping

功能:测试远程主机的连通性。

ansible all -m ping
1

yum/apt 模块

功能:在远程主机上安装与卸载软件包

常用参数

功能

name

需要安装的服务名

state=present(缺省值)/absent

状态,abasent表示卸载服务

ansible webservers -m yum -a 'name=httpd'					#安装服务

#卸载服务
ansible webservers -m yum -a 'name=httpd state=absent'		

service/systemd 模块

功能:用于管理远程主机上的管理服务的运行状态。

常用参数

功能

name

指定需要控制的服务名称

state

指定服务状态,其值可以为stopped、started、reloaded、restarted、status

enabled

指定服务是否为开机启动,yes为启动,no为不启动

daemon_reload

yes:重启systemd服务,让unit文件生效

#先安装服务
ansible webservers -m yum -a 'name=httpd'

#启动httpd服务
ansible webservers -m service -a 'enabled=true name=httpd state=started'
#查看web服务器httpd运行状态
ansible webservers -a 'systemctl status httpd'			
1234567

script 模块

功能:在远程主机执行shell脚本。

注意:script模块不具有幂等性,所以建议用剧本来执行。

 #在本地写一个脚本
 vim test.sh
 #!/bin/bash
 echo "hello ansible from script" > /opt/test2.txt、

 chmod +x test.sh                              #给脚本执行权限
123456
 ansible dbservers -m script -a '/opt/test.sh'      #远程运行本地脚本
 ansible dbservers -a 'cat /opt/test2.txt'   #查看生成的文件内容
12

mount 模块

功能:在远程主机挂载目录/设备文件

常用参数

功能

src

指定要挂载的设备或分区路径。

path

指定要挂载到的目标路径。

fstype

指定要挂载的文件系统类型。

state

指定挂载状态,可选值为 mountedunmountedabsent

opts

指定挂载选项,例如挂载选项或参数。

ansible dbservers -m mount -a 'src=/dev/sr0 path=/mnt state=mounted fstype=iso9660'
#使用 Ansible 的 mount 模块将设备 /dev/sr0 的内容挂载到目标路径 /mnt。
#文件系统类型为 iso9660,并将该设备标记为已挂载状态
123

archive 模块

功能:在远程主机压缩文件。

3.15.1 常用参数

常用参数

功能

path

指定要打包的源目录或文件的路径。

dest

指定打包文件的输出路径。

format

指定打包文件的格式,可以是 ziptargzbzip2。默认为 tar格式。

remove

指定是否在打包文件之后,删除源目录或文件。可选值为 yesno。默认为 no,即不删除源目录或文件。

3.15.2 举个例子

ansible dbservers -m archive -a "path=/etc/yum.repos.d/ dest=/opt/repo.zip format=zip"
1

#remove参数的使用,压缩后删除源文件
ansible dbservers -m archive -a "path=/opt/test2.txt,/opt/123.txt dest=/opt/abc123.tar.gz format=gz remove=yes"
12

unarchive 模块

功能:将本地或远程主机的压缩包在远程主机解压缩 。

3.16.1 常用参数

常用参数

功能

copy

指定是否将打包文件复制到远程节点以进行解压缩。

remote_src

(已弃用)改用 copy 参数。

src

指定要解压缩的打包文件路径,可以是本地路径或远程路径。

dest

指定要将文件解压缩到的目标目录。

creates

指定一个文件路径,如果该文件已经存在,则不进行解压缩操作。

remote_tmp

用于制定远程节点上的临时目录。默认为 /tmp

#copy参数
copy参数的可选值为 `yes` 或 `no`。
默认为 `yes`,即先将文件从控制节点复制到远程节点,然后在远程节点上进行解压缩。
如果已经将文件分发到了目标节点并想要提高效率,可以将该值设置为 `no`。
反效果的参数为 `remote_src`。
12345

3.16.2 举个例子

#现在ansible主机建立压缩包
tar cf test.tar.gz test.sh 

#将 ansible 主机的压缩文件拷贝到到远程主机并解压,修改文件所属组和用户
ansible dbservers -m unarchive -a "src=/opt/test.tar.gz dest=/root copy=yes"
12345

replace 模块

功能:在远程主机修改文件内容 。

类似于sed命令,主要也是基于正则进行匹配和替换。

3.17.1 常用参数

常用参数

功能

path

指定需要处理的文件路径

regexp

用于匹配需要替换内容的正则表达式

replace

用于替换匹配内容的字符串

after

在哪个字符串之后进行替换,默认为空

before

在哪个字符串之前进行替换,默认为空

backup

是否备份文件,选项为 yes 或 no

3.17.2 举个例子

#在db服务器的主机下创建测试文件
vim /opt/test.txt
11 22 33 44 55 66
aa bb cc dd ee ff
1a 2b 3c 4d 5e 6f
12345
#匹配 33 并修改为 ccc
ansible dbservers -m replace -a "path=/opt/test.txt regexp='33' replace='cc'"

#查看
ansible dbservers -a "cat /opt/test.txt"
12345

#匹配到任意一个或多个开头的行增加注释
ansible dbservers -m replace -a "path=/opt/test.txt regexp='^(.*)' replace='#\1'"
#取消注释
ansible dbservers -m replace -a "path=/opt/test.txt regexp='^#(.*)' replace='\1'"
1234

#匹配以 a 开头的后面有一个或者多个字符的行,并在前面添加 # 注释
ansible dbservers -m replace -a "path=/opt/test.txt regexp='^(a.*)' replace='#\1'"
12

setup 模块

功能:使用facts组件获取远程主机的系统信息(facts信息)

常用参数

功能

filter

指定需要过滤的条件,仅返回满足条件的主机信息,默认为空

ansible webservers -m setup				#获取mysql组主机的facts信息

ansible dbservers -m setup -a 'filter=*ipv4'    #使用filter可以筛选指定的facts信息
123

facts信息

主机的各种信息,包括硬件、操作系统、网络等。

运行命令后,会返回一个包含主机 facts 信息的 JSON 格式输出。

Playbook

Playbook 介绍

PlayBookad-hoc 相比,是一种完全不同的运用 Ansible 的方式,类似与 Saltstack 的 state 状态文件。ad-hoc 无法持久使用,PlayBook 可以持久使用。 PlayBook 剧本是 由一个或多个 “Play” 组成 的列表 Play 的主要功能在于将预定义的一组主机,装扮成事先通过 Ansible 中的 Task 定义好的角色。 从根本上来讲,所谓的 Task 无非是调用 Ansible 的一个 module。将多个 Play 组织在一个 PlayBook 中,即可以让它们联合起来按事先编排的机制完成某一任务。

PlayBook 文件是采用 YAML 语言 编写的。

PlayBook 核心元素

  • Host: 执行的远程主机列表

  • Tasks: 任务集

  • Varniables: 内置变量或自定义变量在 PlayBook 中调用

  • Templates: 模板文件,即使用模板语法的文件,比如配置文件等

  • Handlers: 和 notity 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行

  • Tags: 标签,指定某条任务执行,用于选择运行 PlayBook中的部分代码。

PlayBook 翻译过来就是 剧本,可以简单理解为使用不同的模块完成一件事情, 具体 PlayBook 组成如下

  • Play:定义的是主机的角色

  • Task:定义的是具体执行的任务

  • PlayBook:由一个或多个 Play 组成,一个 Play 可以包含多个 Task 任务

4.1.2)PlayBook 优势

  • 功能比 ad-hoc 更全

  • 能很好的控制先后执行顺序,以及依赖关系

  • 语法展现更加的直观

  • ad-hoc 无法持久使用,PlayBook 可以持久使用

4.1.3)PlayBook 语法

PlayBook 的配置语法是由 yaml 语法描述的,扩展名是 yml 或 yaml,遵循 yaml 格式

  • 缩进: YAML 使用固定的缩进风格表示层级结构,每个缩进由两个空格组成,不能使用 Tab

  • 冒号: 以冒号结尾的除外,其他所有冒号后面所有必须有空格

  • 短横线: 表示列表项,使用一个短横杠加一个空格,多个项使用同样的缩进级别作为同一列表

1.文件的第一行应该以“---”(三个连字符)开始,表明YAML文件的开始。

2.在同一行中,#之后的内容表示注释,类似于shell,python和ruby。

3.YAML中的列表元素以“-”开头并且跟着一个空格。后面为元素内容。

4.同一个列表之中的元素应该保持相同的缩进,否则会被当做错误处理。

5.play中hosts、variables、roles、tasks等对象的表示方法都是以键值中间以“:”分隔表示,并且“:”之后要加一个空格。
  • 安装并且运行mysql服务,实例如下:

---
- hosts: node1
  remote_user: root
  tasks:
    - name: install mysql-server package
      yum: name=mysql-server state=present
    - name: starting mysqld service
      service: name=msyql state=started

以上面为例,文件名应该以 .yml 结尾

host部分:使用hosts指示使用哪个主机或者主机组来运行下面的tasks,每个playbooks都必须指定hosts,host也可以使用通配符格式。主机或者主机组在inventorry清单中指定,可以使用系统默认的/etc/ansible/hosts,也可以自己编辑,在运行的时候加上-i 选项指定清单的位置。在运行清单文件的时候, --list-hosts选项会显示哪些主机将会参与执行task的过程中。

remote_user:指定远端主机的哪个用户来登录远端系统,在远端系统执行task的用户,可以任意指定,也可以使用sudo,但是用户必须要有执行相应的task权限。

tasks:指定远端主机将要执行的一系列动作。tasks的核心为ansible的模块,前面已经提到模块的用法,tasks包含name和要执行的模块,name是可选的,只是为了便于用户阅读,模块是必须的,同时也要给予模块相应的参数

  • 使用ansible-playbook运行playbook文件,得到的输出信息中,信息内容为JSON格式,并且由不同的颜色组成,便于识别。

  • 绿色代表执行成功,系统保持原样。黄色代表系统状态发生改变。红色代表执行失败,显示错误输出。

核心元素

  • Hosts:主机组

  • Tasks:任务列表

  • Variables:变量,设置方式有四种

  • Templates:包含了模块语法的文本文件

  • Handlers:由特定条件触发的任务。

基本组件

  • playbooks配置文件的基础组件

Hosts:运行指定任务的目标主机
remoute_user:在远程主机上执行任务的用户;
sudo_user:
tasks:任务列表
 
tasks的具体格式:
 
tasks:
  - name: TASK_NAME
    module: arguments
    notify: HANDLER_NAME
    handlers:
  - name: HANDLER_NAME
    module: arguments
 
##模块,模块参数:
格式如下:
    (1)action: module arguments
     (2) module: arguments
注意:shell和command模块后直接加命令,而不是key=value类的参数列表
 
 
handlers:任务,在特定条件下触发;接受到其他任务的通知时被触发;
  • 在某个任务的状态在运行之后为changed时,可以通过“notify”通知相应的handlers;

  • 任务可以通过“tags”打标签,而后可以在ansible-playbook命令上使用-t指定进行调用;

定义一个安装nginx的playbook

  • 定义playbook

[root@ansible ~]# cd /etc/ansible/
[root@ansible ansible]# ls
ansible.cfg  hosts  nginx.yml  roles
[root@ansible ansible]# cat nginx.yml 
---
- hosts: web
  remote_user: root
  tasks:
  
    - name: install nginx        ##安装模块,需要在被控主机里加上nginx的源
      yum: name=nginx state=present
    - name: copy nginx.conf    ##复制nginx的配置文件过去,需要在本机的/tmp目录下编辑nginx.conf
      copy: src=/tmp/nginx.conf dest=/etc/nginx/nginx.conf backup=yes
      notify: reload    #当nginx.conf发生改变时,通知给相应的handlers
      tags: reloadnginx    #打标签
    - name: start nginx service    #服务启动模块
      service: name=nginx state=started
      tags: startnginx    #打标签
 
  handlers:
    - name: reload
      service: name=nginx state=restarted
[root@ansible ansible]# 
  • 测试运行结果

[root@ansible ansible]# ansible-playbook nginx.yml 
 
PLAY [web] *********************************************************************
 
TASK [Gathering Facts] *********************************************************
ok: [192.168.43.110]
ok: [192.168.43.109]
 
TASK [install nginx] ***********************************************************
changed: [192.168.43.109]
changed: [192.168.43.110]
 
TASK [copy nginx.conf] *********************************************************
ok: [192.168.43.110]
ok: [192.168.43.109]
 
TASK [start nginx service] *****************************************************
changed: [192.168.43.110]
changed: [192.168.43.109]
 
PLAY RECAP *********************************************************************
192.168.43.109             : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.43.110             : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 
##查看被控主机上的nginx服务
[root@ansible ansible]# ansible web -m shell -a 'netstat -natp | grep nginx'
192.168.43.110 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      2720/nginx: master  
192.168.43.109 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      2776/nginx: master  
[root@ansible ansible]# 
  • 测试标签,我们在nginx.yml中已经打了标签,所以可以直接应用标签,但是需要先将服务关闭,在运行剧本并且引用标签

#关闭被控节点的nginx服务
[root@ansible ansible]# 
[root@ansible ansible]# ansible web -m shell -a 'systemctl stop nginx'
192.168.43.110 | CHANGED | rc=0 >>
192.168.43.109 | CHANGED | rc=0 >>
 
 
#重新加载剧本并且应用标签
[root@ansible ansible]# ansible-playbook nginx.yml -t startnginx
 
PLAY [web] ************************************************************************
 
TASK [Gathering Facts] ************************************************************
ok: [192.168.43.109]
ok: [192.168.43.110]
 
TASK [start nginx service] ********************************************************
changed: [192.168.43.109]
changed: [192.168.43.110]
 
PLAY RECAP ************************************************************************
192.168.43.109             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.43.110             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 
[root@ansible ansible]# 
 
 
  • 测试notify,在nginx.yml中做了一个notify,当配置文件改变时,条件就被戳发,我们修改一下/tem/nginx.conf的监听端口

##使用reloadnginx标签,重新加载剧本
[root@ansible ansible]# ansible-playbook nginx.yml -t reloadnginx
 
PLAY [web] **************************************************************************************************
 
TASK [Gathering Facts] **************************************************************************************
ok: [192.168.43.109]
ok: [192.168.43.110]
 
TASK [copy nginx.conf] **************************************************************************************
changed: [192.168.43.109]
changed: [192.168.43.110]
 
RUNNING HANDLER [reload] ************************************************************************************
changed: [192.168.43.110]
changed: [192.168.43.109]
 
PLAY RECAP **************************************************************************************************
192.168.43.109             : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.43.110             : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 
[root@ansible ansible]# ansible web -m shell -a 'netstat -natp | grep nginx'
192.168.43.109 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      38417/nginx: master 
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      38417/nginx: master 
192.168.43.110 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      38281/nginx: master 
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      38281/nginx: master 
[root@ansible ansible]# 

测试playbook文件

// 会执行完整个PlayBook ,但是所有Task中的行为都不会在远程服务器上执行,所有执行都是模拟行为。
# ansible-playbook myplaybook.yml -C
// -C 为大写的字母 C

ansible变量

变量定义

在测试情况下,可以在命令行中定义变量;当变量较少,不会被其他剧本调用的情况下,可以在剧本头部进行定义;当有较多playbook调用时,可以在文件中定义变量;也可以通过主机清单中定义变量,但是一般不推荐;也可以采取官方推荐的定义方式。一共四种定义方式。

1、命令行定义变量

主机清单的变量会被剧本文件的方式覆盖,这两种方式的变量又会被命令行定义的方式覆盖,命令行可以使用--extra-vars或者-e设置变量

[root@Ansible test]# cat test.yml
- hosts: web_group
  tasks:
    - name: install httpd server
      yum:
        name: "{{ web_server }}"
[root@Ansible test]# ansible-playbook test.yml -e "web_server=vsftpd"    #定义变量并运行
 
[root@Ansible test]# cat test.yml
- hosts: web_group
  tasks:
    - name: install httpd server
      yum:
        name: 
          - "{{ web_server }}"
          - "{{ db_server }}"
[root@Ansible test]# ansible-play test.yml -e "web_server=vsftpd" -e "db_server=mariadb-server"    #定义多个变量并运行

2、剧本头部进行定义

#1、将一个列表定义在一个变量中
[root@Ansible test]# cat test.yml
- hosts: web_group
  vars:
    packages:
      - httpd
      - mariadb-server
      - php
      - php-mysql
      - php-pdo
  tasks:
    - name: install httpd ....
      yum: 
        name: "{{ packages }}"
 
#2、以列表的形式,一个内容定义一个变量
[root@Ansible test]# cat test.yml
- hosts: web_group
  vars:
    - web_server: httpd
    - db_server: mariadb-server
    - php_server: php,php-mysql,php-pdo
  tasks:
    - name: install httpd ....
      yum: 
        name: 
          - "{{ web_server }}"
          - "{{ db_server }}"
          - "{{ php_server }}"
 
#3、以列表形式,多个内容由多个变量定义,最后拼接调用
[root@Ansible test]# cat test.yml
- hosts: web_group
  vars:
    - ngx_ver: 1.1
    - ngx_dir: web
  tasks:
    - name: touch
      file: 
        name: /tmp/{{ ngx_dir }}_{{ ngx_ver }}
        state: touch
 
#获取并调用Ansible内置变量
[root@Ansible test]# cat test.yml
- hosts: web_group
  vars: 
    - remote_ip: "{{ ansible_default_ipv4['address'] }}"
    - remote_hostname: "{{ ansible_fqdn }}"
  tasks:
    - name: touch ip file
      file:
        path: /root/{{ remote_ip }}
        state: touch
    - name: touch hostname file
      file:
        path: /root/{{ remote_hostname }}
        state: touch

3、文件中定义变量

剧本头部定义变量的缺陷是只能当前变量使用,其他文件无法调用该变量文件,我们可以在文件中定义变量解决这个问题。

[root@Ansible test]# cat vars1.yml 
web_server: httpd
[root@Ansible test]# cat vars2.yml 
db_server: mariadb-server
[root@Ansible test]# cat test.yml    #调用一个文件一个变量
- hosts: web_group
  vars_files: vars1.yml
  tasks:
    - name: install httpd mariadb
      yum:
        name: "{{ web_server }}"    
 
[root@Ansible test]# cat test.yml    #调用多个文件多个变量
- hosts: web_group
  vars_files: 
    - vars1.yml
    - vars2.yml
  tasks:
    - name: install httpd mariadb
      yum:
        name: 
          - "{{ web_server }}"
          - "{{ db_server }}"

4、主机清单定义变量

在主机清单中定义变量,主机的变量要高于主机组的变量,所以该方法不推荐使用,容易将环境弄乱。

[root@Ansible test]# vim /etc/ansible/hosts
[web_group]
web01 ansible_ssh_host=10.0.0.7
web02 ansible_ssh_host=10.0.0.8
[web_group:vars]
web_server=httpd
index_file=index.html
 
- hosts: web_group
  tasks:
  - name: Install httpd Server
    yum:
      name: "{{ web_server }}"
  - name: Create Index File
    file:
      path: /tmp/{{ index_file }}
      state: touch 

5、官方推荐定义变量

创建group_vars目录,在里面创建以组名命名的文件给组定义变量,想对所有主机进行定义使用组下面的all进行定义;

[root@Ansible test]# mkdir group_vars
[root@Ansible test]# cat group_vars/web_group 
web_server: httpd
[root@Ansible test]# cat test.yml 
- hosts: web_group
  tasks:
    - name: install httpd server
      yum:
        name: "{{ web_server }}"

创建host_vars目录,在里面创建以主机别名命名的文件给主机定义变量。

[root@Ansible test]# mkdir host_vars
[root@Ansible test]# cat host_vars/web01
web_server: nginx
[root@Ansible test]# cat test.yml 
- hosts: web_group
  tasks:
    - name: install httpd server
      yum:
        name: "{{ web_server }}"

变量优先级

变量读取优先级命令行>playbook文件>主机列表文件

注意:尽量使用一种定义变量的方式进行定义,太乱了不容易操作

facts缓存变量

Ansible facts是在客户端主机通过Ansible自动采集发现的变量,facts包含每台特定的主机信息,如被控端的主机名、IP地址、系统版本、CPU数量、内存状态、磁盘状态等等。

1、fasts使用场景

1、通过facts缓存检查CPU,来生成对应的nginx配置文件

2、通过facts缓存检查主机名,生成不同的zabbix配置文件

3、通过facts缓存检索物理机的内存大小来生成不同的mysql配置文件

Ansible facts类似于saltstack中的grains,对于做自动化非常有帮助!

2、fasts用法

[root@Ansible test]# cat test.yml 
- hosts: web_group
  vars_files: vars_file.yml
  tasks:
    - name: get host info
      debug:
        msg: Hostname "{{ ansible_fqdn }}" and IP "{{ an
sible_default_ipv4.address }}"
         
[root@Ansible test]# ansible-playbook test.yml 
 
PLAY [web_group] ***************************************************************
 
TASK [Gathering Facts] *********************************************************
ok: [web01]
ok: [web02]
 
TASK [get host info] ***********************************************************
ok: [web01] => {
    "msg": "Hostname \"Web01\" and IP \"10.0.0.7\""
}
ok: [web02] => {
    "msg": "Hostname \"Web02\" and IP \"10.0.0.8\""
}
 
PLAY RECAP *********************************************************************
web01                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web02                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 
[root@Ansible test]# 

3、关闭fasts

[root@Ansible test]# cat test.yml
- hosts: web_group
  gather_facts: no #关闭信息采集
  vars_files: vars_file.yml
  tasks:
    - name: get host info
      debug:
        msg: Hostname "{{ ansible_fqdn }}" and IP "{{ ansible_default_ipv4.address }}"

4、fasts生成zabbix配置文件

[root@Ansible test]# cat test.yml
- hosts: web_group
  vars: 
    - zabbix_server: 172.16.1.71
  tasks:
    - name: copy zabbix agent conf
      template:                    #作用是将管理机上的变量内容覆盖到客户机的目标配置文件中
        src: ./zabbix_agentd.conf
        dest: /tmp/zabbix_agentd.conf

5、facts生成mysqld配置文件

- hosts: db_group
  tasks:
    - name: Install mysql server
      yum:
        name: mariadb-server
        state: present
 
    - name: copy mysql  conf
      template:                  #作用是将管理机上的变量内容覆盖到客户机的目标配置文件中
        src: ./my.cnf
        dest: /etc/my.cnf
 
[root@m01 ~]# vim /etc/my.cnf
[mysqld]
basedir=/usr
datadir=/var/lib/mysql/
socket=/var/lib/mysql/mysql.sock
log_error=/var/log/mariadb/mariadb.log
innodb_buffer_pool_size={{ ansible_memtotal_mb * 0.8 }}

变量注册

Ansible模块运行时,都会返回一些result结果,类似于执行脚本,我们需要获取这些结果,判断上一步是否执行成功,默认情况下,Ansible的result并不会显示出来,所以,我们可以吧这些返回值储存到变量之中,通过调用对应的变量名,获取这些result。这种将模块的返回值写入到变量的方法叫做变量注册。

[root@Ansible test]# cat test.yml 
- hosts: web_group
  tasks:
    - name: test register vars
      shell: "ls -l /"
[root@Ansible test]# ansible-playbook test.yml    
 
PLAY [web_group] ***************************************************************
 
TASK [Gathering Facts] *********************************************************
ok: [web01]
ok: [web02]
 
TASK [test register vars] ******************************************************
changed: [web02]
changed: [web01]
 
PLAY RECAP *********************************************************************
web01                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web02                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

当我们执行这个命令时,并不会给我们返回结果,只会返回changed是否改变,这时我们使用变量注册

[root@Ansible test]# cat test.yml 
- hosts: web_group
  tasks:
    - name: test register vars
      shell: "ls -l /"
      register: list_dir
    - name: return result
      debug:
        msg: "{{ list_dir }}"                                                
    
[root@Ansible test]# ansible-playbook test.yml
 
PLAY [web_group] ***************************************************************
 
TASK [Gathering Facts] *********************************************************
ok: [web01]
ok: [web02]
 
TASK [test register vars] ******************************************************
changed: [web02]
changed: [web01]
 
TASK [return result] ***********************************************************
ok: [web01] => {
    "msg": {
        "changed": true, 
        "cmd": "ls -l /", 
        "delta": "0:00:00.073085", 
        "end": "2023-04-19 17:30:57.892956", 
        "failed": false, 
        "rc": 0, 
        "start": "2023-04-19 17:30:57.819871", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "total 24\nlrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin\ndr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot\ndrwxr-xr-x    6 www  www    71 Apr 19 08:29 code\ndrwxr-xr-x   19 root root 3200 Apr 18 22:37 dev\ndrwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 home\nlrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib\nlrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 media\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 mnt\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 opt\ndr-xr-xr-x  128 root root    0 Mar 30 20:56 proc\ndr-xr-x---.   5 root root  197 Apr 19 16:25 root\ndrwxr-xr-x   29 root root  740 Apr 19 17:20 run\nlrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 srv\ndr-xr-xr-x   13 root root    0 Apr 19 17:28 sys\ndrwxrwxrwt.  11 root root 4096 Apr 19 17:30 tmp\ndrwxr-xr-x.  13 root root  155 Mar 29 18:30 usr\ndrwxr-xr-x.  21 root root 4096 Apr 19 17:20 var", 
        "stdout_lines": [
            "total 24", 
            "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin", 
            "dr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot", 
            "drwxr-xr-x    6 www  www    71 Apr 19 08:29 code", 
            "drwxr-xr-x   19 root root 3200 Apr 18 22:37 dev", 
            "drwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 home", 
            "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib", 
            "lrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 media", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 mnt", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 opt", 
            "dr-xr-xr-x  128 root root    0 Mar 30 20:56 proc", 
            "dr-xr-x---.   5 root root  197 Apr 19 16:25 root", 
            "drwxr-xr-x   29 root root  740 Apr 19 17:20 run", 
            "lrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 srv", 
            "dr-xr-xr-x   13 root root    0 Apr 19 17:28 sys", 
            "drwxrwxrwt.  11 root root 4096 Apr 19 17:30 tmp", 
            "drwxr-xr-x.  13 root root  155 Mar 29 18:30 usr", 
            "drwxr-xr-x.  21 root root 4096 Apr 19 17:20 var"
        ]
    }
}
ok: [web02] => {
    "msg": {
        "changed": true, 
        "cmd": "ls -l /", 
        "delta": "0:00:00.072887", 
        "end": "2023-04-19 17:30:57.891135", 
        "failed": false, 
        "rc": 0, 
        "start": "2023-04-19 17:30:57.818248", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "total 28\nlrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin\ndr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot\ndrwxr-xr-x    6 www  www    71 Apr 19 08:29 code\ndrwxr-xr-x   19 root root 3200 Apr 18 22:37 dev\ndrwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 home\nlrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib\nlrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 media\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 mnt\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 opt\ndr-xr-xr-x  127 root root    0 Mar 30 20:56 proc\ndr-xr-x---.   5 root root 4096 Apr 19 16:25 root\ndrwxr-xr-x   29 root root  740 Apr 19 17:20 run\nlrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin\ndrwxr-xr-x.   2 root root    6 Apr 11  2018 srv\ndr-xr-xr-x   13 root root    0 Apr 19 17:28 sys\ndrwxrwxrwt.  10 root root 4096 Apr 19 17:30 tmp\ndrwxr-xr-x.  13 root root  155 Mar 29 18:30 usr\ndrwxr-xr-x.  21 root root 4096 Apr 19 17:20 var", 
        "stdout_lines": [
            "total 28", 
            "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin", 
            "dr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot", 
            "drwxr-xr-x    6 www  www    71 Apr 19 08:29 code", 
            "drwxr-xr-x   19 root root 3200 Apr 18 22:37 dev", 
            "drwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 home", 
            "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib", 
            "lrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 media", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 mnt", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 opt", 
            "dr-xr-xr-x  127 root root    0 Mar 30 20:56 proc", 
            "dr-xr-x---.   5 root root 4096 Apr 19 16:25 root", 
            "drwxr-xr-x   29 root root  740 Apr 19 17:20 run", 
            "lrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin", 
            "drwxr-xr-x.   2 root root    6 Apr 11  2018 srv", 
            "dr-xr-xr-x   13 root root    0 Apr 19 17:28 sys", 
            "drwxrwxrwt.  10 root root 4096 Apr 19 17:30 tmp", 
            "drwxr-xr-x.  13 root root  155 Mar 29 18:30 usr", 
            "drwxr-xr-x.  21 root root 4096 Apr 19 17:20 var"
        ]
    }
}
 
PLAY RECAP *********************************************************************
web01                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web02                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

内容很多,我们让其输入自己想要的内容

[root@Ansible test]# cat test.yml
- hosts: web_group
  tasks:
    - name: test register vars
      shell: "ls -l /"
      register: list_dir
    - name: return result
      debug:
        msg: "{{ list_dir.stdout_lines }}"
[root@Ansible test]# ansible-playbook test.yml 
 
PLAY [web_group] ***************************************************************
 
TASK [Gathering Facts] *********************************************************
ok: [web02]
ok: [web01]
 
TASK [test register vars] ******************************************************
changed: [web02]
changed: [web01]
 
TASK [return result] ***********************************************************
ok: [web01] => {
    "msg": [
        "total 24", 
        "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin", 
        "dr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot", 
        "drwxr-xr-x    6 www  www    71 Apr 19 08:29 code", 
        "drwxr-xr-x   19 root root 3200 Apr 18 22:37 dev", 
        "drwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 home", 
        "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib", 
        "lrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 media", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 mnt", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 opt", 
        "dr-xr-xr-x  129 root root    0 Mar 30 20:56 proc", 
        "dr-xr-x---.   5 root root  197 Apr 19 16:25 root", 
        "drwxr-xr-x   29 root root  740 Apr 19 17:20 run", 
        "lrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 srv", 
        "dr-xr-xr-x   13 root root    0 Apr 19 17:28 sys", 
        "drwxrwxrwt.  11 root root 4096 Apr 19 17:34 tmp", 
        "drwxr-xr-x.  13 root root  155 Mar 29 18:30 usr", 
        "drwxr-xr-x.  21 root root 4096 Apr 19 17:20 var"
    ]
}
ok: [web02] => {
    "msg": [
        "total 28", 
        "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 bin -> usr/bin", 
        "dr-xr-xr-x.   5 root root 4096 Mar 29 18:41 boot", 
        "drwxr-xr-x    6 www  www    71 Apr 19 08:29 code", 
        "drwxr-xr-x   19 root root 3200 Apr 18 22:37 dev", 
        "drwxr-xr-x.  90 root root 8192 Apr 19 17:20 etc", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 home", 
        "lrwxrwxrwx.   1 root root    7 Mar 29 18:30 lib -> usr/lib", 
        "lrwxrwxrwx.   1 root root    9 Mar 29 18:30 lib64 -> usr/lib64", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 media", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 mnt", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 opt", 
        "dr-xr-xr-x  127 root root    0 Mar 30 20:56 proc", 
        "dr-xr-x---.   5 root root 4096 Apr 19 16:25 root", 
        "drwxr-xr-x   29 root root  740 Apr 19 17:20 run", 
        "lrwxrwxrwx.   1 root root    8 Mar 29 18:30 sbin -> usr/sbin", 
        "drwxr-xr-x.   2 root root    6 Apr 11  2018 srv", 
        "dr-xr-xr-x   13 root root    0 Apr 19 17:28 sys", 
        "drwxrwxrwt.  10 root root 4096 Apr 19 17:34 tmp", 
        "drwxr-xr-x.  13 root root  155 Mar 29 18:30 usr", 
        "drwxr-xr-x.  21 root root 4096 Apr 19 17:20 var"
    ]
}
 
PLAY RECAP *********************************************************************
web01                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web02                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

debug模块常用参数

msg #调试输出的消息

var #将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出

verbosity #debug的级别(默认是0,全部显示)

ansible任务控制

handlers

Handlers 是一个触发器,也可以理解成是一个特殊的 tasks,与tasks同级,因为他无法直接运行,handlers中的任务会被tasks中的任务进行“调用”,但是,被调用,并不意味着一定会执行,只有当tasks中的任务真正执行以后(真正的操作,发生了改变),handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers即使被调用,也不会执行。

注意:

  • 无论多少个tasks通知了相同的handlers,handlers仅会在所有tasks结束后运行一次,除非调用meta 模块,立即执行的意思

  • 只有tasks发生改变了才会通知handlers,没有改变则不会触发handlers

  • 不能使用handlers替代tasks,因为handlers是一个特殊的tasks。

实例:

假设我们想要将httpd的端口从80改成8088,并且在修改配置以后重启httpd,那么我们可以编写如下剧本。

[root@server4 ~]# vim httpd.yml

---
- hosts: testB
  remote_user: root
  tasks:
  - name: Modify the configuration
    lineinfile:
      path=/etc/httpd/conf/httpd.conf
      regexp="Listen 80"
      line="Listen 8088"
      backrefs=yes
      backup=yes
  - name: restart httpd
    service:
      name=httpd
      state=restarted

上述play表示修改testB主机的/etc/httpd/conf/httpd.conf配置文件,将监听端口80改为监听端口8088,端口修改完成后,重启服务。

在执行这个playbook之前,我们先来确认一下testB(server3)主机的8080端口是否被监听

可以看到testB(server3)主机上的80正常被监听,那么现在我们来执行一下上述playbook,看一下执行效果

[root@server4 ~]# ansible-playbook httpd.yml
1

执行后可以看到,play中的两个任务都被正常执行了,如下图所示

此时再次查看test70主机的端口号,已经从80改为8088,如下图所示

这样没有任何问题,与我们预期的一样,端口号从80修改为8088,重启了服务

那么,我们再来重复执行一遍上述playbook试试,看看会出现什么情况,重复执行效果如下

如上图所示,当我们再次执行同样的playbook时,由于配置文件中的端口号已经是8088,所以,任务”Modify the configuration”的状态为OK(换句话说,这个任务并没有在远程主机进行任何实际操作),这是由于ansible的幂等性造成的(前文已经对幂等性做出了解释,此处不再赘述),因为目标状态与我们预期的状态一致,所以ansible并没有做任何改动,这是完全正常的,从上图可以看出,任务”restart httpd”也正常的执行了,而且是”真正的”执行了,换句话说就是它的确重启了对应的nginx服务,对远程主机进行了实际的操作。

第二次运行剧本的过程似乎没有什么问题,但是仔细想想,又有些不妥,因为我们重启服务的目的是为了在修改配置文件以后使新的配置生效,而第二次运行剧本的这种情况下,我们并没有真正修改服务器配置,因为服务器配置本来 就与我们预期的一致,但是,在没有修改配置的情况下,仍然重启了服务,这种重启是不需要的,我们想要达到的效果是,如果配置文件发生了改变,则重启服务,如果配置文件并没有被真正的修改,则不对服务进行任何操作,这种情况下,我们该怎们办呢?

handlers就是来解决这种问题的,此处我们先大概的描述一下handlers的概念,后面会给出示例

handlers的概念

你可以把handlers理解成另一种tasks,handlers是另一种’任务列表’,handlers中的任务会被tasks中的任务进行”调用”,但是,被”调用”并不意味着一定会执行,只有当tasks中的任务”真正执行”以后(真正的进行实际操作,造成了实际的改变),handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被’调用’,也并不会执行。这样说似乎不容易被理解,我们来写一个小示例,示例如下。

[root@server4 ~]# vim handlers_test.yml

---
- hosts: testB
  remote_user: root
  tasks:
  - name: Modify the configuration
    lineinfile:
      path=/etc/httpd/conf/httpd.conf
      regexp="Listen 80"
      line="Listen 8088"
      backrefs=yes
      backup=yes
    notify:
      restart nginx

  handlers:
  - name: restart httpd
    service:
      name=httpd
      state=restarted

如上例所示,我们使用handlers关键字,指明哪些任务可以被’调用’,之前说过,handlers是另一种任务列表,你可以把handlers理解成另外一种tasks,你可以理解成它们是’平级’的,所以,handlers与tasks是’对齐’的(缩进相同),上例中的handlers中只有一个任务,这个任务的名称为”restart httpd”,之前也说明过,handlers中的任务需要被tasks中的任务调用,那么上例中,”restart httpd”被哪个任务调用了呢?很明显,”restart httpd”被”Modify the configuration”调用了,没错,如你所见,我们使用notify关键字’调用’handlers中的任务,或者说,通过notify关键字’通知’handlers中的任务,所以,综上所述,上例中的play表示,如果”Modify the configuration”真正的修改了配置文件(实际的操作),那么则执行”restart httpd”任务,如果”Modify the configuration”并没有进行任何实际的改动,则不执行”restart httpd” ,这就是handlers的作用。

handlers中的多任务实现

handlers是另一种任务列表,所以handlers中可以有多个任务,被tasks中不同的任务notify,示例如下

[root@server4 ~]# vim mkdir.yml


---
- hosts: testB
  remote_user: root
  tasks:
  - name: make testfile1
    file: path=/testdir/testfile1
          state=directory
    notify: ht2
  - name: make testfile2
    file: path=/testdir/testfile2
          state=directory
    notify: ht1

  handlers:
  - name: ht1
    file: path=/testdir/ht1
          state=touch
  - name: ht2
    file: path=/testdir/ht2
          state=touch
1234567891011121314151617181920212223

如上例所示,tasks与handlers都是任务列表,只是handlers中的任务被tasks中的任务notify罢了,那么我们来执行一下上述playbook,如下图所示

从上图可以看出,handler执行的顺序与handler在playbook中定义的顺序是相同的,与”handler被notify”的顺序无关。

meta模块

如上图所示,默认情况下,所有task执行完毕后,才会执行各个handler,并不是执行完某个task后,立即执行对应的handler,如果你想要在执行完某些task以后立即执行对应的handler,则需要使用meta模块,示例如下

[root@server4 ~]# vim mkdir2.yml 
[root@server4 ~]# cat mkdir2.yml 
---
- hosts: testB
  remote_user: root
  tasks:
  - name: task1
    file: path=/testdir/testfile
          state=touch
    notify: handler1
  - name: task2
    file: path=/testdir/testfile2
          state=touch
    notify: handler2

  - meta: flush_handlers

  - name: task3
    file: path=/testdir/testfile3
          state=touch
    notify: handler3

  handlers:
  - name: handler1
    file: path=/testdir/ht1
          state=touch
  - name: handler2
    file: path=/testdir/ht2
          state=touch
  - name: handler3
    file: path=/testdir/ht3
          state=touch

如上例所示,我在task1与task2之后写入了一个任务,我并没有为这个任务指定name属性,这个任务使用meta模块,meta任务是一种特殊的任务,meta任务可以影响ansible的内部运行方式,上例中,meta任务的参数值为flush_handlers,”meta: flush_handlers”表示立即执行之前的task所对应handler,什么意思呢?意思就是,在当前meta任务之前,一共有两个任务,task1与task2,它们都有对应的handler,当执行完task1与task2以后,立即执行对应的handler,而不是像默认情况那样在所有任务都执行完毕以后才能执行各个handler,那么我们来实际运行一下上述剧本,运行结果如下

正如上图所示,meta任务之前的任务task1与task2在进行了实际操作以后,立即运行了对应的handler1与handler2,然后才运行了task3,在所有task都运行完毕后,又逐个将剩余的handler根据情况进行调用。

如果想要每个task在实际操作后都立马执行对应handlers,则可以在每个任务之后都添加一个meta任务,并将其值设置为flush_handlers。 所以,我们可以依靠meta任务,让handler的使用变得更加灵活。

一个task中调用多个handler

我们还可以在一个task中一次性notify多个handler,怎样才能一次性notify多个handler呢?你可能会尝试将多个handler使用相同的name,但是这样并不可行,因为当多个handler的name相同时,只有一个handler会被执行,所以,我们并不能通过这种方式notify多个handler,如果想要一次notify多个handler,则需要借助另一个关键字,它就是’listen’,你可以把listen理解成”组名”,我们可以把多个handler分成”组”,当我们需要一次性notify多个handler时,只要将多个handler分为”一组”,使用相同的”组名”即可,当notify对应的值为”组名”时,”组”内的所有handler都会被notify,这样说可能还是不容易理解,我们来看个小示例,示例如下

[root@server4 ~]# vim mkdir3.yml 
[root@server4 ~]# cat mkdir3.yml 
---
- hosts: testB
  remote_user: root
  tasks:
  - name: task1
    file: path=/testdir/testfile
          state=touch
    notify: handler group1

  handlers:
  - name: handler1
    listen: handler group1
    file: path=/testdir/ht1
          state=touch
  - name: handler2
    listen: handler group1
    file: path=/testdir/ht2
          state=touch

运行这个play:

如上例所示,handler1与handler2的listen的值都是handler group1,当task1中notify的值为handler group1时,handler1与handler2都会被notify,还是很方便的。

除条件判断外, 另一种分支控制结构是循环结构。Ansible提供了很多种循环结构,一般都命名为with_Xxx,例如with_items、with_list、with_file等,使用最多的是with_items。

循环:迭代,需要重复执行的任务


对迭代项的引用,固定变量名为"item”,使用with_item属性给定要迭代的元素; 这个是以任务为中心,围绕每个任务来跑主机,如果中间某个任务中断,那么所有主机以后的任务就无法安装。

元素:

  • 列表

  • 字符串

  • 字典

基于字符串列表给出元素示例:

 -    hosts: websrvs
    remote_user: root
    tasks:
    - name: install packages
      yum: name={{ item }} state=latest
      with_items:
      - httpd
      - php
      - php-mysql
      - php-mbstring
      - php-gd

基于字典列表给元素示例:item.name .后边的表示键

- hosts: all
  remote_user: root
  tasks:
  - name: create groups
    group: name={{ item }} state=present
    with_items:
    - groupx1
    - groupx2
    - groupx3
  - name: create users
    user: name={{ item.name }} group={{ item.group }} state=present
    with_items:
    - {name: 'userx1', group: 'groupx1'}
    - {name: 'userx2', group: 'groupx2'}
    - {name: 'userx3', group: 'groupx3'}

loop循环


在这里仅介绍loop循环,它是在Ansible 2.5版本中新添加的循环结构,等价于with_list。大多数时候,with_xx的循环都可以通过一定的手段转换成loop循环,所以从Ansible 2.5版本之后,原来经常使用的with_items循环都可以尝试转换成loop。有了循环结构,生活就美妙多了。

例如,在locahost上创建两个文件/tmp/test/{1,2}.txt,不用循环结构的playbook写法∶

[root@localhost ~]# cat loop.yml 
---
- name: play1
  hosts: all
  remote_user: root
  gather_facts: false
  tasks:
   - name: create file /tmp/test1.txt
     file: path=/tmp/test1.txt state=touch
   - name: create file /tmp/test2.txt
     file: path=/tmp/test2.txt state=touch
 
[root@localhost ~]# ansible-playbook loop.yml 
 
PLAY [play1] ***************************************************************************************************
 
TASK [create file /tmp/test1.txt] ******************************************************************************
changed: [192.168.179.99]
 
TASK [create file /tmp/test2.txt] ******************************************************************************
changed: [192.168.179.99]
 
PLAY RECAP *****************************************************************************************************
192.168.179.99             : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

使用loop写法

[root@localhost ~]# cat loop.yml 
---
- name: play1
  hosts: all
  remote_user: root
  gather_facts: false
  tasks:
   - name: create file /tmp/test1.txt
     file: path={{item}} state=touch
     loop:
       - /tmp/test1.txt
       - /tmp/test2.txt
 
 
[root@localhost ~]# ansible-playbook loop.yml 
 
PLAY [play1] ***************************************************************************************************
 
TASK [create file /tmp/test1.txt] ******************************************************************************
changed: [192.168.179.99]
 
TASK [create file /tmp/test2.txt] ******************************************************************************
changed: [192.168.179.99]
 
PLAY RECAP *****************************************************************************************************
192.168.179.99             : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 

显然,循环可以将多个任务组织成单个任务并实现相同的功能。解释下上面的loop和{{item}}。

loop等价于with_list,从名字上可以知道它是遍历数组(列表)的,所以在loop指令中,每个元素都以列表的方式去定义。列表有多少个元素,就循环执行file模块多少次,每轮循环中,都会将本次迭代的列表元素保存在控制变量 item中。

或许,使用伪代码去解释这个loop结构会更容易理解,这里我使用shell伪代码来演示∶

for item in /tmp/test1.txt /tmp/test2.txt; do touch $(item) ;done

当有需要重复性执行的任务时,可以使用迭代机制。其使用格式为将需要迭代的内容定义为item变量引用,并通过loop语句指明迭代的元素列表即可。loop的值是python list数据结构,每个task会循环读取list的值,然后后key的名称是item,list里面也支持python字典。

示例一:安装多个软件

tasks:
  - name: "Install Packages"
    yum: name={{ item }}  state=latest
    loop:
      - httpd
      - mysql-server
      - php

示例二:批量创建多个用户。(with_items)

tasks:
    - name: "add user"
      user: name={{ item.name }} state=present groups={{ item.groups }}
      loop:
        - {name: "test5", groups: "tom"}
        - {name: "test6", groups: "tom"}

其中引用变量时前缀item变量是固定的,而item后跟的键名就是在loop中定义的字典键名。

自己写的示例:

#单个用户变量变量
[root@localhost ansible]# cat host_vars/linux2 
lamp:
  - httpd
  - mariadb
  - php
link:
  - php-mysql
  - mariadb-server

#playbook文件
[root@localhost ansible]# cat host_vars/linux2
---
- hosts: linux2
  remote_user: root
  tasks:
    - name: print
      debug:
        msg: '要安装的服务有{{ lamp }},{{ link }}'
    - name: install nginx php mysql-server
      yum:
        name: '{{ item }}'
        state: present
      loop: '{{ lamp }}'
    - name: link  php-mysql-server
      yum:
        name: '{{ item }}'
        state: present
      loop: '{{ link }}'
    - name: copy httpd.conf
      copy:
        src: ./httpd.conf
        dest: /etc/httpd/conf/
        backup: yes
        notify: reload httpd
    - name: copy index.html
      copy:
        src: ./index.html
        dest: /var/www/html
  handlers:
  - name: reload httpd
    service:
      name: '{{ lamp }}'
      state: restarted

when条件判断


Ansible作为一个编排、协调任务的配置管理工具,它必不可少的功能是提供流程控制功能,比如条件判断、循环、退出等。

在Ansible中,提供的唯——个通用的条件判断是when指令,当when指令的值为true 时,则该任务执行,否则不执行该任务。例如∶

[root@localhost ~]# cat when.yml 
---
- name: play1
  hosts: all
  remote_user: root
  gather_facts: false
  vars:
    myname: test
  tasks:
  - name: task will skip
    debug: msg={{myname}}
    when: myname == "test1"
  
  - name: task will execute
    debug: msg={{myname}}
    when: myname == "test"
 
 
[root@localhost ~]# ansible-playbook when.yml 
 
PLAY [play1] ************************************************************************************************************************
 
TASK [task will skip] ***************************************************************************************************************
skipping: [192.168.179.99]
 
TASK [task will execute] ************************************************************************************************************
ok: [192.168.179.99] => {
    "msg": "test"
}
 
PLAY RECAP **************************************************************************************************************************
192.168.179.99             : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   

需要注意的是,when指令因为已经明确是做条件判断,,所以它的值必定是一个表达式,它会自动隐式地帮我们包围一层{{}},所以在写when指令的条件判断时,不要再手动加上{{}}。正如上面示例中的写法when∶myname =="test",如果改写成when∶{{myname ="junma"}},这表示表达式的嵌套,通常来说不会出错,但在某些场景下是错误的,而且Ansible会给我们警告。

一个比较常见的应用场景是实现跳过某个主机不执行任务或者只有满足条件的主机执行任务。例如∶

when: inventory_hostname =="web1"

这表示只有inventory中设置的主机web1才执行任务,其它主机都不执行。

示例:

Nginx启动逻辑欠缺考虑。 若Nginx的配置文件语法错误则会导致启动Nginx失败,以至于PlayBook执行失败。

如果我们能够在启动之前去对Nginx的配置文件语法做正确性的校验,只有当校验通过的时候我们才去 启动或者重启Nginx;否则则跳过启动Nginx的过程。这样就会避免Nginx 配置文件语法问题而导致的 无法启动Nginx的风险。 Nginx 语法校验

- name: check nginx syntax
  shell: /usr/sbin/nginx -t

那如何将Nginx语法检查的TASK同Nginx启动的TASK关联起来呢? 如果我们能够获得语法检查的TASK的结果,根据这个结果去判断“启动NGINX的TASK”是否执行,这将 是一个很好的方案。 如何和获取到语法检查TASK的结果呢? 此时就可以使用之前学到的 Ansible中的注册变量。 获取Task任务结果

- name: check nginx syntax
  shell: /usr/sbin/nginx -t
  register: nginxsyntax

此时有可能还有疑问,我获取到任务结果,但是结果里面的内容是个什么样子, 我如何根据内容在后续 的PlayBook中使用呢? 通过debug模块去确认返回结果的数据结构

- name: print nginx syntax result
  debug: var=nginxsyntax

通过debug 模块,打印出来的返回结果。 当nginxsyntax.rc 为 0 时语法校验正确。

通过条件判断(when) 指令去使用语法校验的结果

- name: check nginx syntax
  shell: /usr/sbin/nginx -t
  register: nginxsyntax
- name: print nginx syntax
  debug: var=nginxsyntax
- name: start nginx server
  service: name=nginx state=started
  when: nginxsyntax.rc == 0   ##等于0才会启动nginx

示例一:只有当vsftpd服务运行时,才能重启Apache

---
- name: Restart httpd if vsftpd is running
  hosts: all
  tasks:
    - name: Get vsftpd status
      command: /usr/bin/systemctl is-active vsftpd	#判断状态
      ignore_errors: yes	#如果vsftpd没运行或失败,则忽略,继续执行下面动作
      register: result		#定义变量保存结果
    - name: Restart httpd
      service:
        name: httpd
        state: restarted
      when: result.rc == 0  	#退出码为0,则重启httpd
...   

示例二:安装并启动数据库

编写playbook:

############基础写法################
- name: Mariadb is running
  hosts: web1
  vars:
    mariadb_pkgs:
      - mariadb-server
      - python3-PyMySQL
  tasks:
    - name: Mariadb is installed
      yum:
        name: "{{ item }}"
        state: present
      loop: "{{ mariadb_pkgs }}"
    - name: Start Mariadb
      service:
        name: mariadb
        state: started
        enabled: true
#修改 playbook,条件变为受管主机使用 rehdat 操作系统时才执行########
---
- name: Mariadb is running
  hosts: web1
  vars:
    mariadb_pkgs:
      - mariadb-server
      - python3-PyMySQL
  tasks:
    - name: Mariadb is installed
      yum:
        name: "{{ item }}"
        state: present
      loop: "{{ mariadb_pkgs }}"
      when: ansible_distribution == "RedHat"	#更改条件
...     

最后,虽然when指令的逻辑很简单∶值为true则执行任务,否则不执行任务。但是,它的用法并不简单,when指令的值可以是Jinja2的表达式,很多内置在Jinja2中的Python的语法都可以用在when指令中,而这需要掌握Python的基本语法。如果不具备这些知识,那么想要实现某种判断功能可能会感觉到较大的局限性,而且别人写的脚本可能看不懂。但是我的建议是,别强求,掌握常用的条件判断方式,以后有需求再去网上搜索对应用法。

tags

tag用于标记一个或多个任务(task)或是一个或多个角色(role),以便在执行playbook时只运行被标记的任务或角色。这可以帮助我们精细控制playbook的执行范围,只执行我们想要执行的任务或角色,提高执行效率和安全性。

  • 一个任务打一个标签

  • 一个任务打多个标签

  • 多个任务打一个标签

使用标签时:

  • -t:指定某个标签

  • –skip-tags:执行除此标签外的所有标签

示例:

- name: install nginx
  yum:
    name: nginx
    state: latest
  tags: 
    - nginx
ansible-playbook playbook.yml --tags nginx

什么时候使用:

Ansible的标签(tag)用于标记一组任务或一部分特定的操作,使得我们可以在运行Ansible Playbook的时候只运行被标记的任务,而跳过其它的任务。标签可以用于不同的场景:

  1. 部分更新:如果你只想更新一部分主机或者一部分任务,你可以给这些主机和任务打上相应的标签,然后只运行带这些标签的任务。这样可以避免无需的更新和不必要的执行。

  2. 调试:当你遇到一些问题,需要逐步调试任务,你可以给需要调试的一部分任务打上标签,然后只运行带有标签的任务,这样可以快速定位问题。

  3. 条件执行:当你需要在一些特定的条件下才运行某些任务时,你可以使用标签来控制,只有满足条件的任务才会被执行。

需要注意的是,标签并不是必须的,只有在需要的情况下才需要使用。

Include:任务复用

Include用于将一个或多个文件或任务列表包含到当前任务或playbook中。它可以帮助我们组织和重用任务和playbook,提高代码的可读性和可维护性。

比如:A项目需要重启某服务,B项目也需要重启这个服务。那么就可以使用Include来减少工作量

案例:多任务调用相同task

  • 定义一个restart的yml文件

vim restart_nginx.yml

- name: Restart nginx
  systemd:
    name: nginx
    state: restarted
  • A项目调用restart_nginx.yml

- hosts: web
  tasks:
    - name: A project
      command: echo "A"
    - name: Restart nginx
      include: restart_nginx.yml
  • B项目调用restart_nginx.yml

- hosts: web
  tasks:
    - name: B project
      command: echo "B"
    - name: Restart nginx
      include: restart_nginx.yml

什么时候使用:

Ansible的include用于在playbook中引用其他文件或任务。它可以用来将大型playbook分解为可维护的模块,或在多个playbook之间共享任务和变量。

以下是一些使用include的情况:

  1. 引用其他yaml文件:当你有一个大型playbook时,它可能会变得混乱和难以维护。你可以使用include来将文件拆分成小模块,方便理解和修改。

  2. 公共任务:如果你有两个不同的playbook需要完成相同的任务,你可以使用include来避免重复代码。

  3. 动态生成任务:有时你需要根据某些条件动态生成任务。在这种情况下,你可以使用include和变量来创建基于条件的任务。

总的来说,使用include可以让你的playbook更加模块化和易于扩展,从而提高效率和可维护性。

Ignore_errors:错误处理关键字

当在执行Ansible任务时,设置"ignore_errors"参数为True,表示在执行该任务时,如果遇到错误,Ansible不会终止任务的执行,而是会将错误记录下来,然后继续执行后续任务。

这个参数通常用于在某些情况下,某些任务的失败并不会影响整个任务链的执行,需要继续执行后续任务的场景中。

示例:

---
- hosts:
  remote_user: root
  tasks:
    - name: Ignore False
      command: /bin/false
      ignore_errors: yes
    - name: touch file
      file: patch=/tmp/yyang.txt state=touch

这个示例是说,第一个name执行失败后,继续执行后面name,而不是停止。

什么时候使用:

ignore_errors 是 Ansible 中一个非常有用的参数,它用于在任务执行失败时继续处理并执行后续的任务,而不是停止执行并抛出错误。

通常当你需要在 playbook 中执行一些任务,但是不希望遇到错误时中止 playbook 的执行时,可以使用 ignore_errors 参数。例如,当你需要对多个主机进行部署时,其中某些主机可能不可达或者部署失败了,但是这些失败不应该影响其他主机的部署。

另外,ignore_errors 也可以与 register 结合使用,以在任务执行失败时仍然将其添加到变量中。这在某些情况下是非常有用的,特别是当你需要在 playbook 中记录某些操作的状态时。

但是需要注意的是,如果使用了 ignore_errors 参数,那么后续的任务将不会检查该任务的执行结果。因此,在使用 ignore_errors 时需要格外小心,确保后续的任务仍能够正常执行。

roles角色

Roles介绍

ansible1.2版本引入的新特性,用于层次性、结构化地组织playbookroles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令引入即可。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷的include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。主要使用场景代码复用度较高的情况下。

Roles目录结构#

各目录含义解释

复制代码

roles:          <--所有的角色必须放在roles目录下,这个目录可以自定义位置,默认的位置在/etc/ansible/roles
  project:      <---具体的角色项目名称,比如nginx、tomcat、php
    files:     <--用来存放由copy模块或script模块调用的文件。
    templates: <--用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件。
    tasks:     <--此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件。
      main.yml
    handlers:  <--此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作。
      main.yml
    vars:      <--此目录应当包含一个main.yml文件,用于定义此角色用到的变量。
      main.yml
    defaults:  <--此目录应当包含一个main.yml文件,用于为当前角色设定默认变量。
      main.yml
    meta:      <--此目录应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系。
      main.yml

Roles示例#

通过ansible roles安装配置httpd服务,此处的roles使用默认的路径/etc/ansible/roles

1)创建目录

复制代码

[root@ansible ~]# cd /etc/ansible/roles/
# 创建需要用到的目录
[root@ansible roles]# mkdir -p httpd/{handlers,tasks,templates,vars}
[root@ansible roles]# cd httpd/
[root@ansible httpd]# tree .
.
├── handlers
├── tasks
├── templates
└── vars

4 directories, 0 file

复制代码

2)变量文件准备vars/main.yml

[root@ansible httpd]# vim vars/main.yml
PORT: 8088        #指定httpd监听的端口
USERNAME: www     #指定httpd运行用户
GROUPNAME: www    #指定httpd运行组

3)配置文件模板准备templates/httpd.conf.j2

复制代码

# copy一个本地的配置文件放在templates/下并已j2为后缀
[root@ansible httpd]# cp /etc/httpd/conf/httpd.conf templates/httpd.conf.j2

# 进行一些修改,调用上面定义的变量
[root@ansible httpd]# vim templates/httpd.conf.j2
Listen {{ PORT }} 
User {{ USERNAME }}
Group {{ GROUPNAME }}

复制代码

4)任务剧本编写,创建用户、创建组、安装软件、配置、启动等

复制代码

# 创建组的task
[root@ansible httpd]# vim tasks/group.yml
- name: Create a Startup Group
  group: name=www gid=60 system=yes

# 创建用户的task
[root@ansible httpd]# vim tasks/user.yml
- name: Create Startup Users
  user: name=www uid=60 system=yes shell=/sbin/nologin

# 安装软件的task
[root@ansible httpd]# vim tasks/install.yml
- name: Install Package Httpd
  yum: name=httpd state=installed

# 配置软件的task
[root@ansible httpd]# vim tasks/config.yml
- name: Copy Httpd Template File
  template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  notify: Restart Httpd

# 启动软件的task
[root@ansible httpd]# vim tasks/start.yml
- name: Start Httpd Service
  service: name=httpd state=started enabled=yes

# 编写main.yml,将上面的这些task引入进来
[root@ansible httpd]# vim tasks/main.yml
- include: group.yml
- include: user.yml
- include: install.yml
- include: config.yml
- include: start.yml

复制代码

5)编写重启httpd的handlershandlers/main.yml

[root@ansible httpd]# vim handlers/main.yml
# 这里的名字需要和task中的notify保持一致
- name: Restart Httpd
  service: name=httpd state=restarted

6)编写主的httpd_roles.yml文件调用httpd角色

复制代码

[root@ansible httpd]# cd ..
[root@ansible roles]# vim httpd_roles.yml
---
- hosts: all
  remote_user: root
  roles:
    - role: httpd        #指定角色名称

复制代码

7)整体的一个目录结构查看

复制代码

[root@ansible roles]# tree .
.
├── httpd
│   ├── handlers
│   │   └── main.yml
│   ├── tasks
│   │   ├── config.yml
│   │   ├── group.yml
│   │   ├── install.yml
│   │   ├── main.yml
│   │   ├── start.yml
│   │   └── user.yml
│   ├── templates
│   │   └── httpd.conf.j2
│   └── vars
│       └── main.yml
└── httpd_roles.yml

5 directories, 10 files

复制代码

8)测试playbook语法是否正确

复制代码

[root@ansible roles]# ansible-playbook -C httpd_roles.yml 

PLAY [all] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************
ok: [192.168.1.33]
ok: [192.168.1.32]
ok: [192.168.1.31]
ok: [192.168.1.36]

TASK [httpd : Create a Startup Group] ***********************************************************************
changed: [192.168.1.31]
changed: [192.168.1.33]
changed: [192.168.1.36]
changed: [192.168.1.32]

TASK [httpd : Create Startup Users] *************************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.36]

TASK [httpd : Install Package Httpd] ************************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.36]

TASK [httpd : Copy Httpd Template File] *********************************************************************
changed: [192.168.1.33]
changed: [192.168.1.36]
changed: [192.168.1.32]
changed: [192.168.1.31]

TASK [httpd : Start Httpd Service] **************************************************************************
changed: [192.168.1.36]
changed: [192.168.1.31]
changed: [192.168.1.32]
changed: [192.168.1.33]

RUNNING HANDLER [httpd : Restart Httpd] *********************************************************************
changed: [192.168.1.36]
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]

PLAY RECAP **************************************************************************************************
192.168.1.31               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.32               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.33               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.36               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

复制代码

9)上面的测试没有问题,正式执行playbook

复制代码

[root@ansible roles]# ansible-playbook -C httpd_roles.yml 

PLAY [all] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************
ok: [192.168.1.33]
ok: [192.168.1.32]
ok: [192.168.1.31]
ok: [192.168.1.36]

TASK [httpd : Create a Startup Group] ***********************************************************************
changed: [192.168.1.31]
changed: [192.168.1.33]
changed: [192.168.1.36]
changed: [192.168.1.32]

TASK [httpd : Create Startup Users] *************************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.36]

TASK [httpd : Install Package Httpd] ************************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.36]

TASK [httpd : Copy Httpd Template File] *********************************************************************
changed: [192.168.1.33]
changed: [192.168.1.36]
changed: [192.168.1.32]
changed: [192.168.1.31]

TASK [httpd : Start Httpd Service] **************************************************************************
changed: [192.168.1.36]
changed: [192.168.1.31]
changed: [192.168.1.32]
changed: [192.168.1.33]

RUNNING HANDLER [httpd : Restart Httpd] *********************************************************************
changed: [192.168.1.36]
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]

PLAY RECAP **************************************************************************************************
192.168.1.31               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.32               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.33               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.36               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible roles]# ansible-playbook httpd_roles.yml 

PLAY [all] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************
ok: [192.168.1.32]
ok: [192.168.1.33]
ok: [192.168.1.31]
ok: [192.168.1.36]

TASK [httpd : Create a Startup Group] ***********************************************************************
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.33]
changed: [192.168.1.36]

TASK [httpd : Create Startup Users] *************************************************************************
changed: [192.168.1.31]
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.36]

TASK [httpd : Install Package Httpd] ************************************************************************
changed: [192.168.1.31]
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.36]

TASK [httpd : Copy Httpd Template File] *********************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]
changed: [192.168.1.36]

TASK [httpd : Start Httpd Service] **************************************************************************
fatal: [192.168.1.36]: FAILED! => {"changed": false, "msg": "httpd: Syntax error on line 56 of /etc/httpd/conf/httpd.conf: Include directory '/etc/httpd/conf.modules.d' not found\n"}
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]

RUNNING HANDLER [httpd : Restart Httpd] *********************************************************************
changed: [192.168.1.33]
changed: [192.168.1.32]
changed: [192.168.1.31]

PLAY RECAP **************************************************************************************************
192.168.1.31               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.32               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.33               : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.1.36               : ok=5    changed=4    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

复制代码

这里有看到报错,其原因是因为192.168.1.36这台机器是centos6系统,别的都是centos7系统,两个系统安装的httpd默认版本是不一样的,所以报错。如果需要优化。参考这里

ansible roles总结#

1、编写任务(task)的时候,里面不需要写需要执行的主机,单纯的写某个任务是干什么的即可,装软件的就是装软件的,启动的就是启动的。单独做某一件事即可,最后通过main.yml将这些单独的任务安装执行顺序include进来即可,这样方便维护且一目了然。 2、定义变量时候直接安装k:v格式将变量写在vars/main.yml文件即可,然后task或者template直接调用即可,会自动去vars/main.yml文件里面去找。 3、定义handlers时候,直接在handlers/main.yml文件中写需要做什么事情即可,多可的话可以全部写在该文件里面,也可以像task那样分开来写,通过include引入一样的可以。在task调用notify时直接写与handlers名字对应即可(二者必须高度一直)。 4、模板文件一样放在templates目录下即可,task调用的时后直接写文件名字即可,会自动去到templates里面找。注意:如果是一个角色调用另外一个角色的单个task时后,那么task中如果有些模板或者文件,就得写绝对路径了。


评论