六、Ansible Task Control

6.1 when条件语句

when 关键字主要针对 TASK 任务进行判断,对于此前我们使用过的 yum 模块是可以自动检测软件包是否已被安装,无需人为干涉;但对于有些任务则是需要进行判断才可以实现的。比如:web 节点都需要配置 nginx 仓库,但其他节点并不需要,此时就会用到 when 判断。比如: Centos 与 Ubuntu 都需要安装 Apache,而Centos 系统软件包为 httpd,而 Ubuntu系统软件包为httpd2,那么此时就需要判断主机系统,然后为不同的主机系统安装不同的软件包。

6.1.1 案例1-根据不同操作系统安装相同的软件

为所有主机安装 Apache 软件
系统为CentOS:安装 httpd
系统为Ubuntu:安装 httpd2

cat when_system.yml
- hosts: web
  tasks:
    - name: Centos Install httpd # 通过fact变量判断系统为centos才会安装httpd
      yum:
        name: httpd
        state: present
      when: (ansible_distribution == "CentOS")
    - name: Ubuntu Install httpd #通过fact变量判断系统为ubuntu才会安装httpd2
      yum:
        name: httpd2
        state: present
      when: (ansible_distribution == "Ubuntu")

6.1.2 案例2-为特定的主机添加Nginx仓库

为所有主机添加 Nginx 仓库
主机名为web:添加 Nginx 仓库
主机名不为web:不做任何处理

cat when_yum.yml
- hosts: all
  tasks:
    - name: Add Nginx Yum Repository
      yum_repository:
        name: nginx
        description: Nginx Repository
        baseurl: http://nginx.org/packages/centos/7/$basearch/
        gpgcheck: no
      when: (ansible_hostname is match("web*"))
#当然when也可以使用and与or方式
#when: (ansible_hostname is match("web*"))
or
# (ansible_hostname is match("lb*"))

6.1.2 案例3-判断服务是否正常运行

判断 httpd 服务是否处于运行状态
已运行:则重启服务
未运行:则不做处理
1.通过 register 将命令执行结果保存至变量,然后通过 when 语句进行判断

cat when_service.yml
- hosts: webservers
tasks:
- name: Check Httpd Server
command: systemctl is-active httpd
ignore_errors: yes
register: check_httpd
- name: debug outprint #通过debug的
var输出该变量的所有内容
debug: var=check_httpd
- name: Httpd Restart #如果check_httpd执
行命令结果等于0,则执行重启httpd,否则跳过
service: name=httpd state=restarted
when: check_httpd.rc == 0

6.1.4 案例4-为特定的主机执行任务

有2台 server
第一台:172.16.1.7安装了 nginx
第二台:172.16.1.8没有安装 nginx
现在需要在没有安装 nginx的节点上做操作,需
要通过 when 条件语句实现

6.2 loop 循环语句

在写 playbook 的时候发现了很多 task 都要重复引用某个相同的模块,比如一次启动10个服务,或者一次拷贝10个文件,如果按照传统的写法最少要写10次,这样会显得 playbook 很臃肿。如果使用循环的方式来编写playbook,这样可以减少重复编写 task 带来的臃肿;

6.2.1 案例1-使用循环批量启动服务

1.在没有使用循环的场景下,启动多个服务需要写多条task 任务。
cat loop-service.yml
- hosts: web
  tasks:
    - name: Installed Httpd Mariadb Package
      yum: name=httpd,mariadb state=latest
    - name: Start Httpd Server
      service: name=httpd state=started enabled=yes
    - name: Start Mariadb Server
      service: name=mariadb state=started enabled=yes
      
2.我们将如上的 playbook 修改为循环的方式,减少重复编写多条 task 任务。
cat loop-service.yml
- hosts: web
  tasks:
    - name: Installed Httpd Mariadb Package
      yum: name=httpd,mariadb-server state=latest
    - name: Start Httpd Mariadb Server
      service: name={{ item }} state=started enabled=yes
      loop:
        - httpd
        - mariadb
3.执行 playbook
ansible-playbook loop.yml

6.2.2 案例2-使用循环批量安装软件

