13.Ansible自动化运维工具playbook

2 年前 · 来自专栏 linux架构服务

playbook

1.概述

playbook是一个不同于使用Ansible命令行执行方式的模式,其功能更强大灵活。简单来说,playbook是一个非常简单的配置管理和多主机部署系统,不同于任何已经存在的模式,可作为一个适合部署复杂应用程序的基础。Playbook可以定制配置,可以按照指定的操作步骤有序执行,支持同步和异步方式。值得注意的是playbook是通过YAML格式来进行描述定义的。
playbook使用yml标记语言,这是一种标记语言,这种标记语言在文件的最开始需要使用三个“-”来说明文件开始,然后使用缩进来说明代码块的范围。下面通过一个简易的实例,来说明playbook的语法。
---                            #标记文件的开始
- hosts: webservers            #指定该playbook在哪个服务器上执行
  vars:                        #表示下面是定义的变量,
    http_port: 80              #变量的形式,key: value,这里http_port是变量名,80是值
    max_clients: 200
  remote_user: root            #指定远程的用户名,这里缩进和vars保持了一致,说明变量的代码块已经结束。
  tasks:               #下面构成playbook的tasks,每个task都有 - name: 开始,name指定该任务的名称。
  - name: ensure apache is at the latest version  #指定该任务的名称。
    yum: pkg=httpd state=latest #yum说明要是用的模板名称,后面指定对应参数,两行相当于一个shell命令。
  - name: write the apache config file  #每个task之间可以使用空行来做区分。
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf 
    #需要说明的是缩进的意义和python中缩进的意义是一样,是来区分代码块的。

2.YAML语法

* 在单一文件第一行,用连续三个连字号"-" 开始,还有选择性的连续三个点号( ... )用来表示文件的结尾
* 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
* 使用#号注释代码
* 缩进必须是统一的,不能空格和tab混用
* 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
* YAML文件内容是区别大小写的,key/value的值均需大小写敏感
* 多个key/value可同行写也可换行写,同行使用,分隔
* key后面冒号要加一个空格 比如: key: value
* value可是个字符串,也可是另一个列表
* YAML文件扩展名通常为yml或yaml

YAML 支持以下常用几种数据类型:

  • 标量:单个的、不可再分的值
  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
# 1.标量
不可在分的量。包括字符串,布尔值,整数,浮点数,Null,时间,日期。
# 2.key对应value
name: www
# 3.字典
字典由多个key与value构成,key和value之间用 :分隔, 并且 : 后面有一个空格,所有k/v可以放在一行,或者每个 k/v 分别放在不同行。
account: 
 name: kim
 age: 18
# 4.列表
列表由多个元素组成,每个元素放在不同行,且元素前均使用"-"打头,并且 - 后有一个空格, 或者将所有元素用 [ ] 括起来放在同一行。
course:
 - linux: centos
 - golang: gin
 - python: django
