Ansible 基础笔记

1、Ansible搭建(基于CentOS 7.9)

1.1、在控制节点和被控节点获取epel源

[root@controller ~]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

1.2、安装Ansible

[root@controller ~]# yum clean all  #清除缓存
[root@controller ~]# yum makecache    #建立缓存
[root@controller ~]# yum install -y ansible  #开始安装
[root@controller ~]# ansibel --version  #查看Ansible的版本及信息

2、理论

控制端使用ssh服务连接被控端

控制端默认使用root用户控制被控端,工作目录在用户家目录

3、基础配置

3.1、Ansible发送指令的原理

Ansible底层为Python编写,因此Ansible的各种指令在执行时都会转换成Python脚本

    1. 下发Ansible命令时,先转换成Python脚本 然后临时保存到主控端的~/.ansible/tmp目录

    1. 执行Ansible命令时,使用ssh服务将主控端~/.ansible/tmp目录下的Python脚本发送到被控端,被控端执行脚本

    1. 执行完成后,将~/.ansible/tmp下的Python脚本删除

3.2、Ansible配置文件

优先级

3.3、配置文件参数说明

3.4、编写主机清单

在主机清单中写入被控端 IP 地址或本地hosts表中解析的主机名(可直接写IP地址)

3.5、配置ssh免密及提权

3.6、Ansible操作对象

4、常用模块

4.1、shell模块

shell模块包含如下选项:

  • creates:指定一个文件名,如果该文件存在,则该命令不执行

  • removes:指定一个文件名,如果该文件不存在,则该命令执行

  • free_form:要执行的linux指令

  • chdir:在执行指令之前,先切换到该指定的目录,默认工作目录是远程主机的家目录。

  • cmd:后面接shell命令,可

4.2、command模块

  • 该模块通过-a跟上要执行的命令可以直接执行,不过命令里如果有带有如下字符部分则执行不成功 “ "", "|", "&" ;

4.3、raw模块:

  • 用法和shell模块一样,也可以执行任意命令,就像在本机执行一样;和command、shell模块不同的是其没有chdir、creates、removes参数

4.4、script模块:

  • 将管理端的shell 在被管理主机上执行,其原理是先将 shell 复制到远程主机,再在远程主机上执行,原理类似于raw模块。

4.5、authorized_keys模块

用于向被控端推送公钥,通常用于在ansible第一次连接被控端时向其推送ansible主控端的管理公钥。

常用选项:

  • user: 指定将公钥推送给被控端的哪个用户

  • key:指定被推送的公钥的内容

  • path:默认情况下,会将公钥推送至被控端用户家目录的.ssh/authorized_keys文件中,可通过该配置项自定义该路径

  • state:是推送还是删除,present|absent

4.6、file模块

file模块主要用于远程主机上的文件操作,file模块包含如下选项:

  • force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no

  • group:定义文件/目录的属组

  • mode:定义文件/目录的权限

  • owner:定义文件/目录的属主

  • path:必选项,定义文件/目录的路径

  • src:要被链接的源文件的路径,只应用于state=link的情况

  • dest:被链接到的路径,只应用于state=link的情况

  • state:

    • directory:如果目录不存在,创建目录

    • file:即使文件不存在,也不会被创建

    • link:创建软链接

    • hard:创建硬链接

    • touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间

    • absent:删除目录、文件或者取消链接文件

4.7、Copy模块:

复制文件到远程主机,copy模块包含如下选项:

  • backup:在覆盖之前将原文件备份,备份文件包含时间信息。有两个选项:yes|no

  • content:用于替代"src",可以直接设定指定文件的值

  • dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录

  • force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes

  • others:所有的file模块里的相关文件属性选项都可以在这里使用

  • src:要复制到远程主机的文件在本地的地址,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用"/"来结尾,则只复制目录里的内容,如果没有使用"/"来结尾,则包含目录在内的整个内容全部复制,类似于rsync。

  • remote_src:默认是no,即复制主控端的文件到被控端,yes表示被控端的文件复制到被控端

  • validate:测试文件的语法,如果测试不通过,则不执行,测试通过则执行

4.8、group模块

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

  • gid:指定组id

  • name:指定组名

  • state:创建还是删除组,选项:present|absent

  • system:是否将该组创建为系统组,默认为no

4.9、user模块

user模块是请求的是useradd, userdel, usermod三个指令

  • home:指定用户的家目录,需要与createhome配合使用

  • groups:指定用户的附加组

  • group:指定用户属组

  • uid:指定用的uid

  • password:指定用户的密码

  • name:指定用户名

  • createhome:是否创建家目录 yes|no

  • system:是否为系统用户

  • comment:定义用户描述信息

  • remove:当state=absent时,remove=yes则表示连同家目录一起删除,等价于userdel -r

  • state:是创建还是删除

  • shell:指定用户的shell环境

4.10、yum模块

使用yum包管理器来管理软件包,其选项有:

  • name:要进行操作的软件包的名字,也可以传递一个url或者一个本地的rpm包的路径

  • state:状态(present安装,absent卸载,latest最新版本)

4.11、apt模块

使用apt包管理器来管理软件包,其选项有:

  • name:要进行操作的软件包的名字,也可以传递一个url或者一个本地的rpm包的路径

  • state:状态(present安装,absent卸载,latest最新版本)

4.12、service模块

用于管理服务

该模块包含如下选项:

  • arguments:给命令行提供一些选项

  • enabled:是否开机启动 yes|no

  • name:必选项,服务名称

  • pattern:定义一个模式,如果通过status指令来查看服务的状态时,没有响应,就会通过ps指令在进程中根据该模式进行查找,如果匹配到,则认为该服务依然在运行

  • runlevel:运行级别

  • sleep:如果执行了restarted,在则stop和start之间沉睡几秒钟

  • state:对当前服务执行启动,停止、重启、重新加载等操作(started,stopped,restarted,reloaded)

  • daemon_reload:针对使用systemd的系统,重新加载systemd配置,yes/no

4.13、systemd模块

  • daemon_reload:当服务配置文件发生变更时重载服务

  • name:指定服务名称

  • enabled:是否设置开机自启

  • state:管理服务状态,reloaded|restarted|started|stopped

4.14、cron模块

用于管理计划任务

包含如下选项:

  • backup:对远程主机上的原任务计划内容修改之前做备份

  • cron_file:如果指定该选项,则用该文件替换远程主机上的cron.d目录下的用户的任务计划

  • day:日(1-31,,/2,……)

  • hour:小时(0-23,,/2,……)

  • minute:分钟(0-59,,/2,……)

  • month:月(1-12,,/2,……)

  • weekday:周(0-7,*,……)

  • job:要执行的任务,依赖于state=present

  • name:该任务的描述

  • special_time:指定什么时候执行,参数:reboot,yearly,annually,monthly,weekly,daily,hourly

  • state:确认该任务计划是创建还是删除,present创建,absent删除

  • user:以哪个用户的身份执行

4.15、synchronize模块

使用rsync同步文件,其参数如下:

  • archive: 归档,相当于同时开启recursive(递归)、links、perms、times、owner、group、-D选项都为yes ,默认该项为开启

  • checksum: 跳过检测sum值,默认关闭

  • compress:是否开启压缩

  • copy_links:复制链接文件,默认为no ,注意后面还有一个links参数

  • delete: 删除不存在的文件,默认no

  • src:源地址

  • dest:目录路径

  • dest_port:默认目录主机上的端口 ,默认是22,走的ssh协议

  • dirs:传输目录不进行递归,默认为no,即进行目录递归

  • rsync_opts:rsync参数部分

  • set_remote_user:主要用于/etc/ansible/hosts中定义或默认使用的用户与rsync使用的用户不同的情况

  • mode: push或pull 模块,push模式的话,一般用于从本机向远程主机上传文件,pull 模式用于从远程主机上取文件

