05_ansible playbook介绍、简介、语言的示例、主机与用户、Task、Action Shorthand、角色(Roles)和Include语句、Roles、角色默认变量

8.ansible playbook
8.1.Playbooks介绍
8.1.1.Playbooks简介
8.1.2.Playbook语言的示例
8.1.3.playbook基础
8.1.3.1.主机与用户
8.1.3.2.Task
8.1.4.Action Shorthand
8.1.5.Handlers: 在发生改变时执行的操作
8.1.6.执行一个playbook
8.1.7.Ansible-Pull (拉取配置而非推送配置)
8.1.8.提示与技巧
8.2.Playbook角色(Roles)和Include语句
8.2.1.简介
8.2.2.Task Include Files And Encouraging Reuse
8.2.3.Roles
8.2.4.角色默认变量(Role Default Variables)

8.ansible playbook

转自:https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_intro.html

8.1.Playbooks介绍

8.1.1.Playbooks简介

Playbooks 与 adhoc 相比,是一种完全不同的运用 ansible 的方式,是非常之强大的.

简单来说,playbooks 是一种简单的配置管理系统与多机器部署系统的基础.与现有的其他系统有不同之处,且非常适合于复杂应用的部署.

Playbooks 可用于声明配置,更强大的地方在于,在 playbooks 中可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤.并且可以同步或异步的发起任务.

我们使用 adhoc 时,主要是使用 /usr/bin/ansible 程序执行任务.而使用 playbooks 时,更多是将之放入源码控制之中,用之推送你的配置或是用于确认你的远程系统的配置是否符合配置规范.

