Ansible命令大全【上】

mac2025-04-25  5

一、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 ansible@db1.example.com 需要说明的是,通过上面的操作,需要为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,lorin@test.com john,john@example.com sue,sue@exmaple.com

下面是一个使用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>

 

最新回复(0)