Role 与复用
本章目标:把 playbook 里的任务拆成 Role,用 defaults 定义稳定对外接口,让同一角色在 staging/prod、甚至多个仓库间复用。
判断标准:如果某段 YAML 开始被复制到第二个 playbook,就该拆角色了。
1. Playbook vs Role
| Playbook | Role | |
|---|---|---|
| 回答的问题 | 对哪些主机、按什么顺序做 | 如何把某类组件配置到期望状态 |
| 典型内容 | hosts、serial、角色列表 | tasks、templates、defaults |
| 复用方式 | 很少跨项目整份复制 | 可单独版本化、galaxy 分发 |
推荐关系:
site.yml
├─ role: common
├─ role: nginx
└─ role: node_exporter
2. 创建角色骨架
cd ~/ansible-for-ops
ansible-galaxy role init roles/nginx
ansible-galaxy role init roles/common
完整结构(实际可删掉未用目录):
roles/nginx/
├── defaults/main.yml # 对外默认参数(可被覆盖)
├── vars/main.yml # 角色内部常量(难被覆盖,慎用)
├── files/ # copy 用的静态文件
├── templates/ # template 用的 j2
├── tasks/main.yml
├── handlers/main.yml
├── meta/main.yml # 依赖、作者、平台
└── README.md # 接口说明(强烈建议写)
Ansible 查找顺序:roles_path(见 ansible.cfg)→ 相对路径 ./roles。
3. defaults:把角色当成「有文档的函数」
roles/nginx/defaults/main.yml:
---
# ==== 包与服务 ====
nginx_package: nginx
nginx_service: nginx
nginx_user: www-data # RHEL 常为 nginx,可在 group_vars 覆盖
# ==== 配置 ====
nginx_listen_port: 80
nginx_worker_processes: 2
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_healthz_enable: true
# ==== 内容 ====
nginx_index_content: |
<!doctype html>
<title>{{ inventory_hostname }}</title>
<h1>{{ inventory_hostname }}</h1>
# ==== 行为开关 ====
nginx_manage_main_config: true
nginx_start_service: true
约定:
- 全部加角色前缀(
nginx_),避免与其它角色撞名 - defaults 里只放「调用方可调」的值
- 内部写死的路径映射可放
vars/main.yml,但越少越好
roles/nginx/README.md 至少写清:
- 必填变量(若有)
- 可选变量与默认值
- 支持的平台
- 示例调用
4. tasks:可标签、可开关
roles/nginx/tasks/main.yml:
---
- name: 安装 nginx
ansible.builtin.package:
name: "{{ nginx_package }}"
state: present
tags: [nginx, packages]
- name: 站点根目录
ansible.builtin.file:
path: "{{ nginx_root }}"
state: directory
owner: root
group: root
mode: "0755"
tags: [nginx, config]
- name: 首页
ansible.builtin.copy:
content: "{{ nginx_index_content }}"
dest: "{{ nginx_root }}/index.html"
mode: "0644"
tags: [nginx, content]
- name: 主配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
backup: true
validate: "/usr/sbin/nginx -t -c %s"
when: nginx_manage_main_config | bool
notify: Reload nginx
tags: [nginx, config]
- name: 启动服务
ansible.builtin.service:
name: "{{ nginx_service }}"
state: started
enabled: true
when: nginx_start_service | bool
tags: [nginx, services]
roles/nginx/handlers/main.yml:
---
- name: Reload nginx
ansible.builtin.service:
name: "{{ nginx_service }}"
state: reloaded
templates/nginx.conf.j2 可复用模块章中的模板,把端口等变量统一为 nginx_* 前缀。
5. 调用方式
5.1 roles: 列表(简单)
---
- name: Web
hosts: web
become: true
roles:
- role: common
- role: nginx
vars:
nginx_listen_port: 8080
5.2 任务中 import / include
tasks:
- name: 基线
ansible.builtin.import_role:
name: common
- name: 仅在需要时装 nginx
ansible.builtin.include_role:
name: nginx
apply:
tags: [nginx]
when: enable_web | default(true) | bool
import_role | include_role | |
|---|---|---|
| 解析时机 | 解析期静态 | 运行期动态 |
| tags/when | 行为更「整段导入」 | 更灵活,适合条件加载 |
入门用 roles: 列表即可;要做复杂编排再用 include/import。
6. 变量覆盖实战
优先级回顾(简化):defaults < group_vars < host_vars < play/roles[].vars < -e
group_vars/web.yml:
nginx_listen_port: 80
nginx_worker_processes: 4
nginx_server_name: "app.example.com"
验证:
ansible-playbook playbooks/web.yml -l web1 -e nginx_listen_port=8888 --check
roles/nginx/vars/main.yml 优先级高于多数 group_vars,容易让调用方「改了 group_vars 却不生效」。对外接口请放 defaults。
7. meta 依赖
roles/webapp/meta/main.yml:
---
galaxy_info:
role_name: webapp
description: 示例应用依赖 nginx
platforms:
- name: Ubuntu
versions: [all]
dependencies:
- role: nginx
vars:
nginx_listen_port: 80
nginx_root: /var/www/webapp
依赖会在当前角色之前执行。注意:
- 依赖链保持浅(≤2 层)
- 被依赖角色必须可独立、幂等
- 循环依赖会让人崩溃
8. common 角色示例(基线)
roles/common/defaults/main.yml:
---
common_timezone: Asia/Shanghai
common_packages:
- curl
- jq
- ca-certificates
- vim-tiny
common_users: []
# 示例:
# common_users:
# - name: deploy
# groups: sudo
# shell: /bin/bash
roles/common/tasks/main.yml:
---
- name: 确保基础包
ansible.builtin.package:
name: "{{ common_packages }}"
state: present
tags: [common, packages]
- name: 创建用户
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups | default(omit) }}"
shell: "{{ item.shell | default('/bin/bash') }}"
state: present
loop: "{{ common_users }}"
tags: [common, users]
# 时区:优先 community.general.timezone;无集合时可发行版分支处理
- name: 设置时区(需 community.general)
community.general.timezone:
name: "{{ common_timezone }}"
tags: [common]
when: ansible_facts is defined
ansible-galaxy collection install community.general
9. 角色复用与版本
团队内三种分发方式:
| 方式 | 适用 |
|---|---|
monorepo 的 roles/ | 单团队、迭代快 |
| Git submodule / 多仓库 | 角色跨项目共享 |
| Ansible Galaxy / 私有 Galaxy | 正式版本语义 |
无论哪种,都要:改 defaults 视为接口变更;破坏性变更升 major,并在 README 写迁移说明。
10. 从 playbook 迁移到 role 的步骤
ansible-galaxy role init roles/xxx- 把 tasks/handlers/templates 挪进去
- 所有「可调值」提换为
xxx_变量,写入 defaults - playbook 删光具体任务,改为
roles: [xxx] - 连续执行两次,确认幂等
- 用 staging 一组主机
--check --diff再真实 apply
11. 练习与验收
练习 A
把模块章 / Playbook 章里的 Nginx 任务完整迁入 roles/nginx,playbook 不超过 20 行。
练习 B
新增 roles/common,在 site.yml 中先 common 后 nginx。
练习 C
在 host_vars/web1.yml 覆盖 nginx_listen_port,证明 defaults 可被单机覆盖。
验收清单
- 角色有 defaults 前缀与 README
- playbook 不再堆砌安装细节
- 二次 apply 基本无 changed
- 明白 defaults 与 vars 的区别
下一章:幂等、预览与滚动变更 —— 角色写得再好,没有变更护栏照样能一次打挂全站。