ansible剧本playbook详解

1,471 阅读8分钟

Absible playbook

Ansible的任务配置文件被称为playbook,我们可以称之为“剧本”,每一个剧本都包含一系列的任务,就像剧本的每一场分剧场,一个剧本,有很多分剧场组成。Playbook采用yaml语法编写,yaml不是一种标记语言,该语言被开发的时候,它的意思是:yet another markup language(仍是一种标记语言)。

Playbooks组件包括如下:

Target:	定义playbook的远程主机组
Variable:	定义playbook使用的变量
Task:	定义远程主机上的执行的任务列表
Handler:	定义task执行完成以后需要调用的任务,例如配置文件被改动,则启动handler任务重启相关联的服务。

Yaml语言特性

其特性如下:

(1)具有很好的可读性,易于实现

(2)表达能力强,扩展性好

(3)和脚本语言的交互性好

(4)有一个一致的信息模型

(5)可以基于流来处理

检查playbook语法的正确性

ansible-playbook book.yml --syntax-check #检查yaml语法
ansible-playbook book.yml --list-tasks #检查tasks任务
ansible-playbook book.yml --list-hosts #检查生效的主机
ansible-playbook book.yml --start-at-task=”Copy Nginx.conf” #如果你想从指定的任务开始执行playbook,可以使用``–start-at``选项:
ansible-playbook playbook.yml --step #分步运行playbook,ansible在每个任务前会自动停止,并询问是否应该执行该任务.

基于yaml文件安装httpd的playbook例子

1)创建一个专门放剧本的文件夹,方便管理

[root@localhost ~]# mkdir playbook

2)创建一个剧本

[root@localhost ~]# cd playbook/

[root@localhost playbook]# vim http_install.yaml

---
    - hosts: 103.235.168.56   #这个剧本用于哪个host
      remote_user: root      #剧本中的演员,root用户,也可以是有密码或者密钥推送的任何目录
      tasks:    #任务列表,以下是需要执行的哪些任务
          - name: download httpd  #任务的名称
            yum: name=httpd disable_gpg_check=yes   #调用的模块,以及模块中的参数设置
          - name: stopped firewalld
            service: name=firewalld state=stopped
          - name: stopped selinux
            command: 'setenforce 0'
          - name: copy index.html
            copy: src=/root/index.html dest=/var/www/html/
          - name: started httpd
            service: name=httpd state=started

3)拷贝一个index文件到src的位置,ansible主机

[root@localhost ~]# vim index.html
hello world!!!

4)检查yaml文件是否存在问题

5)执行剧本 [root@localhost playbook]# ansible-playbook http_install.yaml

6)到客户端主机验证

[root@localhost ~]# curl 'http://localhost'

使用内置模块提升复杂配置的httpd为例子

[root@localhost playbook]# vim builtin-module_httpd.yaml

---
  - hosts: 103.235.168.56
    remote_user: root
    tasks:
      - name: download httpd
        yum: name={{ item }} state=present
        with_items:
          - httpd
          - httpd-devel 
      - name: copy configfile
        copy:     
          src: "{{ item.src }}"
          dest: "{{ item.dest }}"
          owner: root
          group: root
          mode: 0644
        with_items:
          - {
            src: "/root/index.html",
            dest: "/var/www/html/" }
          - {
            src: "/root/index.html",
            dest: "/root/" }
      - name: start-apache enable-apache
        service: name=httpd state=started enabled=yes

第1行,“--”,这个是YAML语法中注释的用法,就像shell脚本中的“#”号样。

第2行,“- hosts: 103.235.168.56",告诉Ansible具体要在哪些主机上运行我的剧本( Playbook),在本例中是一个ip,即一台主机。如果用组名就是一组主机,如果是all就是所有主机。

第3行,“sudo: yes",告诉Ansible通过sudo来运行相应命令,这样所有命令将会以root身份执行。本例测试没写上这个。

第4行,“tasks:", 指定- 系列将要运行的任务。每一个任务(play)以“- name:install httpd"开头。“-

name:"字段并不是个模块,不会执行任务实质性的操作,它只是给“

task"一个易于识别的名称。即便把name字段对应的行完全删除,也不会有任何问题。 本例中我们使用yum模块来安装Apache,替代了“yum -y install hthttpd-devel"。在每一个play当中,都可以使用with items 米定义变量,并通过“{{变量名}}”的形式来直接使用。我们使用yum模块的state=present选项来确保软件被安装,或者使用state=absent来确保软件被则除。

第二个任务(play) 同样是“一name”字符开头。我们使用copy模块来将“sre” 定义的源文件(必须是Ansible所在服务器上的本地文件)复制到“dest”定义的目的地址(此地址为远程主机上的地址)去。在传递文件的同时,还定义了文件的属主、属组和权限。在这个play中,我们用数组的形式给变量赋值,使用{var1: value, var2: value}的格式来赋值,变量的个数可以任意多,不同变量间以逗号分隔,使用{item.var1}} 的形式来调用变量,本例中为{fitem.src}}。这里特别注意,map里面嵌套了map,缩进一定要做好。

第三个任务(play) 使用了同样的结构,调用了service模块,以保证服务的正常开启。

[root@localhost playbook]# ansible-playbook builtin-module_httpd.yaml

