- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
导读
本文是 90DaysOfDevOps 项目中 2022/pl/day66.md 的深度展开,主题是Ansible Playbook 的进阶工程化:在完成 Vagrant 四节点实验环境与 Web 服务器配置后,通过"拆分 tasks/handlers 到子目录"和"引入 Ansible Roles +ansible-galaxy"两种手段,将单体 Playbook 重构为结构清晰、可复用、可扩展的自动化代码。读完本文,你将掌握 Playbook 瘦身、import_tasks静态导入、ansible-galaxy init角色脚手架、角色目录规范,以及如何处理旧版include语法的弃用警告——这些能力正是把 DevOps 配置管理从小实验推向生产级的基础。
回顾:Day 65 建立的实验环境
在进入本课之前,项目此前已通过 Configmgmt/Vagrantfile 拉起了一个包含 4 台虚拟机的迷你实验室,并指定其中的 Linux 节点作为 Ansible 控制机(control node)。前文已跑通了若干 Playbook 场景,最终得到一份能让 web01 与 web02 各自成为独立 Web 服务器的 Playbook。
也就是说,此刻我们的拓扑是:
- 控制机:Linux 节点,承载 Ansible 与 SSH 管理;
- web01 / web02:已被配置为 Apache2 Web 服务器;
- 另外两台节点:尚无角色,后续将分别承担数据库(mysql)与负载均衡(nginx)职能。
前序 Playbook 的完整代码位于 Configmgmt/ansible-scenario1(首个含模板的单体版本)与 Configmgmt/simple_play.yml(最简示例),可以对照阅读体会演进脉络。
让 Playbook 保持整洁:拆分 tasks 与 handlers
当 Playbook 里任务、处理器、模板越来越多时,单个 YAML 文件会迅速变得难以维护。原文给出的第一步重构,是把**任务(tasks)和处理器(handlers)**分别抽到独立子目录中,让 Playbook 本体只保留"编排"逻辑。
第一步:把 tasks 抽到独立文件
将原本写在 Playbook 中的 Apache 部署任务复制到tasks/apache2_install.yml:
- name: ensure apache is at the latest version apt: name=apache2 state=latest - name: write the apache2 ports.conf config file template: src=templates/ports.conf.j2 dest=/etc/apache2/ports.conf notify: restart apache - name: write a basic index.html file template: src: templates/index.html.j2 dest: /var/www/html/index.html notify: - restart apache - name: ensure apache is running service: name: apache2 state: started这段任务的职责非常清晰,对应四条核心动作:
| 任务 | 模块 | 作用 |
|---|---|---|
apt | apt | 确保 apache2 为最新版本 |
| 写入 ports.conf | template | 用 Jinja2 模板渲染 Apache 端口配置,并notify触发重启 |
| 写入 index.html | template | 渲染自定义欢迎页到/var/www/html/,同样触发重启 |
| 启动服务 | service | 确保 apache2 处于 started 状态 |
注意notify的用法:它并不会立即执行重启,而是"登记"一个 handler,只有在该任务真正改变状态时才在 Playbook 末尾触发——这正是 Ansible 幂等设计的体现。
第二步:把 handlers 抽到独立文件
同样,把重启处理器复制到handlers/main.yml:
- name: restart apache service: name: apache2 state: restarted第三步:Playbook 通过导入引用它们
重构后的 Playbook 更名为playbook2.yml,通过import_tasks把外部文件静态引入。仓库中的真实实现见 Configmgmt/ansible-scenario2/playbook2.yml:
- hosts: webservers become: yes vars: http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 66!" tasks: - import_tasks: tasks/apache2_install.yml handlers: - import_tasks: handlers/main.yml这里值得留意:Playbook 通过vars声明了http_port、https_port、html_welcome_msg三个变量,它们会被模板消费(详见下文"模板如何消费变量")。整份重构后的完整场景代码位于 Configmgmt/ansible-scenario2,目录结构如下:
ansible-scenario2/ ├── handlers/ │ └── main.yml # restart apache 处理器 ├── tasks/ │ └── apache2_install.yml ├── templates/ │ ├── index.html.j2 │ └── ports.conf.j2 └── playbook2.yml验证:用 curl 检查"那次简单的改动"
原文提示:如果你从仓库复制文件,会注意到 "write a basic index.html file" 任务发生了变化——template的写法从src=... dest=...(键值对风格)变成了src: ...和dest: ...(YAML 映射风格)。两种语法等价,后者是更规范、可读性更好的现代写法,仓库中的 ansible-scenario2/tasks/apache2_install.yml 同时保留了这两种风格,正好用来对比学习。
在控制机上执行验证命令:
curl web01:8000curl 之所以访问 8000 端口,是因为ports.conf.j2模板将 Apache 监听端口渲染为{{ http_port }},而http_port在 Playbook 的vars中被赋值为 8000。执行后应能看到由index.html.j2渲染出的欢迎页内容。
到这里,第一层重构完成:Playbook 本体只剩编排信息,任务与处理器各自归位,即使未来任务数量成倍增长,文件也不会失控。
Roles 与 Ansible Galaxy:把 Web 服务器配置变成可复用单元
拆分 tasks/handlers 只是"整理",而Roles(角色)才是 Ansible 实现复用与分层的核心机制。当我们要把剩余节点配置为数据库服务器与负载均衡器时,用 Role 把"某类主机该做什么"完整封装,是标准的工程化路径。
ansible-galaxy:角色管理的瑞士军刀
ansible-galaxy是 Ansible 自带的角色管理命令,既可以从 Ansible Galaxy 共享社区下载现成角色,也可以本地初始化一个标准角色骨架。本课用它来创建 apache2 角色:
ansible-galaxy init roles/apache2执行后,会在roles/apache2/下生成一套标准的角色目录结构。仓库中该角色已完整落地,见 Configmgmt/ansible-scenario3/roles/apache2:
roles/apache2/ ├── defaults/ │ └── main.yml # 角色默认变量(最低优先级) ├── handlers/ │ └── main.yml # restart apache ├── meta/ │ └── main.yml # 角色元数据(作者、依赖等) ├── tasks/ │ ├── apache2_install.yml │ └── main.yml # 角色任务入口 ├── templates/ │ ├── index.html.j2 │ └── ports.conf.j2 ├── tests/ │ ├── inventory │ └── test.yml # 角色自测用例 ├── vars/ │ └── main.yml # 角色内部变量 └── README.md这套目录是 Ansible 的约定优于配置:Ansible 运行角色时,会按固定顺序加载这些目录中的main.yml——先是defaults(默认值,最低优先级),随后按依赖关系加载meta,最后执行tasks;handlers、templates、vars则各司其职。
迁移现有内容到角色结构
骨架建好后,把上一节ansible-scenario2中的成果搬入对应目录:
tasks/apache2_install.yml→roles/apache2/tasks/apache2_install.yml;templates/index.html.j2、templates/ports.conf.j2→roles/apache2/templates/;handlers/main.yml→roles/apache2/handlers/main.yml。
由于 Ansible 约定角色的任务入口固定为tasks/main.yml,还需在入口文件中导入真正的任务清单。仓库实现见 Configmgmt/ansible-scenario3/roles/apache2/tasks/main.yml:
--- # tasks file for roles/apache2 - import_tasks: apache2_install.yml改造 Playbook:从"声明任务"到"声明角色"
接下来把 Playbook 改为引用角色。在playbook1.yml(tasks 直接内联)与playbook2.yml(tasks 用 import_tasks 引入)两个版本中,任务与处理器的声明方式各不相同;而角色化之后,Playbook 只需在roles键下罗列角色名即可。仓库中重构后的 Configmgmt/ansible-scenario3/playbook3.yml 如下:
- hosts: webservers become: yes vars: http_port: 8000 https_port: 4443 html_welcome_msg: "Hello 90DaysOfDevOps - Welcome to Day 66!" roles: - apache2对比playbook2.yml,原来的tasks:与handlers:段落消失了,取而代之的是roles: - apache2。Ansible 会自动寻找roles/apache2/目录并执行其中的任务与处理器,同时角色内部可以访问 Playbook 级vars中声明的变量。
运行角色化后的 Playbook:
ansible-playbook playbook3.yml处理弃用警告:include 与 import_tasks 的取舍
运行playbook3.yml时,输出中会出现一条deprecation(弃用)警告。原因在于:早期版本中tasks/main.yml使用旧式include指令来引入任务文件,而旧式裸include已被 Ansible 官方弃用。虽然 Playbook 仍能执行,但按官方建议应尽快修正。
修复方式是把tasks/main.yml中的引入方式改为import_tasks(静态导入,在 Playbook 解析阶段就把任务展开):
--- # tasks file for roles/apache2 - import_tasks: apache2_install.yml这里可以展开说明两者的本质区别,这也是从源码结构能清晰观察到的设计意图:
import_tasks(静态导入):解析期展开,任务列表在运行前就已确定,支持when条件作用于整段导入、不支持循环变量传递,适合导入结构固定的任务清单——角色入口用它最稳妥;include_tasks(动态导入):运行期按需加载,支持在循环中按变量动态决定导入哪个文件,但会带来一定解析开销与行为不确定性。
修复后再次运行ansible-playbook playbook3.yml,弃用警告即消失。修正后的完整场景代码见 Configmgmt/ansible-scenario3。
模板如何消费变量:从源码看渲染链路
角色化后,模板文件被迁移到roles/apache2/templates/下。阅读仓库源码可以完整还原"变量 → 模板 → 目标文件"的渲染链路:
ports.conf.j2 的核心行:
Listen {{ http_port }} <IfModule ssl_module> Listen {{ https_port }} </IfModule> <IfModule mod_gnutls.c> Listen {{ https_port }} </IfModule>index.html.j2 的完整内容:
<html> <h1>{{ html_welcome_msg }}</h1> </html>可见:ports.conf.j2消费http_port、https_port,index.html.j2消费html_welcome_msg,三者都由 Playbook 级vars提供。这也解释了为什么curl web01:8000能取到 8000 端口上的欢迎页——模板渲染、端口监听、内容展示形成了一条完整的自动化链路。角色内defaults/main.yml与vars/main.yml目前是空的(见 defaults/main.yml),说明变量优先级完全由 Playbook 顶层vars提供;实际生产中可以下沉到defaults,让角色自带默认值并允许被外部覆盖。
为负载均衡与通用配置再建两个角色
Web 服务器角色化只是开始。剩余的数据库节点与负载均衡节点同样需要角色化,继续用ansible-galaxy初始化:
# common:适用于所有服务器(通用工具、基础配置) ansible-galaxy init roles/common # nginx:用于负载均衡 / 反向代理节点 ansible-galaxy init roles/nginx至此,仓库中已具备 apache2、common、nginx 三个角色。项目后续场景(ansible-scenario4、ansible-scenario5、ansible-scenario6、ansible-scenario7)在此基础上持续演进:从 nginx 负载均衡配置(roles/nginx/tasks/configure_nginx.yml、roles/nginx/templates/mysite.j2)、common 通用工具安装(roles/common/tasks/install_tools.yml),到group_vars/all/common_variables.yml的变量分层,以及 mysql 角色(roles/mysql/tasks/install_mysql.yml、setup_mysql.yml)——"把每个节点职能封装成角色"正是本课方法论在后续章节的延续,也是读者可以提前通读这些目录来印证的方向。
小结与下一步
本课完成了两级重构:
- 模块级拆分:tasks、handlers、templates 各归其位,Playbook 只保留编排与变量(
ansible-scenario2); - 角色级抽象:用
ansible-galaxy init生成标准角色骨架,把 Web 服务器配置整体封装为roles/apache2,Playbook 通过roles:一行引用,并顺手修复了旧式include的弃用警告(ansible-scenario3)。
这两种做法都指向同一个目标:让 Playbook 在节点数量与业务复杂度增长时依然保持清晰、可复用、可测试。已部署但尚未配置的数据库与负载均衡节点,将在下一篇(Day 67)中借助 common、nginx、mysql 等角色逐步落地。想要继续动手实践,请把 Configmgmt/ansible-scenario3 的完整目录复制到控制机,对照本文逐文件阅读,再在实验环境中重跑ansible-playbook playbook3.yml观察输出差异。
- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
相关推荐
90DaysOfDevOps Day 66 实战:用 Ansible Galaxy 与 Roles 重构 Apache Playbook
90DaysOfDevOps Day 66 实战:用 Ansible Galaxy 与 Roles 重构 Apache Playbook 本文是 90DaysO
文档/教程90DaysOfDevOps Day66:Ansible Playbook 结构优化——从扁平任务到 Roles 与 Ansible Galaxy 的角色化重构
90DaysOfDevOps Day66:Ansible Playbook 结构优化——从扁平任务到 Roles 与 Ansible Galaxy 的角色化重构
文档/教程90DaysOfDevOps 第 66 天:Ansible Playbooks 进阶——任务与处理器拆分、Roles 与 Ansible Galaxy 实战
90DaysOfDevOps 第 66 天:Ansible Playbooks 进阶——任务与处理器拆分、Roles 与 Ansible Galaxy 实战 本
文档/教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考