1.批量安装软件
cat loop-service-v2.yml
- hosts: web
  tasks:
    - name: Installed Httpd Mariadb Package
      yum: name={{ pack }} state=latest
      vars:
        pack:
          - httpd
          - mariadb-server
2.执行 playbook
ansible-playbook loop-service-v2.yml

6.2.3 案例3-使用循环批量创建用户

1.批量创建用户,使用 key values 字典的方式
cat loop-user.yml
- hosts: webservers
  tasks:
    - name: Add Users
      user:
        name: {{ item.name }}
        groups: {{ item.groups }}
        state: present
      loop:
        - { name: 'testuser1', groups: 'bin' }
        - { name: 'testuser2', groups: 'root' }
2.执行 playbook
ansible-playbook loop-user.yml

6.2.4 案例4-使用循环批量拷贝文件

cat loop-file.yml
- hosts: all
  tasks:
    - name: Configure Rsync Server
      copy: src={{ item.src }} dest=/etc/{{item.dest }} mode={{ item.mode }}
      with_items:
        - {src: "rsyncd.conf", dest: "rsyncd.conf", mode: "0644"}
        - {src: "rsync.passwd", dest: "rsync.passwd", mode: "0600"}

6.3.Handlers与Notify

Handlers 是一个触发器,同时是一个特殊的 tasks,它无法直接运行,它需要被 tasks 通知后才会运行。比如:httpd 服务配置文件发生变更,我们则可通过Notify 通知给指定的 handlers 触发器,然后执行相应重启服务的操作,如果配置文件不发生变更操作,则不会触发 Handlers 任务的执行;

6.3.1 案例1-变更服务配置触发重启

1.使用 Ansible 的 playbook 部署 httpd 服务
cat webserver.yml
- hosts: web
  vars:
    http_port: 8881
  tasks:
    - name: Install Httpd Server
      yum: name=httpd state=present
    - name: Configure Httpd Server
      template: src=./httpd.conf dest=/etc/httpd/conf/httpd.conf
      notify:  #调用名称为Restart Httpd Server的handlers(可以写多个)
        - Restart Httpd Server
    - name: Start Httpd Server
      service: name=httpd state=started enabled=yes
  handlers:
    - name: Restart Httpd Server
      service: name=httpd state=restarted
2.只有当我们修改配置文件才会触发 handlers
ansible-playbook webserver.yml

6.3.2 案例2-变更服务配置触发通知

6.3.3 Handlers注意事项与说明

handlers 注意事项

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

2.只有 task 发生改变了才会通知 handlers ,没有改变则不会触发handlers

3.不能使用 handlers 替代 tasks、因为handlers 是一个特殊的tasks

6.4 tags 任务标签

默认情况下,Ansible 在执行一个 playbook 时,会执行 playbook 中所有的任务。而标签功能是用来指定要运行 playbook 中的某个特定的任务;


1.为 playbook 添加标签的方式有如下几种:

对一个 task 打一个标签

对一个 task 打多个标签

对多个 task 打一个标签

2.task打完标签使用的几种方式

-t 执行指定tag标签对应的任务

--skip-tags 执行除 --skip-tags 标签之外的所有任务

6.4.1 案例1-指定执行某个tags

1.使用 -t 执行指定的 tags 标签对应的任务
cat nfs.yml
---
- hosts: nfs
  remote_user: root
  tasks:
    - name: Install Nfs Server
      yum: name=nfs-utils state=present
      tags:
        - install_nfs
        - install_nfs-server
    - name: Service Nfs Server
      service: name=nfs-server state=started enabled=yes
      tags: start_nfs-server

2.执行 playbook
ansible-playbook f10.yml

3.使用 -t 指定 tags 标签对应的任务, 多个 tags 使用逗号隔开即可
ansible-playbook -t install_nfs-server nfs.yml

6.4.2 案例2-指定排除某个tags

使用 --skip-tags 排除不执行的 tags
ansible-playbook --skip-tags install_nfs-server nfs.yml

6.5 include任务复用

