- 前言
- 读者对象
- 如何阅读本书
- 勘误和支持
- 致谢
- 第 1 篇 高性能网站构建
- 第 1 章 深入理解 DNS 原理与部署 BIND
- 第 2 章 全面解析 CDN 技术与实战
- 第 3 章 负载均衡和高可用技术
- 第 4 章 配置及调优 LVS
- 第 5 章 使用 HAProxy 实现 4 层和 7 层代理
- 第 6 章 实践 Nginx 的反向代理和负载均衡
- 第 7 章 部署商业负载均衡设备 NetScaler
- 第 8 章 配置高性能网站
- 第 9 章 优化 MySQL 数据库
- 第 2 篇 服务器安全和监控
- 第 10 章 构建企业级虚拟专用网络
- 第 11 章 实施 Linux 系统安全策略与入侵检测
- 第 12 章 实践 Zabbix 自定义模板技术
- 第 13 章 服务器硬件监控
- 第 3 篇 网络分析技术
- 第 14 章 使用 tcpdump 与 Wireshark 解决疑难问题
- 第 15 章 分析与解决运营商劫持问题
- 第 16 章 深度实践 iptables
- 第 4 篇 运维自动化和游戏运维
- 第 17 章 使用 Kickstart 完成批量系统安装
- 第 18 章 利用 Perl 编程实施高效运维
- 第 19 章 精通 Ansible 实现运维自动化
- 第 20 章 掌握端游运维的技术要点
- 第 21 章 精通手游运维的架构体系
最佳实践 93:学习 Ansible Playbook 使用要点
使用 Ansible 来执行操作有两种方式,第一,直接使用 ansible 来执行 Ad-hoc(拉丁文中的含义是“临时的”)命令,通常对于一些需要立即执行,但又不具有通用性的任务使用 ansible 命令直接执行是非常方便的。第二,通过 ansible-playbook 命令来执行定义好的 Playbook,这里的 Playbook 笔者理解为“编排”,也就是将完成一个的任务的多个操作步骤,集中地写在这个称谓 Playbook 的文件里面,类似于编一部剧,用一个 Playbook 把所有的剧情串在一起。
在实际环境中 Ansible Playbook 是应用最多的,下面就来更深入地了解一下 Playbook。
Playbook 基本语法和格式
Playbook 可以说是 Ansible 一个非常大的亮点,它提供了非常强大的功能,远不止把多个 ansible Ad-hoc 命令写放到一个文件中来执行那么简单。在 Playbook 中不仅可以定义任务的执行顺序,还能定义不同的变量,定义任务之间的关联关系,定义基于环境和角色的差异化配置推送等,具有很多非常灵活的特性。
可以说 Playbook 的可定制性是非常强大的,尽管如此,Playbook 在设计之初也考虑了易用性,所以对于新手来说,初次接触 Playbook 都会感觉它的语法比较简单,很容易读懂,这就在无形中降低了学习成本,相比 Puppet,这点上 Ansible 是非常占优势的。
Playbook 的编写使用 YAML 语言格式,它的标准格式是以“---”开始“…”结束,例如:
--- //YAML 语言开始于“---” # An employee record //使用“#”来添加注释 name: Martin D'vloper //对于字典或者 hash 类型的 key/values 对,采用“:”分割 job: Developer skill: Elite employed: True foods: - Apple //序列里的项用“-”分割 - Orange languages: perl: Elite //对于字典类型的键值采用“:”分割 education: | <--!对于 key:value 这种格式如果 value 需要多行可以使用“|”或者“>” !--> 4 GCSEs 3 A-Levels BSc in the Internet of Things... ... //YAML 语法最后结束于“...”在一个 Ansible playbook 可以定义多个“---”“...”来
介绍了 YAML 的语法之后,下面就开始介绍 Playbook 具体如何编写。
Ansible Playbook 文件结构上可以分为三部分。
第一部分为 Basic(基础),作为一个 Playbook 的开始,必须包含一些关键字段。
--- - hosts: webservers //hosts 必须指定,用于定义 playbook 的操作对象 vars: //vars 定义变量的值,变量可以在多个地方定义,vars 是可选字 ftp_port:8081 remote_user: root
在 Ansible 1.4 中加入了 remote_user 这个参数,用于指定执行这个 Playbook 任务所使用的用户,在默认的 Ansible 配置文件中定义的执行用户是 root。remote_user 参数可以定义在 Playbook 的全局,也可以定义在具体的任务中,例如:
--- - hosts: webservers tasks: - name: test connection ping: remote_user: yourname
为了配合 remote_user 参数,在完成用户切换之后,如果还需要再次切换到其他用户,或者切换之后再 sudo 到 root 用户,Ansible 还提供了 become:yes 运行再次切换用户,become_method:sudo,通过 sudo 来切换,become_user:otheruser,切换到 otheruser。
第二部分是 Ansible Playbook 的核心内容,Task(任务),定义具体做哪些事,以及如何做。
tasks: - name: make sure apache is running service: name=httpd state=running - name: disable selinux command: /sbin/setenforce 0 - name: create a virtual host file for {{ vhost }} template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} notify: - restart apache
在 tasks 关键字中,定义多个具体的任务,每个任务用–name 来定义任务的名字,通常这个名字用来说明具体的操作。执行任务,则有具体的模块来实现,以上使用了 service,command,template 模块。使用 ansible-doc–l 可以列出当前可以使用的所有模块,目前 Ansible 支持的模块已经有 400 多个了。
[root@localhost ~]# ansible-doc -l less 436 Copyright (C) 1984-2009 Mark Nudelman less comes with NO WARRANTY, to the extent permitted by law. For information about the terms of redistribution, see the file named README in the less distribution. Homepage: http://www.greenwoodsoftware.com/less a10_server Manage A10 Networks AX/SoftAX/Thunder/vThunder devices a10_service_group Manage A10 Networks AX/SoftAX/Thunder/vThunder devices ...
如果需要知道某个模块的具体用法,可以使用 ansible-doc–s module_name。可以输出非常详细的使用说明。
第三部分是 Handlers(管理,调用),这个关键字下面定义了一些具体的操作,用于被 Task 中某个具体任务执行完成后调用。如下定义了一个重启 apache 的操作。
handlers: - name: restart apache service: name=apache state=restarted
在第二部分 Tasks 的举例中,有一个 notify,表示当 template 执行完之后,需要调用 restart apache 操作,这个操作就是在 handlers 中定义好的。
tasks: - name: create a virtual host file for {{ vhost }} template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }} notify: - restart apache
使用 Include、Roles 组织 Playbook
使用 Playbook,我们已经可以有序地组织一些较为复杂的自动化任务了,但 Playbook 还存在两个缺点。
第一,灵活性不够,当我们写了一个很复杂的 Playbook 来完一个任务,再写第二个 Playbook 的时候,发现很多都必须重新来过,其实对于做运维的读者朋友来说,一定清楚,对于一批服务器,初始化过程是大同小异的,其中很多操作适用于所有的服务器,那么单个 Playbook 的方式就变得一点都不灵活。
第二,结构层次混乱,对于完成一个简单的自动化任务来说,并不存在这个问题,因为任务的步骤比较简单,但是对于一个需要上百个 task 才能完成的自动化任务来说,全部写在一个 Playbook 中,读者朋友可以想象,结果会如何,答案一定是非常混乱。之后如果再需要调整的话就变得很麻烦了。
其实,这两个问题,都来自于一点,Playbook 是一个文件,那如果可以将一个复杂的 Playbook 拆分为多个简单的 Playbook 是不是就没问题了呢?拆分只是基础,还需要结构化将这些 Playbook 组织起来,使其能清晰地展现整个自动化任务的结构。Ansible 的设计者也考虑到了这个问题,所以先后引入了 include 和 roles 来解决这些问题。
先来介绍 include,这个单词在很多程序语言中都是关键字,一般的作用也是引入函数库,或者引入文件。在 Ansible Playbook 中使用 include 可以引入其他的 Playbook 中定义的包括 task 或 handlers,在 Ansible 1.0 之后,include 已经支持带参数的引入,使功能变得更加强大,下面通过一个例子来说明。
[AnsibleClientIn] 192.168.122.129 192.168.122.128 192.168.122.127 [AnsibleClientIn:vars] ansible_ssh_user=root ansible_ssh_pass=P@ssw0rd
为了大家可以更好地重现,笔者尽力描述每一个环节。首先在/etc/ansible/hosts 文件中定义 AnsibleClientIn 的分组,并指定登录用户名和密码。
--- - name: Copy specific files copy: src={{filename}} dest=/var/ftp/pub/ group=ftp owner=ftp
定义一个 task 文件 copyfile.yml,用于执行拷贝文件到指定位置,其中包括变量{{filename}}。
--- - hosts: AnsibleClientIn tasks: - include: copyfile.yml filename=1.txt - include: copyfile.yml filename=2.txt
定义一个 Ansible Playbook,名为 main.yml,在其定义的 tasks 中使用了 include 引入 copyfile.yml 这个 Playbook,并传递一个变量 filename,include 了两次,分别传递了 1.txt,2.txt,重复地 include 同一个 Playbook,传递不同的参数,得到的效果是将 1.txt 和 2.txt 都 copy 到了目的主机上。看 main.yml 可以发现结构非常清楚,当然这只是一个简单的例子,用来说明在 tasks 中带变量的 include 如何使用。Include 在 handlers 中的应用也很简单,还是通过一个例子来说明比较直观。
/etc/ansible/hosts 保持不变,然后编写一个 handlers 的 Playbook,命名为 handlers.yml,内容如下。
--- - name: restart vsftpd service: name=vsftpd state=restarted
再定义个 main.yml 在它的 handlers 中使用 include 引入之前定义的 handlers.yml。
--- - hosts: AnsibleClientIn tasks: - name: config ftp server template: src=vsftpd.confdest=/etc/vsftpd/vsftpd.conf notify: - restart vsftpd handlers: - include: handlers.ym
这个 main.yml 的功能是当 vsftpd.conf 发生变化的话,触发重启 vsftpd 的服务。其中 handlers 里面使用了 include 直接引入外部定义的 Playbook,当模板文件被更新时触发重启 vsftpd 服务。
举例的这个应用是非常简单的,在实际的应用过程中,可以在 handlers 的 Playbook 中定义很多需要触发操作的 handlers,而不需要每次都在 main.yml 中定义。
以上介绍了在 Playbook 中使用 include 来引用单独的 Playbook 文件,那是否有一种方式可以既实现调用,又不需要 include 特别指定呢?答案当然是有的,在 Ansible 1.2 之后,引入了一个比 include 还要强大的组织 Playbook 的方式,roles 可以解释为角色。为什么叫 roles 呢?笔者理解为,Ansible 希望将一个服务器以不同的角色来区分,并定义这个角色所需要做的全部任务。
Roles 是通过规范目录结构,自动 include 对应目录下的任务,格式上不再需要通过 include 来明确定义引入哪个 Playbook。以下通过一个 roles 目录结构实例来说明。
├─site.yml //roles 的主文件,可以为空,但一定要有。 ├─webserver.yml //一个具体角色的 yml 文件,可以定义多个,实现差异化定制。 └─ roles ├──nginx //定义具体角色,比如 nginx。 │ ├── defaults <--!ansible 1.3 之后支持,用于定义默认的参数或变量,变量优先级最低 !--> │ │ └──main.yml //roles 会查找各目录下的 main.yml 文件进行引入。 │ ├── files //task 中涉及文件推送的内容,都在 files 目录 │ │ └──epel.repo │ ├── handlers //handlers 中定义的内容都在此目录下 │ ├── meta <--! ansible 1.3 之后支持 role dependencies,可以引用 roles 中的其他角色 !--> │ ├── README.md //说明性文件 │ ├── tasks //定于具体的任务 │ │ └──main.yml │ ├── templates //使用的模板文件统一在这个目录下 │ │ ├── nginx.conf.j2 │ └──vars //变量文件,都存储在这个目录
从 roles 的目录结构中可以看到,基本上是按 Playbook 中各关键字来组织的,比如,tasks,vars,handlers,此外还将文件单独放在 files 中,模板单独放在 templates 中,defaults 和 meta 分别用于默认参数和角色依赖的一些定义。Roles 会在除了 files 和 templates 目录中查找并引入 main.yml,相应地任务可以直接定义在 main.yml 中。
从目前来看,roles 是 Ansible 中非常大的一个亮点,官方希望将 roles 定位为 Ansible 的一个个最佳实践,方便用户之间相互共享,读者朋友可以在以下官网查找合适的 roles, https://galaxy.ansible.com/ ,Ansbile 1.4.2 之后可以通过 ansible-galaxy 命令来管理 roles。
[root@localhost]# ansible-galaxy installbennojoy.mysql - downloading role 'mysql', owned by bennojoy - downloading role from https://github.com/bennojoy/mysql/archive/master.tar.gz - extracting bennojoy.mysql to /etc/ansible/roles/bennojoy.mysql - bennojoy.mysql was installed successfully
用 ansible-galaxy 下载的 roles,默认存放在/etc/ansible/roles/目录下。读者朋友可以下载之后,查看并根据自己的实际环境修改之后再使用,这个比完全自己写要省事很多。同时读者朋友也可以将自己写好的 roles 通过 github 共享给其他人。
Ansible 多样的变量定义与使用法则
变量的使用,使自动化任务的定义变得更加灵活、多样。在 Ansible 中也支持多种形式的变量定义,以下就来逐一介绍。
1.在 inventory 中定义变量
在 inventory(主机清单)定义的同时,不仅可以为特定的变量赋值,比如,ansible_ssh_port,ansible_ssh_user,ansible_ssh_pass,同时也可以自定义一些变量,比如一组服务器我们希望定义 nginx 监听 8081,那么可以直接定义 nginx_port=8081,之后在配置文件模板中直接引用{{nginx_port}}即可。
2.在 Ansible 的 hosts 和 groups 中定义变量
在 Ansible 特定的目录中可以对 hosts 和 groups 定义变量,/etc/ansible/group_vars/目录中定义 groups 的变量,变量文件是.yml 文件,注意定义时文件名必须和 group 的名字相同,同样在/etc/ansible/host_vars/目录中可以定义 host 的变量,文件名和 host 名也必须一致,内容遵循 yml 格式。下面通过一个具体的例子进行说明。
[root@localhost]# cat /etc/ansible/group_vars/AnsibleClientIn.yml --- idc: Shanghai
创建一个以组名 AnsibleClientIn 命名的 yml 文件,其中定义一个变量 idc 并赋值 shanghai。
[root@localhost]# cat /etc/ansible/host_vars/192.168.122.129.yml --- idc: Beijing
创建一个以主机名 192.168.122.129 命令的 yml 文件,其中也定义了变量 idc 并赋值 Beijing。
--- - hosts: AnsibleClientIn tasks: - name: Copy file copy: src=server.txt dest=/root/{{idc}}
定义一个 Playbook,在其中可以直接使用变量 idc,如上例子会将当前目录下的 server.txt 复制到 AnsibleClientIn 中定义的所有主机中,复制到 192.168.122.129 主机中的文件名为 Beijing,其余主机中得到的文件名是 Shanghai,读者朋友们是不是发现,对于相同的变量,如果在 host_vars 和 group_vars 都被定义的时候,host_vars 中定义的优先级要高。待所有变量定义方式都介绍完成之后,再为读者朋友总结一下,变量的定义优先级。
3.在 Playbook 中直接定义和使用变量
Playbook 的 vars 关键字下,定义变量 filesname 为 config.txt,这个定义的变量可以在 tasks 中被使用,如下例子中,使用了 copy 模块,目的文件名直接使用变量定义好的 filename。
--- - hosts: AnsibleClientIn vars: - filename: config.txt tasks: - name: Copy file copy: src=server.txt dest=/root/{{filename}}
4.在独立的文件中定义变量
Playbook 中,提供了一个 vars_files 关键字,用于引用一个独立文件中的变量。也是通过一个简单的例子来具体说明一下。
[root@localhost]# cat /root/ansible-playbook/ansible_variables.yml --- server_roles: Webserver
创建一个单独的用于定义变量的文件,命名为 ansible_variables.yml,其中定义了一个变量 server_roles,并赋值。
--- - hosts: AnsibleClientIn vars_files: - /root/ansible-playbook/ansible_variables.yml tasks: - name: Copy file copy: src=server.txtdest=/root/{{server_roles}}
在 Playbook 中,vars_files 关键字下,引入单独定义的变量文件。上面的例子执行之后,Ansible 会将 server.txt 文件复制到目的主机中,命名为 Webserver。
5.在使用 ansible-playbook 执行 Playbook 时引入
这种方式分为两种情况,第一种通过 vars_prompt 交互方式。另一种是 extra-vars 传递变量。以下还是通过两个简单的例子来说明一下。
通过 vars_prompt 交互方式获取变量举例。
--- - hosts: AnsibleClientIn vars_prompt: - name: 'filename' prompt: 'Input filename' //用户输入时的提示信息 private: no //是否显示用户输入的内容,yes,为不显示,no 为显示 tasks: - name: linefile input copy: src=server.txt dest=/root/{{filename}}
定义一个 Paybook 命名为 EX5.yml,其中使用了 vars_prompt,用于接收用户的输入,并存放于 filename 这个变量中。
[root@localhost ]# ansible-playbook EX5.yml Input filename: testfile
执行 playbook,会提示 Input filename,此时提示输入文件名,因为指定了 private:no,所以用户输入会被显示出来。
通过 extra-vars 方式传递变量举例。
--- - hosts: AnsibleClientIn tasks: - name: filename input copy: src=server.txt dest=/root/{{filename}}
定义 Playbook 命名为 EX5-2.yml,在其中直接引用 filename 变量。
[root@localhost ]# ansible-playbook EX5-2.yml --extra-vars "filename=extra-test"
使用 ansible-playbook 执行 EX5-2.yml 时,通过--extra-vars 来为变量 filename 赋值。
6.使用 ansible setup 模块中获取到的变量
Ansible 默认配置中 setup 模式是开启的,在/etc/ansible/ansible.cfg 中设置 gather_facts:no 来关闭它。Setup 模块非常强大,可以获取到非常多的系统环境及配置信息。
举个简单的例子,使用 setup 模块中的获取到的变量。
--- - hosts: AnsibleClientIn tasks: - name: ansible facts test template: src=setup-test.txt dest=/tmp
定义一个 playbook,其中使用 template 模块传递一个文件到目的服务器,模板文件 setup-test.txt 中定义个一个变量 ansible_all_ipv4_addresses 获取 ipv4 地址。
[root@AnsibleServer ansible-playbook]# cat setup-test.txt {{ansible_all_ipv4_addresses}}
这个变量在 setup 模块中是有的,执行 ansible localhost–m setup 可以看到 setup 模块获取到的所有变量内容。
以上介绍的 6 种变量定义和使用方法是最常用的,当然 Ansible 支持的远不止这些,读者朋友可以在官方网站查看 http://docs.ansible.com/ansible/playbooks_variables.html 。
上面章节中也提到了,如果一个变量被多次定义,Ansible 会根据变量的优先级来对变量最后赋值,具体的变量优先级由低到高按如下排序。
role defaults(role 中 default 目录中定义的变量)<inventory vars(主机清单中定义的变量)<inventory group_vars(在文件中定义的组变量)<inventory host_vars(在文件中定义的主机变量)<playbook group_vars(在 Playbook 中定义的组变量)<playbook host_vars(在 playbook 中定义的主机变量)<host factsregistered vars<set_facts<play vars<play vars_prompt(获取用户的输入变量)<play vars_files<role and include vars(在 role 和 include 中定义的变量)<block vars(only for tasks in block)<task vars(only for the task)<extra vars(always win precedence)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论