在如右的连接中: ansible-examples repository(https://github.com/ansible/ansible-examples/tree/master/wordpress-nginx) ,有一些整套的playbooks,它们阐明了上述的这些技巧.我们建议你在另一个标签页中打开它看看,配合本章节一起看.

即便学完 playbooks 这个章节,仍有许多知识点只是入门的级别,完成本章的学习后,可回到文档索引继续学习.

8.1.2.Playbook语言的示例

Playbooks 的格式是YAML(详见:YAML 语法),语法做到最小化,意在避免 playbooks 成为一种编程语言或是脚本,但它也并不是一个配置模型或过程的模型.

playbook 由一个或多个 ‘plays’ 组成.它的内容是一个以 ‘plays’ 为元素的列表.

在 play 之中,一组机器被映射为定义好的角色.在 ansible 中,play 的内容,被称为 tasks,即任务.在基本层次的应用中,一个任务是一个对 ansible 模块的调用,这在前面章节学习过.

‘plays’ 好似音符,playbook 好似由 ‘plays’ 构成的曲谱,通过 playbook,可以编排步骤进行多机器的部署,比如在 webservers 组的所有机器上运行一定的步骤, 然后在 database server 组运行一些步骤,最后回到 webservers 组,再运行一些步骤,诸如此类.

“plays” 算是一个体育方面的类比,你可以通过多个 plays 告诉你的系统做不同的事情,不仅是定义一种特定的状态或模型.你可以在不同时间运行不同的 plays.

对初学者,这里有一个 playbook,其中仅包含一个 play:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: pkg=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service: name=httpd state=started
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

在下面,我们将分别讲解playbook语言的多个特征

8.1.3.playbook基础

8.1.3.1.主机与用户

你可以为playbook中的每一个play,分别地选择操作的目标机器是哪些,以哪个用户身份去完成要执行的步骤(called tasks)。

hosts行的内容是一个或多个组或主机的patterns,以逗号为分隔符,详见Pattern章节
remote_user就是账户名:

---
- hosts: webservers
  remote_user: root

注意:
参数remote_user以前写做user,在 Ansible 1.4 以后才改为 remote_user.主要为了不跟 user 模块混淆(user 模块用于在远程系统上创建用户).

再者,在每一个task中,可以定义自己的远程用户:

---
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connection
      ping:
      remote_user: yourname

Note:
task中的remote_user参数在1.4版本以后添加。

也支持从sudo执行命令:

- hosts: webservers
 remote_user: yourname
 sudo: yes

同样的,你可以仅在一个task中,使用sudo执行命令,而不是在整个play中使用sudo:

---
- hosts: webservers
  remote_user: yourname
  tasks:
    - service: name=nginx state=started
      sudo: yes

你也可以登录后,sudo到不同的用户身份,而不是使用root:

---
- hosts: webservers
 remote_user: yourname
 sudo: yes
 sudo_user: postgres

注意:
当使用 sudo_user 切换到 非root 用户时,模块的参数会暂时写入 /tmp 目录下的一个随机临时文件. 当命令执行结束后,临时文件立即删除.这种情况发生在普通用户的切换时,比如从 ‘bob’ 切换到 ‘timmy’, 切换到 root 账户时,不会发生,如从 ‘bob’ 切换到 ‘root’,直接以普通用户或root身份登录也不会发生. 如果你不希望这些数据在短暂的时间内可以被读取(不可写),请避免在 sudo_user 中传递未加密的密码. 其他情况下,’/tmp’ 目录不被使用,这种情况不会发生.Ansible 也有意识的在日志中不记录密码参数.

8.1.3.2.Task

每一个 play 包含了一个 task 列表(任务列表).一个 task 在其所对应的所有主机上(通过 host pattern 匹配的所有主机)执行完毕之后,下一个 task 才会执行.有一点需要明白的是(很重要),在一个 play 之中,所有 hosts 会获取相同的任务指令,这是 play 的一个目的所在,也就是将一组选出的 hosts 映射到 task.(注:此处翻译未必准确,暂时保留原文)

在运行 playbook 时(从上到下执行),如果一个 host 执行 task 失败,这个 host 将会从整个 playbook 的 rotation 中移除. 如果发生执行失败的情况,请修正 playbook 中的错误,然后重新执行即可.

每个 task 的目标在于执行一个 moudle, 通常是带有特定的参数来执行.在参数中可以使用变量(variables).

modules 具有”幂等”性,意思是如果你再一次地执行 moudle(译者注:比如遇到远端系统被意外改动,需要恢复原状),moudle 只会执行必要的改动,只会改变需要改变的地方.所以重复多次执行 playbook 也很安全.

对于 command module 和 shell module,重复执行 playbook,实际上是重复运行同样的命令.如果执行的命令类似于 ‘chmod’ 或者 ‘setsebool’ 这种命令,这没有任何问题.也可以使用一个叫做 ‘creates’ 的 flag 使得这两个 module 变得具有”幂等”特性 (不是必要的).

每一个 task 必须有一个名称 name,这样在运行 playbook 时,从其输出的任务执行信息中可以很好的辨别出是属于哪一个 task 的. 如果没有定义 name,‘action’ 的值将会用作输出信息中标记特定的 task.

如果要声明一个 task,以前有一种格式: “action: module options” (可能在一些老的 playbooks 中还能见到).现在推荐使用更常见的格式:”module: options” ,本文档使用的就是这种格式.

下面是一种基本的 task 的定义,service moudle 使用 key=value 格式的参数,这也是大多数 module 使用的参数格式:

tasks:
  - name: make sure apache is running
    service: name=httpd state=running

比较特别的两个 modudle 是 command 和 shell ,它们不使用 key=value 格式的参数,而是这样:

tasks:
  - name: disable selinux
    command: /sbin/setenforce 0

使用 command module 和 shell module 时,我们需要关心返回码信息,如果有一条命令,它的成功执行的返回码不是0, 你或许希望这样做:

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

或者是这样:

tasks:
   - name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True

如果action行看起来太长,你可以使用space(空格) 或者 indent (缩进)隔开连续的一行。

tasks:
   - name: Copy ansible inventory file to client
copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
     owner=root group=root mode=0644  

在 action 行中可以使用变量.假设在 ‘vars’ 那里定义了一个变量 ‘vhost’ ,可以这样使用它:

tasks:
   - name: create a virtual host file for {
    
    {
    
     vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{
    
    {
    
     vhost }}

这些变量在templates中也是可用的,稍后会讲到。

在一个基础的playbook中,所有的task都是在一个play中列出,稍后将介绍一种更合理的task的方式,使用’include’指令。

8.1.4.Action Shorthand

New in version 0.8

在0.8及以后的版本中,ansible 更喜欢使用如下的格式列出modules:

template: src=templates/foo.j2 dest=/etc/foo.conf

在早期的版本中,使用以下的格式:

action: template src=templates/foo.j2 dest=/etc/foo.conf

早期的格式在新版本中仍然可用,并且没有计划这种旧的格式弃用。

8.1.5.Handlers: 在发生改变时执行的操作

上面我们曾提到过,module 具有”幂等”性,所以当远端系统被人改动时,可以重放 playbooks 达到恢复的目的. playbooks 本身可以识别这种改动,并且有一个基本的 event system(事件系统),可以响应这种改动.

(当发生改动时)’notify’ actions 会在 playbook 的每一个 task 结束时被触发,而且即使有多个不同的 task 通知改动的发生, ‘notify’ actions 只会被触发一次.

举例来说,比如多个 resources 指出因为一个配置文件被改动,所以 apache 需要重新启动,但是重新启动的操作只会被执行一次.
这里有一个例子,当一个文件的内容被改动时,重启两个 services:

- name: template configuration file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
     - restart memcached
     - restart apache

‘notify’下列出的即是 handlers.

Handlers 也是一些 task 的列表,通过名字来引用,它们和一般的 task 并没有什么区别.Handlers是由通知者进行notify, 如果没有被notify,handlers不会执行.不管有多少个通知者进行了notify,等到 play 中的所有 task 执行完成之后,handlers 也只会被执行一次.

这里是一个 handlers 的示例:

handlers:
   - name: restart memcached
service: name=memcached state=restarted
   - name: restart apache
    service: name=apache state=restarted

Handlers 最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了.

注意:
handler会按照声明的顺序执行

Roles将在下一章节讲述,值得指出的是,handlers会在’pre_tasks’,’roles’, ‘tasks’和’post_tasks’之间自动执行。如果你想立即执行所有的handler命令,在1.2及以后的版本,你可以这样做:

tasks:
- shell: some tasks go here
- meta: flush_handlers
- shell: some other tasks

在以上的例子中,任何在排队等候的handlers会在执行到’meta’部分时,优先执行。这个技巧在有些时候也能派上用场。

8.1.6.执行一个playbook

既然现在你已经学习了 playbook 的语法,那要如何运行一个 playbook 呢?这很简单,这里的示例是并行的运行 playbook,并行的级别 是10:

ansible-playbook playbook.yml -f 10

8.1.7.Ansible-Pull (拉取配置而非推送配置)

我们可不可以将 ansible 的体系架构颠倒过来,让托管节点从一个 central location 做 check in 获取配置信息,而不是 推送配置信息到所有的托管节点?是可以的.

Ansible-pull 是一个小脚本,它从 git 上 checkout 一个关于配置指令的 repo,然后以这个配置指令来运行 ansible-playbook.

假设你对你的 checkout location 做负载均衡,ansible-pull 基本上可以无限的提升规模.

可执行ansible-pull --help获取详细的帮助信息。

也有一个叫做clever playbook的东西:clever playbook(https://github.com/ansible/ansible-examples/blob/master/language_features/ansible_pull.yml)。这个可以通过crontab来配置ansible-pull (from push mode)。

8.1.8.提示与技巧

在 playbook 执行输出信息的底部,可以找到关于托管节点的信息.也可看到一般的失败信息,和严重的 “unreachable” 信息. 这两个是分开计数的.
如果你想看到执行成功的 modules 的输出信息,使用 --verbose flag(否则只有执行失败的才会有输出信息).这在 0.5 及以后的版本中可用.
如果安装了 cowsay 软件包,ansible playbook 的输出已经进行了广泛的升级.可以尝试一下!
在执行一个 playbook 之前,想看看这个 playbook 的执行会影响到哪些 hosts,你可以这样做:

ansible-playbook playbook.yml --list-hosts

8.2.Playbook角色(Roles)和Include语句

8.2.1.简介

当我们刚开始学习运用 playbook 时,可能会把 playbook 写成一个很大的文件,到后来可能你会希望这些文件是可以方便去重用的,所以需要重新去组织这些文件。

基本上,使用 include 语句引用 task 文件的方法,可允许你将一个配置策略分解到更小的文件中。使用 include 语句引用 tasks 是将 tasks 从其他文件拉取过来。因为handlers也是tasks,所以你也可以使用 include 语句去引用handlers文件。handlers文件来自‘handlers:’ section。

如果你想复习一下这些概念的话,请参见Playbooks 。

Playbook 同样可以使用 include 引用其他 playbook 文件中的 play。这时被引用的 play 会被插入到当前的 playbook 中,当前的 playbook 中就有了一个更长的play列表。

当你开始思考这些概念:tasks, handlers, variables 等等,是否可以将它们抽象为一个更大的概念呢。我们考虑的不再是”将这些 tasks,handlers,variables 等等应用到这些 hosts 中”,而是有了更抽象的概念,比如:”这些 hosts 是 dbservers” 或者 “那些 hosts 是 webservers”(译者注:dbserver,webservers 即是”角色”)。这种思考方式在编程中被称为”封装”,将其中具体的功能封装了起来。举个例子,你会开车但并不需要知道引擎的工作原理(译者注:同样的道理,我们只需要知道”这些 hosts 是 dbservers”,而不需要知道其中有哪些 task,handlers 等)。

Roles 的概念来自于这样的想法:通过 include 包含文件并将它们组合在一起,组织成一个简洁、可重用的抽象对象。这种方式可使你将注意力更多地放在大局上,只有在需要时才去深入了解细节。

我们将从理解如何使用 include 开始,这样你会更容易理解 roles 的概念。但我们的终极目标是让你理解 roles,roles 是一个很棒的东西,每次你写 playbook的时候都应该使用它。

在ansible-examples (https://github.com/ansible/ansible-examples)中有很多实例,如果你希望深入学习可以在单独的页面打开它。

8.2.2.Task Include Files And Encouraging Reuse

假如你希望在多个play或者多个playbook中重用同一个 task 列表,你可以使用 include files 做到这一点。 当我们希望为系统定义一个角色时,使用include去包含task列表是一种很好用的方法。需要记住的是,一个 play 所要达成 的目标是将一组系统映射为多个角色。下面我们来看看具体是如何做的:

一个task include file由一个普通的task列表组成,像这样:

---
# possibly saved as tasks/foo.yml

- name: placeholder foo
  command: /bin/foo

- name: placeholder bar
  command: /bin/bar

Include指令看起来像下面这样,在一个 playbook 中,Include 指令可以跟普通的 task 混合在一起使用:

tasks:
   - include: tasks/foo.yml

你也可以给include传递变量。我们称之为”参数化的include”。

举个例子,如果我们要部署多个wordpress 实例,我们可将所有的 wordpress task 写在一个 wordpress.yml 文件中, 然后像下面这样使用 wordpress.yml 文件:

tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob

如果你运行的是Ansible 1.4及以后的版本,include语法可更为精简,这样写法同样允许传递列表和字典参数:

tasks:
 - {
    
     include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }

使用上述任意一种语法格式传递变量给 include files 之后,这些变量就可以在 include 包含的文件中使用了。 关于变量的详细使用方法请查看 Variables(https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_variables.html)。变量可以这样去引用:

{
    
    {
    
     wp_user }}

(除了显示传递的参数,所有在vars section中定义的变量也可以在这里使用)

从1.0版开始,Ansible支持另一种传递变量到 include files 的语法,这种语法支持结构化的变量:

tasks:
  - include: wordpress.yml
    vars:
        wp_user: timmy
        some_list_variable:
          - alpha
          - beta
          - gamma

在 Playbooks 中可使用 include 包含其他 playbook,我们将在稍后的章节介绍这个用法。

注意:
从1.0版开始,task include 语句可以在任意层次使用。在这之前,include 语句 只能在单个层次使用,所以在之前版本中由 include 所包含的文件,其中不能再有include包含出现。

Include 语句也可以用在 ‘handlers’ section 中,比如,你希望定义一个重启 apache 的 handler, 你只需要定义一次,然后便可在所有的 playbook 中使用这个 handler。你可以创建一个 handlers.yml 文件如下:

---
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: name=apache state=restarted

然后在你的主 playbook 文件中,在一个 play 的最后使用 include 包含 handlers.yml:

handlers:
  - include: handlers/handlers.yml

Include 语句可以和其他非 include 的 tasks 和 handlers 混合使用。

Include语句也可用来将一个 playbook 文件导入另一个playbook文件。这种方式允许你定义一个顶层的playbook,这个顶层 playbook由其他playbook所组成。

举个例子:

- name: this is a play at the top level of a file
  hosts: all
  remote_user: root

  tasks:

  - name: say hi
    tags: foo
    shell: echo "hi..."

- include: load_balancers.yml
- include: webservers.yml
- include: dbservers.yml

注意:当你在 playbook 中引用其他 playbook 时,不能使用变量替换。

8.2.3.Roles

你现在已经学过 tasks 和 handlers,那怎样组织 playbook 才是最好的方式呢?简单的回答就是:使用 roles ! Roles 基于一个已知的文件结构,去自动的加载某些 vars_files,tasks 以及 handlers。基于 roles 对内容进行分组,使得我们可以容易地与其他用户分享 roles。

一个项目的结构如下:

site.yml
webservers.yml
fooservers.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
   webservers/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/

一个playbook如下:

---
- hosts: webservers
  roles:
     - common
     - webservers

这个playbook为一个角色’x’指定了如下的行为:

  • 如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play 中
  • 如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play 中
  • 如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play 中
  • 如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中 (1.3 and later)
  • 所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径。
  • 所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径。
  • 所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径。
  • 所有include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径。

在Ansible 1.4 及之后版本,你可以为”角色”的搜索设定 roles_path 配置项。使用这个配置项将所有的 common 角色 check out到一个位置,以便在多个 playbook 项目中可方便的共享使用它们。查看 Ansible的配置文件 详细了解设置这个配置项的细节,该配置项是在 ansible.cfg 中配置。

注意:
稍后将讨论”角色依赖”的概念

如果 roles 目录下有文件不存在,这些文件将被忽略。比如 roles 目录下面缺少了 ‘vars/’ 目录,这也没关系。

注意:你仍然可以在 playbook 中松散地列出 tasks,vars_files 以及 handlers,这种方式仍然可用,但 roles 是一种很好的具有组织性的功能特性,我们强烈建议使用它。如果你在 playbook 中同时使用 roles 和 tasks,vars_files 或者 handlers,roles 将优先执行。

而且,如果你愿意,也可以使用参数化的 roles,这种方式通过添加变量来实现,比如:

---

- hosts: webservers
  roles:
    - common
    - {
    
     role: foo_app_instance, dir: '/opt/a',  port: 5000 }
    - {
    
     role: foo_app_instance, dir: '/opt/b',  port: 5001 }

当一些事情不需要频繁去做时,你也可以为roles设置触发条件,像这样:

---

- hosts: webservers
  roles:
    - {
    
     role: foo, tags: ["bar", "baz"] }

如果 play 仍然包含有 ‘tasks’ section,这些 tasks 将在所有 roles 应用完成之后才被执行。

如果你希望定义一些 tasks,让它们在 roles 之前以及之后执行,你可以这样做:

---

- hosts: webservers

  pre_tasks:
    - shell: echo 'hello'

  roles:
    - {
    
     role: some_role }

  tasks:
    - shell: echo 'still busy'

  post_tasks:
    - shell: echo 'goodbye'

注意:
如果对 tasks 应用了 tags(tags 是一种实现部分运行 playbook 的机制,将在后面的章节讨论),需确保给 pre_tasks 以及 post_tasks 也同样应用 tags,并且将它们一并传递。特别是当 pre_tasks 和 post_tasks 被用来监视 “停止窗口控制” 或者 “负载均衡” 时要确保这样做。

8.2.4.角色默认变量(Role Default Variables)

New in version 1.3.

“角色依赖” 使你可以自动地将其他 roles 拉取到现在使用的 role 中。”角色依赖” 保存在 roles 目录下的 meta/main.yml 文件中。这个文件应包含一列 roles和 为之指定的参数,下面是在 roles/myapp/meta/main.yml 文件中的示例:

---
dependencies:
  - {
    
     role: common, some_parameter: 3 }
  - {
    
     role: apache, port: 80 }
  - {
    
     role: postgres, dbname: blarg, other_parameter: 12 }

“角色依赖”可以通过绝对路径指定,如同顶级角色的设置:

---
dependencies:
   - {
    
     role: '/path/to/common/roles/foo', x: 1 }

“角色依赖” 也可以通过源码控制仓库或者 tar 文件指定,使用逗号分隔:路径、一个可选的版本(tag, commit, branch 等等)、一个可选友好角色名(尝试从源码仓库名或者归档文件名中派生出角色名):

---
dependencies:
- {
    
     role: wheel, n: 1 }
- {
    
     role: wheel, n: 2 }
- {
    
     role: wheel, n: 3 }
- {
    
     role: wheel, n: 4 }

wheel 角色的 meta/main.yml 文件包含如下内容:

---
allow_duplicates: yes
dependencies:
- {
    
     role: tire }
- {
    
     role: brake }

最终的执行顺序是这样的:

tire(n=1)
brake(n=1)
wheel(n=1)
tire(n=2)
brake(n=2)
wheel(n=2)
...
car

注意:
关于变量继承和范围的详细讨论,请查看 Variables。

猜你喜欢

转载自blog.csdn.net/toto1297488504/article/details/132228406