ansible-playbook详解
为什么引入playbook?
一般运维人员完成一个任务,
比如安装部署一个httpd服务会需要多个模块(一个模块也可以称之为task)提供功能来完成。而playbook就是组织多个task的容器,它的实质就是一个文件,有着特定的组织格式,它采用的语法格式是YAML(Yet
Another Markup Language)。YAML语法能够简单的表示散列表,字典等数据结构。简单来说, playbook是由一个或多个模块组成的,使用多个不同的模块,完成一件事情。
Ansible核心功能
- pyYAML用于ansible编写剧本所使用的语言格式(saltstack---python);
- rsync-ini语法, sersync-xml语法, nsible-pyYAML语法;
- paramiko远程连接与数据传输;
- Jinja2用于编写ansible的模板信息;
YAML三板斧
缩进: YAML使用一个固定的缩进风格表示层级结构,每个缩进由两个空格组成, 不能使用tabs;
冒号: 以冒号结尾的除外,其他所有冒号后面所有必须有空格;
短横线: 表示列表项,使用一个短横杠加一个空格。多个项使用同样的缩进级别作为同一列表;
YAML基本语法
Ansible-playbook采用YAML语法编写。连续的项目(即列表)用 -
减号来表示,key/value(字典)用冒号:
分隔。
列表:每一个列表成员前面都要有一个短横线和一个空格
fruits: - Apple - Orange - Strawberry - Mango 或者: fruits: ['Apple', 'Orange', 'Strawberry', 'Mango']
字典:每一个成员由键值对组成,注意冒号后面要有空格
martin: name: Martin D'vloper job: Developer skill: Elite 或者 martin: {name: Martin D'vloper, job: Developer, skill: Elite}
列表和字典可以混合使用
- martin: name: Martin D'vloper job: Developer skills: - python - perl - pascal - tabitha: name: Tabitha Bitumen job: Developer skills: - lisp - fortran - erlang
示例如下:
[root@localhost ~]# cat httpd.yaml
--- - hosts: control-node #将要执行任务的主机,已经在hosts文件中定义好了,可是单个主机或主机组 remote_user: root #在目标主机上执行任务时的用户身份 vars: - pkg: httpd tasks: - name: "install httpd package." yum: name={{ pkg }} state=installed - name: "copy httpd configure file to remote host." copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf notify: restart httpd #当这个任务执行状态发生改变时,触发handlers执行. - name: "boot httpd service." service: name=httpd state=started handlers: #handlers与tasks是同一级别 - name: restart httpd service: name=httpd state=restarted enabled=yes
playbook语法特性
1. 以 --- (三个减号)开始,必须顶行写;
2. 次行开始写Playbook的内容,但是一般要求写明该playbook的功能;
3. 严格缩进,并且不能用Tab键缩进;
4. 缩进级别必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的;
5. K/V的值可同行写,也可换行写。同行使用 :分隔,换行写需要以 - 分隔;
playbook基础组件
Hosts:运行执行任务(task)的目标主机
playbook中的每个play的目的都是为了让特定主机以某个指定用户身份执行任务,hosts主要用于指定要执行的任务主机,须事先定义在主机清单中
- hosts: websrvs - hosts: 192.168.0.21 - hosts: websrvs:appsrvs # 两个组的并集 - hosts: websrvs:&appsrvs # 两个组的交集 - hosts: websrvs:!appsrvs # 在websrvs组中,但不在appsrvs中
remote_user:在远程主机上执行任务的用户
可用于Host和Task中,也可以通过指定其通过sudo方式在远程主机上执行任务,其可用于play全局或某个任务中;可以在sudo时使用become_user指定sudo时切换的用户,默认为root
- hosts: websrvs remote_user: root tasks: - name: test connection ping: remote_user: admin sudo: yes sudo_user: yuan # become: yes # become_user: yuan
注意:2.9版本 sudo已经替换为become
tasks任务列表和action组件
play主体部分是task list,task list有一个或多个task,每个task按次序逐个在hosts中指定的所有主机上执行,即在所有主机上执行完一个task后,再执行下一个task;task的目的是使用指定的参数执行模块,再模块参数中可以使用变量。模块执行为幂等的,意味着多次执行是安全的,其结果一致 ;每个task都应该有其name,用于playbook执行结果输出,建议其内容能够清晰描述任务执行步骤,如果没有提供name,则action的结果将用于输出
#task的两种格式 action: module arguments #示例: action: shell wall hello module: arguments # 推荐使用此格式 # 示例:shell: wall hello #示例1 ͬͬ˱ - hosts: websrvs remote_user: root gather_facts: no # 不收集系统信息,提高执行效率 tasks: - name: test network connection ping: - name: excute command command: wall "hello world!" #示例2 ͬͬ˱ - hosts: websrvs remote_user: root gather_facts: no tasks: - name: install httpd yum: name=httpd - name: start httpd service: name=httpd state=started enabled=yes
handlers:任务,与tasks不同的是只有在接受到通知时才会被触发
templates:使用模板语言的文本文件,使用jinja2语法。
variables:变量,变量替换{{ variable_name }}
整个playbook是以task为中心,表明要执行的任务。hosts和remote_user表明在远程主机以何种身份执行,其他组件让其能够更加灵活。下面介绍插件:
1. variable
变量定义在资产 (inventory) 中, 默认就是/etc/ansible/hosts文件中
主机变量: 192.168.200.22 http_port=808 maxRequestsPerChild=808 192.168.200.23 http_port=8080 maxRequestsPerChild=909 主机组变量: [websers] 192.168.200.22 192.168.200.23 [websers:vars] ntp_server=ntp.exampl.com proxy=proxy.exampl.com
变量定义在playbook中
- hosts: webservers vars: http_port: 80
使用facts变量
facts变量是由setup模块获取远程主机的信息。 用法: # ansible 192.168.200.23 -m setup
在roles中定义变量, 这个后面会介绍到.
ansible-playbook 命令中传入参数
使用 -e选项传入参数 # ansible-playbook 192.168.200.23 -e "httpd_port=808" httpd04.yml
变量的引用
{{ var_name }}
2. templates
它是一个模块功能,与copy不同的是他的文本文件采用了jinga2语法,jinga2基本语法如下:
字面量: 字符串:使用单引号或双引号 数字:整型,浮点数 列表:{item1,item2,...} 字典:{key1:value1,key2:value2,...} 布尔型:true/false 算术运算: +,-,*,/,//,%,** 比较运算: ==,!=,>,>=,<,<= 逻辑运算: and,or,not
注意:template只能在palybook中使用。
3. tasks
执行的模块命令
格式: action:模块参数(此种方式只在较新的版本中出现) module:参数(已键值对的形式出现) 每一个task都有一个名称,用于标记此任务。任务示例: name: install httpd yum: name=httpd state=present 注意:shell和command没有参数,可在后面直接跟命令 shell: ss -tnl | grep :80 1)某任务的运行状态为changed后,可通过相应的notify通知相应的handlers 2)任务可以通过tags打标签,然后通过palybook命令-t选项调用.
playbook命令及调用方式
用法:
ansible-playbook <filename.yml> ... [options]
<filename.yml>: yaml格式的playbook文件路径,必须指明
[options]: 选项
Options: --ask-vault-pass #ask for vault password #加密playbook文件时提示输入密码 -C, --check #don't make any changes; instead, try to predict some of the changes that may occur #模拟执行,不会真正在机器上执行(查看执行会产生什么变化)。即并不在远程主机上执行,只是测试。 -D, --diff #when changing (small) files and templates, show the differences in those files; works great with --check #当更新的文件数及内容较少时,该选项可显示这些文件不同的地方,该选项结合-C用会有较好的效果 -e EXTRA_VARS, --extra-vars=EXTRA_VARS #set additional variables as key=value or YAML/JSON #在Playbook中引入外部参数变量 --flush-cache #clear the fact cache #清理fact缓存,将fact清除到的远程主机缓存 --force-handlers #run handlers even if a task fails #强制运行handlers的任务,即使在任务失败的情况下 -f FORKS, --forks=FORKS #specify number of parallel processes to use(default=5) #并行任务数。FORKS被指定为一个整数,默认是5 -h, --help #show this help message and exit #打开帮助文档API -i INVENTORY, --inventory-file=INVENTORY #specify inventory host path (default=/etc/ansible/hosts) or comma separated host list. #指定要读取的Inventory清单文件 -l SUBSET, --limit=SUBSET #further limit selected hosts to an additional pattern #限定执行的主机范围 --list-hosts #outputs a list of matching hosts; does not execute anything else #列出执行匹配到的主机,但并不会执行任何动作。 --list-tags #list all available tags #列出所有可用的tags --list-tasks #list all tasks that would be executed #列出所有即将被执行的任务 -M MODULE_PATH, --module-path=MODULE_PATH #specify path(s) to module library (default=None) #要执行的模块的路径 --new-vault-password-file=NEW_VAULT_PASSWORD_FILE #new vault password file for rekey # --output=OUTPUT_FILE #output file name for encrypt or decrypt; use - for stdout # --skip-tags=SKIP_TAGS #only run plays and tasks whose tags do not match these values #跳过指定的tags任务 --start-at-task=START_AT_TASK #start the playbook at the task matching this name #从第几条任务(START_AT_TASK)开始执行 --step #one-step-at-a-time: confirm each task before running #逐步执行Playbook定义的任务,并经人工确认后继续执行下一步任务 --syntax-check #perform a syntax check on the playbook, but do not execute it #检查Playbook中的语法书写,并不实际执行 -t TAGS, --tags=TAGS #only run plays and tasks tagged with these values #指定执行该tags的任务 --vault-password-file=VAULT_PASSWORD_FILE #vault password file # -v, --verbose #verbose mode (-vvv for more, -vvvv to enable connection debugging) #执行详细输出 --version #show program's version number and exit #显示版本 ############Connection Options,即下面时连接权限############ control as whom and how to connect to hosts -k, --ask-pass #ask for connection password # --private-key=PRIVATE_KEY_FILE, --key-file=PRIVATE_KEY_FILE #use this file to authenticate the connection # -u REMOTE_USER, --user=REMOTE_USER #connect as this user (default=None) #指定远程主机以USERNAME运行命令 -c CONNECTION, --connection=CONNECTION #connection type to use (default=smart) #指定连接方式,可用选项paramiko (SSH)、ssh、local,local方式常用于crontab和kickstarts -T TIMEOUT, --timeout=TIMEOUT #override the connection timeout in seconds(default=10) #SSH连接超时时间设定,默认10s --ssh-common-args=SSH_COMMON_ARGS #specify common arguments to pass to sftp/scp/ssh (e.g.ProxyCommand) # --sftp-extra-args=SFTP_EXTRA_ARGS #specify extra arguments to pass to sftp only (e.g. -f, -l) # --scp-extra-args=SCP_EXTRA_ARGS #specify extra arguments to pass to scp only (e.g. -l) # --ssh-extra-args=SSH_EXTRA_ARGS #specify extra arguments to pass to ssh only (e.g. -R) # ############Privilege Escalation Options, 即下面时权限提升权限############ control how and which user you become as on target hosts -s, --sudo #run operations with sudo (nopasswd) (deprecated, use become) #相当于Linux系统下的sudo命令 -U SUDO_USER, --sudo-user=SUDO_USER #desired sudo user (default=root) (deprecated, use become) #使用sudo,相当于Linux下的sudo命令 -S, --su #run operations with su (deprecated, use become) # -R SU_USER, --su-user=SU_USER #run operations with su as this user (default=root)(deprecated, use become) -b, --become #run operations with become (does not imply password prompting) # --become-method=BECOME_METHOD #privilege escalation method to use (default=sudo),valid choices: [ sudo | su | pbrun | pfexec | doas |dzdo | ksu | runas ] # --become-user=BECOME_USER #run operations as this user (default=root) # --ask-sudo-pass #ask for sudo password (deprecated, use become) #传递sudo密码到远程主机,来保证sudo命令的正常运行 --ask-su-pass #ask for su password (deprecated, use become) # -K, --ask-become-pass #ask for privilege escalation password #
ansible-playbook需要注意的两个命令
1)检查语法,只检查是否是yaml语法格式。并不做逻辑校验。(记住这个要经常使用, 它是判断语法是否正确!!!)
# ansible-playbook --syntax-check kevin.yml
2)模拟执行(不是真的执行)
# ansible-playbook -C kevin.yml
关闭Facts
如果不需要使用主机的任何fact数据,可以选择关闭fact数据的获取,这样有利于增强Ansible面对大量系统的push模块。
在playbook中关闭Facts方法(gather_facts: no):
--- - hosts: webserver gather_facts: no
palybook书写格式
--- # 也可以不使用这一行。可以省略。 - hosts: 192.168.200.22 #处理指定服务器. - (空格)hosts:(空格)192.168.200.22 task: #剧本所要干的事情; (空格)(空格)task: - name: #(两个空格)-(空格)name。 command: echo hello clsn linux #(四个空格)command:(空格) 需要注意: Task任务里的name可以省略不写,将-(空格)放到下一行模块墙面。例如: --- - hosts: 192.168.200.22 task: - command: echo hello clsn linux 小示例: [root@localhost ansible]# cat haha.yaml --- - hosts: test remote_user: root gather_facts: no tasks: - file: path=/opt/task1.txt state=touch
palybook格式示例
[root@server01 ~]# cat test.yaml - hosts: test tasks: - name: Install httpd yum: name=httpd state=installed #playbook检查方法 [root@server01 ~]# ansible-playbook --syntax-check test.yaml playbook: test.yaml [root@server01 ~]# ansible-playbook -C test.yaml PLAY [test] ************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************** ok: [192.168.200.22] ok: [192.168.200.23] TASK [Install httpd] **************************************************************************************************************************************************************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] PLAY RECAP ************************************************************************************************************************************************************************************************************************** 192.168.200.22 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 上面两个检查命令, 第一个是进行playbook剧本配置信息语法检查; 第二个是模拟playbook剧本执行(彩排)
palybook剧本文件示例
ansible-playbook编写内容扩展:剧本任务编写多个任务
- hosts: all tasks: - name: restart-network cron: name='restart network' minute=00 hour=00 job='/usr/sbin/ntpdate time.nist.gov >/dev/null 2>&1' - name: sync time cron: name='sync time' minute=*/5 job="/usr/sbin/ntpdate pool.ntp.com >/dev/null 2>&1"
剧本编写内容扩展:剧本任务编写多个主机
- hosts: 192.168.200.22 tasks: - name: restart-network cron: name='restart network' minute=00 hour=00 job='/usr/sbin/ntpdate time.nist.gov >/dev/null 2>&1' - name: sync time cron: name='sync time' minute=*/5 job="/usr/sbin/ntpdate pool.ntp.com >/dev/null 2>&1" - hosts: 192.168.200.33 tasks: - name: show ip addr to file shell: echo $(hostname -i) >> /tmp/ip.txt
playbook剧本编写方式
- 多主机单任务编写方式
- 多主机多任务编写方式
- 不同主机多任务编写方式
来看一个比较完整的ansible的yml文件写法:
--- - host: webservers ###要管理的远程服务器组名称, 服务器地址维护在/etc/ansible/hosts 里, 也可以直接写地址 vars: port: 8081 ###定义了一个变量 端口号 remote_user: root ###远程登录后用什么用户执行 pre_tasks: ###执行正式 task 之前执行的任务 - name: pre task ###任务名称 shell: echo 'execute pre task' ###执行一行 shell 命令, 支持 >> 等符号 roles: ###引入 roles, 可以理解为引用了一个其他项目 ansible 包, 引用的 roles 可以是另一个完整的 ansible 脚本 - role: my_role ###要引用的 role 名称 when: "ansible_os_family == 'RedHat'" ###判断条件, ansible_os_family 是一个内置变量, 可直接使用 tasks: ###按顺序执行以下 task - include: my_tasks/some_task.yml ###可以引入其他 yml 文件 - name: get hostname ###这是一个 task, 名称 command: cat log.log ###执行一行 command , 和 shell 类似, 但是不支持 >> 等操作符 register: result ###执行的结果, 设到 result 这个变量中, 后面可以使用 - name: set hostname shell: cat {{result.stdout}} >> host.text - name: task1 command: echo 'execute task1' - name: task2 start apache service: ###启动 httpd 服务器, service 是一个 ansible 内置模块, 读者可以自行查看更多模块, 包括下载复制等等 name: httpd state: started tags: - apache ###这是一个标签, 可以用 ansible-playbook main.yml --tags "apache" 指定只执行这个任务 - name: copy and set value of index.html template: ###这是一个复制方法, 也叫模块, 并且.j2文件中可以使用{{}}来设置需要替换的变量 src: templates/index.html.j2 dest: /etc/httpd/index.html notify: ###唤醒执行后面的 handlers 中名字叫 restart apache 的任务 - restart apache post_tasks: ###最后需要执行的任务 - name: posy task shell: echo 'execute post task' handlers: - name: restart apache debug: ###这是一个打印模块 msg: start restart apche
palybook剧本中的方法
1. handlers 任务触发
在需要被监控的任务(tasks)中定义一个notify,只有当这个任务被执行时,才会触发notify对应的handlers去执行相应操作。例如配置文件被修改后,有可能需要重启程序,此时我们可以配置一个handlers,类似触发器。注意:handlers下的name名称必须要和它对应的notify名称相同!否则不会执行!!
[root@server01 ~]# cat httpd.yaml --- - hosts: test remote_user: root vars: - pkg: httpd tasks: - name: "install httpd package." yum: name={{ pkg }} state=installed - name: "copy httpd configure file to remote host." copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf notify: restart httpd - name: "boot httpd service." service: name=httpd state=started handlers: - name: restart httpd service: name=httpd state=restarted enabled=yes
######## 在使用handlers的过程中,需要注意下面几点 ########
1. handlers只有在其所在的任务被执行完时,它才会被运行;如果一个任务中定义了notify调用Handlers,但由于条件判断等原因,该任务未被执行,则Handlers同样不会被执行。
2. handlers只会在Play的末尾运行一次;如果想在一个Playbook的中间运行handlers,则需要使用meta模块来实现,例如:-meta: flush_handlers。
3. 可以直接在Handlers中使用notify选项,实现Handlers调用Handlers。
4. 可以使用listen关键字,在一个tasks任务中一次性notify多个handler。即将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify。
5. 如果一个Play在运行到调用handlers的语句之前失败了,那么这个handlers将不会被执行。但是可以使用mega模块的--force-handlers选项来强制执行handlers,即使在handlers所在Play中途运行失败也能执行。需要注意:--force-handlers参数主要针对即使playbook执行失败,也要执行代码块成功了的handlers(即执行成功的task任务), 如果代码块本身执行失败(即执行失败的task任务),那么它所对应的handlers应当不会被执行!
handlers可以理解成另一种tasks,handlers是另一种"任务列表",可以理解handlers和tasks是"平级关系",所以他们的缩进相同。handlers的任务会被tasks中的任务进行"调用",但是,被"调用"并不意味着一定会执行,只有当tasks中的任务"真正执行"以后,handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被"调用",也并不会执行。handlers中可以有多个任务,被tasks中不同的任务notify。
场景1:headlers在所有tasks任务被执行完时才执行。
[root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task1 file: path=/opt/task1.txt state=touch notify: task1 - name: make file task2 file: path=/opt/task2.txt state=touch notify: task2 handlers: - name: task1 file: path=/opt/task1.txt mode=777 owner=root group=root - name: task2 file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes #执行结果: [root@server01 ~]# ansible-playbook haha.yaml PLAY [test] *********************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [192.168.200.22] ok: [192.168.200.23] TASK [make file task1] ************************************************************************ changed: [192.168.200.22] changed: [192.168.200.23] TASK [make file task2] ************************************************************************ changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task1] *********************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task2] *********************************************************************** [WARNING]: Both option path and its alias dest are set. changed: [192.168.200.22] changed: [192.168.200.23] PLAY RECAP ************************************************************************************ 192.168.200.22 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
从上面运行结果看出,Handlers执行的顺序与Handlers在playbook中定义的顺序是相同的,与"handler"被notify的顺序无关。
场景2:使用meta模块,headlers会在它所对应的task任务执行完后立即被触发并执行,即在playbook的中间环节运行。
默认情况下,所有的task执行完毕后,才会执行各个handles,并不是执行完某个task后,立即执行相应的handler,如果想要在执行完某些task以后立即执行对应的handlre,那么需要使用meta模块。
[root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task1 file: path=/opt/task1.txt state=touch notify: task1 - meta: flush_handlers - name: make file task2 file: path=/opt/task2.txt state=touch notify: task2 handlers: - name: task1 file: path=/opt/task1.txt mode=777 owner=root group=root - name: task2 file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes #执行结果: root@server01 ~]# ansible-playbook haha.yaml PLAY [test] ************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************** ok: [192.168.200.23] ok: [192.168.200.22] TASK [make file task1] ************************************************************************************************************************************************************************************************************** changed: [192.168.200.23] changed: [192.168.200.22] RUNNING HANDLER [task1] ************************************************************************************************************************************************************************************************************* ok: [192.168.200.23] ok: [192.168.200.22] TASK [make file task2] ************************************************************************************************************************************************************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task2] ************************************************************************************************************************************************************************************************************* [WARNING]: Both option path and its alias dest are set. ok: [192.168.200.22] ok: [192.168.200.23] PLAY RECAP ************************************************************************************************************************************************************************************************************************** 192.168.200.22 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
上面使用了meta模块后,注意它的执行顺序于场景1做下对比!
场景3:Handlers调用Handlers
若实现Handlers调用Handlers,则直接在Handlers中使用notify选项即可以。
[root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task11 file: path=/opt/task11.txt state=touch notify: task11 - name: make file task22 file: path=/opt/task22.txt state=touch handlers: - name: task11 file: path=/opt/task11.txt mode=777 owner=root group=root notify: task22 - name: task22 file: path=/opt/task22.txt src=/opt/task22.txt dest=/opt/heihei state=link force=yes #执行结果: [root@server01 ~]# ansible-playbook haha.yaml PLAY [test] ************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************************************************************************************************************** ok: [192.168.200.23] ok: [192.168.200.22] TASK [make file task11] ************************************************************************************************************************************************************************************************************* changed: [192.168.200.22] changed: [192.168.200.23] TASK [make file task22] ************************************************************************************************************************************************************************************************************* changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task11] ************************************************************************************************************************************************************************************************************ changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task22] ************************************************************************************************************************************************************************************************************ [WARNING]: Both option path and its alias dest are set. changed: [192.168.200.22] changed: [192.168.200.23] PLAY RECAP ************************************************************************************************************************************************************************************************************************** 192.168.200.22 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 注意:上面执行的顺序是:make file task11 > make file task22 > task11 > task22 #也可以改成下面的方式:实现Handlers调用Handlers [root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task12 file: path=/opt/task12.txt state=touch notify: task12 handlers: - name: task12 file: path=/opt/task12.txt mode=777 owner=root group=root notify: task23 - name: task23 file: path=/opt/task23.txt state=touch notify: task34 - name: task34 file: path=/opt/task23.txt src=/opt/task23.txt dest=/opt/heihei state=link force=yes #执行结果: [root@server01 ~]# ansible-playbook haha.yaml PLAY [test] *********************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [192.168.200.22] ok: [192.168.200.23] TASK [make file task12] *********************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task12] ********************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task23] ********************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task34] ********************************************************************** [WARNING]: Both option path and its alias dest are set. changed: [192.168.200.22] changed: [192.168.200.23] PLAY RECAP ************************************************************************************ 192.168.200.22 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 注意:上面的执行顺序是:make file task12 > task12 > task23 > task34
场景4:使用listen关键字,在一个tasks任务中一次性notify多个handler
怎么才能一次性notify多个handler呢?如果尝试将多个handler使用相同的name呢?其实这样并不可行!因为当多个handler的name相同时,只有一个handler会被执行。要想实现一次notify多个handler,需要借助一个关键字,它就是"listen",可以把listen理解成"组名",可以把多个handler分成"组",当需要一次性notify多个handler时,只要将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify。需要注意:listen的名称要和notify名称保持一致!
[root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task1 file: path=/opt/task1.txt state=touch notify: group1_handler handlers: - name: task1 listen: group1_handler file: path=/opt/task1.txt mode=777 owner=root group=root - name: task2 listen: group1_handler file: path=/opt/task1.txt src=/opt/task1.txt dest=/opt/heihei state=link force=yes - name: task3 listen: group1_handler shell: echo "this is test,haha...." >> /opt/task1.txt #执行结果: [root@server01 ~]# ansible-playbook haha.yaml PLAY [test] *********************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [192.168.200.22] ok: [192.168.200.23] TASK [make file task1] ************************************************************************ changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task1] *********************************************************************** changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task2] *********************************************************************** [WARNING]: Both option path and its alias dest are set. changed: [192.168.200.22] changed: [192.168.200.23] RUNNING HANDLER [task3] *********************************************************************** changed: [192.168.200.23] changed: [192.168.200.22] PLAY RECAP ************************************************************************************ 192.168.200.22 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
场景5:使用--force-handlers选项来强制执行handlers
当playbook剧本执行失败以后,handlers可能并没有被触发,也就不会执行了!如果想不管task任务是否成功执行,都强制执行handlers。在这个时候,可以在执行playbook的时候,添加--force-handlers来强制执行handlers!但是必须要注意的是:--force-handlers参数主要针对即使playbook执行失败,也要执行代码块成功了的handlers(即执行成功的task任务), 如果代码块本身执行失败(即执行失败的task任务),那么它所对应的handlers应当不会被执行!
[root@server01 ~]# cat haha.yaml --- - hosts: test remote_user: root become: yes become_method: sudo tasks: - name: make file task1 file: path=/opt/task1.txt state=touch notify: task1 - name: make file task2 file: path=/opt/kevin/task2.txt state=touch notify: task2 handlers: - name: task1 file: path=/opt/task1.txt mode=777 owner=root group=root - name: task2 shell: ln -s /opt/task1.txt /opt/task2.txt #执行结果: [root@server01 ~]# ansible-playbook haha.yaml PLAY [test] *********************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [192.168.200.23] ok: [192.168.200.22] TASK [make file task1] ************************************************************************ changed: [192.168.200.23] changed: [192.168.200.22] TASK [make file task2] ************************************************************************ fatal: [192.168.200.22]: FAILED! => {"changed": false, "msg": "Error, could not touch target: [Errno 2] 没有那个文件或目录: '/opt/kevin/task2.txt'", "path": "/opt/kevin/task2.txt"} fatal: [192.168.200.23]: FAILED! => {"changed": false, "msg": "Error, could not touch target: [Errno 2] 没有那个文件或目录: '/opt/kevin/task2.txt'", "path": "/opt/kevin/task2.txt"} RUNNING HANDLER [task1] *********************************************************************** PLAY RECAP ************************************************************************************ 192.168.200.22 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 #如上执行结果,由于/opt/kevin目录不存在,导致task的第二个任务执行失败,这个时候handler根本没有被触发,也就不会执行。 即使第一个任务执行成功,但是它对应的第一个handler也不会被执行!! ################################################################################### #接下来使用--force-handlers选项来强制执行handlers(强制执行的是:成功执行的task对应的handler) [root@server01 ~]# ansible-playbook haha.yaml --force-handlers PLAY [test] *********************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [192.168.200.22] ok: [192.168.200.23] TASK [make file task1] ************************************************************************ changed: [192.168.200.23] changed: [192.168.200.22] TASK [make file task2] ************************************************************************ fatal: [192.168.200.22]: FAILED! => {"changed": false, "msg": "Error, could not touch target: [Errno 2] 没有那个文件或目录: '/opt/kevin/task2.txt'", "path": "/opt/kevin/task2.txt"} fatal: [192.168.200.23]: FAILED! => {"changed": false, "msg": "Error, could not touch target: [Errno 2] 没有那个文件或目录: '/opt/kevin/task2.txt'", "path": "/opt/kevin/task2.txt"} RUNNING HANDLER [task1] *********************************************************************** changed: [192.168.200.23] changed: [192.168.200.22] PLAY RECAP ************************************************************************************ 192.168.200.22 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 192.168.200.23 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 #如上执行结果,即使playbook执行中有task任务执行失败,但是执行成功的task任务所调用的handler依然会被强制触发并执行!但是执行失败的task任务所调用的handler依然不会被执行。 即handlers中的task1会被执行,task2不会被执行!
2. tags任务标签
tags用于让用户选择运行playbook中的部分代码。ansible具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时如果确信其没有变化,就可以通过tags跳过此些代码片断。tags可以看作是ansible的任务控制!
ansible的标签(Tags)功能可以给角色(Roles)、文件、单独的任务,甚至整个Playbook打上标签,然后利用这些标签来指定要运行Playbook中的个别任务,或不执行指定的任务。如果有一个很大的playbook剧本,而只想运行playbook其中的某个或部分task任务,而不是运行playbook中所有的任务,这个时候tags是你的最佳选择。
2.1 ansible支持"tags:"属性,执行playbook时,可以通过两种方式根据"tags"过滤任务:
1. 在命令行上,使用或选项"--tags或 --skip-tags",后面使用空格或"="都可以。
2. 在ansible配置设置中,使用和选项"TAGS_RUN或TAGS_SKIP";
3. 可以使用"--list-tags"查看playbook中有哪些tags会被执行;
2.2 ansible系统中内置的特殊tags(目前有5个特殊的tags)
到ansible 2.5版本以后,目前系统内置的tags有以下几个:
always: 除非--skip-tags指定这个标签,否则该标记为always的task一直都会执行。"--tags always"只执行标记了always的tasks;
never: 除非--tags指定了这个标签,否则该标记为never的task一直都不会执行。"--tags never"执行标记了always和never的tasks;
tagged: --tags tagged表示执行所有有tags标签的tasks任务,但不包括tags标签是never的tasks任务;--skip-tags tagged表示所有有tags标签的tasks任务都跳过,即不会执行。
untagged: --tags untagged表示执行所有没有tags标签的tasks任务和tags标签为always的tasks任务;--skip-tags untagged效果相反!
all:--tags all表示执行所有的tags标签为非never的task,包括有tags标签和无tags标签的tasks。
执行ansible-playbook命令时,使用下面两个参数的含义(自定义的tags可以是单个,也可以是多个,多个之间使用逗号隔开):
"--tags 自定义的tag" 表示执行tags为指定的标签名的tasks和tags为always的tasks。如果执行命令ansible-playbook site.yml 时不指定tags,则会执行所有tags为非never的tasks
"--skip-tags 自定义tag" 表示执行所有非指定tag和非never的tasks