说明

  • archive:-a

  • compress: --compress

  • delete:--delete

  • copy_links:--links

  • dest:/data

  • src:/data/*

  • rsync_opts:-pU

  • mode:push/pull push 在主控端执行

push 在被控端执行

4.16、filesystem模块

在块设备上创建文件系统

常用选项:

  • dev:目标块设备

  • force:在一个已有文件系统的设备上强制创建

  • fstype:文件系统的类型

  • opts:传递给mkfs命令的选项

4.17、mount模块

配置挂载点

选项:

  • boot:是否开机自动挂载

  • fstype:必选项,挂载文件的类型

  • name:必选项,挂载点

  • opts:传递给mount命令的参数

  • src:必选项,要挂载的文件

  • state:必选项

  • present:只处理fstab中的配置

  • absent:删除挂载点

  • mounted:自动创建挂载点并挂载之

  • umounted:卸载

4.18、get_url 模块

该模块主要用于从http、ftp、https服务器上下载文件(类似于wget),主要有如下选项:

  • sha256sum:下载完成后进行sha256 check;

  • timeout:下载超时时间,默认10s

  • url:下载的URL

  • url_password、url_username:主要用于需要用户名密码进行验证的情况

  • use_proxy:是事使用代理,代理需事先在环境变更中定义

4.19、unarchive模块

用于解压文件,模块包含如下选项:

  • remote_src:默认为no,直接将主控端的压缩包解压到被控端,如果为yes,则是将被控端的压缩包解压到被控端

  • creates:指定一个文件名,当该文件存在时,则解压指令不执行

  • dest:远程主机上的一个路径,即文件解压的路径

  • group:解压后的目录或文件的属组

  • list_files:如果为yes,则会列出压缩包里的文件,默认为no,2.0版本新增的选项

  • mode:解决后文件的权限

  • src:如果copy为yes,则需要指定压缩文件的源路径

  • owner:解压后文件或目录的属主

4.20、assemble模块:

用于组装文件,即将多个零散的文件,合并一个大文件

常用参数:

  • src:原文件(即零散文件)的路径

  • dest:合并后的大文件路径

  • group:合并后的大文件的属组

  • owner:合并后的大文件的属主

  • mode:合并后的大文件的权限

  • validate:与template的validate相同,指定命令验证文件

  • ignore_hidden:组装时,是否忽略隐藏文件,默认为no,该参数在2.0版本中新增

4.21、fetch模块:

用于将被控端的文件发送到主控端

  • flat:不生成目录结构,直接从根目录开始

4.22、stat模块:

判断被控端文件是否存在

5、Playbook

5.1、Ansible Playbook简介

ansbile-playbook是一系列ansible命令的集合,利用yaml 语言编写。playbook命令根据自上而下的顺序依次执行。同时,playbook开创了很多特性,它可以允许你传输某个命令的状态到后面的指令,如你可以从一台机器的文件中抓取内容并附为变量,然后在另一台机器中使用,这使得你可以实现一些复杂的部署机制,这是ansible命令无法实现的。

playbook通过ansible-playbook命令使用,它的参数和ansible命令类似,如参数-k(–ask-pass) 和 -K (–ask-sudo) 来询问ssh密码和sudo密码,-u指定用户,这些指令也可以通过规定的单元写在playbook 。ansible-playbook的简单使用方法: ansible-playbook example-play.yml 。

5.2、Playbook基本语法

下面是一个简单的ansible-playbook示例,可以了解其构成:

配置项说明:

  • name:对该playbook实现的功能做一个概述,后面执行过程中,会打印 name变量的值

  • hosts:指定对哪些被管理机进行操作;

  • remote_user:指定在远程被管理机上执行操作时使用什么用户,如不指定,则使用ansible.cfg中配置的remote_user

  • gather_facts:指定在执行任务之前,是否先执行setup模块获取主机相关信息,如未用到,可不指定

  • vars:定义后续任务中会使用到的变量,如未用到,可不指定

  • tasks:定义具体需要执行的任务

  • name:对任务的描述,在执行过程中会打印出来。

  • user:指定调用user模块;

  • name:user模块里的一个参数,用于指定创建的用户名称

同样,如果想实现把这个新增的用户删除,只需将该playbook文件的最后一行替换为如下行再执行相应的playbook即可:

5.3、Playbook简单示例

下面通过playbook管理一个httpd服务器来简单了解下playbook的应用:

1、创建playbook

2、执行playbook

5.4、ansible-playbook常用选项

1. 打印详细信息

  • -v:打印任务运行结果

  • -vv:打印任务运行结果以及任务的配置信息

  • -vvv:包含了远程连接的一些信息

  • -vvvv:Adds extra verbosity options to the connection plug-ins,including the users being used in the managed hosts to execute scripts, and what scripts have been executed

2. 校验playbook语法

3. 测试运行playbook

通过-C选项可以测试playbook的执行情况,但不会真的执行:

5.5、Multiple Plays

6. Ansible Playbook的结构及handler用法

6.1、playbook的结构说明 playbook是由一个或多个"play"组成的列表。play的主要功能就是对一组主机应用play中定义好的task。从根本上来讲一个task就是对ansible一个module的调用。而将多个play按照一定的顺序组织到一个playbook中,我们称之为编排。

playbook主要有以下四部分构成:

  • Target section: 用于定义将要执行playbook的远程主机组及远程主机组上的用户,还包括定义通过什么样的方式连接远程主机(默认ssh)

  • Variable section: 定义playbook运行时需要使用的变量

  • Task section: 定义将要在远程主机上执行的任务列表

  • Handler section: 定义task执行完成以后需要调用的任务

  • 6.2、Target section

  • playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务。

6.3、Playbook中的远程用户

playbook中的远程用户和ad-hoc中的使用没有区别,默认不定义,则直接使用ansible.cfg配置中的用户相关的配置。也可在playbook中定义如下:

6.4、Playbook中的hosts

playbook中的hosts即inentory中的定义主机与主机组,在《Ansible Inventory》中我们讲到了如何选择主机与主机组,在这里也完全适用。

6.5、Task section

play的主体部分是任务列表。

任务列表中的各任务按次序逐个在hosts中指定的所有主机上执行,在所有主机上完成第一个任务后再开始第二个。在自上而下运行某playbook时,如果中途发生错误,则整个playbook会停止执行,由于playbook的幂等性,playbook可以被反复执行,所以即使发生了错误,在修复错误后,再执行一次即可。

定义task可以使用action: module options或module: options的格式,推荐使用后者以实现向后兼容。

如果命令或脚本的退出码不为零可以使用如下方式替代:

可以使用ignore_errors来忽略错误信息:

6.6、Handler section

  • 在Ansible Playbook中,handler事实上也是个task,只不过这个task默认并不执行,只有在被触发时才执行。

  • handler通过notify来监视某个或者某几个task,一旦task执行结果发生变化,则触发handler,执行相应操作。

  • handler会在所有的play都执行完毕之后才会执行,这样可以避免当handler监视的多个task执行结果都发生了变化之后而导致handler的重复执行(handler只需要在最后执行一次即可)。

注:在notify中定义内容一定要和tasks中定义的 - name 内容一样,这样才能达到触发的效果,否则会不生效。

默认情况下,在一个play中,只要有task执行失败,则play终止,即使是与handler关联的task在失败的task之前运行成功了,handler也不会被执行。如果希望在这种情况下handler仍然能够执行,则需要使用如下配置:(一对一的方式)

如果与handler关联的task还未执行,在其前的task已经失败,整个play终止,则handler未被触发,也不会执行。

6.7、补充:handler的另外一种定义方(一对多的方式)

7. Ansible变量之自定义变量

简单说明 ansible支持变量,用于存储会在整个项目中重复使用到的一些值。以简化项目的创建与维护,降低出错的机率。

变量的定义:

  • 变量名应该由字母、数字下划数组成

  • 变量名必须以字母开头

  • ansible内置关键字不能作为变量名

7.1 在Inventory中定义变量

7.1.1、定义主机变量

内置主机变量

所谓内置变量其实就是ansible.cfg配置文件中的选项,在其前加上ansible_即成为内置变量。当然内置变拥有比ansible.cfg中选项更高的优先级,而且针对不同的主机,可以定义不同的值。

下面是一个简单的示例:

ini格式定义变量:

yaml格式定义变量:

7.1.2、定义主机组变量

变量也可以通过组名,应用到组内的所有成员:

主机组变量示例:

7.2.、在Playbook中定义变量

7.2.1、变量的定义方式

变量的定义格式是成键值对出现的,键值对之间可以嵌套,最终形成一个大字典

通过vars关键字定义:

通过vars_files关键字引入变量文件

在playbook中通过host_vars和group_vars目录定义变量

下面这是一个项目的playbook目录结构。这个项目中,包含一个ansible.cfg文件,一个inventory文件,一个playbook.yml文件,一个group_vars目录和一个host_vars目录:

其中inventory文件的示例如下:

可以看到group_vars目录中,定义了三个文件,分别以inventory文件中的三个主机组命名,所以这三个文件中定义的就分别是这三个组可以使用的变量。

在host_vars目录中,定义了三个文件,分别以inventory文件中的四个主机命名,所以这四个文件中定义的就分别是这四个主机可以使用的变量。

需要说明的是,如果主机组定义的变量与主机冲突,主机变量优先级最高

注册变量

在有些时候,可能需要将某一条任务执行的结果保存下来,以便在接下的任务中调用或者做些判断。可以通过register关键字来实现将某一任务结果保存为一个变量。

下面是个简单的例子,将whoami命令执行的结果注册到变量login:

注册变量的应用场景:

  • 在一台远端的服务器获取一个目录下的一列表的文件,然后下载这些文件

  • 在handler执行之前,发现前面一个task发生了changed,然后执行一个指定的task

  • 获取远端服务器的ssh key的内容,构建出known_hosts文件

通过命令行设置变量(优先级最高)

也可以写成类似如下方式:

7.2.2、使用与调试变量

我们通过以上5种方式在playbook中定义了各种变量。说到底,最终的目的,还是为了方便使用。下面我们就看一看具体如何使用这些变量

变量的引用

下面是一个变量的基本使用示例,前面的变量定义部分,直接使用的2.1.1中的变量示例:

在上面通过vars_files引用了一个文件user_vars.yml,在该文件中定义了一个稍微复杂的字典变量,现在我想要获取users中bjones用户的first_name和acook用户的home_dirs,可以使用如下方法

变量的调试输出

有些时候,我们在引用变量的时候,可能需要知道变量中包含哪些信息,以方便在执行过程中,对变量做些处理。ansible提供一个debug模块用于调试变量输出:

执行后输出如下:

关于输出的debug部分重点说明如下:

  • login: 变量名,其值为一个字典

  • changed:ansible基于此来判断是否发生了状态改变

  • cmd:被调用的命令

  • failed:是否运行失败

  • rc:返回值,0代表正常,非0代表异常

  • stderr:如果出现异常,会在这里显示错误输出

  • stderr_lines:按行分割的错误输出

  • stdout:如果指令正常运行,则在这里输出返回结果

  • stdout:按行分割的返回结果

需要说明的是,通过register注册的变量的结果并不是一成不变的,在不确定返回值的情况下,尽量调试看看输出结果。 关于debug的更多用法说明: 调试模块,用于在调试中输出信息

常用参数:

  • msg:调试输出的消息

  • var:将某个变量传递给debug模块,debug会直接将其打印输出

  • verbosity:debug的级别

7.3、定义变量:

7.3.1、第一种方式:

7.3.2、第二章方式:

7.4、变量取值:

7.5、关于变量取值的思路:

  1. 要使用debug打印变量

  2. 查看变量的逻辑结构,如果是列表则使用索引取值,如果是字典则用key取值

7.6、变量优先级:(从低到高)

inventory( group < hosts )> group_vars > host_vars > playbook ( vars = vars_files ) > tasks > 命令行中定义的变量( -e 引入变量)

8. Ansible变量之fact

fact简介 ansible有一个模块叫setup,用于获取远程主机的相关信息,并可以将这些信息作为变量在playbook里进行调用。而setup模块获取这些信息的方法就是依赖于fact。

setup获取的这些信息,都是可用于该主机的变量。

setup模块其他用法示例:

8.1、自定义fact

8.1.1、手动设置fact

ansible除了能获取到预定义的fact的内容,还支持手动为某个主机定制fact。称之为本地fact。本地fact默认存放于被控端的/etc/ansible/facts.d目录下,如果文件为ini格式或者json格式,ansible会自动识别。以这种形式加载的fact是key为ansible_local的特殊变量。

下面是一个简单的示例,在ansibler主控端定义一个ini格式的custom.fact文件内容如下:

然后我们编写一个playbook文件名为setup_facts.yml内容如下:

执行该playbook,完成facts的推送:

此时,我们可以在被控端看到新的facts已经生成:

我们可以写一个简单的playbook来使用这些facts:

8.1.2、使用set_fact模块定义新的变量

set_fact模块可以自定义facts,这些自定义的facts可以通过template或者变量的方式在playbook中使用。如果你想要获取一个进程使用的内存的百分比,则必须通过set_fact来进行计算之后得出其值,并将其值在playbook中引用。

生效范围:是从定义变量开始,到playbook文件结束,即该playbook整个文件可以继承前面playbook中通过set_fact定义的变量,但前提是必须在同一台主机上运行

下面是一个set_fact模块的应用示例:

执行playbook如下:

8.2、手动采集fact

通常情况下,我们在运行play的时候,ansible会先尝试ssh到被控端采集fact,如果此时,被控制端的ssh还没有完全启动,就会导致整个play执行失败。这个时候,我们可以先显式的关闭fact采集,然后在task中通过wait_for等待被控端ssh端口被正常监听,再在task中使用setup模块来手动采集fact:

8.3、启用fact缓存

如果在play中需要引入fact,则可以开启fact缓存。fact缓存目前支持三种存储方式,分别为JSON、memcached、redis。

8.3.1、Json文件fact缓存后端

使用JSON文件作为fact缓存后端的时候,ansible将会把采集的fact写入到控制主机的文件中。

ansible.cfg配置如下:

选项说明:

  • gathering: 是否启用fact,有三个选项:

    • smart:默认收集facts,但在facts已有的情况下就不收集,即使用facts缓存

    • implicit:默认收集facts,要禁止收集,必须显式的申明:gather_facts: false

    • explicit:默认不收集,要收集,必须显示的申明: gather_facts: true

  • fact_cacheing_timeout:缓存时间,单位为s

  • fact_caching:缓存的方式,支持jsonfile、redis、memcached

  • fact_caching_connection:指定ansible缓存fact的连接方式,如果是jsonfile,则指定jsonfile的缓存路径

8.3.2、Redis fact缓存后端

使用redis作为fact缓存后端,需要在控制主机上安装redis服务并保持运行。需要安装python操作redis的软件包。

ansible.cfg配置如下:

8.3.3. Memcached fact缓存后端

使用memcached作为fact缓存后端,需要在控制主机上安装Memcached服务并保持运行,需要安装python操作memcached的软件包。

ansible.cfg配置如下:

8.4、关闭fact

如果不想从fact中获取变量,或者说整个playbook当中都没有使用到fact变量,可以通过如下方法关闭fact以提升执行效率:

也可以在ansible.cfg中添加如下配置:

9. Ansible魔法变量及变量优先级

魔法变量 Ansible默认会提供一些内置的变量以实现一些特定的功能,我们称之为魔法变量。下面列举一些常用的魔法变量。

9.1、hostvars

获取某台指定的主机的相关变量。如果有一台web服务器的配置文件中需要指定db服务器的ip地址,我们假定这台db服务器的hostname为db.exmaple.com,ip地址绑定在eth0网卡上,我们可以通过如下方法在web服务器上调用db服务器的ip地址:

9.2、inventory_hostname(获取主机清单中定义的主机名)

inventory_hostname是Ansible所识别的当前正在运行task的主机的主机名。如果在inventory里定义过别名,那么这里就是那个别名,如果inventory包含如下一行:

则inventory_hostname即为server1

利用hostvars和inventory_hostname变量,可以输出与当前主机相关联的所有变量:

与inventory_hostname相近的还有一个inventory_hostname_short,如果一台主机的inventory_hostname为server1.exmaple.com,则inventory_hostname_short的值为server1

9.3、group_names(任务在哪个主机上运行,则获取该主机所在的组)

用于标识当前正在执行task的目标主机位于的主机组。假如我们有三台主机,用来配置成一主二从的mysql服务器。inventory配置如下:

mysql配置文件my.conf.j2示例如下:

我们执行如下task:

9.4、groups(获取主机清单中定义的所有组)

  • groups.mysql:(获取主机清单中mysql组的主机)

  • groups是inventory中所有主机组的列表,可用于枚举主机组中的所有主机。

假如我们有一个inventory文件定义如下:

在配置一台HAproxy的负载均衡器时,我们的配置文件肯定需要web主机组的所有服务器的IP,配置文件包含如下片段:

最终生成的文件如下:

再给一个例子,在所有的dbservers组的服务器上创建一个数据库用户kate:

9.5、play_hosts

当前playbook会在哪些hosts上运行

9.6、inventory_dir

主机清单所在目录

9.7、inventory_file

主机清单文件

9.8、变量优先级

    1. extra vars(命令中-e)最优先

    1. inventory 主机清单中连接变量(ansible_ssh_user等)

    1. play 中 vars、vars_files 等

    1. 剩余的在 inventory 中定义的变量

    1. 系统的 facts 变量

    1. 角色定义的默认变量(roles/rolesname/defaults/main.yml)

子组会覆盖父组,主机总是覆盖组定义的变量

10. 使用lookup生成变量

简单说明 在通常情况下,所有的配置信息都会被作为ansible的变量保存了,而且可以保存在ansible允许定义变量的各种地方,诸如vars区段,vars_files加载的文件中,以及host_vars和group_vars目录中。

但在有些时候,我们希望从诸如文本文件或者.csv文件中收集数据作为ansible的变量,或者直接获取某些命令的输出作为ansible的变量,甚至从redis或者etcd这样的键值存储中取得相应的值作为ansible的变量。这个时候,我们就需要通过ansible的lookup插件来从这些数据源中读取配置数据,传递给ansbile变量,并在playbook或者模板中使用这些数据。

ansible支持一套从不同数据源获取数据的lookup,包括file, password, pipe, env, template, csvfile, dnstxt, redis_kv, etcd等

查询lookup插件:

10.1、file

使用file lookup可以从文本文件中获取数据,并在这些数据传递给ansible变量,在task或者jinja2模板中进行引用。下面是一个从文本文件中获取ssh公钥并复制到远程主机的示例:

authorized_keys.j2模板文件示例如下:

10.2、pipe

lines可代替pipe,不同的是,lines可自动换行,格式化显示

使用pipe lookup可以直接调用外部命令,并将命令执行的结果打印到标准输出,作为ansible变量。下面的例子通过pipe调用date指令拿到一个以时间数字组成的字串

10.3、env

env lookup实际就是获取在控制主机上的某个环境变量的值。下面是一个读取控制机上$JAVA_HOME变量值的示例:

10.4、url

读取一个url的内容

10.5、template

template lookup可以指定一个jinja2模板,然后返回这个模板中的变量被替换以后的结果。

假设我们有一个message.j2模板,内容如下:

定义一个如下的task:

输出的msg的结果如下:

10.6、csvfile

csvfile可以从.csv文件中读取一个条目。假设我们有如下示例的名为users.csv的文件:

下面是一个使用csvfile lookkup提取sue的电子邮件地址的task示例:

可以看到,一共向插件传递了四个参数:sue, file=users.csv, delimiter=,以及col=1。说明如下:

  • 第一个参数指定一个名字,该名字必须出现在其所在行的第0列,需要说明的是,如果指定的第一个参数名字在文件中出现多次,则匹配第一次出现的结果

  • 第二个参数指定csv文件的文件名

  • 第三个参数指定csv文件的中条目的分隔符,默认是tab

  • 第四个参数指定要取得哪一列的值,0代表第1列,1代表第2列,默认是1

如果我们想要查找的用户存储在名为username的变量中,则可以使用"+"符号来连接username字串和其他的参数字串,来构建完整的参数字符串:

10.7、redis_kv

redis_kv lookup可以直接从redis存储中来获取一个key的value,key必须是一个字符串,如同Redis GET指令一样。需要注意的是,要使用redis_kv lookup,需要在主控端安装python的redis客户端,在centos上,软件包为python-redis。

下面是一个在playbook中调用redis lookup的task,从本地的redis中取中一个key为weather的值:

其中URL部分如果不指定,该模块会默认连接到redis://localhost:6379,所以实际上在上面的实例中,调用可以直接写成如下:

10.8、etcd

etcd是一个分布式的key-value存储,通常被用于保存配置信息或者被用于实现服务发现。可以使用etcd lookup来从etcd中获取指定key的value。

我们通过如下方法往一个etcd中写入一个key:

定义一个调用etcd插件的task:

默认情况下,etcd lookup会在http://127.0.0.1:2379上查找etcd服务器。但我们在执行playbook之前可以通过设置ANSIBLE_ETCD_URL环境变量来修改这个设置。

10.9、password

password lookup会随机生成一个密码,并将这个密码写入到参数指定的文件中。如下示例,创建一个名为bob的mysql用户,并随机生成该用户的密码,并将密码写入到主控端的bob-password.txt中:

10.10、dnstxt

dnstxt lookup用于获取指定域名的TXT记录。需要在主控端安装python-dns。

使用方法如下:

如果某一个主机有多个相关联的TXT记录,那么模块会把他们连在一起,并且每次调用时的连接顺序可能不同

10.11、补充:

ansible2.5版本以后,query逐渐替代lookup,lookup返回的值采用逗号为分隔符,而query总是返回一个列表。

11. Ansible Playbook条件语句

简介 在有的时候play的结果依赖于变量、fact或者是前一个任务的执行结果,或者有的时候,我们会基于上一个task执行返回的结果而决定如何执行后续的task。这个时候就需要用到条件判断。

条件语句在Ansible中的使用场景:

  • 在目标主机上定义了一个硬限制,比如目标主机的最小内存必须达到多少,才能执行该task

  • 捕获一个命令的输出,根据命令输出结果的不同以触发不同的task

  • 根据不同目标主机的facts,以定义不同的task

  • 根据目标机的cpu的大小,以调优相关应用性能

  • 用于判断某个服务的配置文件是否发生变更,以确定是否需要重启服务

11.1、when关键字

11.1.1、when基本使用

在ansible中,使用条件判断的关键字就是when。

如在安装包的时候,需要指定主机的操作系统类型,或者是当操作系统的硬盘满了之后,需要清空文件等,可以使用when语句来做判断 。when关键字后面跟着的是python的表达式,在表达式中你能够使用任何的变量或者fact,当表达式的结果返回的是false,便会跳过本次的任务

下面是一个基本的用法示例:

11.1.2、比较运算符

在上面的示例当中,我们使用了"=="的比较运算符,在ansible中,还支持如下比较运算符:

  • ==:比较两个对象是否相等,相等则返回真。可用于比较字符串和数字

  • !=:比较两个对象是否不等,不等则为真。

  • :比较两个对象的大小,左边的值大于右边的值,则为真

  • =:比较两个对象的大小,左边的值大于等于右边的值,则为真 下面是一些简单的示例:

11.1.3、逻辑运算符

在Ansible中,除了比较运算符,还支持逻辑运算符:

  • and:逻辑与,当左边和右边两个表达式同时为真,则返回真

  • or:逻辑或,当左右和右边两个表达式任意一个为真,则返回真

  • not:逻辑否,对表达式取反

  • ():当一组表达式组合在一起,形成一个更大的表达式,组合内的所有表达式都是逻辑与的关系

示例:

一个完整的例子:

11.2、条件判断与tests

在shell当中,我们可使用test命令来进行一些常用的判断操作,如下:

事实上,在ansible中也有类似的用法,只不过ansible没有使用linux的test命令,而是jinja2模板的tests。

下面是一个简单示例:

上面的示例中,我们使用了is exists用于路径存在时返回真,也可以使用is not exists用于路径不存在时返回真。也可以在整个条件表达式的前面使用not以取反:

在ansible中,除了能够使用exists这种tests之外,还有一些别的tests。接下来我们详细说一说。

11.2.1、判断变量

  • defined:判断变量是否已定义,已定义则返回真

  • undefined:判断变量是否未定义,未定义则返回真

  • none:判断变量的值是否为空,如果变量已定义且值为空,则返回真 示例:

11.2.2、判断执行结果

  • sucess或succeeded:通过任务执行结果返回的信息判断任务的执行状态,任务执行成功则返回true

  • failure或failed:任务执行失败则返回true

  • change或changed:任务执行状态为changed则返回true

  • skip或skipped:任务被跳过则返回true 示例:

11.2.3、判断路径

  • file:判断指定路径是否为一个文件,是则为真

  • directory:判断指定路径是否为一个目录,是则为真

  • link:判断指定路径是否为一个软链接,是则为真

  • mount:判断指定路径是否为一个挂载点,是则为真

  • exists:判断指定路径是否存在,存在则为真 特别注意:关于路径的所有判断均是判断主控端上的路径,而非被控端上的路径

示例:

11.2.4、判断字符串

  • lower:判断字符串中的所有字母是否都是小写,是则为真

  • upper:判断字符串中的所有字母是否都是大写,是则为真

11.2.5、判断整除

  • even:判断数值是否为偶数,是则为真

  • odd:判断数值是否为奇数,是则为真

  • divisibleby(num):判断是否可以整除指定的数值,是则为真 示例:

11.2.6、其他tests

version可用于对比两个版本号的大小,或者与指定的版本号进行对比,使用语法为version("版本号","比较操作符")

version中使用的比较运算符说明:

  • subset判断一个list是不是另一个list的子集

  • superset判断一个list是不是另一个list的父集"

in判断一个字符串是否存在于另一个字符串中,也可用于判断某个特定的值是否存在于列表中

  • string判断对象是否为一个字符串,是则为真

  • number判断对象是否为一个数字,是则为真

11.3、条件判断与block

11.3.1、block

我们在前面使用when做条件判断时,如果条件成立则执行对应的任务。但这就面临一个问题,当我们要使用同一个条件判断执行多个任务的时候,就意味着我们要在某一个任务下面都写一下when语句,而且判断条件完全一样。这种方式不仅麻烦而且显得low。Ansible提供了一种更好的方式来解决这个问题,即block。

在ansible中,使用block将多个任务进行组合,当作一个整体。我们可以对这一个整体做条件判断,当条件成立时,则执行块中的所有任务:

下面是一个稍微有用点儿的例子:

使用block注意事项:

  • 可以为block定义name(ansible 2.3增加的特性)

  • 可以直接对block使用when,但不能直接对block使用loop

11.3.2、rescue

block除了能和when一起使用之外,还能作错误处理。这个时候就需要用到rescue关键字:

在上面的例子中,当block中的任务执行失败时,则运行rescue中的任务。如果block中的任务正常执行,则rescue的任务就不会被执行。如果block中有多个任务,则任何一个任务执行失败,都会执行rescue。block中可以定义多个任务,同样rescue当中也可以定义多个任务。

11.3.3、always

当block执行失败时,rescue中的任务才会被执行;而无论block执行成功还是失败,always中的任务都会被执行:

11.4、条件判断与错误处理

在上面讲block的使用方法的时候,我们说block除了可以将多个任务组合到一起,还有错误处理的功能。接下来我们继续说一说错误处理。

11.4.1、fail模块

在shell中,可能会有这样的需求:当脚本执行至某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行。在shell中,可以直接调用"exit"即可执行退出。事实上,在playbook中也有类似的模块可以做这件事。即fail模块。

fail模块用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。

选项只有一个:

  • msg:终止前打印出信息 示例:

11.4.2、failed_when

事实上,当fail和when组合使用的时候,还有一个更简单的写法,即failed_when,当满足某个条件时,ansible主动触发失败。

也可以直接通过fail模块和when条件语句,写成如下:

ansible一旦执行返回失败,后续操作就会中止,所以failed_when通常可以用于满足某种条件时主动中止playbook运行的一种方式。

ansible默认处理错误的机制是遇到错误就停止执行。但有些时候,有些错误是计划之中的。我们希望忽略这些错误,以让playbook继续往下执行。这个时候就可以使用ignore_errors忽略错误,从而让playbook继续往下执行。

11.4.3、changed_when

当我们控制一些远程主机执行某些任务时,当任务在远程主机上成功执行,状态发生更改时,会返回changed状态响应,状态未发生更改时,会返回OK状态响应,当任务被跳过时,会返回skipped状态响应。我们可以通过changed_when来手动更改changed响应状态。示例如下:

11.5、在循环语句中使用条件语句

12. Ansible Playbook with_X循环语句

循环语句

简介 我们在编写playbook的时候,不可避免的要执行一些重复性操作,比如指安装软件包,批量创建用户,操作某个目录下的所有文件等。正如我们所说,ansible一门简单的自动化语言,所以流程控制、循环语句这些编程语言的基本元素它同样都具备。

在Ansible 2.5以前,playbook通过不同的循环语句以实现不同的循环,这些语句使用with_作为前缀。这些语法目前仍然兼容,但在未来的某个时间点,会逐步废弃。

下面列出一些较常见的with_X循环语句:

  • with_items

  • with_flattened

  • with_list

  • with_together

  • with_nested

  • with_indexed_items

  • with_sequence

  • with_random_choice

  • with_dict

  • with_subelement

  • with_file

  • with_fileglob

  • with_lines

  • 12.1、with_items

12.1、with_items

item:ansible内置变量,指的是引用变量中所有的元素

简单的列表循环

12.1.1、场景一: 循环打印inventory中所有未分组的主机

12.1.2、场景二: 直接在with_items中定义被循环的列表

也可以写成如下方式:

12.1.3、场景三: 在with_items中定义更复杂的列表

12.2、with_list

与with_items一样,也是用于循环列表。区别是,如果列表的值也是列表,with_items会将第一层嵌套的列表拉平,而with_list会将值作为一个整体返回。

示例:

12.3、with_flattened

with_flattened与with_items类似,当处理复杂的多级列表嵌套时,会将所有的列表全部拉平:

返回结果:

12.4、with_together

with_together可以将两个列表中的元素对齐合并

示例如下:

可以看到第一个列表中的第一个元素a与第二个列表中的第一个元素1合并输出,第一个列表中的b与第二个列表中的第二个元素2合并输出了

上面的示例是基于两个列表的元素完全相同的结果,如果两个列表中的元素不同:

12.5、with_nested

嵌套循环

item[0]是循环的第一个列表的值['alice','bob']。item[1]是第二个列表的值;以上的执行输出如下:

下面是一个稍微有用点儿的示例:

with_cartesian与其功能完全一致

12.6、with_indexed_items

在循环处理列表时,为列表中的每一项添加索引(从0开始的数字索引)

简单示例:

执行之后,返回结果如下:

所以我们可以使用with_indexed_items执行如下操作:

下面再看一个稍微复杂的列表结构:

这个时候,返回的结果如下:

可以看到,其在处理更复杂列表的时候,会将列表拉平,类似于with_items。

与with_items一样,其也只会拉平第一层列表,如果存在多层列表嵌套,则更深的嵌套不会被拉平:

此时的返回结果:

12.7、with_sequence

用于返回一个数字序列

参数说明:

  • start:指定起始值

  • end:指定结束值

  • stride:指定步长,即从start至end,每次增加的值

  • count:生成连续的数字序列,从1开始,到count的值结束

  • format:格式化输出,类似于linux命令行中的printf格式化输出

关于format参数,更多的格式化输出参数可参考:12printf命令详解-朱双印博客

12.8、with_random_choice

用于从一个列表的多个值中随机返回一个值

下面的示例,一个列表当中有四个值,连续执行playbook,每次都随机返回一个:

12.9、with_dict

循环字典

输出如下:

12.10、with_subelement

with_subelement简单来讲,就是在一个复杂的列表当中,可以对这个列表变量的子元素进行遍历

下面是一个简单的示例:

输出结果如下:

可以看到,其按照我们指定的变量users的子项hobby进行了组合输出。with_elementes将hobby子元素的每一项作为一个整体,将其他子元素作为整体,然后组合在一起。

假如现在需要遍历一个用户列表,并创建每个用户,而且还需要为每个用户推送特定的SSH公钥以用于实现远程登录。同时为某一个用户创建独立的mysql登录帐号并为其授权。

示例如下:

12.11、with_file

用于循环主控端的文件列表,获取文件中的内容

注意: 循环的是主控端的文件列表,不是被控端的

输出如下:

12.12、with_fileglob

上面with_file用于获取文件的内容,而with_fileglob则用于匹配文件名称。可以通过该关键字,在指定的目录中匹配符合模式的文件名。与with_file相同的是,with_fileglob操作的文件也是主控端的文件而非被控端的文件

12.13、with_lines

with_lines循环结构会让你在控制主机上执行任意命令,并对命令的输出进行逐行迭代。

假设我们有一个文件test.txt包含如下行:

我们可以通过如下方法进行逐行输出:

12.14、do-Until循环

重复执行shell模块,当shell模块执行的命令输出内容包含"all systems go"的时候停止。重试5次,延迟时间10秒。retries默认值为3,delay默认值为5。任务的返回值为最后一次循环的返回结果。

13. Ansible Playbook loop循环语句

loop关键字说明 在ansible 2.5及以前的版本当中,所有的循环都是使用with_X风格。但是从2.6版本开始,官方开始推荐使用loop关键字来代替with_X风格的关键字。

在playbook中使用循环,直接使用loop关键字即可。

如下示例,启动httpd和postfix服务:

那么在这个示例当中,其实就是使用loop代替了with_list循环。

事实上,我们可以使用loop关键字搭配一些过滤器来替换更多的、更复杂的with_X循环。

13.1、loop_control

loop_control用于在循环时,获取列表的索引

输出结果:

参数说明:

index_var:loop_control的选项,让我们指定一个变量,loop_control会将列表元素的索引值存放到这个指定的变量中

13.2、with_list

loop可以替代with_list,当处理嵌套列表时,列表不会被拉平

13.3、with_flattened

将所有嵌套都拉平

13.5、with_indexed_items

通过flatten过滤器(加参数),再配合loop_control关键字,可以替代with_indexed_items,当处理多层嵌套的列表时,只有列表中的第一层会被拉平

参数说明:

  • loop_control: 用于控制循环的行为,比如在循环时获取到元素的索引

  • index_var:loop_control的选项,让我们指定一个变量,loop_control会将列表元素的索引值存放到这个指定的变量中

13.6、with_together

zip_longest过滤器配合list过滤器,可以替代with_together

当多个列表使用with_together进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest过滤器也会像with_together一样,对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。

在使用zip_longest过滤器代替with_together关键字时,默认也是使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用"NONE"代替空值:

zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:

13.7、with_nested/with_cartesian

可使用product过滤器配合list过滤器以替代with_nested或者with_cartesian。product过滤器也是需要将组合后的数据进行列表化,所以需要与list过滤器配合使用:

13.8、with_sequence

使用range过滤器配合list过滤器可以替代with_sequence:

上例中表示生成数字,从0开始,到6结束,步长为2。但是需要说明的是,range函数的操作不包含结束范围,也就是说上面的循环只会生成0,2,4三个数字,而不包含6。

另外,with_sequence还有格式化的功能:

可使用format配合loop实现:

13.9、with_random_choice

使用random函数可以替代with_random_choice,由于random函数是随机取出列表中的一个值,并不涉及循环操作,所以并不使用loop关键字:

13.10、with_dict

可使用loop配合dict2items过滤器实现with_dict功能:

13.11、with_subelements

可使用loop配合subelements过滤器替代with_subelements:

13.12、使用zip_longest过滤器将两个列表中的元素对齐合并

输出结果如下:

当多个列表进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest过滤器会对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。

在使用zip_longest过滤器时,默认使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用"NONE"代替空值:

zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:

13.13、在循环语句中注册变量

下面是一个register的变量在循环中使用的例子:

在循环语句中注册变量:

执行语句,可以看到变量的返回结果为一个字典列表:

14. Ansible文件管理模块及Jinja2过滤器

对于任何自动管理工具而言,对于文件的管理都是其绕不开的话题。同样,ansible也围绕文件管理提供了众多的模块。同时还提供了Jinja2模板语法来配置文件模板。

14.1、常用文件管理模块

14.1.1、file

我们在讲ansible ad-hoc的时候,已经说过file模块,在playbook中的使用也没什么不同,下面给个简单的示例:

14.1.2、synchronize

synchronize模块示例:

14.1.3、copy

同样的,我们已经介绍过copy模块,示例如下:

14.1.4、fetch

fetch模块与copy模块正好相反,copy是把主控端的文件复制到被控端,而fetch则是把被控端的文件复制到主控端。并且在主控端指定的目录下,以被控端主机名的形式来组织目录结构。

在主控端文件存储的目录树如下:

参考:https://docs.ansible.com/ansible/latest/modules/fetch_module.html#fetch-module

14.1.5、lineinfile

lineinfile是一个非常有用的模块,而且相对来说,也是用法比较复杂的模块,可直接参考《Ansible lineinfile模块》

14.1.6、stat

stat模块与linux中的stat命令一样,用来显示文件的状态信息。

参考: https://docs.ansible.com/ansible/latest/modules/stat_module.html#stat-module

14.1.7、blockinfile

围绕着被标记的行插入、更新、删除一个文本块。

执行后结果如下:

更多blockinfile用法参考:ansible.builtin.blockinfile module – Insert/update/remove a text block surrounded by marker lines — Ansible Documentation

14.2、Jinja2模板管理

Jinja2简介 Jinja2是基于python的模板引擎。那么什么是模板?

假设说现在我们需要一次性在10台主机上安装redis,这个通过playbook现在已经很容易实现。默认情况下,所有的redis安装完成之后,我们可以统一为其分发配置文件。这个时候就面临一个问题,这些redis需要监听的地址各不相同,我们也不可能为每一个redis单独写一个配置文件。因为这些配置文件中,绝大部分的配置其实都是相同的。这个时候最好的方式其实就是用一个通用的配置文件来解决所有的问题。将所有需要修改的地方使用变量替换,如下示例中redis.conf.j2文件:

那么此时,redis.conf.j2文件就是一个模板文件。{{ ansible_eth0.ipv4.address }}是一个fact变量,用于获取被控端ip地址以实现替换。

14.2.1、在playbook中使用jinja2

现在我们有了一个模板文件,那么在playbook中如何来使用呢?

playbook使用template模块来实现模板文件的分发,其用法与copy模块基本相同,唯一的区别是,copy模块会将原文件原封不动的复制到被控端,而template会将原文件复制到被控端,并且使用变量的值将文件中的变量替换以生成完整的配置文件。

下面是一个完整的示例:

执行完成之后,我们可以看到被控端/etc/redis.conf配置文件如下:

关于template模块的更多参数说明:

  • backup:如果原目标文件存在,则先备份目标文件

  • dest:目标文件路径

  • force:是否强制覆盖,默认为yes

  • group:目标文件属组

  • mode:目标文件的权限

  • owner:目标文件属主

  • src:源模板文件路径

  • validate:在复制之前通过命令验证目标文件,如果验证通过则复制

14.2.2、Jinja2条件语句

在上面的示例中,我们直接取了被控节点的eth0网卡的ip作为其监听地址。那么假如有些机器的网卡是bond0,这种做法就会报错。这个时候我们就需要在模板文件中定义条件语句如下:

我们可以更进一步,让redis主从角色都可以使用该文件:

我们定义一个inventory如下:

测试

14.2.3、Jinja2循环语句

定义一个inventory示例如下:

现在把proxy主机组中的主机作为代理服务器,安装nginx做反向代理,将请求转发至后面的两台webserver,即webserver组的服务器。

现在我们编写一个playbook如下:

模板文件 templates/nginx.conf.j2示例如下:

for循环只能循环列表,不能循环字典,字典需要转换为列表才能循环

14.2.3、Jinja2过滤器

  1. default过滤器

当指定的变量不存在时,用于设定默认值

简单示例:

复杂一点儿的实例:

  1. 字符串操作相关的过滤器

  • upper:将所有字符串转换为大写

  • lower:将所有字符串转换为小写

  • capitalize:将字符串的首字母大写,其他字母小写

  • reverse:将字符串倒序排列

  • first:返回字符串的第一个字符

  • last:返回字符串的最后一个字符

  • trim:将字符串开头和结尾的空格去掉

  • center(30):将字符串放在中间,并且字符串两边用空格补齐30位

  • length:返回字符串的长度,与count等价

  • list:将字符串转换为列表

  • shuffle:list将字符串转换为列表,但是顺序排列,shuffle同样将字符串转换为列表,但是会随机打乱字符串顺序 示例:

  1. 数字操作相关的过滤器

  • int: 将对应的值转换为整数

  • float:将对应的值转换为浮点数

  • abs:获取绝对值

  • round:小数点四舍五入

  • random:从一个给定的范围中获取随机值 示例

  1. 列表操作相关的过滤器

  • length: 返回列表长度

  • first:返回列表的第一个值

  • last:返回列表的最后一个值

  • min:返回列表中最小的值

  • max:返回列表中最大的值

  • sort:重新排列列表,默认为升序排列,sort(reverse=true)为降序

  • sum:返回纯数字非嵌套列表中所有数字的和

  • flatten:如果列表中包含列表,则flatten可拉平嵌套的列表,levels参数可用于指定被拉平的层级

  • join:将列表中的元素合并为一个字符串

  • random:从列表中随机返回一个元素

  • shuffle

  • upper

  • lower

  • union:将两个列表合并,如果元素有重复,则只留下一个

  • intersect:获取两个列表的交集

  • difference:获取存在于第一个列表中,但不存在于第二个列表中的元素

  • symmetric_difference:取出两个列表中各自独立的元素,如果重复则只留一个 示例:

  1. hash和Encoding过滤器

  • hash过滤器

  • password_hash过滤器

  • base64加密和解密

  1. 查找替换过滤器

  • replace过滤器

  • regex_search 过滤器

  • regex_replace过滤器

正则表达式:

  1. 应用于字典变量的过滤器

  • combine 将两个字典合成一个

  • dict2items 将字典转换为列表 示例一:

示例二:

  • items2dict 将列表转换为字典 示例一:

示例二:

  • json_query 将变量转换成json,并查询子元素

  • from_json/from_yaml

  • to_json/to_yaml/to_nice_json/to_nice_yaml

示例变量:

  1. ipaddr过滤器处理网络地址

ipaddr过滤器主要用于处理网络地址, 其支持的过滤器参数如下:

  • address

  • host

  • prefix

  • size

  • network

  • netmask

  • broadcast

  • subnet

  • ipv4 and ipv6 要想使用ipaddr过滤器,需要在主控端安装python-netaddr包:

示例:

输出:

  1. url分割过滤器

urlspilt用于分割url,并取出相应字段

  1. 应用于注册变量的过滤器

正常情况下,当某个task执行失败的时候,ansible会中止运行。此时我们可以通过ignore_errors来捕获异常以让task继续往下执行。然后调用debug模块打印出出错时的内容,拿来错误结果后,主动失败。

任务返回值过滤器:

  • failed: 如果注册变量的值是任务failed则返回True

  • changed: 如果注册变量的值是任务changed则返回True

  • success:如果注册变量的值是任务succeeded则返回True

  • skipped:如果注册变量的值是任务skipped则返回True 在ansible2.9中,该方式会被废弃,不推荐使用

  1. 应用于文件路径的过滤器

  • basename:返回文件路径中的文件名部分

  • dirname:返回文件路径中的目录部分

  • expanduser:将文件路径中的~替换为用户目录

  • realpath:处理符号链接后的文件实际路径 下面是一个示例:

  1. 自定义过滤器

举个简单的例子,现在有一个playbook如下:

templates/test.conf.j2如下:

执行playbook后,在目标机上的test.conf如下:

现在如果希望目标机上的test.conf文件返回结果如下:

没有现成的过滤器来帮我们做这件事情。我们可以自己简单写一个surround_by_quote.py内容如下:

将刚刚编写的代码文件放入/usr/share/ansible/plugins/filter目录下,然后修改templates/test.conf.j2如下:

再次执行playbook,最后返回结果:

关于jinja2更多用法参考:http://docs.jinkan.org/docs/jinja2/

15. Ansible Playbook高级用法

15.1、任务委托 在有些时候,我们希望运行与选定的主机或主机组相关联的task,但是这个task又不需要在选定的主机或主机组上执行,而需要在另一台服务器上执行。

这种特性适用于以下场景:

  • 在告警系统中启用基于主机的告警

  • 向负载均衡器中添加或移除一台主机

  • 在dns上添加或修改针对某个主机的解析

  • 在存储节点上创建一个存储以用于主机挂载

  • 使用一个外部程序来检测主机上的服务是否正常

可以使用delegate_to语句来在另一台主机上运行task:

如果delegate_to: 127.0.0.1的时候,等价于local_action

15.2、本地执行

如果希望在控制主机本地运行一个特定的任务,可以使用local_action语句。

假设我们需要配置的远程主机刚刚启动,如果我们直接运行playbook,可能会因为sshd服务尚未开始监听而导致失败,我们可以在控制主机上使用如下示例来等待被控端sshd端口监听:

15.3、任务暂停 有些情况下,一些任务的运行需要等待一些状态的恢复,比如某一台主机或者应用刚刚重启,我们需要需要等待它上面的某个端口开启,此时就需要将正在运行的任务暂停,直到其状态满足要求。

Ansible提供了wait_for模块以实现任务暂停的需求

wait_for模块常用参数:

  • connect_timeout:在下一个任务执行之前等待连接的超时时间

  • delay:等待一个端口或者文件或者连接到指定的状态时,默认超时时间为300秒,在这等待的300s的时间里,wait_for模块会一直轮询指定的对象是否到达指定的状态,delay即为多长时间轮询一次状态。

  • host:wait_for模块等待的主机的地址,默认为127.0.0.1

  • port:wait_for模块等待的主机的端口

  • path:文件路径,只有当这个文件存在时,下一任务才开始执行,即等待该文件创建完成

  • state:等待的状态,即等待的文件或端口或者连接状态达到指定的状态时,下一个任务开始执行。当等的对象为端口时,状态有started,stoped,即端口已经监听或者端口已经关闭;当等待的对象为文件时,状态有present或者started,absent,即文件已创建或者删除;当等待的对象为一个连接时,状态有drained,即连接已建立。默认为started

  • timeout:wait_for的等待的超时时间,默认为300秒 示例:

15.4、滚动执行 默认情况下,ansible会并行的在所有选定的主机或主机组上执行每一个task,但有的时候,我们会希望能够逐台运行。最典型的例子就是对负载均衡器后面的应用服务器进行更新时。通常来讲,我们会将应用服务器逐台从负载均衡器上摘除,更新,然后再添加回去。我们可以在play中使用serial语句来告诉ansible限制并行执行play的主机数量。

下面是一个在amazon EC2的负载均衡器中移除主机,更新软件包,再添加回负载均衡的配置示例:

在上述示例中,serial的值为1,即表示在某一个时间段内,play只在一台主机上执行。如果为2,则同时有2台主机运行play。

一般来讲,当task失败时,ansible会停止执行失败的那台主机上的任务,但是继续对其他主机执行。在负载均衡的场景中,我们会更希望ansible在所有主机执行失败之前就让play停止,否则很可能会面临所有主机都从负载均衡器上摘除并且都执行失败导致服务不可用的场景。这个时候,我们可以使用serial语句配合max_fail_percentage语句使用。max_fail_percentage表示当最大失败主机的比例达到多少时,ansible就让整个play失败。示例如下:

假如负载均衡后面有4台主机,并且有一台主机执行失败,这时ansible还会继续运行,要让Play停止运行,则必须超过25%,所以如果想一台失败就停止执行,我们可以将max_fail_percentage的值设为24。如果我们希望只要有执行失败,就放弃执行,我们可以将max_fail_percentage的值设为0。

15.5、只执行一次

某些时候,我们希望某个task只执行一次,即使它被绑定到了多个主机上。例如在一个负载均衡器后面有多台应用服务器,我们希望执行一个数据库迁移,只需要在一个应用服务器上执行操作即可。

可以使用run_once语句来处理:

还可以与local_action配合使用,如下:

还可以与delegate_to配合使用,让这个只执行一次的任务在指定的机器上运行:

15.6、设置环境变量

我们在命令行下执行某些命令的时候,这些命令可能会需要依赖环境变量。比如在安装某些包的时候,可能需要通过代理才能完成完装。或者某个脚本可能需要调用某个环境变量才能完成运行。

ansible 支持通过environment关键字来定义一些环境变量。

在如下场景中可能需要用到环境变量:

  • 运行shell的时候,需要设置path变量

  • 需要加载一些库,这些库不在系统的标准库路径当中 下面是一个简单示例:

事实上,environment也可以存储在变量当中:

15.7、交互式提示

在少数情况下,ansible任务运行的过程中需要用户输入一些数据,这些数据要么比较秘密不方便,或者数据是动态的,不同的用户有不同的需求,比如输入用户自己的账户和密码或者输入不同的版本号会触发不同的后续操作等。ansible的vars_prompt关键字就是用来处理上述这种与用户交互的情况的。

vars_prompt常用选项说明:

  • private: 默认为yes,表示用户输入的值在命令行不可见

  • default:定义默认值,当用户未输入时则使用默认值

  • confirm:如果设置为yes,则会要求用户输入两次,适合输入密码的情况

16. Ansible Playbook之tags

在大型项目当中,通常一个playbook会有非常多的task。而我们每次执行这个playbook时,都会将所有task运行一遍。而事实上,在实际使用过程中,我们可能只是想要执行其中的一部分任务而已,并不想把整个playbook完整跑一遍。这个时候就需要用到tags。

通过tags,我们可以给playbook中的某一些任务打上“标签”,而在执行playbook的时候,我们可以通过选定标签的方式指定只执行哪一些任务或者不执行哪一些任务。

16.1、为task打tag

下面是一个安装httpd的简单示例:

在上面的示例中,我们为两个task定义了三个tags:install_httpd、conf_httpd以及start_httpd。

16.2、使用tag

16.2.1、执行指定tag的task

有了tags之后,我们就可以只运行playbook中指定标签的task了:

也可以一次指定多个tag执行:

16.2.2、排除指定tag的task

通过下面的方式可以排除指定了tag的task,即除了指定tag的task不执行,其他task都执行:

执行效果跟上面一样。

16.2.3、查看playbook中的所有tag

可以通过--list-tags参数列出指定的playbook中所有的tag

16.3、打tag的几种方式

16.3.1、为一个任务指定一个标签

​​​​​​​

这种方式就是上面示例中的方法:

16.3.2、为一个任务指定多个标签

可以通过列表的方式为一个任务指定多个标签:

16.3.3、为一个play指定一组标签

当为一个play指定一组标签后,该play下的所有task都会自动继承该标签,各task也可以自定义自己的标签。

ansible内置tag

除了用户自定义tag,ansible也内置了几个tag,这些tag都包含特殊含义:

  • always:一旦某个task被打上了always的tag,则无论是playbook的完整执行,还是指定tag执行,不管你指定的tag是啥,该任务总是会被执行。除非明确指定"--skip-tags=always"选项,才不会执行该task。

  • never:该标签与always正好相反,总是不会执行,除非明确指定"--tags=never"选项。

  • tagged:在调用时使用

  • untagged:在调用时使用

  • all:表示所有任务都会被执行,在默认情况下,不指定任何标签,则使用的就是该标签

17. Ansible Playbook Include

说明 在前面的学习当中,我们一直使用一个playbook文件来组织所有的task任务。但是,当我们项目越来越大,task越来越多的时候,如果还将所有的task都写到一个playbook当中,可读性就会变差,这个时候我们就需要重新来组织playbook了。

我们可以将一个大的playbook拆成若干个小的playbook文件,在主配置文件中将这些零碎的小文件引入进来,而这种方式就叫做playbook的"include"。

17.1、include

playbook的include其实就是使用include关键字

17.1.1、tasks include

  1. include简单示例

下面是两个playbook示例,分别用于安装lamp和lnmp环境:

在上面的示例当中,我们可以看到lamp和lnmp中mysql和php的安装都是一样的,所以我们可以将这两个任务提取出来,放到一个单独的task文件中,然后在lnmp和lamp中引入:

  1. 在include时引入变量

也可以在include的时候,传入变量:

通过如下方式带入变量:

再给一个例子:

在上面的示例当中,两个Include分别对应两个tag,如果我们在执行test_include.yml时,指定tag为t2,那么in2.yml中的所有任务都会被执行。所以tag是针对include的所有任务生效。

  1. 在include中使用条件判断

  1. 在include中使用循环

下面是一个简单的循环示例:

可以看到in.yml被循环执行了两次。

我们可以稍微修改in.yml示例如下:

再次执行playbook的结果如下:

可以看到item的值就来自test_include中的loop循环。那么这就引出了一个问题:如果正好in.yml当中也有循环时怎么办?

再次执行test_include,结果如下:

这个时候,可以看到最终item的值来自in.yml中的循环。那如果我就想要使用test_include中的循环的值怎么办? 我们再次修改test_include.yml以及in.yml如下:

再次查看结果:

可以看到,outer_item中的值正是外层循环中item的值。当出现这个双层循环时,可以在外层循环中使用loop_var选项指定一个变量,这个变量用于替代外层循环中的item变量,以便在内层循环中获取到外层循环的item的值,从而避免两层循环中item变量名的冲突。

17.1.2、handlers include

handlers include与tasks include大体类似,直接给例子:

17.2、include_tasks

17.2.1、基本使用

在前面我们详细说了include的用法,然而事实上在后续的ansible版本当中,include语法可能会被弃用。而使用一些新的关键字来代替include的原始用法,include_tasks就是其中之一。

我们知道include可以用于包含tasks,handlers,playbooks等,而include_tasks则专门用于包含tasks:

执行结果如下:

可以看到,当我们使用include_tasks时,include_tasks本身会被当做一个task,这个task会把include的文件的路径输出在控制台中中, 这就是include_tasks和include之间的区别。include是透明的,而include_tasks是可见的,include_tasks更像是一个任务,这个任务包含了其他的一些任务。

在ansible 2.7版本当中,include_tasks还加入了新的参数,下面是一个简单用法示例:

当然这种使用方法与include_tasks: in.yml的效果完全相同。

17.2.2、在include_tasks中使用tags

在前面我们提到过,如果为include添加tags,那么tags是对include中所有任务生效的。也就是说,如果调用include对应的tag,那么include文件中的所有任务都会执行。

但是对include_tasks添加tags,则只会对include_tasks本身生效,include_tasks中所有的任务都不生效。示例如下:

执行结果如下:

执行结果:

在上一篇我们讲到tags的时候说过,如果一个任务被打上了tags: always标签,则即使我们调用其他任务的标签,该任务也会被执行。

需要说明的是,在这里,tags: always标签只针对include_tasks本身生效,也就是说,如果其他任务的标签被调用,include_tasks本身会被调用,而其包含的任务不会被调用。如果要想其包含的任务也总是被调用,可修改配置如下:

17.3、import_tasks

import_tasks与include_tasks用法类似,都用于包含一个任务列表:

执行结果:

可以看到,import_tasks模块并不会像include_tasks模块一样,在控制台输出自身的任务信息,其相对透明。

除此之外,import_tasks和include_tasks还有如下不同:

import_tasks是静态的,被import的文件在playbook被加载时就预处理了,而include_tasks是动态的,被include的文件在playbook被运行时候才开始处理。一个简单的例子:

在上面的示例中,include_tasksimport_tasks均会被执行。

再看下面的例子:

此时,import_tasks就会出错:

当使用静态的import时,请确保文件名中使用到的变量被定义在vars、vars_files或者extra-vars中,不支持其他的方式传入变量。

  1. 如果想要对包含的任务列表进行循环操作,则只能使用include_tasks,import_tasks不支持循环操作。也就是说,使用loop或者with_X对include文件进行循环操作时,只能配合include_tasks才能正常使用

  2. 当使用when对include文件添加条件判断时,include_tasks和import_tasks

有着本质的不同:

  1. 当对include_tasks使用when时,when对应的条件只会应用于include_tasks任务本身,当执行被包含的任务时,不会对这些被包含的任务重新进行条件判断

  2. 当对import_tasks使用when时,when对应的条件会被应用于被import的文件中的每一个任务,当执行被import的任务时,会对每一个被包含的任务进行同样的条件判断。

示例如下:

执行结果:

17.4、在handlers中使用include_tasks及import_tasks

我们知道,handlers中执行的其实也是任务,只不过是被触发才会运行,所以如果要在handlers中引入任务,也可直接使用include_tasks和import_tasks。没有include_handlers的说法。

17.5、import_playbook

我们在前面提到过,include除了可以引用任务列表,还可以引用整个playbook,在之后的版本中,如果想要引入playbook,则需要使用import_playbook模块。在2.8版本后,使用include引用整个playbook的特性会被弃用。

示例:

18. Ansible Playbook Roles

18.1、角色(roles) 在Ansible中,role是将playbook分割为多个文件的主要机制。它大大简化了复杂playbook的编写,同时还使得它们非常易于复用。

18.1.1、role的基本构成 roles文件组织结构示例:

roles各目录的作用及可用的文件:

  • files:用于存放一些非模板文件的文件,如https证书等。

  • tempaltes:用于存放角色相关的Jinja2模板文件,当使用角色相关的模板时,如未明确指定模板路径,则默认使用此目录中的模板

  • tasks:角色所要执行的所有任务文件都存放于此,包含一个主文件main.yml,可以在主文件中通过include的方式引入其他任务文件

  • handlers:用于定义角色中需要调用 的handlers,包含一个主配置文件main.yml,可通过include引入其他的handlers文件。

  • vars:用于定义此角色用到的变量,包含一个主文件main.yml

  • meta:用于存储角色的元数据信息,这些元数据用于描述角色的相关属性,包括作者,角色的主要作用,角色的依赖关系等。默认这些信息会写入到当前目录下的main.yml文件中

  • defaults:除了vars目录,defaults目录也用于定义此角色用到的变量,与vars不同的是,defaults中定义的变量的优先级最低。

创建role的步骤如下:

    1. 创建以roles命名的目录

    1. 在roles目录中分别创建角色名称命名的目录,如websrvs等

    1. 在每个角色命名的目录中分别创建files、handlers、meta、tasks、teamplates和vars目录,用不到的目录可以创建为空目录,也可以不创建。

    1. 在playbook文件中,调用各角色

需要说明的是,以上目录并不都是必须的,如果你的roles当中并不需要用到某一个目录,也可以不用创建,比如我们将所有的变量都放到defaults中,则可以不需要vars目录,如果未用到模板文件,则不需要templates目录。

18.1.2、在playbook中使用roles

基本引用的方法:

也可以通过如下方法引用时带入变量:

还可以在引用时使用条件语句:

通过include引入role

18.1.3、pre_tasks和post_tasks

如果在执行一个role时,需要在其前或其后依然要执行某些任务,我们可以使用pre_tasks及post_tasks来声明。pre_tasks是在role之前执行,而post_tasks则在role之后执行:

18.1.4、role的依赖

如果当前role在执行前需要依赖另一个role,我们可以在roles的meta目录中的main.yml中定义role的依赖关系。

示例1:

18.2、Ansible Galaxy

ansible-galaxy是一个工具,我们可以利用它快速的创建一个标准的roles目录结构,还可以通过它在Ansible Galaxy上下载别人写好的roles,直接拿来用。

初始化一个roles的目录结构:

安装别人写好的roles:

列出已安装的roles:

查看已安装的roles信息:

卸载roles:

补充:通过ansible-galaxy初始化一个collection:

collection集合案例:

19、Ansible Vault配置加密

简介 在使用ansible的过程中,不可避免的会存储一些敏感信息,比如在变量文件中存储帐号密码信息等。

ansible通过ansible-vault命令行工具来提供对敏感文件的加密和解密。

ansible-vault可以创建、加密、解密和查看文件。其可以加密任何ansible使用的文件,包括inventory文件,playbook中调用的变量文件等。

19.1、Ansible-vault常用操作

1、​​​​​​​创建加密文件

2、编辑加密文件

3、重置密码

4、加密已有文件

5、解密文件

6、查看文件

19.2、Ansible-vault配置示例

1、创建一个user.yml的变量文件,内容如下:

2、加密上面创建的变量文件:

3、编写playbook文件如下:

4、执行playbook

也可以通过如下操作执行playbook:

20、动态Inventory管理

20.1、动态主机管理模块

20.1.1、add_host

在playbook执行的过程中,动态的添加主机到指定的主机组中

常用参数:

  • groups:添加主机至指定的组

  • name:要添加的主机名或IP地址 示例:

20.1.2、group_by

在playbook执行的过程中,动态的创建主机组

示例:

20.2、动态inventory管理

20.2.1动态inventory简介

在前面我们所有的选取主机组的操作都是通过维护inventory文件来完成的。而事实上,当在大规模应用当中,如果主机达成千上万台,这个时候还手动维护inventory文件将会给运维工作带来巨大的挑战。

在这种大规模的应用场景中,通常的做法是,将所有的主机都存储在cmdb当中。当需要对某一组主机或者某一类型的主机执行相应操作时,通过cmdb将相应主机取出来,动态的生成inventory,然后交由ansible处理即可。

所以其实Ansible Inventory包含静态inventory和动态inventory两部分。而我们前面通过手动在inventory文件中维护主机列表的方式即称之为静态inventory。而动态inventory则是指通过外部脚本获取主机列表,并按照ansible所要求的格式返回给ansible指令的操作方式。

动态inventory一般都会结合cmdb或者云计算平台等获取主机信息,由于主机资源一般会动态的进行增减,而这些系统一般会智能更新。我们需要通过这些工具提供的api或者接入库查询等方式返回主机列表。

20.2.2、动态inventory脚本规约

动态inventory脚本最终返回的满足ansible输出格式的json数据。ansible对于使用什么语言来实现动态inventory没有要求。但脚本必须支持两个参数:

  • --list:用于返回所有的主机组信息,每个组所包含的主机列表hosts、子组列表children、主机变量列表vars都应该是字典形式的,而_meta则用于存放主机变量

  • --host :返回指定主机的变量列表,也可返回一个空字典

20.2.3、动态inventory脚本示例

[root@controller ansible]# cat dynamic_inventory.py

脚本需要设置x权限,否则ansible会提示没有权限调用:

执行该脚本,返回如下:

通过ansible操作示例如下:

21. Ansible性能调优

简介

在当前配置管理工具大行其道的应用中,ansible凭借其轻量级、agentless等特性得以占据一席之地。然而其也并不是完美无缺,事实上,其最为人所诟病的就是在大规模服务器应用场景当中表现出的性能问题。所以本篇文档就来说一说如何通过一些配置优化来加速ansible的运行。

21.1、开启ansible性能监测

ansible附带了一组回调插件,可以通过callback_whitelist 指令在ansible.cfg文件中启用这些插件

  • cgroup_perf_recap可查看一个playbook消耗的cpu和内存

  • timer 可用于查看playbook执行所消耗的时间

也可通过如下指令直接执行:

21.2、任务优化

高效复制文件到被控端

  • 尽可能可能的使用模板取代lineinfile

21.3、优化ssh连接

默认情况下,ansible基于ssh连接被控端,当ansible在运行playbook的时候,会建立很多的ssh连接来执行相应的task,而每个task都会创建一个新的ssh连接,ssh连接的建立毫无疑问需要额外tcp建立连接的开销。

openssh支持一个优化,叫做ssh multiplexing,也被称作ControlPersist。当启用了该特性,则多个连接到相同主机的ssh会话将会共享相同的tcp连接。这样就只有第一次连接的时候需要进行TCP三次握手。

当启用Multiplexing后:

  • 第一次ssh连接到主机的时候,openssh会创建一个主连接

  • 紧接着openssh会创建一个控制套接字,通过主连接与远程主机关联

  • 当有新的ssh尝试连接到主机时,openssh将使用控制套接字与远程主机通信,并不会创建新的tcp连接 开启该配置项:

配置项说明:

  • ControlMaster:启用ssh multiplexing,允许多个同时与远程主机连接的ssh会话使用单一网络连接。auto用于告诉ssh在主连接和控制套接字不存在的情况下,自动创建它们。第一个ssh会话建立,则与同一主机连接的其他会话重复利用此连接,从而绕过较慢的初始过程,ssh在最后一个会话关闭后,立即销毁共享的连接

  • ControlPersist:使连接在后台保持打开,而不是在上一次会话后销毁连接。其配置空闲的连接保持打开的时间长度,每个新会话将重置此空闲计时器。

21.4、pipelining

在说明pipelining之前,需要先了解下ansible是如何执行一个task的:

  • 基于playbook中的task生成一个python脚本

  • 将生成的python脚本复制到被控主机上

  • 在被控主机上运行这个脚本 上面三个步骤中,后面两个步骤会产生两个ssh会话。而在pipelinin模式下,ansible执行python脚本时并不会复制它,而是通过管道传递给ssh会话。这样一来,就会让原本需要的两个ssh会话减少为一个,以降低开销,节省时间。

启用pipelining:

需要注意的是,如果开启pipelining,则需要在被控端的/etc/sudoers文件中关闭requiretty:

21.5、并发执行

默认情况下,ansible在批量连接客户端执行play时的并发数是5个,可以通过调整该值来提高并发数:

即使提高了并发个数,playbook也可能在执行一个需要较长时间的任务时导致阻塞,针对这类任务,可以使用异步的方式来运行:

当使用上面的任务控制超过forks设置的节点个数时,'install mlocate'任务会先在forks个节点上跑,完成后再继续下一批,这个并发数是由我们设置的forks选项控制的。而'Run updatedb'这个任务会一次性在所有节点上都执行,执行的超时时间为300s,然后每10s轮循一次检查它是否完成。需要说明的是,因为需要等待其执行完成,所以如果这个任务比较耗时,仍然需要等待其执行完毕后才能开始下一个任务。

但是如果该任务只是一个后台任务,比如只是在后台执行一个脚本或者启动一个服务,不需要等待其返回结果。那就不需要检查这个任务是否完成,只需要继续执行其它任务,最后再使用wait_for这个模块去检查之前的脚本或者服务是否按预期中运行了便可。这时候只需要把poll的值设置为0,便可以按上面的要求配置ansible不等待job的完成。

还有一种需求,假如一个task,它就是需要运行很长的时间,不能让它超时退出,需要一直等待这个task完成。这个时候就需要将async的值设置为0。

总结来说,大概有以下的一些场景需要使用到ansible的polling特性:

  • 某个task需要运行很长的时间,这个task很可能会达到timeout。

  • 某个task需要在大量的机器上面运行

  • 某个task是不需要等待它完成的

当然也有一些场景是不适合使用:

  • 这个任务是需要运行完后才能继续另外的任务的

  • 这个任务能很快的完成

22. Ansible调试

22.1、运行前检查

1、当我们在运行ansible-playbook时,使用--check选项时,将不会对受控主机作出任何更改,而是通过模拟运行的方式执行所有task,以用于检查playbook在运行时的状态:

2、在运行ansible-playbook时,如果使用--diff选项配合--check选项,可以用于检查本次执行play时,相较上一次产生了哪些改变:

3、有些时候,我们在检测模式下运行play时,我们会希望某个play总是运行,我们可以使用always_run子句:

需要说明的是,如果一个task中同时包含when和always_run,如果when返回了false,即使alwys_run为true,任务依然会被跳过。 22.2、打印详细输出信息 参考《6. Ansible Playbook基本使用》

22.3、使用debug模块

在前面debug模块使用的比较多了,这里直接再给个示例:

22.4、使用assert模块

assert模块会在指定的条件不符合的时候返回错误并失败退出。

当调试playbook的时候,插入assert模块在我们设定的某些条件不成立时立刻失败,对调试很有用。

下面示例用于检查目标文件是否是一个目录,如果不是,则失败退出:

22.5、限制特定的task运行

22.5.1、指定任务执行

可以通过--start-at-task参数告诉Ansible从指定的task开始运行playbook,而不是从头开始运行。如果你的playbook因为某一个task中有bug而失败了,在你修复了这个bug后希望从被修复的这个task开始再次执行playbook的时候,就可以使用这个参数。

22.5.2、分步执行

可以通过--step选项来交互式的执行playbook:

这样ansible在每个任务前会自动停止,并询问是否应该执行该任务。

假如有一个名为"configure ssh"的任务,playbook执行到这里会停止并询问:

  • "y"会执行该任务

  • "n"会跳过该任务

  • "c"则会继续执行剩余的所有任务而不再询问

22.5.3、tags

参考《17. Ansible Playbook之tags》

23. Ansible lineinfile模块

简介 之所以专门说一说这个模块,是因为lineinfile在实际使用中非常有用。

实际上,在大多数时候,我们在linux上的操作,就是针对文件的操作,通过配置管理工具对配置文件作统一的配置修改是一个非常酷的功能。

下面是官方针对该模块的说明:

简单讲,这个模块就是针对一个文件中行内容的操作。

下面我们详细说一说其具体可以做的事情。

23.1、修改匹配行

下面是一个简单的task示例:

23.2、在匹配行前或后添加内容

示例文件如下:

23.3、在匹配行前添加

在http.conf文件的Listen 80前面添加一行Listen 8080,task示例如下:

23.4、在匹配行后添加

在http.conf文件的Port后面添加一行just a test,task示例如下:

23.5、修改文件内容及权限

示例文件:

修改/etc/hosts,将以127.0.0.1开头的行替换为 127.0.0.1 localhost,并将/etc/hosts的属主和属组都修改为root,权限改为644,如下:

删除以10.1.61.130开头的行:

文件存在则添加一行内容

往/etc/hosts里添加一行10.1.61.131 test.dz11.com(多次执行,不会重复添加),示例如下:

23.7、如果有匹配的行则修改该行,如果不匹配则添加

示例原文件/tmp/test.txt内容如下:

下面的示例task中,匹配以%wheel开头的行,匹配到,则执行替换,未匹配,则添加。因为原文件中,没有以%wheel开头的行,所以会添加一行:

修改后的文件如下:

23.8、参数backrefs,backup说明

  • backup: 是否备份原文件,默认为no

  • backrefs:

    • 当backrefs为no时,如果regex没有匹配到行,则添加一行,如果Regx匹配到行,则修改该行

    • 当backrefs为yes时,如果regex没有匹配到行,则保持原文件不变,如果regex匹配到行,则修改该行

    • backrefs默认为no,所以上面那个示例中,我们没有配置backrefs,而默认没有匹配,则修改。 下面我们看一看backrefs为yes时匹配到行的示例:

示例原文件:

task示例:

修改后的文件:

23.9、使用validate验证文件是否正确修改

在一些场景下,我们修改完文件后,需要对文件做一下测试,用以检查文件修改之后,是否能正常运行。如http.conf、nginx.conf等,一旦改错,而不加以测试,可能会直接导致http服务挂掉。

可以使用validate关键字,在修改完成以后,对文件执行检测:

Last updated