第四节:Ansible剧本构成详解

一、plabook结构说明

一个playbook就好比是你编排的一场大戏
这场大戏由于很多小片段构成,一个片段就称之为一个play
一个play的本质就是为一组主机关联上多个task任务,对应的就是tasks字段,tasks下面有很多小task任务
一个小的task任务本质就是对ansible某一个模块的调度

把上面的内容按照你想要的顺序组织在一起,就称之为编排,像排练一场演出一场电影一样。
流程大致如下

file


plabook由四部分构成
1、Target section: 定义远程主机、用户、连接信息
2、Variable section: 定义playbook运行时需要使用的变量
3、Task section: 定义将要在远程主机上执行的任务列表
4、Handler section: 定义task执行完成以后需要触发的任务

参考我们的主机配置信息,开展后续讲解

[root@lb ansible]# cat /etc/ansible/hosts
[group1]
192.168.71.12 ansible_ssh_pass='1'
192.168.71.13 ansible_ssh_pass='1'
[group2]
192.168.71.12 ansible_ssh_pass='1'
192.168.71.13 ansible_ssh_pass='1'
192.168.71.14 ansible_ssh_pass='1'
[group3]
192.168.71.12 ansible_ssh_pass='1'
192.168.71.13 ansible_ssh_pass='1'
192.168.71.15 ansible_ssh_pass='1'
[group4]
192.168.71.13 ansible_ssh_pass='1'
[group5]
192.168.71.16 ansible_ssh_pass='1'

二、Target Section

1、hosts:即inentory中的定义主机与主机组,你在ad-doc模式里学习到的host-pattern,在这里也完全适用

- name: 创建目录
  hosts: group1:&group2
  tasks:
    - name: 测试
      file:
        path: /tmp/xxx
        state: directory

2、用户部分如果未定义,则默认使用/etc/ansible/ansible.cfg中用户相关配置

file

鉴于安全性和审计原因,Ansible可能需要先以非特权用户身份连接远程主机,然后再通过特权升级获得root用户身份的管理权限。
这可以在Ansible配置文件的[privilege_escalation]部分中设置,代表升级特权

# /etc/ansible/ansible.cfg # 每一行末尾不要加注释,会无法识别

[defaults]
# 1、指定主机清单文件
inventory = /etc/ansible/hosts

# 2、以什么用户远程被管理主机
remote_user = egon666

[privilege_escalation]
# 3、是否需要切换用户
become = true

# 4、如何切换用户
become_method = sudo

# 5、切换成什么用户
become_user = root

# 6、sudo是否需要输入密码
become_ask_pass = False

# 7、建议:制作ssh秘钥登录,这样就不用输入密码了,并且在目标主机中设置sudo以root用户身份运行命令而不必输入密码,非常方便且安全

在剧本中自己配置的话,优先级更高

- hosts: group5
  name: /etc/hosts is up to date
  remote_user: egon666
  become: true
  become_method: sudo
  become_user: root

  tasks:
    - name: www.egon.com in /etc/hosts
      lineinfile:
        path: /etc/hosts
        line: '192.168.71.200 www.egon.com'
        state: present

一、执行剧本前,目标主机必须
#1、创建用户并设置好密码
useradd egon666
passwd egon666 # 设置好密码

# 2、添加sudo收取
cat /etc/sudoers里添加
egon666 ALL=(ALL)       NOPASSWD:ALL

二、然后在控制机上做秘钥登录
ssh-keygen
ssh-copy-id -i egon666@目标主机的ip地址

三、Variable section

https://egonlin.com/?p=9815

四、Task section

一个plabook文件中可以包含很多play,每个play都可以选中自己的主机然后执行其下的tasks,如下playbook中定义了两个play

[root@lb workspace]# cat 1.yaml 
- hosts: group1
  name: "我是playbook下的第一个play"
  tasks:
    - name: 命令1
      shell: "echo 111 > /tmp/1.txt"

    - name: 命令2会出错
      shell: "cat /xxxxx > /tmp/2.txt"

    - name: 命令3
      shell: "echo 333 > /tmp/3.txt"
