ansible-playbook(剧本)

Posted givenchy_yzl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ansible-playbook(剧本)相关的知识,希望对你有一定的参考价值。

playbook-剧本

储备知识

什么是剧本?

playbooks是一个不同于使用Ansible命令行执行方式的模式,playbook是一个非常简单的配置管理和多主机部署系统,不同于任何已经存在的模式,可作为一个适合部署复杂应用程序的基础。Playbook可以定制配置,可以按照指定的操作步骤有序执行,支持同步和异步方式。值得注意的是playbook是通过YAML格式来进行描述定义的。

为什么用剧本?

1)功能比ansible命令更强大
2)能很好的控制先后执行顺序,以及依赖关系
3)语法展现更加的直观
4)ansible 命令无法持久使用,playbook可以持久使用

如何使用playbook(剧本)

在这里插入图片描述

YAML语法结构

playbook使用yaml标记语言,这是一种标记语言,这种标记语言在文件的最开始需要使用三个“-”来说明文件开始,然后使用缩进来说明代码块的范围。下面通过一个简易的实例,来说明playbook的语法。

YAML 官方网站:http://www.yaml.org

---                             #标记文件的开始
- 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中缩进的意义是一样,是来区分代码块的。
YAML语言特性
  • YAML的可读性好
  • YAML和脚本语言的交互性好
  • YAML使用实现语言的数据类型
  • YAML有一个一致的信息模型
  • YAML易于实现、轻量级
  • YAML可以基于流来处理
  • YAML表达能力强,扩展性好
YAML语法简介
  • 在单一文件第一行,用连续三个连字号"-" 开始,还有选择性的连续三个点号( … )用来表示文件的结尾
  • 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
  • 使用#号注释代码
  • 缩进必须是统一的,不能空格和tab混用
  • 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
  • YAML文件内容是区别大小写的,key/value的值均需大小写敏感
  • 多个key/value可同行写也可换行写,同行使用,分隔
  • key后面冒号要加一个空格 比如: key: value
  • value可是个字符串,也可是另一个列表
  • YAML文件扩展名通常为yml或yaml
支持的数据类型

YAML 支持以下常用几种数据类型:
标量:单个的、不可再分的值
对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)

标量
不可在分的量。包括字符串,布尔值,整数,浮点数,Null,时间,日期。

key对应value

name: chenyang

字典
字典由多个key与value构成,key和value之间用 :分隔, 并且 : 后面有一个空格,所有key/vlaue可以放在一行,或者每个 key/value 分别放在不同行。

account: 
  name: chenyang
  age: 30

列表
列表由多个元素组成,每个元素放在不同行,且元素前均使用"-"打头,并且 - 后有一个空格, 或者将所有元素用 [ ] 括起来放在同一行。

course:
 - linux: centos
 - golang: gin
 - python: django

三种常见的数据格式

  • XML:Extensible Markup Language,可扩展标记语言,可用于数据交换和配置
  • JSON:javascript Object Notation, JavaScript 对象表记法,主要用来数据交换或配置,不支持注释
  • YAML:YAML Ain’t Markup Language YAML 不是一种标记语言, 主要用来配置,大小写敏感,不支持tab
    可以用工具互相转换,参考网站:
    https://www.json2yaml.com/
    http://www.bejson.com/json/json2yaml/

Playbook核心组件

一个playbook 中由列表组成,其中所用到的常见组件类型如下:

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

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

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: chenyang
      sudo: yes #默认sudo为root
      sudo_user: shanhe #sudo为shanhe
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
playbook命令

格式

ansible-playbook <filename.yml> ... [options]

常见选项

--syntax-check      #语法检查
-C --check                     #只检测可能会发生的改变,但不真正执行操作
--list-hosts    #列出运行任务的主机
--list-tags         #列出tag
--list-tasks         #列出task
--tags                     # 只run 一个tag
--skip-tags        # 跳过某个task
--limit 主机列表 #只针对主机列表中的特定主机执行
-v -vv  -vvv #显示过程

案例

[root@instance-gvpb80ao ~]# cat hello.yaml 
- hosts: public
  remote_user: root
  gather_facts: no
  tasks:
    - name: hello world
      command: echo "Hello Chenyang"
[root@instance-gvpb80ao ~]# ansible-playbook hello.yaml 

PLAY [public] ************************************************************************************************

TASK [hello world] *******************************************************************************************
changed: [106.13.81.75]

PLAY RECAP ***************************************************************************************************
106.13.81.75               : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

案例2

[root@instance-gvpb80ao ~]# ansible-playbook -v hello.yaml 
Using /etc/ansible/ansible.cfg as config file

