Ansible Playbook

简介

playbook是ansible用于配置、部署和管控受控节点的剧本

通过playbook的详细描述,执行其中的一系列tasks,可以让远程主机达到预期的状态。

playbook就像Ansible控制器给受控节点列出的一系列to-do-list,而受控节点必须要完成。

使用场景

执行一些简单的任务,使用ad-hoc命令可以方便的解决问题,但是有时一个设施过于复杂,需要大量的操作时候,执行ad-hoc命令是不适合的,最好使用playbook。

就像执行shell命令和写shell脚本一样,也可以理解为批处理任务,不过playbook有自己的语法格式。

使用playbook你可以方便的重用这些代码,可以移植到不同的机器上面,就像函数一样,最大化的利用代码。在使用Ansible的过程中,也会发现,你所处理的大部分操作都是编写playbook,可以把常用的应用都编写成playbook,之后管理服务器会变得十分简单。

格式

格式简介

playbook由yaml语言编写,yaml参考了其他多种语言,包括:xml、C、python、perl以及电子邮件格式RFC2822,Clark Evants在2001年5月首次发表了这种语言。

YAML格式是类似于JSON的文件格式,便于人理解和阅读,同时便于书写。首先学习一下YAML的格式,对我们以后书写playbook很有帮助。以下是playbook常用的YAML格式:

  1. 文件的第一行应该以“---”开始,表明YAML文件的开始
  2. 在同一行中,#之后的内容表示注释,类似于shell、python和ruby
  3. YAML中的列表元素以“-”开头,然后紧跟一个空格,后面为元素内容
  4. 同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。
  5. play中hostsvariablesrolestasks等对象的表示方法都是键值中间以“:”分隔表示,后面还要加上一个空格

示例:

---
#安装与运行mysql服务
- host: node1
  remote_user: root
  tasks:
    - name: install mysql-server package
      yum: name=mysql-server state=present
    - name: starting mysqld service
      service: name=mysql state=started

我们的文件名称以.yml结尾,像上述例子就是mysql.yml。其中,有三个部分组成:

  • hosts部分:使用hosts指示使用哪个主机或主机组来运行下面的tasks,每个playbook都必须指定hosts,hosts也可以使用通配符。主机或主机组在inventory清单中指定,可以使用系统默认的/etc/ansible/hosts,也可以自己编辑,在运行的时候加上-i选项,指定清单的位置即可。在运行清单的时候,-list-hosts选项会显示哪些主机将会参与执行task的过程中。
  • remote_user:指定远程主机中的哪个用户来登陆远端系统,在远端系统执行task的用户,可以任意指定,也可以使用sudo,但是用户必须要有相应的task的权限。
  • tasks:指定远程主机将要执行的一系列动作。task的核心为Ansible的模块。前面已经提到模块的用法。tasks包含name要执行的模块,name是可选的,只是为了方便用户阅读,不过还是建议加上去,模块名是必须的,同时也要给予模块响应的参数

使用ansible-playbook运行playbook文件,得到如下输出信息,输出内容为JSON格式。并且由不同颜色组成,便于识别。一般而言

  • 绿色代表执行成功,系统保持原样
  • 黄色表示系统状态发生改变
  • 红色代表执行失败,显示错误输出

核心元素

  • hosts:主机组
  • tasks:任务列表
  • variables:变量,设置方式有四种
  • templates:包含了模块语法的文本文件
  • handlers:由特定条件触发的任务