[root@localhost ~]# curl 'http://localhost' #到客户端主机验证

什么是handlers

1)首先我们来描述一个场景,修改端口重启httpd

[root@localhost playbook]# vim modify_http.yaml

---
  - hosts: 103.235.168.40
    remote_user: root
    tasks:
      - name: modify http_conf
        lineinfile: 
          path: /etc/httpd/conf/httpd.conf
          regexp: "^Listen" 
          line: "Listen 8080" 
          backup: yes
          backrefs: yes
      - name: restart httpd 
        service: name=httpd state=restarted

[root@localhost playbook]# ansible-playbook modify_http.yaml

可以看到配置已经被修改了,而且也重启了httpd服务器,而且在客户端也可以看到8080端口正常被监听了。

我们再次执行一次yaml任务

于是,这样是不妥的,因为我们是为了修改配置文件,采取重启apache服务,但是我们配置文件没有修改,也去重启了服务。如果我们的apache运行了线上任务呢?服务突然断开了,用户体验就会变差。所以这种重启是完全不需要的。那现在我们需要怎么做呢?

2)所以handlers就是来改变这种问题的,handlers是用来解决触发事件的,也就是当一个tasks真正的执行后,结果发生了变化,才会去触发另一个task。

理解:

handlers可以理解成另一种tasks,handlers是另一种’任务列表‘,handlers的任务会被tasks中的任务进行”调用“,但是,被”调用“并不意味着一定会执行,只有当tasks中的任务”真正执行“以后,handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被’调用‘,也并不会执行。

[root@localhost playbook]# cat handlers_httpd.yaml

---
  - hosts: 103.235.168.56
    remote_user: root
    tasks:
      - name: modify http_conf
        lineinfile: 
          path: /etc/httpd/conf/httpd.conf
          regexp: "^Listen" 
          line: "Listen 8080" 
          backup: yes
          backrefs: yes
        notify:
          restart httpd
    handlers:
      - name: restart httpd 
        service: name=httpd state=restarted

这个文件的意思是,modify http_conf这个任务执行了,然后用的notify去触发handlers 中的restart httpd这个任务。也就是说tasks和handlers都是任务列表,只是handlers中的任务被tasks中的任务notify罢了。

3)handlers执行结果

同时调用多个handlers

使用上面的例子

---
  - hosts: 103.235.168.56
    remote_user: root
    tasks:
      - name: modify http_conf
        lineinfile: 
          path: /etc/httpd/conf/httpd.conf
          regexp: "^Listen" 
          line: "Listen 8080" 
          backup: yes
          backrefs: yes
        notify:
          restart httpd
          restart firewalld 
    handlers:
      - name: restart httpd 
        service: name=httpd state=restarted
      - name: restart httpd 
        service: name=firewalld state=restarted

handlers被不同的任务notify

[root@localhost playbook]# vim different_motify.yaml

---
  - hosts: 103.235.168.56
    remote_user: root
    tasks:
      - name: make dt1
        file: path=/root/testdir/dt1 state=directory
        notify: ht1
      - name: make dt2
        file: path=/root/testdir/dt2 state=directory
        notify: ht2

    handlers:
      - name: ht1
        file: path=/root/testdir/ht1 state=touch
      - name: ht2
        file: path=/root/testdir/ht2 state=touch

[root@localhost playbook]# ansible-playbook different_motify.yaml

执行可以看到是先执行tasks的任务,然后再执行的handlers里面的任务,与tasks定义的notify是无关的

使用handler注意点

在使用Handlers的过程中,有以下几点需要格外注意。

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

· Handlers只会在Play的末尾运行一次; 如果想在一个Playbook的中间运行Handlers, 则需要使用meta模块来实现,例如: - meta: flush handlers。

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

1)play中间执行handlers

[root@localhost playbook]# vim motify_meta.yaml

---
  - hosts: 103.235.168.56
    remote_user: root
    tasks:
      - name: make dt1
        file: path=/root/testdir/dt1 state=directory
        notify: ht1
      - meta: flush_handlers
      - name: make dt2
        file: path=/root/testdir/dt2 state=directory
        notify: ht2

    handlers:
      - name: ht1
        file: path=/root/testdir/ht1 state=touch
      - name: ht2
        file: path=/root/testdir/ht2 state=touch

[root@localhost playbook]# ansible-playbook motify_meta.yaml

Playbook 与Shell脚本差异对比

当我们]把Shell脚本转换为Playbook运行的时候,Ansible 会留下清晰的执行痕迹,明确告诉我们在每一-台主机上的每一步都做了什么。

同时,Ansible自带幂等判断机制也为运维省去不少伤脑费心的人脑逻辑判断运算。当我们重复执行一个Playbook时,当Ansible发现系统的现有状态与Playbook所定义的将要实现的状态一致时, Ansile 将自动跳过该操作。

我们再次执行Playbook: temp.yml,当Ansible发现Playbook中的play都已完成时,它将直接返回ok状态码,速度非常之快。如果是Shell脚本,肯定会把所用操作再做-遍。

在正式运行Playbook之前,可以使用--check或-C选项来检测Playbook都会改变哪些内容,显示的结果跟真正执行时~模-样, 但不会真的对被管理的服务器产生实际影响。

[root@localhost playbook]# ansible-playbook builtin-module_httpd.yaml -C