PLAY [public] ************************************************************************************************

TASK [hello world] *******************************************************************************************
changed: [106.13.81.75] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": ["echo", "Hello Chenyang"], "delta": "0:00:00.022512", "end": "2021-05-21 10:33:43.493102", "rc": 0, "start": "2021-05-21 10:33:43.470590", "stderr": "", "stderr_lines": [], "stdout": "Hello Chenyang", "stdout_lines": ["Hello Chenyang"]}

PLAY RECAP ***************************************************************************************************
106.13.81.75               : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Playbook 初步

利用playbook创建mysql用户

[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   

[root@instance-gvpb80ao ~]# 

利用playbook安装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
利用 playbook 安装和卸载 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

Playbook中使用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   

注意此处的notify,必须与name保存一致,不用缩进,handler必须文件放在最下面,同时handler与tasks对齐

Playbook中使用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

Playbook中使用变量

变量名:仅能由字母、数字和下划线组成,且只能以字母开头

在playbook的开头通过vars进行定义

[root@m01 project1]# cat  p2.yml 
- hosts: webservers
  vars:
    - web_package: httpd
    - ftp_package: vsftpd
  tasks:
    - name: Installed Packages
      yum: 
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present

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
      yum: 
        name: 
          - "{{ web_package }}"
          - "{{ ftp_package }}"
        state: present

在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
更好的方式是在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
      yum: 
        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   

通过命令行覆盖变量

令行直接指定变量所覆盖。使用--extra-vars或-e设定变量。
[root@m01 project1]# ansible-playbook p4.yml -e "web_package=zarafa-devel" -e "ftp_package=zarafa-utils"
变量优先级

命令行变量—>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 }}

Playbook(二)

template模板

模板是一个文本文件,可以做为生成文件的模版,并且模板文件中还可嵌套jinja语法。
jinja2语言
官方网站:
http://jinja.pocoo.org/
https://jinja.palletsprojects.com/en/2.11.x/

数据类型

jinja2 语言支持多种数据类型和操作:
字符串:使用单引号或双引号,
数字:整数,浮点数
列表:[item1, item2, …]
元组:(item1, item2, …)
字典:{key1:value1, key2:value2, …}
布尔型:true/false
算术运算:+, -, *, /, //, %, **
比较操作:==, !=, >, >=, <, <=
逻辑运算:and,or,not
流表达式:For,If,When

template
template功能:可以根据和参考模块文件,动态生成相类似的配置文件,template文件必须存放于templates目录下,且命名为 .j2 结尾,yaml/yml 文件需和templates目录平级,目录结构如下示例:

 ./
├── temnginx.yml
└── templates
    └── nginx.conf.j2

范例:利用template 同步nginx配置文件

#准备templates/nginx.conf.j2文件
[root@ansible ~]#vim temnginx.yml
---
- hosts: web
  remote_user: root
  tasks:
    - name: template config to remote hosts
     template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
 [root@ansible ~]#ansible-playbook temnginx.yml

template变更替换

#修改文件nginx.conf.j2 
[root@ansible ~]#mkdir templates
[root@ansible ~]#vim templates/nginx.conf.j2
......
worker_processes {{ ansible_processor_vcpus }};
......
[root@ansible ~]#vim temnginx2.yml
---
- hosts: web
  remote_user: root
  
  tasks:
    - name: install nginx
      yum: name=nginx
    - name: template config to remote hosts
      template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf 
    - name: start service
      service: name=nginx state=started enabled=yes
       
[root@ansible ~]#ansible-playbook temnginx2.yml

template算术运算

[root@ansible ansible]#vim templates/nginx.conf.j2
worker_processes {{ ansible_processor_vcpus**3 }};
[root@ansible ansible]#cat templnginx.yml
---
- hosts: websrvs
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx
    - name: template config to remote hosts
      template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
      notify: restart nginx
    - name: start service
      service: name=nginx state=started enabled=yes
 handlers:
    - name: restart nginx
      service: name=nginx state=restarted
[root@ansible ~]#-playbook templnginx.yml --limit 10.0.0.8

template中使用流程控制for和if
template中也可以使用流程控制 for 循环和 if 条件判断,实现动态生成文件功能

#temlnginx2.yml
---
- hosts: websrvs
 remote_user: root
 vars:
   nginx_vhosts:
     - 81
     - 82
     - 83
 tasks:
   - name: template config
     template: src=nginx.conf2.j2 dest=/data/nginx.conf