有时,我们发现大量的 Playbook 内容需要重复编写,各 Tasks 之间功能需相互调用才能完成各自功能,Playbook 庞大到维护困难,这时我们需要使用include比如:A项目需要用到重启 httpd,B项目需要用到,重启 httpd,那么我们可以使用 Include来减少重复编写。

6.5.1 案例1-多个项目调用相同task

1.编写 restart_httpd.yml 文件
#注意这是一个tasks所有没有play的任何信息
cat restart_httpd.yml
- name: Restart Httpd Server
  service: name=httpd state=restarted
  
2.A Project 的 playbook 如下
cat a_project.yml
- hosts: webserver
  tasks:
    - name: A Project command
      command: echo "A"
    - name: Restart httpd
      include: restart_httpd.yml
      
3.B Project 的 playbook 如下
cat b_project.yml
- hosts: webserver
  tasks:
    - name: B Project command
      command: echo "B"
    - name: Restart httpd
      include: restart_httpd.yml
      
6.A Project 和 B Project 执行后的测试结果如下
ansible-playbook a_project.yml

6.5.2 案例2-Inlcude结合tags应用

通过指定标签tags,来说明是安装 tomcat8 还是tomcat9
  1.准备入口 main.yml 文件,然后包含install_tomcat8.yml以及install_tomcat9.yml
  2.在执行 main.yml时,需要通过 --tags 指明要安装的版本

1.编写 main.yml 入口文件
cat main.yml
- hosts: localhost
  tasks:
    - name: Installed Tomcat8 Version
      include: install_tomcat8.yml
      tags: tomcat8
    - name: Installed Tomcat9 Version
      include: install_tomcat9.yml
      tags: tomcat9
      
2.编写 install_tomcat8.yml
cat install_tomcat8.yml
- hosts: webservers
  vars:
    - tomcat_version: 8.5.63
    - tomcat_install_dir: /usr/local
  tasks:
    - name: Install jdk1.8
      yum:
        name: java-1.8.0-openjdk
        state: present
    - name: Download tomcat
      get_url:
        url: http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v{{ tomcat_version }}/bin/apache-tomcat-{{ tomcat_version }}.tar.gz
        dest: /tmp
    - name: Unarchive tomcat-{{ tomcat_version }}.tar.gz
      unarchive:
        src: /tmp/apache-tomcat-{{ tomcat_version }}.tar.gz
        dest: "{{ tomcat_install_dir }}"
        copy: no
    - name: Start tomcat
      shell: cd {{ tomcat_install_dir }} &&mv apache-tomcat-{{ tomcat_version }} tomcat8 &&cd tomcat8/bin && nohup ./startup.sh &

3.编写 install_tomcat9.yml
cat install_tomcat9.yml
- hosts: webservers
  vars:
    - tomcat_version: 9.0.43
    - tomcat_install_dir: /usr/local
  tasks:
    - name: Install jdk1.8
      yum:
        name: java-1.8.0-openjdk
        state: present
    - name: Download tomcat
      get_url:
        url: http://mirrors.hust.edu.cn/apache/tomcat/tomcat-9/v{{ tomcat_version }}/bin/apache-tomcat-{{ tomcat_version }}.tar.gz
        dest: /tmp
    - name: Unarchive tomcat-{{ tomcat_version }}.tar.gz
      unarchive:
        src: /tmp/apache-tomcat-{{ tomcat_version }}.tar.gz
        dest: "{{ tomcat_install_dir }}"
        copy: no
    - name: Start tomcat
      shell: cd {{ tomcat_install_dir }} &&mv apache-tomcat-{{ tomcat_version }} tomcat9 &&cd tomcat9/bin && nohup ./startup.sh &

6.执行 main.yml文件,然后通过 --tags 执行对应的版本
ansible-playbook main.yml --tags tomcat8
ansible-playbook main.yml --tags tomcat9

6.7 Playbook异常处理

6.7.1 案例1-Playbook错误忽略

在 playbook 执行的过程中,难免会遇到一些错误。由于 playbook 遇到错误后,不会执行之后的任务,不便于调试,此时,可以使用 ignore_errors 来暂时忽略错误,使得 playbook 继续执行。

