Ansible命令大全【上】

一、ansible简介与安装
1、ansilbe特性
Agentless:不需要再被管理节点上安装客户端,只要有sshd即可
Serverless:在服务端不需要启动任何服务,只需要执行命令就行
YAML:使用yaml语言定制playbook
SSH by default:默认使用ssh控制各节点
Strong multi-tier solution:可实现多级控制

2、ansible的基本组建
核心:ansible
核心模块(Core Modules):这些都是ansible自带的模块
扩展模块(Custom Modules):如果核心模块不足以完成某种功能,可以添加扩展模块
插件(Plugins):完成模块功能的补充
剧本(Playbooks):把需要完成的多个任务定义在剧本中
连接插件(Connectior Plugins):ansible基于插件连接到各个主机上,虽然ansible是使用ssh连接到各个主机的。
主机群(Host Inventory):ansible在管理多台主机时,可以选择只对其中的一部分执行某些操作

3、工作原理
Ansible 在管理节点将 Ansible 模块通过SSH协议(或者 Kerberos、LDAP)推送到被管理端执行,执行完之后自动删除,可以使用版本控制系统(git/svn)来管理自定义模块及playbooks。

4、ansible的安装
# 解决依赖关系:
yum install -y python36 python36-devel python36-setuptools gcc libffi-devel openssl-devel
# 下载ansible:
wget https://github.com/ansible/ansible/archive/v2.8.0.tar.gz
#解压安装
tar xf v2.8.0.tar.gz
cd ansible-2.8.0/
python setup.py build
python setup.py install
mkdir /etc/ansible
cp -r examples/* /etc/ansible

二、ansible的inventory
在使用Ansible来批量管理主机的时候,通常我们需要先定义要管理哪些主机或者主机组,而这个用于管理主机与主机组的文件就叫做Inventory,也叫主机清单,该文件默认位于/etc/ansible/hosts。
当然我们也可以通过修改ansible配置文件的hostfile配置项来修改默认inventory的位置。
1、简单的主机和组
mail.yanruogu.com
[webservers]
web1.yanruogu.com
web2.yanruogu.com 
[dbservers]
db1.yanruogu.com
db2.yanruogu.com
# 中括号中的名字代表组名,可以根据自己的需求将庞大的主机分成具有标识的组,如上面分了两个组webservers和dbservers组;  
# 主机(hosts)部分可以使用域名、主机名、IP地址表示;当然使用前两者时,也需要主机能反解析到相应的IP地址,一般此类配置中多使用IP地址;

2、指定主机范围
[webservers]
www[01:50].yanruogu.com
[databases]
db-[a:f].yanruogu.com
# 下面指定了从web01到web50,webservers组共计50台主机;databases组有db-a到db-f共6台主机

3、定义主机嵌套
# 如下示例中,production组包含两个子组,分别为webservers和dbservers,webservers和dbservers组分别包含若干主机
[webservers]
web1.lab.example.com
web2.lab.example.com
[dbservers]
db1.lab.example.com
db2.lab.example.com
[production:children]
webservers
dbservers

4、选择主机和组
(1)匹配所有主机
# ansible all --list-hosts
可以通过all或者*来指定匹配所有主机,通过如下指令查看all匹配到的主机:

(2)匹配指定的主机或主机组
#ansible prod --list-hosts       #匹配prod主机组
#ansible db2.example.com --list-hosts   #匹配单台主机
#ansible 'lb1.lab.example.com,s1.lab.example.com,db1.example.com' --list-hosts   #匹配多台主机
#ansible 'london,boston' --list-hosts    #匹配多个组
#ansible ungrouped --list-hosts   #匹配不属于任何组的主机

(3)通配符匹配
#ansible '*.example.com' --list-hosts  #匹配'*.example.com'所有主机
#ansible '172.25.*' --list-hosts   #匹配172.25.*开头的主机
#ansible 's*' --list-hosts    #匹配以s开头的主机及主机组

(4)通配符组合匹配
#ansible '*.example.com,!*.lab.example.com' --list-hosts    #匹配包含*.example.com但不包含*.lab.example.com的主机
#ansible 'prod,172*,*lab*' --list-hosts  #匹配包含prod以及172开头、包含lab关键字的主机或组
#ansible 'db,&london' --list-hosts  #匹配属于db组同时还属于london组的主机
#ansible 'boston,london,&prod,!lb' --list-hosts  #匹配在london组或者boston组,还必须在prod组中且必须不在lb组中的主机:

(5)正则表达式
在开头的地方使用”~”,用来表示这是一个正则表达式:
#ansible '~(s|db).*example\.com' --list-hosts  #表示匹配以s或者db开头的,并以example.com结尾的主机信息。

(6)通过--limit明确指定主机或组
#ansible ungrouped  --limit srv1.example.com --list-hosts  #通过明确指定主机域名。
#ansible ungrouped  --limit @retry_hosts.txt --list-hosts  #通过文件来指定域名。
vi retry_hosts.txt
srv1.example.com

三、ansible的配置文件管理
ansible的配置文件名为ansible.cfg,它一般会存在于四个地方:
(1)ANSIBLE_CONFIG:首先,Ansible命令会检查该环境变量,及这个环境变量将指向的配置文件。
(2)./ansible.cfg:当前工作目录,即当前执行ansible指令的目录,如果ANSIBEL_CONFIG环境变量未定义,则优先使用该配置文件
(3)~/.ansible.cfg:当前用户家目录下的一个隐藏文件,如果当前工作目录下不存在ansible.cfg配置文件,则会查找用户家目录下的该隐藏文件
(4)/etc/ansible/ansible.cfg:默认配置文件,如果上面两个路径下的ansible.cfg都不存在,则使用该文件
注意:配置文件中所有的配置项都可以通过环境变量的方式来定义,而环境变量定义的配置项具有最高优先级,会覆盖掉所有配置文件中的配置项。

ansible.cfg的配置文件为8段:
[defaults]:通用配置项
[inventory]:与主机清单相关的配置项
[privilege_escalation]:特权升级相关的配置项
[paramiko_connection]:使用paramiko连接的相关配置项,Paramiko在RHEL6以及更早的版本中,默认使用的ssh连接方式
[ssh_connection]:使用OpenSSH连接的相关配置项,OpenSSH是Ansible在RHEL6之后默认使用的ssh连接方式
[persistent_connection]:持久连接的配置项
[accelerate]:加速模式配置项
[selinux]:selinux相关的配置项
[colors]:ansible命令输出的颜色相关的配置项
[diff]:定义是否在运行时打印diff(变更前与变更后的差异)

配置参数说明:
[default]
inventory      = /etc/ansible/hosts    #定义默认使用的主机清单
remote_user    = root   #ansible在操作远程主机时,使用远程主机上的哪个用户身份,默认是root
ask_pass       = false  #ansible在操作远程主机时,是否交互提示密码验证,默认为true。如果使用密钥认证的话,建议将其设置为false
log_path       = /var/log/ansible.log  #打开该配置项,所有的命令执行后,都会将日志输出到/var/log/ansible.log文件。
[privilege_escalation]
become=True         #如果ansible在操作远程主机时,使用的是远程主机上的普通用户,该普通用户是否允许提权
become_method=sudo  #如果允许提权,使用何种提权方式,默认是sudo
become_user=root    #提权到哪个用户身份,默认是root
become_ask_pass=False   #提权时,是否交互提示密码验证,默认为False
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s  #ansible通过ssh连接远程被管理机,这里用于定义一些ssh连接时的参数,如-C启用压缩传输,ControlPersist用于提升性能。
host_key_checking = False #通过ssh首次连接远程主机时,由于在本机的~/.ssh/known_hosts文件中并有fingerprint key串,ssh第一次连接的时候一般会提示输入yes/no进行确认将key字符串加入到~/.ssh/known_hosts文件中。将此项设置为False将跳过该确认过程。

四、Ad-hoc与命令行执行模块
Ad-hoc定义:是指ansible下临时执行的一条命令,并且不需要保存的命令,对于复杂的命令会使用playbook。Ad-hoc的执行依赖于模块,ansible官方提供了大量的模块。 如:command、raw、shell、file、cron等。
(1)ansible-doc -l 进行查看模块信息。
(2)ansible-doc -s module来查看某个模块的参数。
(3)ansible-doc help module来查看该模块更详细的信息。

Ad-hoc格式:ansible 主机或组 -m 模块名 -a '模块参数'  ansible参数
主机和组:是在/etc/ansible/hosts 里进行指定的部分,当然动态Inventory 使用的是脚本从外部应用里获取的主机。
模块名:可以通过ansible-doc -l 查看目前安装的模块,默认不指定时,使用的是command模块,具体可以查看/etc/ansible/ansible.cfg 的 “#module_name = command ” 部分,默认模块可以在该配置文件中进行修改;
模块参数:可以通过 “ansible-doc -s 模块名” 查看具体的用法及后面的参数;
ansible参数:可以通过ansible命令的帮助信息里查看到,这里有很多参数可以供选择,如是否需要输入密码、是否sudo等。

1、低级用户说明:
在被管理机上创建一个普通用户ansible,并配置sudo提权:
# 创建ansible用户
useradd ansible

# 设置ansible用户可免密提权
vim /etc/sudoers.d/ansible
ansible    ALL=(ALL)       NOPASSWD:ALL

chmod 400 /etc/sudoers.d/ansible

mkdir /home/ansible/.ssh
chown ansible.ansible /home/ansible/.ssh
chmod 700 /home/ansible/.ssh
在管理端修改ansible.cfg配置文件如下:
[default]
remote_user = ansible
ask_pass = False

[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
将管理端用户的id_rsa.pub复制到被管理端的/home/ansible/.ssh//home/ansible/.ssh/authorized_keys文件中,并设置该文件的权限为400
# 管理端执行:
ssh-copy-id -i .ssh/id_rsa.pub [email protected]
需要说明的是,通过上面的操作,需要为ansible用户先创建密码,在实际生产当中,不建议为该用户设置密码。可在安装系统时,提前将管理端公钥直接写入到被管理节点的ansible用户下

2、命令执行模块
命令执行模块包含如下 四个模块:
command模块:该模块通过-a跟上要执行的命令可以直接执行,不过命令里如果有带有如下字符部分则执行不成功 “ "<", ">", "|", "&" ;
shell 模块:用法基本和command一样,不过其是通过/bin/sh进行执行,所以shell 模块可以执行任何命令,就像在本机执行一样;
raw模块:用法和shell模块一样,也可以执行任意命令,就像在本机执行一样;
script模块:将管理端的shell 在被管理主机上执行,其原理是先将shell 复制到远程主机,再在远程主机上执行,原理类似于raw模块。
raw模块和comand、shell 模块不同的是其没有chdir、creates、removes参数,chdir参数的作用就是先切到chdir指定的目录后,再执行后面的命令,这在后面很多模块里都会有该参数 。
(1)command模块
free_form:要执行的linux指令
chdir:在执行指令之前,先切换到该指定的目录
removes:一个文件名,当该文件不存在,则该选项不执行
creates:一个文件名,当该文件存在,则该命令不执行
executable:切换shell来执行指令,该执行路径必须是一个绝对路径

chdir示例:
# 三个命令都会返回执行成功的状态。不过实际上只有前两个文件会被创建成功。使用raw模块的执行的结果文件事实上也被正常创建了,不过不是在chdir指定的目录,而是在当前执行用户的家目录
ansible 192.168.1.1 -m command -a 'chdir=/tmp/ touch test.file'
ansible 192.168.1.1 -m shell -a 'chdir=/tmp/ touch test2.file'
ansible 192.168.1.1 -m raw -a 'chdir=/tmp/ touch test3.file'
creates与removes示例:
ansible 192.168.1.1 -a 'creates=/tmp/server.txt uptime' #当/tmp/server.txt文件存在时,则不执行uptime指令
ansible 192.168.1.1 -a 'removes=/tmp/server.txt uptime' #当/tmp/server.txt文件不存在时,则不执行uptime指令

(2)script模块
#要执行的脚本文件script.sh内容如下:【在服务器端】 

#/bin/bash
ifconfig
df -hT

# 执行ansible指令:
ansible 10.212.52.252 -m script -a 'script.sh' 

(3)ping模块
ansible test -m ping

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

force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no
group:定义文件/目录的属组
mode:定义文件/目录的权限
owner:定义文件/目录的属主
path:必选项,定义文件/目录的路径
src:要被链接的源文件的路径,只应用于state=link的情况
dest:被链接到的路径,只应用于state=link的情况
state:
directory:如果目录不存在,创建目录
file:即使文件不存在,也不会被创建
link:创建软链接
hard:创建硬链接
touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
absent:删除目录、文件或者取消链接文件

ansible 192.168.8.120 -m file -a 'path=/tmp/test.txt state=touch owner=root group=root mode=644'
ansible 192.168.8.120 -m file -a 'src=/tmp/test.txt dest=/root/test.txt state=link'
ansible 192.168.8.120 -m file -a 'path=/tmp/test.txt state=file'
ansible 192.168.8.120 -m file -a 'path=/tmp/test state=directory owner=root group=root mode=755'
ansible 192.168.8.120 -m file -a 'path=/tmp/test2/test3/aaa/bbb state=directory owner=root group=root mode=755'
ansible 192.168.8.120 -m file -a 'path=/tmp/test2 state=absent'

(5)Copy模块
复制文件到远程主机,copy模块包含如下选项:
backup:在覆盖之前将原文件备份,备份文件包含时间信息。有两个选项:yes|no
content:用于替代"src",可以直接设定指定文件的值
dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录
force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes
others:所有的file模块里的相关文件属性选项都可以在这里使用
src:要复制到远程主机的文件在本地的地址,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用"/"来结尾,则只复制目录里的内容,如果没有使用"/"来结尾,则包含目录在内的整个内容全部复制,类似于rsync。
示例如下:
ansible 192.168.8.120 -m copy -a 'src=/etc/ansible/ansible.cfg dest=/usr/local/src/ owner=root group=root mode=644'
ansible 192.168.8.120 -m copy -a 'backup=yes src=/etc/fstab dest=/usr/local/src/ansible.cfg owner=root group=root mode=644'
ansible 192.168.8.120 -m copy -a 'content="just a test!" dest=/usr/local/src/test.txt'
ansible 192.168.8.120 -m copy -a 'src=/data dest=/usr/local/src/'
ansible 192.168.8.120 -m copy -a 'src=/data/ dest=/usr/local/src/'
ansible 192.168.8.120 -m copy -a "src=/mine/sudoers dest=/etc/sudoers validate='visudo -cf %s'"

(6)Yum模块
使用yum包管理器来管理软件包,其选项有:
name:要进行操作的软件包的名字,也可以传递一个url或者一个本地的rpm包的路径
state:状态(present,absent,latest)
示例如下:
ansible test -m yum -a 'name=httpd state=latest'
ansible test -m yum -a 'name="@Development tools" state=present'
ansible test -m yum -a 'name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present'

(7)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
使用示例:

ansible test -m service -a "name=httpd state=started enabled=yes"
asnible test -m service -a "name=foo pattern=/usr/bin/foo state=started"
ansible test -m service -a "name=network state=restarted args=eth0"

(8)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:确认该任务计划是创建还是删除
user:以哪个用户的身份执行
示例:
ansible test -m cron -a 'name="a job for reboot" special_time=reboot job="/some/job.sh"'
ansible test -m cron -a 'name="yum autoupdate" weekday="2" minute=0 hour=12 user="root"'
ansible test -m cron  -a 'backup="True" name="test" minute="0" hour="5,2" job="ls -alh > /dev/null"'
ansilbe test -m cron -a 'cron_file=ansible_yum-autoupdate state=absent'

(9)user模块与group模块
user模块是请求的是useradd, userdel, usermod三个指令,goup模块请求的是groupadd, groupdel, groupmod 三个指令。
home:指定用户的家目录,需要与createhome配合使用
groups:指定用户的属组
uid:指定用的uid
password:指定用户的密码
name:指定用户名
createhome:是否创建家目录 yes|no
system:是否为系统用户
remove:当state=absent时,remove=yes则表示连同家目录一起删除,等价于userdel -r
state:是创建还是删除
shell:指定用户的shell环境
使用示例:

user: name=johnd comment="John Doe" uid=1040 group=admin
user: name=james shell=/bin/bash groups=admins,developers append=yes user: name=johnd state=absent remove=yes
user: name=james18 shell=/bin/zsh groups=developers expires=1422403387
#生成密钥时,只会生成公钥文件和私钥文件,和直接使用ssh-keygen指令效果相同,不会生成authorized_keys文件
user: name=test generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_rsa  
需要说明的是,在指定password参数时,不能使用明文密码,因为后面这一串密码会被直接传送到被管理主机的/etc/shadow文件中,所以需要先将密码字符串进行加密处理。然后将得到的字符串放到password中即可。

echo "123456" | openssl passwd -1 -salt $(< /dev/urandom tr -dc '[:alnum:]' | head -c 32) -stdin
$1$4P4PlFuE$ur9ObJiT5iHNrb9QnjaIB0

#使用上面的密码创建用户
ansible all -m user -a 'name=foo password="$1$4P4PlFuE$ur9ObJiT5iHNrb9QnjaIB0"'
不同的发行版默认使用的加密方式可能会有区别,具体可以查看/etc/login.defs文件确认,centos 6.5版本使用的是SHA512加密算法。

group示例
group: name=somegroup state=present

(10)synchronize模块
使用rsync同步文件,其参数如下:
archive: 归档,相当于同时开启recursive(递归)、links、perms、times、owner、group、-D选项都为yes ,默认该项为开启
checksum: 跳过检测sum值,默认关闭
compress:是否开启压缩
copy_links:复制链接文件,默认为no ,注意后面还有一个links参数
delete: 删除不存在的文件,默认no
dest:目录路径
dest_port:默认目录主机上的端口 ,默认是22,走的ssh协议
dirs:传输目录不进行递归,默认为no,即进行目录递归
rsync_opts:rsync参数部分
set_remote_user:主要用于/etc/ansible/hosts中定义或默认使用的用户与rsync使用的用户不同的情况
mode: push或pull 模块,push模的话,一般用于从本机向远程主机上传文件,pull 模式用于从远程主机上取文件
使用示例:

src=some/relative/path dest=/some/absolute/path rsync_path="sudo rsync"
src=some/relative/path dest=/some/absolute/path archive=no links=yes
src=some/relative/path dest=/some/absolute/path checksum=yes times=no
src=/tmp/helloworld dest=/var/www/helloword rsync_opts=--no-motd,--exclude=.git mode=pull

(11)filesystem模块
在块设备上创建文件系统
常用选项:
dev:目标块设备
force:在一个已有文件系统 的设备上强制创建
fstype:文件系统的类型
opts:传递给mkfs命令的选项
示例:
ansible test -m filesystem -a 'fstype=ext2 dev=/dev/sdb1 force=yes'
ansible test -m filesystem -a 'fstype=ext4 dev=/dev/sdb1 opts="-cc"'

(12)mount模块
配置挂载点,选项:
fstype:必选项,挂载文件的类型
name:必选项,挂载点
opts:传递给mount命令的参数
src:必选项,要挂载的文件
state:必选项
present:只处理fstab中的配置
absent:删除挂载点
mounted:自动创建挂载点并挂载之
umounted:卸载
示例:
name=/mnt/dvd src=/dev/sr0 fstype=iso9660 opts=ro state=present
name=/srv/disk src='LABEL=SOME_LABEL' state=present
name=/home src='UUID=b3e48f45-f933-4c8e-a700-22a159ec9077' opts=noatime state=present

ansible test -a 'dd if=/dev/zero of=/disk.img bs=4k count=1024'
ansible test -a 'losetup /dev/loop0 /disk.img'
ansible test -m filesystem 'fstype=ext4 force=yes opts=-F dev=/dev/loop0'
ansible test -m mount 'name=/mnt src=/dev/loop0 fstype=ext4 state=mounted opts=rw'

(13)get_url 模块
该模块主要用于从http、ftp、https服务器上下载文件(类似于wget),主要有如下选项:
sha256sum:下载完成后进行sha256 check;
timeout:下载超时时间,默认10s
url:下载的URL
url_password、url_username:主要用于需要用户名密码进行验证的情况
use_proxy:是事使用代理,代理需事先在环境变更中定义
示例:
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf sha256sum=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

(14)unarchive模块
用于解压文件,模块包含如下选项:
copy:在解压文件之前,是否先将文件复制到远程主机,默认为yes。若为no,则要求目标主机上压缩包必须存在。
creates:指定一个文件名,当该文件存在时,则解压指令不执行
dest:远程主机上的一个路径,即文件解压的路径
group:解压后的目录或文件的属组
list_files:如果为yes,则会列出压缩包里的文件,默认为no,2.0版本新增的选项
mode:解决后文件的权限
src:如果copy为yes,则需要指定压缩文件的源路径
owner:解压后文件或目录的属主
示例如下:

- unarchive: src=foo.tgz dest=/var/lib/foo
- unarchive: src=/tmp/foo.zip dest=/usr/local/bin copy=no
- unarchive: src=https://example.com/example.zip dest=/usr/local/bin copy=no
分类: 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 。
(1)Playbook基本语法
# cat user.yml
- name: create user
  hosts: all
  remote_user: root
  gather_facts: false  #指定在执行任务之前,是否先执行setup模块获取主机相关信息
  vars:
    user:"test"  #定义后续任务中会使用到的变量,如未用到,可不指定
  tasks:         #定义具体需要执行的任务
    - name: create  user
      user: name="{{ user }}"  #如果想删除user: name="{{ user }}" state=absent remove=yes
(2)playbook简单示例
1、创建playbook
- name: play to setup web server
  hosts: all
  tasks:
    - name: latest httpd version installed
      yum:
        name: httpd
        state: latest
        
    - name: correct index.html is present
      copy: 
        src: files/index.html
        dest: /var/www/html/index.html
        
    - name: start httpd service
      service:
        name: httpd
        state: started
        enabled: true
2、执行playbook语句
# ansible-playbook  manage_apache.yml
PLAY [play to setup web server] ************************************************************************************
TASK [Gathering Facts] *********************************************************************************************
ok: [10.1.61.187]
PLAY RECAP *****************************************************************************
10.1.61.187                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

ansible-playbook常用选项:
(1)打印详细信息
-v:打印任务运行结果
-vv:打印任务运行结果以及任务的配置信息
-vvv:包含了远程连接的一些信息

(2)校验playbook语法
# ansible-playbook --syntax-check  manage_apache.yml  

(3)测试运行playbook
# ansible-playbook -C  manage_apache.yml  #通过-C选项可以测试playbook的执行情况,但不会真的执行。

(4)多个play设置
- name: first play
  hosts: web.example.com
  tasks:
    - name: first task
      yum:
        name: httpd
        status: present
    - name: second task
      service:
        name: httpd
        state: started
    
- name: second play
  hosts: db.example.com
  tasks:
    - name: first task
      yum:
        name: mariadb-server
        status: present
    - name: second task
      service:
        name: mariadb
        state: started


六、Playbook的结构及handler语法
1、Playbook的结构说明
Target: 用于定义主机组及远程主机组上的用户,还包括定义通过什么样的方式连接远程主机(默认ssh)
Variable: 定义playbook运行时需要使用的变量
Task: 定义将要在远程主机上执行的任务列表
Handler: 定义task执行完成以后需要调用的任务

(1)远程用户和ad-hoc中的使用没有区别,默认不定义,则直接使用ansible.cfg配置中的用户相关的配置。也可在playbook中定义如下:
- name: /etc/hosts is up to date
  hosts: datacenter
  remote_user: automation
  become: yes
  become_mothod: sudo
  become_user: root

(2)Handler section
在Ansible Playbook中,handler事实上也是个task,只不过这个task默认并不执行,只有在被触发时才执行。
handler通过notify来监视某个或者某几个task,一旦task执行结果发生变化,则触发handler,执行相应操作。
handler会在所有的play都执行完毕之后才会执行,这样可以避免当handler监视的多个task执行结果都发生了变化之后而导致handler的重复执行(handler只需要在最后执行一次即可)。
tasks:
  - name: template configuration file
    template: 
      src: template.j2 
      dest: /etc/foo.conf
    notify:                 #关联后面的handlers语句。
      - restart memcached
      - restart apache
  - name: start memcached
    service:
      name: memcached
      state: started
  - name: start apache
    service
      name: httpd
      state: started
handlers:
  - name: restart memcached
    service:
      name: memcached
      state: restarted
  - name: restart apache
    service:
      name: httpd
      state: restarted

七、变量之自定义变量
1、在Inventory中定义变量
(1)在定义主机inventory时,在其前加上ansible_即成为内置变量。
# 一般连接
ansible_ssh_host     #用于指定被管理的主机的真实IP
ansible_ssh_port     #用于指定连接到被管理主机的ssh端口号,默认是22
ansible_ssh_user     #ssh连接时默认使用的用户名

# 特定ssh连接
ansible_connection     #SSH连接的类型:local, ssh, paramiko,在ansible 1.2之前默认是paramiko,后来智能选择,优先使用基于ControlPersist的ssh(如果支持的话)
ansible_ssh_pass       #ssh连接时的密码
ansible_ssh_private_key_file     #秘钥文件路径,如果不想使用ssh-agent管理秘钥文件时可以使用此选项
ansible_ssh_executable    #如果ssh指令不在默认路径当中,可以使用该变量来定义其路径

# 特权升级
ansible_become        #相当于ansible_sudo或者ansible_su,允许强制特权升级
ansible_become_user   #通过特权升级到的用户,相当于ansible_sudo_user或者ansible_su_user
ansible_become_pass   #提升特权时,如果需要密码的话,可以通过该变量指定,相当于ansible_sudo_pass或者ansible_su_pass
ansible_sudo_exec     #如果sudo命令不在默认路径,需要指定sudo命令路径

# 远程主机环境参数
ansible_shell_executable       #设置目标机上使用的shell,默认为/bin/sh
ansible_python_interpreter     #用来指定python解释器的路径,默认为/usr/bin/python 同样可以指定ruby 、perl 的路径
ansible_*_interpreter          #其他解释器路径,用法与ansible_python_interpreter类似,这里"*"可以是ruby或才perl等其他语言
下面是一个简单的示例:
# 指定了三台主机,三台主机的用密码分别是P@ssw0rd、123456、45789,指定的ssh连接的用户名分别为root、breeze、bernie,ssh 端口分别为22、22、3055 ,这样在ansible命令执行的时候就不用再指令用户和密码等了

[test]
192.168.1.3 ansible_ssh_user=bernie ansible_ssh_port=3055 ansible_ssh_pass='456789'

(2)定义主机组变量
# test组中包含两台主机,通过对test组指定vars变更,相应的host1和host2相当于相应的指定了ntp_server和proxy变量参数值
[test]
host1
host2
[test:vars]
ntp_server=192.168.1.10
proxy=192.168.1.20

# 下面是一个示例,指定了一个武汉组有web1、web2;随州组有web3、web4主机;又指定了一个湖北组,同时包含武汉和随州;同时为该组内的所有主机指定了2个vars变量。设定了一个组中国组,包含湖北、湖南。
[wuhan]
web1
web2
[suizhou]
web4
web3
[hubei:children]
wuhan
suizhou
[hubei:vars]
ntp_server=192.168.1.10
zabbix_server=192.168.1.10

2、在Playbook中定义变量
(1)通过vars关键词
- name: use vars define invrionmemnt
  hosts: test
  user: ansible
  vars: 
    http_port: 80
    server_name: localhost
    conf_file: /etc/nginx/conf/default.conf

(2)通过vars_files关键词
- hosts: all
  remote_user: root
  vars:
    favcolor: blue
  vars_files:
    - vars/external_vars.yml
    - vars/user_vars.yml

# vars/user_vars.yml示例:
users:
  bjones:
    first_name: Bob
    last_name: Jones
    home_dirs: /users/bjones
  acook:
    first_name: Anne
    last_name: Cook
    home_dirs: /users/acook
#变量的定义格式是成键值对出现的,键值对之间可以嵌套,最终形成一个大字典

(3)注册变量
在有些时候,可能需要将某一条任务执行的结果保存下来,以便在接下的任务中调用或者做些判断。可以通过register关键字来实现将某一任务结果保存为一个变量。
#下面是个简单的例子,将whoami命令执行的结果注册到变量login
- name: register variables
  hosts: test
  tasks:
    - name: capture output of whoami command
      command: whoami
      register: login

(4)通过命令行设置变量
ansible-playbook release.yml --extra-vars "hosts=vipers user=starbuck"
也可以改写成:--extra-vars '{"hosts":"vipers","user":"starbuck"}'

3、使用与调试变量
(1)变量的引用
- name: use vars define variables
  hosts: test
  vars: 
    http_port: 80
    server_name: localhost
    conf_file: /etc/nginx/conf/default.conf
    
  tasks:
    - name: print variables
      shell: echo "{{ http_port }} {{ server_name }} {{ conf_file }}"  > /tmp/text.txt

(2)变量的调试输出
- name: register variables
  hosts: test
  tasks:
    - name: capture output of whoami command
      command: whoami
      register: login
    - debug: var=login

关于debug的更多用法说明:调试模块,用于在调试中输出信息
msg:调试输出的消息
var:将某个变量传递给debug模块,debug会直接将其打印输出
verbosity:debug的级别

# Example that prints the loopback address and gateway for each host
- debug: msg="System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"

- debug: msg="System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}"
  when: ansible_default_ipv4.gateway is defined

- shell: /usr/bin/uptime
  register: result
- debug: var=result verbosity=2    #直接将上一条指令的结果作为变量传递给var,由debug打印出result的值

- name: Display all variables/facts known for a host
  debug: var=hostvars[inventory_hostname] verbosity=4


八、变量之Fact
fact简介:ansible有一个模块叫setup,用于获取远程主机的信息,并可以将这些信息作为变量在playbook里进行调用。而setup模块获取这些信息的方法就是依赖于fact。
# ansible test -m setup
10.1.61.187 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.1.61.187"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::f816:3eff:fe4f:6611"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "04/01/2014",
        "ansible_bios_version": "Ubuntu-1.8.2-1ubuntu1~cloud0",
        
        ...output omitted...
}
setup获取的这些信息,都是可用于该主机的变量。

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

下面是一个简单的示例,在ansibler主控端定义一个ini格式的custom.fact文件内容如下:
more /etc/ansible/facts.d/custom.fact
[general]
package = httpd
service = httpd
state = started

在服务器端执行
# ansible 10.1.61.187 -m setup        
10.1.61.187 | SUCCESS => {
    "ansible_facts": {
    
        ...output omitted...
        
        "ansible_local": {
            "custom": {
                "general": {
                    "package": "httpd",
                    "service": "httpd",
                    "state": "started"
                }
            }
        },

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

- name: Install Apache and starts the service
  hosts: test
  tasks:
    - name: Install the required package
      yum: 
        name: "{{ ansible_facts.ansible_local.custom.general.package }}"
        state: latest
    - name: Start the service
      service: 
        name: "{{ ansible_facts.ansible_local.custom.general.service }}"
        state: "{{ ansible_facts.ansible_local.custom.general.state }}"

2. 使用set_fact模块定义新的变量
set_fact模块可以自定义facts,这些自定义的facts可以通过template或者变量的方式在playbook中使用。如果你想要获取一个进程使用的内存的百分比,则必须通过set_fact来进行计算之后得出其值,并将其值在playbook中引用。
下面是一个set_fact模块的应用示例:
- name: set_fact example
  hosts: test
  tasks:
    - name: Calculate InnoDB buffer pool size
      set_fact: innodb_buffer_pool_size_mb="{{ ansible_memtotal_mb / 2 |int }}"    
    - debug: var=innodb_buffer_pool_size_mb

执行playbook如下:
# ansible-playbook set_fact_ex.yaml 
PLAY [set_fact example] *******************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************
ok: [10.1.61.187]
TASK [Calculate InnoDB buffer pool size] *********************************************************************************
ok: [10.1.61.187]
TASK [debug] ***************************************************************************************************************
ok: [10.1.61.187] => {
    "innodb_buffer_pool_size_mb": "3911.0" }
PLAY RECAP *********************************************************************************************************************
10.1.61.187                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
这种设置方式只在当前playbook当中有效

3、启用fact缓存
如果在play中需要引入fact,则可以开启fact缓存。fact缓存目前支持三种存储方式,分别为JSON、memcached、redis。
(1) Json文件fact缓存后端
使用JSON文件作为fact缓存后端的时候,ansible将会把采集的fact写入到控制主机的文件中。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400    #缓存时间,单位为秒 
fact_caching = jsonfile #指定ansible包含fact的json文件位置,如果目录不存在,会自动创建
fact_caching_connection = /tmp/ansible_fact_cache   #指定文件存储路径

(2)Redis fact缓存后端
使用redis作为fact缓存后端,需要在控制主机上安装redis服务并保持运行。需要安装python操作redis的软件包。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400 
fact_caching = redis

(3)Memcached fact缓存后端
使用memcached作为fact缓存后端,需要在控制主机上安装Memcached服务并保持运行,需要安装python操作memcached的软件包。
ansible.cfg配置如下:
[defaults]
gathering = smart
fact_caching_timeout = 86400 
fact_caching = memcached

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

- hosts: test
  gather_facts: no
也可以在ansible.cfg中添加如下配置:
或者:
[defaults]
gathering = explicit

九、使用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等
1、file
使用file lookup可以从文本文件中获取数据,并在这些数据传递给ansible变量,在task或者jinja2模板中进行引用。下面是一个从文本文件中获取ssh公钥并复制到远程主机的示例:
{{ lookup('file', '/users/breeze/.ssh/id_rsa.pub')}}

2、pipe
使用pipe lookup可以直接调用外部命令,并将命令执行的结果打印到标准输出,作为ansible变量。下面的例子通过pipe调用date指令拿到一个以时间数字组成的字串
- name: Flamingo | Get release version
  set_fact:
    flamingo_release_version: "{{ lookup('pipe', 'date +%Y%m%d%H%M%SZ') }}"

3、env
env lookup实际就是获取在控制主机上的某个环境变量的值。下面是一个读取控制机上$JAVA_HOME变量值的示例:
- name: get JAVA_HOME
  debug: msg="{{ lookup('env', 'JAVA_HOME')}}"

4、template
template lookup可以指定一个jinja2模板,然后返回这个模板中的变量被替换以后的结果。
假设我们有一个message.j2模板,内容如下:
This host runs {{ ansible_distribution }}

定义一个如下的task:
- name: print message from template
  debug: msg="{{ lookup('template', 'message.j2')}}"
输出的msg的结果如下:
This host runs CentOS

5. csvfile
csvfile可以从.csv文件中读取一个条目。假设我们有如下示例的名为users.csv的文件:
username,email
lorin,[email protected]
john,[email protected]
sue,[email protected]

下面是一个使用csvfile lookkup提取sue的电子邮件地址的task示例:
- name: get sue's email
  debug: msg="{{ lookup('csvfile','sue file=users.csv delimiter=, col=1')}}"
可以看到,一共向插件传递了四个参数:sue, file=users.csv, delimiter=,col=1。说明如下:
第一个参数指定一个名字,该名字必须出现在其所在行的第0列,需要说明的是,如果指定的第一个参数名字在文件中出现多次,则匹配第一次出现的结果
第二个参数指定csv文件的文件名
第三个参数指定csv文件的中条目的分隔符
第四个参数指定要取得哪一列的值,这一列正是第一个参数所在行的那一列的值
如果我们想要查找的用户存储在名为username的变量中,则可以使用"+"符号来连接username字串和其他的参数字串,来构建完整的参数字符串:
lookup('csvfile', username+'file=users.csv' delimiter=, col=1)

6、password
password lookup会随机生成一个密码,并将这个密码写入到参数指定的文件中。如下示例,创建一个名为bob的mysql用户,并随机生成该用户的密码,并将密码写入到主控端的bob-password.txt中:
- name: create deploy mysql user
  mysql_user: name=bob password={{ lookup('password', 'bob-password,txt')}} priv=*.*:ALL state=present

十、使用vault配置加密
在使用ansible的过程中,不可避免的会存储一些敏感信息,比如在变量文件中存储帐号密码信息等。ansible通过ansible-vault命令行工具来提供对敏感文件的加密和解密。
ansible-vault  #可以创建、加密、解密和查看文件。其可以加密任何ansible使用的文件,包括inventory文件,playbook中调用的变量文件等。
ansible-vault create file    #创建加密文件
ansible-vault edit file      #编辑加密文件
ansible-vault rekey file     #重置密码
ansible-vault encrypt file   #加密已有文件
ansible-vault decrypt file   #解密文件
ansible-vault view file      #查看文件

Ansible-vault配置示例:
(1)创建一个user.yml的变量文件,内容如下:
username: "user1"
pwhash: "$1$GkTPu7we$ZZtdsLPIHkS.fmoVcn3v51"
(2)加密上面创建的变量文件:
# ansible-vault encrypt user.yml 
New Vault password: 
Confirm New Vault password: 
Encryption successful
(3)编写playbook文件如下:
- name: create user accounts for all our servers
  hosts: test
  become: True
  remote_user: ansible
  vars_files:
    - user.yml
  tasks:
    - name: Creating user from user.yml
      user:
        name: "{{ username }}"
        password: "{{ pwhash }}"
(4)执行playbook
# ansible-playbook create_user.yml --ask-vault-pass
Vault password: 

也可以通过如下操作执行playbook:
echo redhat > vault-pass
chmod 600 vault-pass

ansible-playbook create_user.yml --vault-password-file=vault-pass

十一、Ansible lineinfile模块详解
1、修改匹配的行
# 将/etc/selinux/config中匹配到以'SELINUX='开头的行,将其替换为'SELINUX=disabled'
- name: modify selinux to disabled
  lineinfile:
    path: /etc/selinux/config
    regex: '^SELINUX='
    line: 'SELINUX=disabled'

2、在匹配行前或后添加内容
# cat /etc/http.conf
Listen 127.0.0.1:80
Listen 80
Port

(1)在匹配行前添加
在http.conf文件的Listen 80前面添加一行Listen 8080,task示例如下:
- name: add line before Listen 80
  lineinfile:
    dest: /etc/http.conf
    insertbefore: '^Listen 80'
    line: 'Listen 8080'

(2)在匹配行后添加
在http.conf文件的Port后面添加一行just a test,task示例如下:
- name: add line before Listen 80
  lineinfile:
    dest: /etc/http.conf
    insertafter: '^Port'
    line: 'just a test'

3、修改文件内容及权限
示例文件:
#cat /etc/hosts
127.0.0.1       localhost.localdomain localhost ::1       localhost6.localdomain6 localhost6
10.1.61.130     hub.dz11.com  
修改/etc/hosts,将以127.0.0.1开头的行替换为127.0.0.1 localhost,并将/etc/hosts的属主和属组都修改为root,权限改为644,如下:

- name: modify hosts
  lineinfile:
    dest: /etc/hosts
    regex: '^127\.0\.0\.1'
    line: '127.0.0.1 localhost'
    owner: root
    group: root
    mode: 0644

4、删除一行内容
示例原文件:
#cat /etc/hosts
127.0.0.1       localhost.localdomain localhost ::1       localhost6.localdomain6 localhost6
10.1.61.130     hub.dz11.com
删除以10.1.61.130开头的行:

- name: delete a line
  lineinfile:
    dest: /etc/hosts
    regex: '^10\.1\.61'
    state: absent

5、文件存在则添加一行内容
往/etc/hosts里添加一行10.1.61.131 test.dz11.com(多次执行,不会重复添加),示例如下:
- name: add a line
  lineinfile:
    dest: /etc/hosts
    line: '10.1.61.131 test.dz11.com'

6、如果有匹配的行则修改该行,如果不匹配则添加
示例原文件/tmp/test.txt内容如下:
# %wheel   ALL=(ALL)   ALL
下面的示例task中,匹配以%wheel开头的行,匹配到,则执行替换,未匹配,则添加。因为原文件中,没有以%wheel开头的行,所以会添加一行:
- name: add or modify a line
  lineinfile: 
    dest: /tmp/test.txt
    regex: '^%wheel'
    line: '%wheel  ALL=(ALL)       NOPASSWD: ALL'
修改后的文件如下:

#cat /tmp/text.txt
# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL

7、参数backrefs,backup说明
backup: 是否备份原文件,默认为no
backrefs:
    当backrefs为no时,如果regex没有匹配到行,则添加一行,如果Regx匹配到行,则修改该行
    当backrefs为yes时,如果regex没有匹配到行,则保持原文件不变,如果regex匹配到行,则修改该行
    backrefs默认为no,所以上面那个示例中,我们没有配置backrefs,而默认没有匹配,则修改。
下面我们看一看backrefs为yes时匹配到行的示例:

示例原文件:

# cat /tmp/testfile

# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL
#?bar
task示例:

 - name: test backrefs
  lineinfile:
      backup: yes
      state: present
      dest: /tmp/testfile
      regexp: '^#\?bar'
      backrefs: yes
      line: 'bar'
修改后的文件:

# cat /tmp/testfile

# %wheel   ALL=(ALL)   ALL
%wheel  ALL=(ALL)       NOPASSWD: ALL

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

可以使用validate关键字,在修改完成以后,对文件执行检测:
- name: test validate
  lineinfile:
      dest: /etc/sudoers
      state: present
      regexp: '^%ADMIN ALL='
      line: '%ADMIN ALL=(ALL)'
      validate: 'visudo -cf %s'
  tags:
    - testsudo


十二、playbook循环语句
循环语句:我们在编写playbook的时候,不可避免的要执行一些重复性操作,比如指安装软件包,批量创建用户,操作某个目录下的所有文件等。正如我们所说,ansible一门简单的自动化语言,所以流程控制、循环语句这些编程语言的基本元素它同样都具备。
1、loop关键字说明
tasks:
  - name: postfix and httpd are running
    service:
      name: "{{ item }}"
      state: started
    loop:
      - postfix
      - httpd
2、在循环语句中注册变量01
# cat register_loop.yml 
- name: registered variable usage as a loop list
  hosts: test
  tasks:
      - name: ensure /mnt/bkspool exists     #确保目录已经存在
        file:
          path: /mnt/bkspool
          state: directory
      - name: retrieve the list of home directories  #查看该目录下的文件
        command: ls /home
        register: home_dirs
      - name: Show home_dirs results   #输出目录下的文件
        debug:
          var: home_dirs.stdout_lines
      - name: add home dirs to the backup spooler   #把目录备份到指定的目录下
        file: 
          path: /mnt/bkspool/{{ item }}
          src: /home/{{ item }}
          state: link
          force: yes
        loop: "{{ home_dirs.stdout_lines }}"

3、在循环中注册变量02
- name: Loop Register test
  gather_facts: no
  hosts: test
  tasks:
    - name: Looping Echo Task
      shell: "echo this is my item: {{ item }}"
      loop:
        - one
        - two
      register: echo_results
    - name: Show echo_results variable
      debug:
        var: echo_results

4、循环语句关键字    描述    
with_lines           
with_sequence    
with_subelement       #遍历子元素
with_together       #遍历数据并行集合

(1) with_items   #简单的列表循环
- hosts: test
  vars:
    data:
      - user0
      - user1
      - user2
  tasks:
    - name: "with_items"
      debug:
        msg: "{{ item }}"
      with_items: "{{ data }}"

(2)with_nested   #嵌套循环
tasks: 
  - name: debug loops
    debug: msg="name is {{ item[0] }}  vaule is {{ item[1] }} num is {{ item[2] }}"
    with_nested:
      - ['alice','bob']
      - ['a','b','c']
      - ['1','2','3']


(3)with_dict  #循环字典
# 假如有如下变量内容:
users:
  alice:
    name: Alice Appleworth
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    telephone: 987-654-3210

# 现在需要输出每个用户的用户名和手机号:
tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
    with_dict: "{{ users }}"


4. with_fileglob   #循环指定目录中的所有文件
- hosts: test
  tasks:
    - name: Make key directory     
      file: 
        path: /root/.sshkeys 
        state: directory 
        mode: 0700 
        owner: root 
        group: root 
        
    - name: Upload public keys     
      copy: 
        src: "{{ item }}"
        dest: /root/.sshkeys
        mode: 0600 
        owner: root 
        group: root  
      with_fileglob:
        - /root/.ssh/*.pub 
        
    - name: Assemble keys into authorized_keys file     
      assemble: 
        src: /root/.sshkeys 
        dest: /root/.ssh/authorized_keys
        mode: 0600 
        owner: root 
        group: root

5. with_lines #循环一个文件中的所有行
with_lines循环结构会让你在控制主机上执行任意命令,并对命令的输出进行逐行迭代。假设我们有一个 文件test.txt包含如下行:
Breeze Yan
Bernie Yang
jerry Qing
我们可以通过如下方法进行逐行输出:

- name: print all names
  debug: msg="{{ item }}"
  with_lines:
    - cat test.txt

6. with_subelement #
假如现在需要遍历一个用户列表,并创建每个用户,而且还需要为每个用户配置以特定的SSH key登录。变量文件内容如下:

users:
  - name: alice
    authorized:
      - /tmp/alice/onekey.pub
      - /tmp/alice/twokey.pub
    mysql:
        password: mysql-password
        hosts:
          - "%"
          - "127.0.0.1"
          - "::1"
          - "localhost"
        privs:
          - "*.*:SELECT"
          - "DB1.*:ALL"
  - name: bob
    authorized:
      - /tmp/bob/id_rsa.pub
    mysql:
        password: other-mysql-password
        hosts:
          - "db1"
        privs:
          - "*.*:SELECT"
          - "DB2.*:ALL"
playbook中定义如下:

tasks:
  - user: name={{ item.name }} state=present generate_ssh_key=yes
    with_items: "{{users}}"
  - authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
    with_subelements:
     - users
     - authorized
也可以遍历嵌套的子列表:

- name: Setup MySQL users
  mysql_user: name={{ item.0.name }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}
  with_subelements:
    - users
    - mysql.hosts
7. with_sequence   #生成一个自增的整数序列,可以指定起始值和结束值以及步长。参数以key=value的形式指定,format指定输出的格式。数字可以是十进制、十六进制、八进制
- hosts: all
  tasks:
    # create groups
    - group: name=evens state=present
    - group: name=odds state=present
    # create some test users
    - user: name={{ item }} state=present groups=evens
      with_sequence: start=0 end=32 format=testuser%02d
    # create a series of directories with even numbers for some reason
    - file: dest=/var/stuff/{{ item }} state=directory
      with_sequence: start=4 end=16 stride=2    # stride用于指定步长
    # a simpler way to use the sequence plugin
    # create 4 groups
    - group: name=group{{ item }} state=present
      with_sequence: count=4

8. with_random_choice  #从列表中随机取一个值:

- debug: msg={{ item }}
  with_random_choice:
     - "go through the door"
     - "drink from the goblet"
     - "press the red button"
     - "do nothing"

9. do-Util循环
- action: shell /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10
重复执行shell模块,当shell模块执行的命令输出内容包含"all systems go"的时候停止。重试5次,延迟时间10秒。retries默认值为3,delay默认值为5。任务的返回值为最后一次循环的返回结果。


10. with_together
示例:

- hosts: webservers
  remote_user: root
  vars:
    alpha: [ 'a','b','c','d']
    numbers: [ 1,2,3,4 ]
  tasks:
    - debug: msg="{{ item.0 }} and {{ item.1 }}"
      with_together:
         - "{{ alpha }}"
         - "{{ numbers }}"
# 输出的结果为:
ok: [192.168.1.65] => (item=['a', 1]) => {
    "item": [
        "a",
        1
    ],
    "msg": "a and 1"
}
ok: [192.168.1.65] => (item=['b', 2]) => {
    "item": [
        "b",
        2
    ],
    "msg": "b and 2"
}
ok: [192.168.1.65] => (item=['c', 3]) => {
    "item": [
        "c",
        3
    ],
    "msg": "c and 3"
}
ok: [192.168.1.65] => (item=['d', 4]) => {
    "item": [
        "d",
        4
    ],
    "msg": "d and 4"
}

十三、Playbook条件语句
介绍:
在有的时候play的结果依赖于变量、fact或者是前一个任务的执行结果,或者有的时候,我们会基于上一个task执行返回的结果而决定如何执行后续的task。这个时候就需要用到条件判断。
条件语句在Ansible中的使用场景:
在目标主机上定义了一个硬限制,比如目标主机的最小内存必须达到多少,才能执行该task
捕获一个命令的输出,根据命令输出结果的不同以触发不同的task
根据不同目标主机的facts,以定义不同的task
根据目标机的cpu的大小,以调优相关应用性能
用于判断某个服务的配置文件是否发生变更,以确定是否需要重启服务

1、when关键字
在ansible中,使用条件判断的关键字就是when。
如在安装包的时候,需要指定主机的操作系统类型,或者是当操作系统的硬盘满了之后,需要清空文件等,可以使用when语句来做判断。
when关键字后面跟着的是python的表达式,在表达式中你能够使用任何的变量或者fact,当表达式的结果返回的是false,便会跳过本次的任务
---
- name: Install vim
  hosts: all
  tasks:
    - name:Install VIM via yum
      yum: 
        name: vim-enhanced 
        state: installed
      when: ansible_os_family =="RedHat"
      
    - name:Install VIM via apt
      apt: 
        name: vim 
        state: installed
      when: ansible_os_family =="Debian"
      
    - name: Unexpected OS family
      debug: msg="OS Family {{ ansible_os_family }} is not supported" fail=yes
      when: not ansible_os_family =="RedHat" or ansible_os_family =="Debian"

2、比较运算符
在上面的示例当中,我们使用了"=="的比较运算符,在ansible中,还支持如下比较运算符:
==:比较两个对象是否相等,相等则返回真。可用于比较字符串和数字
!=:比较两个对象是否不等,不等则为真。
>:比较两个对象的大小,左边的值大于右边的值,则为真
<:比较两个对象的大小,左边的值小于右边的值,则为真
>=:比较两个对象的大小,左边的值大于等于右边的值,则为真
<=:比较两个对象的大小,左边的值小于等于右边的值,则为真
when: ansible_machine == "x86_64" 
when: max_memory <= 512

3、逻辑运算符
在Ansible中,除了比较运算符,还支持逻辑运算符:
and:逻辑与,当左边和右边两个表达式同时为真,则返回真
or:逻辑或,当左右和右边两个表达式任意一个为真,则返回真
not:逻辑否,对表达式取反
():当一组表达式组合在一起,形成一个更大的表达式,组合内的所有表达式都是逻辑与的关系

# 逻辑或
when: ansible_distribution == "RedHat" or ansible_distribution == "Fedora"
# 逻辑与
when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"
when:
  - ansible_distribution_version == "7.5"
  - ansible_kernel == "3.10.0-327.el7.x86_64"
# 组合
when: => 
  ( ansible_distribution == "RedHat" and ansible_distribution_major_version == "7" )
  or
  ( ansible_distribution == "Fedora" and ansible_distribution_major_version == "28")

4、条件判断与tests
在shell当中,我们可使用test命令来进行一些常用的判断操作,如下:
# 判断/test文件是否存在
test -e /test
# 判断/testdir是否存在且为一个目录
test -d /testdir
事实上,在ansible中也有类似的用法,只不过ansible没有使用linux的test命令,而是jinja2模板的tests。
下面是一个简单示例:
# 通过条件语句判断testpath的路径是否存在
- hosts: test
  vars:
    testpath: /testdir
  tasks:
    - debug:
        msg: "file exist"
      when: testpath is exists
上面的示例中,我们使用了is exists用于路径存在时返回真,也可以使用is not exists用于路径不存在时返回真。也可以在整个条件表达式的前面使用not以取反:

- hosts: test
  vars:
    testpath: /testdir1
  tasks:
    - debug:
        msg: "file not exist"
      when: not testpath is exists
在ansible中,除了能够使用exists这种tests之外,还有一些别的tests。接下来我们详细说一说。

5、判断变量
defined:判断变量是否已定义,已定义则返回真
undefined:判断变量是否未定义,未定义则返回真
none:判断变量的值是否为空,如果变量已定义且值为空,则返回真
示例:
- hosts: test
  gather_facts: no
  vars:
    testvar: "test"
    testvar1:
  tasks:
    - debug:
        msg: "testvar is defined"
      when: testvar is defined
    - debug:
        msg: "testvar2 is undefined"
      when: testvar2 is undefined
    - debug:
        msg: "testvar1 is none"
      when: testvar1 is none
判断执行结果
sucess或succeeded:通过任务执行结果返回的信息判断任务的执行状态,任务执行成功则返回true
failure或failed:任务执行失败则返回true
change或changed:任务执行状态为changed则返回true
skip或skipped:任务被跳过则返回true

示例:
- hosts: test
  gather_facts: no
  vars:
    doshell: true
  tasks:
    - shell: 'cat /testdir/aaa'
      when: doshell
      register: result
      ignore_errors: true
    - debug:
        msg: "success"
      when: result is success
      
    - debug:
        msg: "failed"
      when: result is failure
      
    - debug:
        msg: "changed"
      when: result is change
      
    - debug:
        msg: "skip"
      when: result is skip

6、判断路径
file:判断指定路径是否为一个文件,是则为真
directory:判断指定路径是否为一个目录,是则为真
link:判断指定路径是否为一个软链接,是则为真
mount:判断指定路径是否为一个挂载点,是则为真
exists:判断指定路径是否存在,存在则为真
特别注意:关于路径的所有判断均是判断主控端上的路径,而非被控端上的路径

示例:
- hosts: test
  gather_facts: no
  vars:
    testpath1: "/testdir/test"
    testpath2: "/testdir"
  tasks:
    - debug:
        msg: "file"
      when: testpath1 is file
    - debug:
        msg: "directory"
      when: testpath2 is directory

7、判断字符串
lower:判断字符串中的所有字母是否都是小写,是则为真
upper:判断字符串中的所有字母是否都是大写,是则为真
- hosts: test
  gather_facts: no
  vars: 
    str1: "abc"
    str2: "ABC"
  tasks:
    - debug:
        msg: "str1 is all lowercase"
      when: str1 is lower
    - debug:
        msg: "str2 is all uppercase"
      when: str2 is upper

8、判断整除
even:判断数值是否为偶数,是则为真
odd:判断数值是否为奇数,是则为真
divisibleby(num):判断是否可以整除指定的数值,是则为真
示例:

- hosts: test
  gather_facts: no
  vars: 
    num1: 6
    num2: 8 
    num3: 15
  tasks:
    - debug: 
        msg: "num1 is an even number"
      when: num1 is even
    - debug:
        msg: "num2 is an odd number"
      when: num2 is odd
    - debug:
        msg: "num3 can be divided exactly by"
      when: num3 is divisibleby(3)

9、其他tests
(1)version
可用于对比两个版本号的大小,或者与指定的版本号进行对比,使用语法为version("版本号","比较操作符")
- hosts: test 
  vars: 
    ver1: 1.2 
    ver2: 1.3 
  tasks: 
    - debug: 
        msg: "ver1 is greater than ver2" 
      when: ver1 is version(ver2,">") 
    - debug: 
        msg: "system version {{ ansible_distribution_version }} greater than 7.3" 
      when: ansible_distribution_version is version("7.3","gt")
version中使用的比较运算符说明:
大于: >, gt
大于等于: >=, ge
小于: <, lt
小于等于: <=, le
等于: =, ==, eq
不等于: !=, <>, ne

(2)subset
判断一个list是不是另一个list的子集

(3)superset
判断一个list是不是另一个list的父集"
- hosts: test
  gather_facts: no
  vars:
    a:
      - 2
      - 5
    b: [1,2,3,4,5]
  tasks:
    - debug:
        msg: "A is a subset of B"
      when: a is subset(b)
    - debug:
        msg: "B is the parent set of A"
      when: b is superset(a)

(4)in
判断一个字符串是否存在于另一个字符串中,也可用于判断某个特定的值是否存在于列表中
- hosts: test
  vars:
    supported_distros:
      - RedHat
      - CentOS
  tasks:
    - debug:
        msg: "{{ ansible_distribution }} in supported_distros"
      when: ansible_distribution in supported_distros

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

(6)number
判断对象是否为一个数字,是则为真
- hosts: test
  gather_facts: no
  vars:
    var1: 1
    var2: "1"
    var3: a
  tasks:
    - debug:
        msg: "var1 is a number"
      when: var1 is number
    - debug:
        msg: "var2 is a string"
      when: var2 is string
    - debug:
        msg: "var3 is a string"
      when: var3 is string
      
10、条件判断与block
(1)block
我们在前面使用when做条件判断时,如果条件成立则执行对应的任务。但这就面临一个问题,当我们要使用同一个条件判断执行多个任务的时候,就意味着我们要在某一个任务下面都写一下when语句,而且判断条件完全一样。这种方式不仅麻烦而且显得low。Ansible提供了一种更好的方式来解决这个问题,即block。
在ansible中,使用block将多个任务进行组合,当作一个整体。我们可以对这一个整体做条件判断,当条件成立时,则执行块中的所有任务:
- hosts: test
  tasks:
    - debug:
        msg: "task1 not in block"
    - block:
        - debug:
            msg: "task2 in block1"
        - debug:
            msg: "task3 in block1"
      when: 2 > 1
下面是一个稍微有用点儿的例子:
- hosts: test
  tasks:
    - name: set /etc/resolv.conf
      template: 
        src: resolv.conf.j2 
        dest: /etc/resolv.conf 
        owner: root 
        group: root 
        mode: 0644
    - block:
        - name: ensure /etc/resolvconf/resolv.conf.d/base file for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolvconf/resolv.conf.d/base
       
        - name: config dns for ubuntu 16.04
          template: 
            src: resolv.conf.j2
            dest: /etc/resolv.conf
      when: ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "16" 
使用block注意事项:
可以为block定义name(ansible 2.3增加的特性)
可以直接对block使用when,但不能直接对block使用loop

(2)rescue
block除了能和when一起使用之外,还能作错误处理。这个时候就需要用到rescue关键字:
- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
在上面的例子中,当block中的任务执行失败时,则运行rescue中的任务。如果block中的任务正常执行,则rescue的任务就不会被执行。如果block中有多个任务,则任何一个任务执行失败,都会执行rescue。block中可以定义多个任务,同样rescue当中也可以定义多个任务。

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

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
      always:
        - debug:
            msg: 'This task always executes'

11、条件判断与错误处理
在上面讲block的使用方法的时候,我们说block除了可以将多个任务组合到一起,还有错误处理的功能。接下来我们继续说一说错误处理。
(1)fail模块
在shell中,可能会有这样的需求:当脚本执行至某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行。在shell中,可以直接调用"exit"即可执行退出。事实上,在playbook中也有类似的模块可以做这件事。即fail模块。
fail模块用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。
选项只有一个:
msg:终止前打印出信息
示例:
# 使用fail模块中断playbook输出
- hosts: test
  tasks:
    - shell: echo "Just a test--error" 
      register: result
    - fail:
        msg: "Conditions established,Interrupt running playbook"
      when: "'error' in result.stdout"
    - debug:
        msg: "Inever execute,Because the playbook has stopped"
(2)failed_when
事实上,当fail和when组合使用的时候,还有一个更简单的写法,即failed_when,当满足某个条件时,ansible主动触发失败。
# 如果在command_result存在错误输出,且错误输出中,包含了`FAILED`字串,即返回失败状态:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  failed_when: "'FAILED' in command_result.stderr"
也可以直接通过fail模块和when条件语句,写成如下:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  ignore_errors: True
- name: fail the play if the previous command did not succeed
  fail: msg="the command failed"
  when: " command_result.stderr and 'FAILED' in command_result.stderr"
ansible一旦执行返回失败,后续操作就会中止,所以failed_when通常可以用于满足某种条件时主动中止playbook运行的一种方式。
ansible默认处理错误的机制是遇到错误就停止执行。但有些时候,有些错误是计划之中的。我们希望忽略这些错误,以让playbook继续往下执行。这个时候就可以使用ignore_errors忽略错误,从而让playbook继续往下执行。

(3)changed_when
当我们控制一些远程主机执行某些任务时,当任务在远程主机上成功执行,状态发生更改时,会返回changed状态响应,状态未发生更改时,会返回OK状态响应,当任务被跳过时,会返回skipped状态响应。我们可以通过changed_when来手动更改changed响应状态。示例如下:
- shell: /usr/bin/billybass --mode="take me to the river"
register: bass_result
changed_when: "bass_result.rc != 2"    #只有该条task执行以后,bass_result.rc的值不为2时,才会返回changed状态
# this will never report 'changed' status
- shell: wall 'beep'
  changed_when: False    #当changed_when为false时,该条task在执行以后,永远不会返回changed状态

11、在循环语句中使用条件语句
# 只打印大于5的值
tasks:
    - command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5
# 确保将mariadb-server安装到根分区且根分区的可用空间要大于300M
- name: install mariadb-server if enough space on root
  yum: 
    name: mariadb-server
    state;拉特st
  loop: "{{ ansible_mounts }}"
  when: item.mount == "/" and item.size_available > 300000000

十四、文件管理模块及Jinja2过滤器
1、常用文件管理模块
(1)file  #我们在讲ansible ad-hoc的时候,已经说过file模块,在playbook中的使用也没什么不同,
下面给个简单的示例:
- name: Touch a file and set permissions
  file:
    path: /path/to/file
    owner: user1
    group: group1
    mode: 0640
    state: touch

(2)synchronize  #同步模块
synchronize模块示例:
- name: synchronize local file to remote files
  synchronize:
    src: file
    dest: /path/to/file

(3)copy 
- name: copy a file to managed hosts
  copy:
    src: file
    dest: /path/to/file

(4)fetch #fetch模块与copy模块正好相反,copy是把主控端的文件复制到被控端,而fetch则是把被控端的文件复制到主控端。并且在主控端指定的目录下,以被控端主机名的形式来组织目录结构。
- name: Use the fetch module to retrieve secure log files
  hosts: all
  user: ansible
  tasks:
    - name: Fetch the /var/log/secure log file from managed hosts
      fetch:
        src: /var/log/secure
        dest: secure-backups
        flat: no
在主控端文件存储的目录树如下:

# tree  secure-backups/
secure-backups/
└── 10.1.61.187
    └── var
        └── log
            └── secure
3 directories, 1 file

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

(6)stat  #查看文件的状态
stat模块与linux中的stat命令一样,用来显示文件的状态信息。
- name: Verify the checksum of a file
  stat:
    path: /path/to/file
    checksum_algorithm: md5
  register: result
  
- debug:
    msg: "The checksum of the file is {{ result.stat.checksum }}"

(7)blockinfile
围绕着被标记的行插入、更新、删除一个文本块。
#cat files/test.html
<html>
  <head>
  </head>
  <body>
  </body>
</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章