- hosts: group5
  name: "我是playbook下的第二个play"
  tasks:
    - name: 命令4
      shell: "echo 444 > /tmp/4.txt"

    - name: 命令5会出错
      shell: "cat /xxxxx > /tmp/5.txt"

    - name: 命令6
      shell: "echo 666 > /tmp/6.txt"

执行的时候,会先执行完第一个play后再执行第二个play
执行每个play的时候
1、会对所有hosts选中的主机,执行tasks下的命令1
2、然后对所有hosts选中的主机,执行tasks下的命令2
3、最后对所有hosts选中的主机,执行tasks下的命令3

如果中途发生错误,则整个playbook都会终止掉,有时后你想忽略错误,继续后续执行,那就需要用到忽略错误,有两种方式实现
1、在命令末尾加|| /bin/true让其返回的状态码永远为0,此时ansible并不将任何报错信息打印出来,(该方案对command模块就不适用了,因为要用到特殊符号,所以下一个方案更通用,本方案了解即可)

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

2、使用ignore_errors来忽略错误信息,此时ansible会将报错信息打印出来

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

你可以用上面的案例进行测试,当你忽略错误后,后续的task,已经下一个play都会得到正常的执行

补充:
1、即便你不忽略错误,你也要知道一点,由于playbook的幂等性,playbook可以被反复执行,所以即使发生了错误,在修复错误后,再执行一次也不会有问题
2、ignore_errors: True 通常会跟上一小节介绍的变量register一起使用,在忽略错误后,拿到错误结果,在后续的逻辑中进行判断处理

- hosts: web_group
  tasks:
    - name: Check Httpd Server
      #使用命令检查服务启动状态时,如果服务没有启动则会得到错误结果,剧本会停止运行
      command: systemctl is-active httpd
      #配置忽略错误可以继续执行剧本
      ignore_errors: yes
      register: check_httpd

    - name: debug outprint
      debug: 
        msg: "{{ check_httpd }}"

    - name: Httpd Restart
      service:
        name: httpd
        state: restarted
      when: check_httpd.rc == 0

五、Handler section

5.1、什么是handlers?

handler也是一种task,只不过该task默认并不会执行,只有在被通知时才会执行,所以又可称之为触发器

什么时候handler收到通知呢?
只有在 playbook 中的某个任务改变了系统状态后,才会触发其notify指定的 handler

"系统状态改变"是指任务执行的结果导致了实际的改变(如配置改变、文件内容变化、服务状态变化),是通过比较模块执行前后的状态来判断的
在执行任务的过程中,如果模块发现它的操作导致了状态的变化(例如,执行前和执行后的状态不同),
则 Ansible 会将这个任务标记为 "changed",如果仅仅是查看或获取状态信息的操作,则不会被视为 "changed"。

简单示例

[root@m01 project]# cat nginx.yml 
- hosts: nginx
  tasks:
    - name: Config Nginx Server
      copy:
        src: /etc/nginx/nginx.conf
        dest: /etc/nginx/
      notify: restart_nginx # 名字与handlers中定义的某个name一致

  handlers:
    - name: restart_nginx
      systemd:
        name: nginx
        state: restarted

5.2、什么场景用handler

主要在以下场景中使用:
1、配置改变后需要重启服务:这可能是使用 handler 的最常见场景。例如,如果你修改了某个服务的配置文件,你可能需要重启该服务以使新的配置生效。你可以在修改配置文件的任务中使用 notify 指令触发一个 handler,这个 handler 的任务是重启相应的服务。
2、在多个任务完成后执行一次操作:如果你有多个任务都可能触发相同的操作,但你只希望在所有任务完成后执行一次,那么你可以使用 handler。无论有多少任务通知了同一处理器,该处理器只会在所有任务完成之后执行一次。
3、在长期运行的任务完成后执行操作:如果你有一个需要长时间运行的任务,当这个任务完成后,你可能需要执行一些清理工作,例如删除临时文件。你可以将删除临时文件的任务作为一个 handler,当长期运行的任务完成时触发这个 handler。
这也是 handler 的一个关键特征:无论它被通知多少次,每次 playbook 运行,每个 handler 只会执行一次,并且只有在所有任务都执行完毕后,handler 才会执行。