**三种常见的数据格式**
​```bash
XML:Extensible Markup Language,可扩展标记语言,可用于数据交换和配置
JSON:JavaScript Object Notation, JavaScript 对象表记法,主要用来数据交换或配置,不支持注释
YAML:YAML Ain't Markup Language YAML 不是一种标记语言, 主要用来配置,大小写敏感,不支持tab

3.Playbook 核心组件

一个playbook 中由列表组成,其中所用到的常见组件类型如下:
* Hosts 执行的远程主机列表
* Tasks 任务集,由多个task的元素组成的列表实现,每个task是一个字典,一个完整的代码块功能需最少元素需包括 name 和 task,一个name只能包括一个task
* Variables 内置变量或自定义变量在playbook中调用
* Templates 模板,可替换模板文件中的变量并实现一些简单逻辑的文件
* Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
* tags 标签 指定某条任务执行,用于选择运行playbook中的部分代码。ansible具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过tags跳过此些代码片断

1) host组件

Hosts:playbook中的每一个play的目的都是为了让特定主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,须事先定义在主机清单中。
one.example.com
one.example.com:two.example.com
192.168.1.50
192.168.1.*
public:private     #或者,两个组的并集
public:&private   #与,两个组的交集
public:!private  #在public组,但不在private组
- hosts: public:!private

2) remote_user 组件

remote_user: 可用于Host和task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
- hosts: public
  remote_user: root
  gather_facts: no             #不收集对应主机的信息,这样运行会快点。
  tasks:
    - name: test connection
      ping:
      remote_user: www
      sudo: yes                         #默认sudo为root
      sudo_user: dm                 #sudo为dm

3) task列表

* playbook的主体部分是task list,task list中有一个或多个task,各个task 按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个task后,再开始第二个task。
* task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。
* 每个task都应该有其name,用于playbook的执行结果输出,建议其内容能清晰地描述任务执行步骤。
* 如果未提供name,则action的结果将用于输出
- hosts: public
  remote_user: root
  gather_facts: no
  tasks:
    - name: install httpd
      yum: name=httpd 
    - name: start httpd
      service: name=httpd state=started enabled=yes

4) Handlers 和 notify

Handlers本质是task list ,类似于MySQL中的触发器触发的行为,其中的task与前述的task并没有本质上的不同,主要用于当关注的资源发生变化时,才会采取一定的操作。而Notify对应的action可用于在每个play的最后被触发,这样可避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作。
- hosts: public 
  remote_user: root
  gather_facts: no
  tasks:
    - name: Install httpd
      yum: name=httpd state=present
    - name: Install configure file
      copy: src=httpd.conf dest=/etc/httpd/conf/
      notify: restart httpd
    - name: ensure apache is running
      service: name=httpd state=started enabled=yes
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted
[root@instance-gvpb80ao ~]# ansible-playbook test.yaml 
PLAY [public] *****************************************************************************************
TASK [Install httpd] *****************************************************************************************
ok: [106.13.81.75]
TASK [Install configure file] ********************************************************************************
changed: [106.13.81.75]
TASK [ensure apache is running] ******************************************************************************
changed: [106.13.81.75]
RUNNING HANDLER [restart httpd] ******************************************************************************
changed: [106.13.81.75]
PLAY RECAP *****************************************************************************************
106.13.81.75 : ok=4  changed=3 unreachable=0 failed=0  skipped=0  rescued=0  ignored=0

5) tags 组件

在playbook文件中,可以利用tags组件,为特定 task 指定标签,当在执行playbook时,可以只执行特定tags的task,而非整个playbook文件。
- hosts: public 
  remote_user: root
  gather_facts: no
  tasks:
    - name: Install httpd
      yum: name=httpd state=present
      tags: install
    - name: Install configure file
      copy: src=httpd.conf dest=/etc/httpd/conf/
      notify: restart httpd
    - name: ensure apache is running
      service: name=httpd state=started enabled=yes
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted
ansible-playbook –t install httpd.yml

6) 变量

变量名:仅能由字母、数字和下划线组成,且只能以字母开头。
# 1.在playbook的开头通过vars进行定义变量。
[root@m01 project1]# cat  p2.yml 
- hosts: webservers
  vars:
    - web_package: httpd
    - ftp_package: vsftpd
  tasks:
    - name: Installed Packages
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present
# 2.在playbook中用vars_files指定文件作为文件变量
[root@m01 project1]# cat vars.yml 
web_package: httpd
ftp_package: vsftpd
[root@m01 project1]# cat p2.yml 
- hosts: webservers
  vars_files: ./vars.yml
  tasks:
    - name: Installed Packages
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present
---------------------------------------
# 3.在inventory中定义变量,主机变量优先级高于主机组变量(不推荐,容易将环境变混乱)
[root@m01 project1]# vim /etc/ansible/hosts 
[webservers]
web01 ansible_ssh_host=172.16.1.7
web02 ansible_ssh_host=172.16.1.8
[webservers:vars]
filename=group_vars
[root@m01 project1]# cat p3.yml 
- hosts: webservers
  tasks:
    - name: Create File
      file: path=/tmp/{{ filename }} state=touch
---------------------------------------
# 4.针对上面主机清单的情况,可以在ansible的项目目录中创建额外的两个变量目录
group_vars目录下必须存放和inventory清单文件中定义的组名一致,如下
[root@m01 project1]# cat /etc/ansible/hosts 
[webservers]
web01 ansible_ssh_host=172.16.1.7
web02 ansible_ssh_host=172.16.1.8
[root@m01 project1]# cat group_vars/webservers 
web_package: httpd
ftp_package: vsftpd
# 注意:系统提供了特殊的组,all,也就说在group_vars目录下创建一个all文件,定义变量对所有的主机都生效
[root@m01 project1]# cat host_vars/web01 
web_package: zlib-static
ftp_package: zmap
[root@m01 project1]# cat group_vars/webservers 
web_package: httpd
ftp_package: vsftpd
[root@m01 project1]#  cat p4.yml 
- hosts: webservers
  tasks:
    - name: Installed Packages
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present
[root@m01 project1]# ansible-playbook p4.yml 
PLAY [webservers] *****************************************************************************************
TASK [Gathering Facts] *****************************************************************************************
ok: [web02]
ok: [web01]
TASK [Installed Packages] ****************************************************************************************
ok: [web02]
changed: [web01]
PLAY RECAP ****************************************************************************************
web01                      : ok=2    changed=1    unreachable=0    failed=0   
web02                      : ok=2    changed=0    unreachable=0    failed=0   
---------------------------------------
# 5.通过命令覆盖变量
令行直接指定变量所覆盖。使用--extra-vars或-e设定变量。
[root@m01 project1]# ansible-playbook p4.yml -e "web_package=zarafa-devel" -e "ftp_package=zarafa-utils"
---------------------------------------
# 6.变量优先级
命令行变量--->play中的vars_files--->play中的vars变量-->host_vars中定义的变量--->group_vars/组--->group_vars/all
[root@m01 project1]# cat p5.yml 
- hosts: webservers
#  vars:
#    filename: play_vars
#  vars_files:
#    - ./vars.yml
  tasks:
    - name: Create 
      shell: mkdir -pv /tmp/{{ filename }}
      register: mk_test
    - name: debug
      debug: msg={{ mk_test }}

4.playbook使用

1) playbook命令

# 1.格式
ansible-playbook <filename.yml> ... [options]
# 2.参数
--syntax-check                 #语法检查
-C --check                     #只检测可能会发生的改变,但不真正执行操作
--list-hosts                   #列出运行任务的主机
--list-tags                    #列出tag
--list-tasks                   #列出task
--tags                         # 只run 一个tag
--skip-tags                    # 跳过某个task
--limit 主机列表                 #只针对主机列表中的特定主机执行
-v -vv  -vvv                   #显示过程

2) 案例

# 1.创建用户
[root@instance-gvpb80ao ~]# cat mysql.yaml 
- hosts: public
  remote_user: root
  gather_facts: no
  tasks:
    - name: Create Group
      group: name=mysql system=yes gid=666
    - name: Create User
      user: name=mysql shell=/sbin/nologin system=yes group=mysql uid=666 home=/home/mysql create_home=no
[root@instance-gvpb80ao ~]# ansible-playbook mysql.yaml 
PLAY [public] *****************************************************************************************
TASK [Create Group] *****************************************************************************************
ok: [106.13.81.75]
TASK [Create User] *****************************************************************************************
changed: [106.13.81.75]
PLAY RECAP *****************************************************************************************
106.13.81.75  : ok=2 changed=1 unreachable=0 failed=0 skipped=0  rescued=0 ignored=0   
-----------------------------------------------
# 2.安装nginx
- hosts: public
  remote_user: root
  gather_facts: no
  tasks:
    - name: Create Group
      group: name=nginx system=yes gid=666
    - name: Create User
      user: name=nginx shell=/sbin/nologin system=yes group=nginx uid=666 home=/home/nginx create_home=no
    - name: Install nginx
      yum: name=nginx state=present
    - name: Start Nginx
      service:  name=nginx state=started enabled=yes
-----------------------------------------------
# 3.安装和卸载httpd
# 安装httpd
- hosts: public
  remote_user: root
  gather_facts: no
  tasks:
    - name: Create Group
      group: name=www system=yes gid=777
    - name: Create User
      user: name=www shell=/sbin/nologin system=yes group=www uid=777 home=/home/www create_home=no
    - name: Install nginx
      yum: name=httpd state=present
    - name: Start Nginx
      service:  name=httpd state=started enabled=yes
# 卸载httpd
- hosts: public
  remote_user: root
  tasks:
    - name: remove httpd package
      yum: name=httpd state=absent
    - name: remove apache user
      user: name=www state=absent
    - name: remove config file
      file: name=/etc/httpd state=absent
    - name: remove web html
      file: name=/var/html/ state=absent

5.部署rsync客户端和服务端

1) 配置主机清单

[root@m01 lnmp]# cat /etc/ansible/hosts 
[web_group]
web01 ansible_ssh_pass='1'
web02 ansible_ssh_pass='1'
[nfs_server]
nfs ansible_ssh_pass='1'
[rsync_server]
backup ansible_ssh_pass='1'
[db_server]
db01 ansible_ssh_pass='1'
[www:children]
web_group
nfs_server
rsync_server

2) 准备rsync配置文件

[root@m01 lnmp]# vim /etc/rsyncd.conf 
uid = www
gid = www
port = 873
fake super = yes
use chroot = no
max connections = 200
timeout = 600
ignore errors
read only = false
list = false
auth users = rsync_backup
secrets file = /etc/rsync.passwd
log file = /var/log/rsyncd.log
#####################################
[backup]
comment = backup!
path = /backup

3) 准备sersync

#1.准备包
[root@m01 ~]# ll sersync2.5.4_64bit_binary_stable_final.tar.gz 
-rw-r--r-- 1 root root 727290 Aug 23 12:22 sersync2.5.4_64bit_binary_stable_final.tar.gz
#2.准备配置文件
[root@m01 ~]# vim GNU-Linux-x86/confxml.xml
    <inotify>
        <delete start="true"/>
        <createFolder start="true"/>
        <createFile start="true"/>
        <closeWrite start="true"/>
        <moveFrom start="true"/>
        <moveTo start="true"/>
        <attrib start="true"/>
        <modify start="true"/>
    </inotify>
    <sersync>
        <localpath watch="/data">
            <remote ip="172.16.1.41" name="backup"/>
        </localpath>
        <rsync>
            <commonParams params="-artuz"/>
            <auth start="true" users="rsync_backup" passwordfile="/etc/rsync.password"/>
    ... ...
    </sersync>

4) 编写剧本

[root@m01 lnmp]# cat rsync_client.yml 
- hosts: nfs_server
  tasks:
    - name: Install Rsync Server
        name: rsync
        state: present
    - name: Install Inotify-Tools Server
        name: inotify-tools
        state: present
    - name: Install Sersync Server
      unarchive:
        src: /root/sersync2.5.4_64bit_binary_stable_final.tar.gz
        dest: /usr/local/
    - name: Rename Sersync Dir
      shell: "mv /usr/local/GNU-Linux-x86 /usr/local/sersync"
    - name: Config Sersync Server
      copy:
        src: /root/GNU-Linux-x86/confxml.xml
        dest: /usr/local/sersync/
    - name: Chmod Sersync
      copy:
        src: /root/GNU-Linux-x86/sersync2
        dest: /usr/local/sersync/
        mode: 755
    - name: Config Rsync Client Password File
      copy: