10-Ansible 角色管理
实验环境准备
操作流程:
- 创建工作目录并进入
- 配置 Ansible 全局设置(
ansible.cfg
) - 定义主机清单(
inventory
)
# 创建名为web的工作目录并进入
[bq@controller ~]$ mkdir web && cd web
# 创建Ansible配置文件ansible.cfg,定义默认参数
[bq@controller web]$ cat > ansible.cfg <<'EOF'
[defaults]
remote_user = bq # 默认远程登录用户为bq
inventory = ./inventory # 指定inventory文件路径为当前目录下的inventory
[privilege_escalation]
become = True # 允许提权(切换到root)
become_user = root # 提权目标用户为root
become_method = sudo # 提权方式为sudo
become_ask_pass = False # 提权时不询问密码(需提前配置sudo免密)
EOF
# 创建主机清单文件,定义需要管理的主机
[bq@controller web]$ cat > inventory <<'EOF'
controller # 控制节点
node1 # 被控节点1
node2 # 被控节点2
node3 # 被控节点3
node4 # 被控节点4
EOF
Ansible 角色
角色介绍:为什么需要角色?
在实际工作中,我们编写的 Playbook 会越来越复杂:可能包含几十甚至上百个任务,还会引用各种文件、模板和变量。如果每次部署类似服务(比如 Web 服务器、数据库)都重新写一遍 Playbook,不仅效率低,还容易出错。
Ansible 角色就是为解决这个问题而生的:它把部署一个服务所需的所有任务、变量、文件、模板等资源,按标准化的目录结构打包。这样一来,我们可以把角色复制到不同项目中,直接调用即可,大大提高了代码的复用性和可维护性。
角色结构:标准化的目录布局
Ansible 角色有固定的目录结构,通过ansible-galaxy init
命令可以自动创建。我们先看一个示例:
# 初始化一个名为bq的角色(自动创建标准目录结构)
[bq@controller web]$ ansible-galaxy init bq
- bq was created successfully
# 查看角色的目录结构
[bq@controller web]$ tree bq
bq
├── defaults # 角色默认变量(优先级低,可被覆盖)
│ └── main.yml
├── files # 静态文件(如配置文件、脚本等,可被任务直接引用)
├── handlers # 处理程序(如服务重启等,由任务触发)
│ └── main.yml
├── meta # 角色元信息(作者、依赖、支持的平台等)
│ └── main.yml
├── README.md # 角色说明文档(用途、用法、依赖等)
├── tasks # 核心任务(角色的主要操作,如安装、配置等)
│ └── main.yml
├── templates # Jinja2模板文件(动态生成配置文件,带变量)
├── tests # 测试相关(测试用的inventory和Playbook)
│ ├── inventory
│ └── test.yml
└── vars # 角色内部变量(优先级高,不建议在Playbook中修改)
└── main.yml
8 directories, 8 files
各目录作用详解:
defaults/main.yml
:默认变量,几乎所有其他变量(如清单变量、Playbook 中的 vars)都能覆盖它。适合作为 “可自定义的参数”,比如 Web 服务的默认端口。files
:存放静态文件(如固定内容的配置文件),任务中引用时无需写路径,Ansible 会自动在这里查找。handlers/main.yml
:处理程序,比如 “重启 Apache”“重载 Nginx” 等操作,由任务通过notify
触发。meta/main.yml
:元信息,包括作者、支持的操作系统(如 CentOS、Ubuntu)、角色依赖(如部署 Web 服务前需要先部署防火墙)等。tasks/main.yml
:核心任务集合,定义角色要执行的操作(如安装软件、启动服务、复制文件等)。templates
:存放 Jinja2 模板(文件名通常以.j2
结尾),可通过变量动态生成配置文件(如根据主机名生成不同的虚拟主机配置)。vars/main.yml
:角色内部变量,优先级较高(仅低于事实变量、注册变量等),用于角色内部逻辑,不建议在外部修改。
角色存放位置:Ansible 去哪里找角色?
Ansible 默认会从以下目录查找角色(优先级从高到低):
~/.ansible/roles
(当前用户的角色目录)/usr/share/ansible/roles
(系统级共享角色目录)/etc/ansible/roles
(系统默认角色目录)
如果想自定义角色路径,可以在ansible.cfg
中通过roles_path
设置:
[defaults]
# 多个路径用冒号分隔,Ansible会按顺序查找
roles_path = ./roles:/etc/ansible/roles/custom
创建角色:实战示例(部署 Apache 服务)
实验流程:
- 创建
roles
目录(统一存放角色) - 初始化
apache
角色 - 配置角色的核心文件(任务、变量、模板、处理程序等)
- 编写 Playbook 调用角色
- 执行并验证结果
步骤细化:
- 创建角色目录并初始化
apache
角色
# 创建roles目录(用于存放所有角色)
[bq@controller web]$ mkdir roles
# 初始化apache角色,并指定存放路径为./roles
[bq@controller web]$ ansible-galaxy init apache --init-path=./roles
- apache was created successfully
# 进入apache角色目录,准备配置文件
[bq@controller web]$ cd roles/apache/
- 配置
tasks/main.yml
(核心任务)
任务定义了角色要执行的操作:安装 Apache、启动服务、生成配置文件等。
---
# tasks file for apache:部署Apache服务的核心任务
# 安装Web服务软件(包名通过变量web_package指定)
- name: install web server
yum:
name: "{{ web_package }}" # 引用变量(默认值在defaults中定义)
state: latest # 确保安装最新版本
# 启动Web服务并设置开机自启
- name: start and enable web service
service:
name: "{{ web_service }}" # 服务名通过变量web_service指定
state: started # 确保服务处于启动状态
enabled: yes # 开机自启
# 生成/etc/motd文件(登录提示,用模板动态生成)
- name: generate motd file
template:
src: motd.j2 # 模板文件(位于templates目录)
dest: /etc/motd # 目标路径
# 生成Apache虚拟主机配置文件
- name: generate virtual host config
template:
src: bq.conf.j2 # 虚拟主机模板
dest: /etc/httpd/conf.d/bq.conf # Apache额外配置目录
notify: # 配置文件变化时,触发处理程序
- restart_web # 重启Web服务(在handlers中定义)
# 创建网站根目录(路径包含当前主机名,确保每个主机独立)
- name: create document root
file:
path: "/var/www/html/{{ ansible_hostname }}" # ansible_hostname是facts变量(主机名)
state: directory # 确保目录存在
mode: '0755' # 权限设置
# 生成网站首页(动态显示主机名)
- name: generate index.html
template:
src: index.html.j2 # 首页模板
dest: "/var/www/html/{{ ansible_hostname }}/index.html"
- 配置
defaults/main.yml
(默认变量)
定义可自定义的变量,比如 Web 服务的包名和服务名(默认是httpd
,如需部署 Nginx 可在此修改)。
---
# defaults file for apache:可自定义的默认变量
web_package: httpd # Web服务软件包名(如httpd、nginx)
web_service: httpd # Web服务名(需与包名对应)
- 配置
templates
目录下的模板文件
模板文件用于动态生成配置,这里以 3 个关键模板为例:
templates/motd.j2
(登录提示模板):
hello guys!
Welcome to {{ ansible_fqdn }}! # ansible_fqdn是facts变量(主机全名)
templates/bq.conf.j2
(Apache 虚拟主机模板):
# {{ ansible_managed }} # 标记此文件由Ansible管理
<VirtualHost *:80>
ServerAdmin bq@{{ ansible_fqdn }} # 管理员邮箱(包含主机名)
ServerName {{ ansible_fqdn }} # 虚拟主机域名
ErrorLog logs/{{ ansible_hostname }}-error.log # 错误日志路径(含主机名)
CustomLog logs/{{ ansible_hostname }}-common.log common # 访问日志路径
DocumentRoot /var/www/html/{{ ansible_hostname }}/ # 网站根目录
<Directory /var/www/html/{{ ansible_hostname }}/>
Options +Indexes +FollowSymlinks +Includes # 目录选项
Order allow,deny
Allow from all # 允许所有IP访问
</Directory>
</VirtualHost>
templates/index.html.j2
(网站首页模板):
Welcome to {{ ansible_fqdn }} ! # 显示主机全名
- 配置
handlers/main.yml
(处理程序)
定义需要被任务触发的操作(如配置文件更新后重启服务)。
---
# handlers file for apache:处理程序(由任务notify触发)
- name: restart_web # 处理程序名称(需与tasks中的notify对应)
service:
name: "{{ web_service }}" # 服务名(引用变量)
state: restarted # 重启服务
- 配置
meta/main.yml
(元信息)
说明角色的作者、支持的平台、标签等(方便其他人理解和使用)。
---
galaxy_info:
author: bq # 作者
description: Deploy Apache web server # 角色描述
company: bq world # 公司/组织
license: GPLv2 # 许可证
min_ansible_version: 2.4 # 最低支持的Ansible版本
platforms: # 支持的操作系统
- name: Fedora
versions:
- all
- 25
galaxy_tags: [apache, web] # 标签(方便搜索)
dependencies: [] # 角色依赖(暂时为空)
调用角色:在 Playbook 中使用角色
角色配置完成后,只需在 Playbook 中通过roles
关键字调用即可。
示例 Playbook(deploy_apache.yml
):
---
- name: deploy apache web server # Play名称
hosts: node1 # 目标主机(部署到node1)
roles:
- apache # 调用apache角色(从roles目录查找)
执行与验证:
# 执行Playbook
[bq@controller web]$ ansible-playbook deploy_apache.yml
# 验证1:登录node1,查看motd(登录提示)
[bq@controller web]$ ssh root@node1
# 预期输出:
hello guys!
Welcome to node1.lab.example.com! # 显示node1的主机全名
# 验证2:访问Web服务(node1的首页)
[root@node1 ~]# curl http://node1/
# 预期输出:
Welcome to node1.bq.cloud ! # 显示node1的主机全名
角色依赖:一个角色依赖另一个角色
场景:部署 Web 服务时,需要先确保防火墙放行 80 端口。这时可以让apache
角色依赖firewall
角色,实现自动部署防火墙并配置规则。
实验流程:
- 修改
apache
角色的元信息,添加对firewall
角色的依赖 - 创建
firewall
角色,配置其任务和默认变量 - 执行 Playbook,验证依赖是否生效(防火墙自动配置并放行 http)
步骤细化:
- 配置
apache
角色的依赖(roles/apache/meta/main.yml
)
---
galaxy_info:
# 省略其他配置...
dependencies:
- role: firewall # 依赖firewall角色
service: http # 传递变量给firewall角色(放行http服务)
- 创建
firewall
角色并配置
# 初始化firewall角色,存放于roles目录
[bq@controller web]$ ansible-galaxy init firewall --init-path=./roles
- firewall was created successfully
# 进入firewall角色目录
[bq@controller web]$ cd roles/firewall/
- 配置
tasks/main.yml
(防火墙任务):
---
# tasks file for firewall:配置防火墙并放行指定服务
# 安装firewalld软件
- name: install firewalld
yum:
name: firewalld
state: latest
# 启动firewalld并设置开机自启
- name: start and enable firewalld
service:
name: firewalld
state: started
enabled: true
# 放行指定服务(服务名通过变量service传递)
- name: allow service through firewall
firewalld:
state: enabled # 永久生效
immediate: true # 立即生效(无需重启服务)
permanent: true # 永久配置(重启后不失效)
service: "{{ service }}" # 服务名(从依赖传递的变量)
- 配置
defaults/main.yml
(防火墙默认变量):
---
# defaults file for firewall:默认放行ssh服务(如未传递变量则使用此值)
service: ssh
- 验证依赖效果
再次执行部署apache
角色的 Playbook,Ansible 会先自动执行firewall
角色,再执行apache
角色:
# 执行Playbook
[bq@controller web]$ ansible-playbook deploy_apache.yml
# 验证:查看node1的防火墙规则,确认http服务已放行
[bq@controller web]$ ansible node1 -m shell -a "firewall-cmd --list-services"
# 预期输出包含http
任务执行顺序:pre_tasks、roles、tasks、post_tasks
Ansible 中,任务的执行顺序是固定的,与 Playbook 中编写的顺序无关:
pre_tasks
:在角色执行前运行的任务roles
:角色中的任务tasks
:Playbook 中直接定义的任务post_tasks
:在所有任务和角色执行后运行的任务
实验验证:
- 创建测试角色
test_task_exec_order
# 初始化角色
[bq@controller web]$ ansible-galaxy init test_task_exec_order --init-path=roles
# 配置角色任务(roles/test_task_exec_order/tasks/main.yml)
[bq@controller web]$ vim roles/test_task_exec_order/tasks/main.yml
---
# 角色中的任务:输出提示信息
- name: task in role
shell: echo 'task in role'
- 编写测试 Playbook(
test_order.yml
)
---
- name: test task execute order # 测试任务执行顺序
hosts: node1
gather_facts: false # 关闭facts收集(简化输出)
pre_tasks:
- name: task in pre_tasks
shell: echo 'task in pre_tasks' # 输出pre_tasks提示
roles:
- test_task_exec_order # 调用测试角色
tasks:
- name: task in tasks
shell: echo 'task in tasks' # 输出tasks提示
post_tasks:
- name: task in post_tasks
shell: echo 'task in post_tasks' # 输出post_tasks提示
- 执行并查看顺序
[bq@controller web]$ ansible-playbook test_order.yml
# 预期输出顺序:
# task in pre_tasks → task in role → task in tasks → task in post_tasks
Handlers 执行顺序:何时触发处理程序?
处理程序(Handlers)的执行顺序与任务阶段相关,规则如下:
- 执行
pre_tasks
→ 触发pre_tasks
中的 Handlers - 执行
roles
→ 执行tasks
- 触发
roles
和tasks
中的 Handlers - 执行
post_tasks
→ 触发post_tasks
中的 Handlers
实验验证:
- 修改测试角色,添加 Handlers(
roles/test_task_exec_order/handlers/main.yml
)
---
# 角色中的处理程序
- name: role_handler
shell: echo 'handle in role'
- 修改角色任务,触发 Handlers(
roles/test_task_exec_order/tasks/main.yml
)
---
- name: task in role
shell: echo 'task in role'
notify: # 触发角色中的处理程序
- role_handler
- 编写测试 Playbook(
test_handler_order.yml
)
---
- name: test handler execute order
hosts: node1
gather_facts: false
pre_tasks:
- name: task in pre_tasks
shell: echo 'task in pre_tasks'
notify: iamhandler # 触发全局Handlers
roles:
- test_task_exec_order # 角色任务会触发role_handler
tasks:
- name: task in tasks
shell: echo 'task in tasks'
notify: iamhandler # 触发全局Handlers
post_tasks:
- name: task in post_tasks
shell: echo 'task in post_tasks'
notify: iamhandler # 触发全局Handlers
handlers: # 全局处理程序
- name: iamhandler
shell: echo 'iamhandler'
- 执行并查看顺序
[bq@controller web]$ ansible-playbook test_handler_order.yml
# 预期输出顺序:
# 1. task in pre_tasks → 2. iamhandler(pre_tasks触发)
# 3. task in role → 4. task in tasks
# 5. role_handler(角色触发) → 6. iamhandler(tasks触发)
# 7. task in post_tasks → 8. iamhandler(post_tasks触发)
include_role 和 import_role:在任务中动态调用角色
这两个模块用于在tasks
中调用角色(而非直接在roles
中定义),支持when
条件判断,灵活度更高。
import_role
:在 Playbook 解析阶段加载角色(类似 “静态导入”),即使when
条件为false
,也会先解析角色(若角色有语法错误,会直接报错)。include_role
:在任务执行到此时才加载角色(类似 “动态导入”),若when
条件为false
,则不会解析角色。
示例:
---
- hosts: node1
tasks:
- shell: echo 'first task' # 第一个任务
- name: use role in tasks
import_role: # 或include_role
name: hello # 调用hello角色
when: ansible_hostname == 'node1' # 仅在node1上执行
Ansible 角色优势与开发最佳实践
优势
- 代码复用:一次编写,多处调用(如部署多个 Web 服务器时直接复用同一角色)。
- 并行开发:不同管理员可分别开发不同角色(如一人写数据库角色,一人写 Web 角色)。
- 易于维护:角色结构标准化,便于多人协作和后期修改。
开发最佳实践
- 不存储敏感信息:密码、SSH 密钥等敏感数据应通过变量传递(如用 Ansible Vault 加密),而非硬编码到角色中。
- 精简目录:用
ansible-galaxy init
创建角色后,删除不需要的目录(如无需测试可删除tests
)。 - 完善文档:维护
README.md
(说明用法)和meta/main.yml
(说明作者、依赖等)。 - 功能单一:一个角色专注于一件事(如 “部署 Apache” 而非 “部署 Apache+MySQL+PHP”)。
- 避免重复:优先重构现有角色以支持新场景,而非创建新角色。
使用系统角色(以 RHEL 为例)
系统角色介绍
RHEL 系统提供了rhel-system-roles
软件包,包含标准化的 Ansible 角色(如时间同步、SELinux 配置等),可跨 RHEL 6/7/8 版本使用,无需担心不同版本的配置差异。
安装与查看
# 安装系统角色
[bq@controller ~]$ sudo yum install -y rhel-system-roles
# 系统角色存放路径(Ansible默认会搜索此目录)
[bq@controller ~]$ ls -1 /usr/share/ansible/roles/
# 查看角色文档(每个角色的用法和变量说明)
[bq@controller ~]$ ls /usr/share/doc/rhel-system-roles/
案例 1:用 timesync 角色配置时间同步
rhel-system-roles.timesync
角色可统一配置 NTP 时间同步,支持 RHEL 各版本。
Playbook 示例:
---
- name: configure time synchronization
hosts: node1
vars:
# 指定NTP服务器(classroom.example.com),启用快速同步(iburst: yes)
timesync_ntp_servers:
- hostname: classroom.example.com
iburst: yes
timezone: "Asia/Shanghai" # 设置时区为上海
roles:
- rhel-system-roles.timesync # 调用时间同步角色
tasks:
# 额外任务:设置时区
- name: set timezone
timezone:
name: "{{ timezone }}"
案例 2:用 selinux 角色配置 SELinux
rhel-system-roles.selinux
角色可简化 SELinux 配置(如模式切换、布尔值设置等)。
Playbook 示例:
---
- hosts: node1
vars:
selinux_state: permissive # 设置SELinux为宽容模式(仅记录不阻止)
# 启用httpd访问家目录的布尔值
selinux_booleans:
- name: 'httpd_enable_homedirs'
state: 'on'
persistent: 'yes' # 永久生效
# 设置/srv/www目录的SELinux上下文为httpd_sys_content_t(Web内容)
selinux_fcontexts:
- target: '/srv/www(/.*)?'
setype: 'httpd_sys_content_t'
state: 'present'
# 对/srv/www目录执行restorecon(恢复SELinux上下文)
selinux_restore_dirs:
- /srv/www
# 允许TCP 82端口使用http_port_t类型(支持HTTP服务)
selinux_ports:
- ports: '82'
setype: 'http_port_t'
proto: 'tcp'
state: 'present'
roles:
- rhel-system-roles.selinux # 调用SELinux角色
使用 Ansible Galaxy 获取公共角色
Ansible Galaxy(galaxy.ansible.com)是社区维护的角色仓库,提供大量现成角色(如部署 Docker、Nginx 等),可直接下载使用。
常用ansible-galaxy
命令
命令 | 作用 |
---|---|
ansible-galaxy list | 查看本地已安装的角色 |
ansible-galaxy search 关键词 | 搜索 Galaxy 仓库中的角色 |
ansible-galaxy info 角色名 | 查看角色详情(作者、用法等) |
ansible-galaxy install 角色名 | 安装角色到本地 |
实战:用 Galaxy 角色部署 Web 集群
需求:
- node3 作为负载均衡器(haproxy)
- node1 和 node2 作为 Web 服务器(apache)
实验流程:
- 配置主机清单,区分负载均衡器和 Web 服务器
- 从 Galaxy 下载
geerlingguy.haproxy
(负载均衡)和geerlingguy.apache
(Web 服务)角色 - 编写 Playbook,分别部署负载均衡器和 Web 服务器
- 执行并验证集群效果
步骤细化:
- 配置主机清单(
inventory
)
[loadbalancers]
loadbalancer ansible_host=node3 # node3作为负载均衡器
[webservers]
node1 # Web服务器1
node2 # Web服务器2
- 安装 Galaxy 角色
# 安装haproxy角色(用于负载均衡)
[bq@controller web]$ ansible-galaxy install geerlingguy.haproxy
# 安装apache角色(用于Web服务)
[bq@controller web]$ ansible-galaxy install geerlingguy.apache
- 编写部署 Playbook(
deploy_web_cluster.yml
)
---
# 第一个Play:部署负载均衡器(haproxy)
- name: deploy load balancer
hosts: loadbalancers
vars:
# 配置haproxy后端服务器(指向node1和node2的80端口)
haproxy_backend_servers:
- name: node1
address: 10.1.8.11:80 # node1的IP和端口
- name: node2
address: 10.1.8.12:80 # node2的IP和端口
roles:
- geerlingguy.haproxy # 调用haproxy角色
# 第二个Play:部署Web服务器(apache)
- name: deploy web servers
hosts: webservers
roles:
- geerlingguy.apache # 调用apache角色
tasks:
# 生成Web首页(显示当前主机名)
- name: create index.html
copy:
content: "Welcome to {{ ansible_fqdn }}.\n" # 动态内容
dest: /var/www/html/index.html # Apache默认首页路径
- 执行并验证
# 执行Playbook
[bq@controller web]$ ansible-playbook deploy_web_cluster.yaml
# 测试负载均衡效果(多次访问node3,会交替显示node1和node2的页面)
[bq@controller web]$ curl http://node3/
Welcome to node1.bq.cloud. # 第一次输出
[bq@controller web]$ curl http://node3/
Welcome to node2.bq.cloud. # 第二次输出(负载均衡生效)
如涉及版权问题,请联系作者处理!!!!!!