5.3、配置触发器

每个play的tasks全部执行完后才会触发1次handler的执行,然后执行下一个play

[root@lb workspace]# cat test.yaml 
#第一个play
- hosts: group5
  tasks:
    - name: 命令111111
      copy:
        src: /etc/hosts
        dest: /tmp/1.txt
      notify: say_ok

    - name: 命令222222
      shell: "sleep 5"
      notify: say_ok
  handlers:
    - name: say_ok
      shell: "echo ok >> /tmp/play1.log"

#第二个play
- hosts: group5
  tasks:
    - name: 命令4444444
      copy:
        src: /etc/hosts
        dest: /tmp/2.txt
      notify: say_hello

    - name: 命令5555555
      shell: "sleep 5"
      notify: say_hello
  handlers:
    - name: say_hello
      shell: "echo hello >> /tmp/play2.log"

5.4、触发器使用注意事项

1.无论多少个task通知了相同的handlers,handlers仅会在所有tasks结束后运行一次。

2.Handlers只有在其所在的任务被执行时,才会被运行;如果一个任务中定义了notify调用Handlers,但是由于条件判断等原因,该任务未被执行,那么Handlers同样不会被执行。

3.Handlers只会在每一个play的末尾运行一次;如果想在一个playbook中间运行Handlers,则需要使用meta模块来实现。例如: -meta: flush_handlers。

4.如果一个play在运行到调用Handlers的语句之前失败了,那么这个Handlers将不会被执行。我们可以使用meta模块的--force-handlers选项来强制执行Handlers,即使Handlers所在的play中途运行失败也能执行。

5.不能使用handlers替代tasks

5.5、具体应用示例

应用示例1

[root@m01 project]# cat php.yml 
- hosts: web_group
  tasks:
    - name: Tar php Package
      unarchive:
        src: /project/package/php.tar.gz
        dest: /tmp/

    - name: Check php Install Status
      shell: "rpm -qa | grep php | wc -l"
      register: get_php_instll_status

    - name: Install php Server
      shell: "yum localinstall -y /tmp/*.rpm"
      when: get_php_instll_status.stdout_lines == 0

    - name: Config php Server
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
      with_items:
        - { src: "/project/conf/php.ini", dest: "/etc" }
        - { src: "/project/conf/www.conf", dest: "/etc/php-fpm.d/" }
      notify: restart_php

    - name: Start php Server
      systemd:
        name: php-fpm
        state: started

  handlers:
    - name: restart_php
      systemd:
        name: php-fpm
        state: restarted

应用示例2

tasks:
  - name: template configuration file
    template: 
      src: template.j2 
      dest: /etc/foo.conf
    notify:
      - 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

5.6 强制handler执行

如果tasks下某个task出错了,并且你也没用用||/bin/true或者ignore_errors的方式把错误忽略掉,那么程序崩溃,handler自然不会执行,
想要强制执行需要设置:force_handlers: yes

[root@lb workspace]# cat 1.yaml 
- hosts: group1
  name: "我是playbook下的第一个play"
  force_handlers: True
  tasks:
    - name: 命令1
      shell: "echo 111 > /tmp/1.txt"
      notify: test

    - name: 命令2会出错
      shell: "cat /xxxxx > /tmp/2.txt"
     # ignore_errors: True

    - name: 命令3
      shell: "echo 333 > /tmp/3.txt"

  handlers:
    - name: test
      shell: "echo test > /tmp/ttttttt.txt"
- hosts: group5
  name: "我是playbook下的第二个play"
  tasks:
    - name: 命令4
      shell: "echo 444 > /tmp/4.txt"

    - name: 命令5会出错
      shell: "cat /xxxxx > /tmp/5.txt"

    - name: 命令6
      shell: "echo 666 > /tmp/6.txt"
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术