跳到主要内容

Role 与复用

本章目标:把 playbook 里的任务拆成 Role,用 defaults 定义稳定对外接口,让同一角色在 staging/prod、甚至多个仓库间复用。

判断标准:如果某段 YAML 开始被复制到第二个 playbook,就该拆角色了。


1. Playbook vs Role

PlaybookRole
回答的问题对哪些主机、按什么顺序做如何把某类组件配置到期望状态
典型内容hostsserial、角色列表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

约定:

  1. 全部加角色前缀nginx_),避免与其它角色撞名
  2. defaults 里只放「调用方可调」的值
  3. 内部写死的路径映射可放 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_roleinclude_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
vars/main.yml 别当配置入口

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 的步骤

  1. ansible-galaxy role init roles/xxx
  2. 把 tasks/handlers/templates 挪进去
  3. 所有「可调值」提换为 xxx_ 变量,写入 defaults
  4. playbook 删光具体任务,改为 roles: [xxx]
  5. 连续执行两次,确认幂等
  6. 用 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 的区别

下一章:幂等、预览与滚动变更 —— 角色写得再好,没有变更护栏照样能一次打挂全站。