基本组件

  • hosts:运行指定任务的目标主机
  • remote_user:在远程主机上执行任务的用户
  • sudo_user:
  • tasks:任务列表
  • 模块,模块参数(注意:shell和command模块后面直接跟命令,而不是key=value类的参数列表
  • handlers:任务,在特定条件下触发;接收到其他任务的通知时触发
  • 某任务的状态在运行后为changed时,可通过notify通知给相应的handlers
  • 任务可以通过tags打标签,而后可在ansible-playbook命令上使用-t指定进行调用

实验一

第一步:定义playbook

[root@k8s-master home]# vim nginx.yml
---
- hosts: web
  remote_user: root
  tasks:
    - name: install nginx
      yum: name=nginx state=present
    - name: copy nginx.conf
      copy: src=/tmp/nginx.conf dest=/etc/nginx/nginx.conf backup=yes
      notify: reload #当nginx.conf发生改变时,通知给相应的handlers
      tags: reloadnginx
    - name: start nginx service
      service: name=nginx state=started
      tags: startnginx

  handlers:
    - name: reload
      service: name=nginx state=restarted

第二步:测试运行结果

#测试运行结果
[root@k8s-master home]# ansible-playbook nginx.yml

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [install nginx] ******************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [copy nginx.conf] ****************************************************************************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

TASK [start nginx service] ************************************************************************************************************
changed: [192.168.133.12]
changed: [192.168.133.11]

RUNNING HANDLER [reload] **************************************************************************************************************
changed: [192.168.133.12]
changed: [192.168.133.11]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

第三步:验证部署是否成功

#验证nginx是否部署成功
[root@k8s-master home]# ansible web -m shell -a "ss -tunlp | grep nginx"
192.168.133.12 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:80                    *:*                   users:(("nginx",pid=80299,fd=6),("nginx",pid=80298,fd=6))
192.168.133.11 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:80                    *:*                   users:(("nginx",pid=4329,fd=6),("nginx",pid=4328,fd=6))

实验二:测试打标签

#停止nginx服务
[root@k8s-master home]# ansible web -m shell -a "systemctl stop nginx"
192.168.133.12 | CHANGED | rc=0 >>

192.168.133.11 | CHANGED | rc=0 >>

#打标签
[root@k8s-master home]# ansible-playbook nginx.yml -t startnginx

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [start nginx service] ************************************************************************************************************
changed: [192.168.133.12]
changed: [192.168.133.11]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

实验三:测试notify

其触发条件是配置文件修改,修改一下配置文件中的端口

#修改端口
[root@k8s-master home]# vim /tmp/nginx.conf 
	listen       8080;
#重新加载nginx
[root@k8s-master home]# ansible-playbook nginx.yml

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [install nginx] ******************************************************************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [copy nginx.conf] ****************************************************************************************************************
changed: [192.168.133.12]
changed: [192.168.133.11]

TASK [start nginx service] ************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

RUNNING HANDLER [reload] **************************************************************************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
#发现我们执行的就是reload段以及我们定义的notify部分。

#测试端口号
[root@k8s-master home]# ansible web -m shell -a "ss -tunlp | grep nginx"
192.168.133.12 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:8080                  *:*                   users:(("nginx",pid=91694,fd=6),("nginx",pid=91693,fd=6))
192.168.133.11 | CHANGED | rc=0 >>
tcp    LISTEN     0      128       *:8080                  *:*                   users:(("nginx",pid=33017,fd=6),("nginx",pid=33016,fd=6))
#发现端口号变成了8080

variables部分

前面,我们提到了variables是变量,有四种定义方法,具体如下:

  • facts:可直接调用。之前提到setup这个模块,该模块就是通过调用facts组件来实现的,这里的variables也可以直接调用facts组件。
  • 用户自定义变量:可直接使用用户自定义变量,有以下两种方式:
  • 通过命令行传入:ansible-playbook命令的命令行中的-e VARS,--extra-vars=VARS,这样可直接把自定义的变量传入
  • 在playbook中定义变量:
vars:
  - var1: value1
  - var2: value2

示例:

#1、定义剧本
[root@k8s-master home]# vim nginx.yml
---
- hosts: web
  remote_user: root
  tasks:
    - name: install {{ rpmname }}
      yum: name={{ rpmname }} state=present
    - name: copy {{ rpmname }}.conf
      copy: src=/tmp/{{ rpmname }}.conf dest=/etc/{{ rpmname }}/{{ rpmname }}.conf backup=yes
      notify: reload #当{{ rpmname }}.conf发生改变时,通知给相应的handlers
      tags: reload{{ rpmname }}
    - name: start {{ rpmname }} service
      service: name={{ rpmname }} state=started
      tags: start{{ rpmname }}

  handlers:
    - name: reload
      service: name={{ rpmname }} state=restarted
      
#2、拷贝配置文件(如果想在被控主机上安装什么服务的话,就直接将主机server端上的对应服务配置拷贝至/tmp目录下),以`keepalive`服务为例
[root@k8s-master keepalived]# cp /etc/keepalived/keepalived.conf /tmp/

#3、运行playbook,变量由命令行传入
[root@k8s-master home]# ansible-playbook nginx.yml -e rpmname=keepalived

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [install keepalived] *************************************************************************************************************
changed: [192.168.133.12]
changed: [192.168.133.11]

TASK [copy keepalived.conf] ***********************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [start keepalived service] *******************************************************************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

#4、修改剧本,直接定义变量
[root@k8s-master home]# vim nginx.yml
---
- hosts: web
  remote_user: root
  vars:
    - rpmname: keepalived
  tasks:
    - name: install {{ rpmname }}
      yum: name={{ rpmname }} state=present
    - name: copy {{ rpmname }}.conf
      copy: src=/tmp/{{ rpmname }}.conf dest=/etc/{{ rpmname }}/{{ rpmname }}.conf backup=yes
      notify: reload #当{{ rpmname }}.conf发生改变时,通知给相应的handlers
      tags: reload{{ rpmname }}
    - name: start {{ rpmname }} service
      service: name={{ rpmname }} state=started
      tags: start{{ rpmname }}

  handlers:
    - name: reload
      service: name={{ rpmname }} state=restarted

ansible playbook执行shell脚本无窗口模式 ansible playbook shell_nginx

#再次运行nginx.yml脚本
[root@k8s-master home]# ansible-playbook nginx.yml

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [install keepalived] *************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [copy keepalived.conf] ***********************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

TASK [start keepalived service] *******************************************************************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
#也是可以正常运行的
  • 通过roles传递变量:具体操作我们在后文的roles部分在阐述
  • Host Inventory:可以直接在主机清单中定义。定义方法如下:
  • 向不同的主机传递不同的变量
IP/HOSTNAME variable=value var2=value2
  • 向组中的主机传递相同的变量
[groupname:vars]
variable=value

模板templates

模板是一个文本文件,嵌套有脚本(使用模板编程语言编写)

Jinja2:Jinja2是python的一种模板语言,以Django的模板语言为原本。

模板支持:

  • 字符串:使用单引号或双引号
  • 数字:整数、浮点数
  • 列表:[item1,item2...]
  • 元祖:(item1,item2...)
  • 字典:
  • 布尔型:true/false
  • 算术运算:+,-,*,/,//,%,**
  • 比较操作:==,!=,>,>=,<,<=
  • 逻辑运算:and,or,not

示例

1、定义模板

我们直接将之前定义的/tmp/nginx.conf改个名,然后编辑一下,就可以定义成我们的模板文件了:

[root@k8s-master ~]# cd /tmp
[root@k8s-master tmp]# ls
facts  keepalived.conf  nginx.conf
[root@k8s-master tmp]# mv nginx.conf nginx.conf.j2
[root@k8s-master tmp]# vim nginx.conf.j2
[root@k8s-master tmp]# 
	worker_processes  {{ ansible_processor_vcpus }};
	listen       {{ nginxport }};
2、修改剧本

修改剧本来定义变量

[root@k8s-master home]# vim nginx.yml

ansible playbook执行shell脚本无窗口模式 ansible playbook shell_YAML_02

3、运行剧本
[root@k8s-master home]# ansible-playbook nginx.yml -t reloadnginx

PLAY [web] ****************************************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [copy nginx.conf] ****************************************************************************************************************
ok: [192.168.133.12]
ok: [192.168.133.11]

PLAY RECAP ****************************************************************************************************************************
192.168.133.11             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.133.12             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

角色定制:roles

简介

对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。

roles用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handler等。要使用roles只需要在playbook中使用include指令即可。

简单来讲,roles就是通过分别将变量(vars)、文件(file)、任务(tasks)、模块(modules)以及处理器(handlers)放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以用于构建守护进程等场景中。

角色集合

角色集合:roles/

mysql/

httpd/

nginx/

files/:存储由copy或script等模块调用的文件

tasks/:此目录中至少应该有一个名为main.yml的文件,用于定义各task;其它的文件需要由main.yml进行“包含”调用

handlers/:此目录中至少应该有一个名为main.yml的文件,用于定义各handler;其它的文件需要由main.yml进行“包含”调用

vars/:此目录中至少应该有一个名为mian.yml的文件,用于定义各variable;其它的文件需要由main.yml进行“包含”调用

templates/:存储由template模块调用的模板文件

meta/:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要由main.yml进行“包含”调用

default/:此目录中至少应该有一个名为main.yml的文件,用于设定默认变量

角色定制实例

1、在roles目录下生成对应的目录结构
[root@k8s-master ansible]# pwd
/etc/ansible
[root@k8s-master ansible]# ll
总用量 28
-rw-r--r-- 1 root root 19985 1月  16 05:55 ansible.cfg
-rw-r--r-- 1 root root  1051 5月  27 14:42 hosts
-rw-r--r-- 1 root root   629 5月  30 08:53 nginx.yml
drwxr-xr-x 5 root root    45 5月  30 10:12 roles
[root@k8s-master roles]# mkdir -pv ./{nginx,mysql,httpd}/{files,templates,vars,tasks,handlers,meta,default}
mkdir: 已创建目录 "./nginx"
mkdir: 已创建目录 "./nginx/files"
mkdir: 已创建目录 "./nginx/templates"
mkdir: 已创建目录 "./nginx/vars"
mkdir: 已创建目录 "./nginx/tasks"
mkdir: 已创建目录 "./nginx/handlers"
mkdir: 已创建目录 "./nginx/meta"
mkdir: 已创建目录 "./nginx/default"
mkdir: 已创建目录 "./mysql"
mkdir: 已创建目录 "./mysql/files"
mkdir: 已创建目录 "./mysql/templates"
mkdir: 已创建目录 "./mysql/vars"
mkdir: 已创建目录 "./mysql/tasks"
mkdir: 已创建目录 "./mysql/handlers"
mkdir: 已创建目录 "./mysql/meta"
mkdir: 已创建目录 "./mysql/default"
mkdir: 已创建目录 "./httpd"
mkdir: 已创建目录 "./httpd/files"
mkdir: 已创建目录 "./httpd/templates"
mkdir: 已创建目录 "./httpd/vars"
mkdir: 已创建目录 "./httpd/tasks"
mkdir: 已创建目录 "./httpd/handlers"
mkdir: 已创建目录 "./httpd/meta"
mkdir: 已创建目录 "./httpd/default"
[root@k8s-master ansible]# tree
.
├── ansible.cfg
├── hosts
├── nginx.yml
└── roles
    ├── httpd
    │   ├── default
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks
    │   ├── templates
    │   └── vars
    ├── mysql
    │   ├── default
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks
    │   ├── templates
    │   └── vars
    └── nginx
        ├── default
        ├── files
        ├── handlers
        ├── meta
        ├── tasks
        ├── templates
        └── vars

25 directories, 3 files
2、定义配置文件

需要修改的配置文件为/tasks/main.yml

[root@k8s-master roles]# vim nginx/tasks/main.yml
- name: cp
  copy: src=nginx-1.10.2-1.el7.ngx.x86_64.rpm dest=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm
- name: install
  yum: name=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm state=latest
- name: conf
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
  tags: nginxconf
  notify: new conf to reload
- name: start service
  service: name=nginx state=started enabled=true
3、放置我们所需要的文件到指定目录

因为我们定义的角色已经有了新的组成方式,所以我们需要把文件都放到指定的位置,这样,才能让配置文件找到这些并进行加载。

rpm包放在files目录下,模板放在templates目录下:

[root@k8s-master nginx]# cp /tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm ./files/
[root@k8s-master nginx]# cp /tmp/nginx.conf.j2 ./templates/
[root@k8s-master nginx]# tree
.
├── default
├── files
│   └── nginx-1.10.2-1.el7.ngx.x86_64.rpm
├── handlers
├── meta
├── tasks
│   └── main.yml
├── templates
│   └── nginx.conf.j2
└── vars

7 directories, 3 files
4、修改变量文件

我们在模板中定义的变量,也要去配置文件中加上:

[root@k8s-master nginx]# vim vars/main.yml
nginxprot: 9999
5、定义handlers文件

我们在配置文件中定义了nofity,所以我们也需要定义handlers,我们来修改配置文件:

[root@k8s-master nginx]# vim handlers/main.yml
- name: new conf to reload
  service: name=nginx state=restarted
6、定义剧本文件

接下来,我们就来定义剧本文件,由于大部分设置我们都单独配置在了roles里面,所以,接下来剧本就只需要写一点点内容即可:

[root@k8s-master ansible]# vim roles.yml 
- hosts: web
  remote_user: root
  roles:
    - nginx
7、启动服务

剧本定义完成以后,就可以来启动服务了

[root@k8s-master ansible]# ansible-playbook roles.yml
PLAY [web] *********************************************************************

TASK [setup] *******************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [nginx : cp] **************************************************************
ok: [192.168.133.11]
ok: [192.168.133.12]

TASK [nginx : install] *********************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

TASK [nginx : conf] ************************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

TASK [nginx : start service] ***************************************************
changed: [192.168.133.11]
changed: [192.168.133.12]

RUNNING HANDLER [nginx : new conf to reload] ***********************************
changed: [192.168.133.11]
changed: [192.168.133.12]

PLAY RECAP *********************************************************************
192.168.133.11             : ok=6    changed=4    unreachable=0    failed=0   
192.168.133.12             : ok=6    changed=4    unreachable=0    failed=0

启动后测试

[root@k8s-master ansible]# ansible web -m shell -a "ss -ntulp |grep 9999"
192.168.133.11 | SUCCESS | rc=0 >>
tcp    LISTEN     0      128       *:9999                  *:*                   users:(("nginx",pid=7831,fd=6),("nginx",pid=7830,fd=6),("nginx",pid=7829,fd=6))

192.168.133.12  | SUCCESS | rc=0 >>
tcp    LISTEN     0      128       *:9999                  *:*                   users:(("nginx",pid=9654,fd=6),("nginx",pid=9653,fd=6),("nginx",pid=9652,fd=6))