1.编写 playbook,当有 task 执行失败则会立即终止后续 task 运行
cat ignore.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: Ignore False
      command: /bin/false
      ignore_errors: no
    - name: touch new file
      file: path=/tmp/oldxu_ignore state=touch
2.执行 playbook,会报错,后续的任务也没有执行。
ansible-playbook ignore.yml

3.此时我们给对应的 task 任务添加忽略错误
cat ignore.yml
- hosts: web
  tasks:
    - name: Ignore False
      command: /bin/false #该命令会返回非0,代表命令执行失败
      ignore_errors: yes #忽略错误
    - name: touch new file
      file: path=/tmp/oldxu_ignore state=touch
      
4.再次执行 playbook 如果碰到指定的 tasks 错误,会自动忽略,继续执行剩下的 tasks

6.7.1 案例2-task执行失败强制调用handlers

通常情况下,当 task 失败后,play将会终止,任何在前面已经被 tasks notify 的 handlers 都不会被执行。如果你在 play 中设置了 force_handlers: yes参数,被通知的 handlers 就会被强制执行。(有些特殊场景可能会使用到)

cat igno_handlers.yml
- hosts: web
  force_handlers: yes #强制调用handlers
  tasks:
    - name: Touch File
      file: path=/tmp/bgx_handlers state=touch
      notify: Restart Httpd Server
    - name: Installed Packages
      yum: name=sb state=latest
  handlers:
    - name: Restart Httpd Server
      service: name=httpd state=restarted
2.执行 playbook
ansible-playbook igno_handlers.yml

6.7.2 案例3-控制Tasks报告状态为OK

1.编辑 playbook
cat change.yml
- hosts: web
  tasks:
#获取系统httpd服务启动状态,将其结果写入Httpd_Port变量中
    - name: Get Httpd Server Port
      shell: netstat -lntp|grep httpd
      register: Httpd_Port
#输出Httpd_Port变量中的内容
    - name: Out Httpd Server Status
      debug: msg={{ Httpd_Port.stdout_lines }}
      ignore_errors: yes
      
2.执行 playbook 会发现第一个 task 运行 shell 模块报告的改变,即使它没有真正的在远端系统做出改变,如果你一直运行,它会一直处在改变状态。

3.shell 任务不应该每次都报告 changed 状态,因为它没有在被管理主机执行后发生变化。添加changed_when: false 来抑制这个改变
cat change.yml
- hosts: web
  tasks:
    - name: Get Httpd Server Port
      shell: netstat -lntp|grep httpd
      register: Httpd_Port
      changed_when: false #该task不发生changed提示
    - name: Out Httpd Server Status
      debug: msg={{ Httpd_Port.stdout_lines }}
      ignore_errors: yes

6.7.3 案例4-changed_when检查任务结果

编写 Httpd 配置管理,具备配置文件健康检查功能;
cat changed_when.yml
- hosts: webservers
  tasks:
    - name: configure httpd server
      template: src=./httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
      notify: Restart Httpd Server
    - name: Check HTTPD
      shell: /usr/sbin/httpd -t
      register: httpd_check
      changed_when:
        - httpd_check.stdout.find('OK') #查找变量返回的结果是否有ok,如不存在则终止该tasks
        - false
    - name: start httpd server
      service: name=httpd state=started enabled=yes
  handlers:
    - name: Restart Httpd Server
      systemd: name=httpd state=restarted

编写 Nginx 配置管理,具备配置文件健康检查功能;
cat f21.yml
- hosts: webserver
  tasks:
    - name: Install Nginx Server
      yum: name=nginx state=present
    - name: Configure Nginx Server
      template: src=./nginx.conf.j2 dest=/etc/nginx/nginx.conf
      notify: Restart Nginx Server
    - name: Check Nginx Server
      shell: /usr/sbin/nginx -t
      register: check_nginx
      changed_when:
        - check_nginx.stdout.find('successful')
        - false
    - name: Start Nginx Server
      service: name=nginx state=started enabled=yes
  handlers:
    - name: Restart Nginx Server
      systemd: name=nginx state=restarted