#templates/nginx.conf2.j2
{% for vhost in nginx_vhosts %}
server {
   listen {{ vhost }}
}
{% endfor %}
ansible-playbook -C templnginx2.yml --limit 192.168.15.8
#生成的结果:
server {
   listen 81   
}
server {
   listen 82   
}
server {
   listen 83   
}




#templnginx4.yml
- hosts: websrvs
 remote_user: root
 vars:
   nginx_vhosts:
     - listen: 8080
       server_name: "web1.oldboy.com"
       root: "/var/www/nginx/web1/"
     - listen: 8081
       server_name: "web2.oldboy.com"
       root: "/var/www/nginx/web2/"
     - {listen: 8082, server_name: "web3.oldboy.com", root: 
"/var/www/nginx/web3/"}
 tasks:
   - name: template config 
     template: src=nginx.conf4.j2 dest=/data/nginx4.conf
      
   
# templates/nginx.conf4.j2
{% for vhost in nginx_vhosts %}
server {
   listen {{ vhost.listen }}
   server_name {{ vhost.server_name }}
   root {{ vhost.root }}  
}{% endfor %} 

[root@ansible ~]#ansible-playbook templnginx4.yml --limit 10.0.0.8
#生成结果:
server {
   listen 8080
   server_name web1.oldboy.com
   root /var/www/nginx/web1/  
}
server {
   listen 8081
   server_name web2.oldboy.com
   root /var/www/nginx/web2/  
}
server {
   listen 8082
   server_name web3.oldboy.com
   root /var/www/nginx/web3/  
}

playbook使用when
when语句,可以实现条件测试。如果需要根据变量、facts或此前任务的执行结果来做为某task执行与否的前提时要用到条件测试,通过在task后添加when子句即可使用条件测试,jinja2的语法格式。

tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
   command: /sbin/shutdown -t now
   when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")

playbook使用迭代with_items(loop)
迭代:当有需要重复性执行的任务时,可以使用迭代机制对迭代项的引用,固定内置变量名为"item",要在task中使用with_items给定要迭代的元素列表

注意: ansible2.5版本后,可以用loop代替with_items

---
- hosts: websrvs
 remote_user: root
  
 tasks:
    - name: add several users
     user: name={{ item }} state=present groups=wheel
     with_items:
        - testuser1
        - testuser2
        - testuser3
        
#上面语句的功能等同于下面的语句
    - name: add several users
      user: name=testuser1 state=present groups=wheel
    - name: add several users
      user: name=testuser2 state=present groups=wheel
    - name: add several users
      user: name=testuser3 state=present groups=wheel



---
#remove mariadb server
- hosts: 172.16.1.7
  remote_user: root
  tasks:
    - name: stop service
      shell: /etc/init.d/mysqld stop
    - name: delete files and dir
      file: path={{item}} state=absent
      with_items:
        - /usr/local/mysql
        - /usr/local/mariadb-10.2.27-linux-x86_64
        - /etc/init.d/mysqld
        - /etc/profile.d/mysql.sh
        - /etc/my.cnf
        - /data/mysql
    - name: delete user
      user: name=mysql state=absent remove=yes

迭代嵌套子变量:在迭代中,还可以嵌套子变量,关联多个变量在一起使用


  • hosts: websrvs
    remote_user: root

    tasks:

    • name: add some groups
      group: name={{ item }} state=present
      with_items:
      • nginx
      • mysql
      • apache
    • name: add some users
      user: name={{ item.name }} group={{ item.group }} state=present
      with_items:
      • { name: ‘nginx’, group: ‘nginx’ }
      • { name: ‘mysql’, group: ‘mysql’ }
      • { name: ‘apache’, group: ‘apache’ }

管理节点过多导致的超时问题解决方法

默认情况下,Ansible将尝试并行管理playbook中所有的机器。对于滚动更新用例,可以使用serial关键字定义Ansible一次应管理多少主机,还可以将serial关键字指定为百分比,表示每次并行执行的主机数占总数的比例

#vim test_serial.yml
---
- hosts: all
  serial: 2  #每次只同时处理2个主机,将所有task执行完成后,再选下2个主机再执行所有task,直至所有主机
  gather_facts: False
  tasks:
    - name: task one
  comand: hostname
    - name: task two
      command: hostname


# 案例2:
- name: test serail
  hosts: all
  serial: "20%"   #每次只同时处理20%的主机

以上是关于ansible-playbook(剧本)的主要内容,如果未能解决你的问题,请参考以下文章

Ansible-playbook 剧本编写

Ansible-playbook 剧本编写

ansible-playbook剧本初体验

ansible-playbook剧本基础

编写二进制安装mariadb10.2的ansible-playbook剧本

你是自己的主角,写个ansible剧本,让架构搭建更容易