名称: ansible-roles 用户可调用: false 描述: 使用Ansible角色来结构化和重用代码,以实现模块化、可维护的自动化和配置管理。 允许工具: [Bash, Read]
Ansible 角色
使用Ansible角色来结构化和重用自动化代码,以实现模块化、可维护的基础设施。
角色目录结构
一个组织良好的Ansible角色遵循标准化的目录结构:
roles/
└── webserver/
├── README.md
├── defaults/
│ └── main.yml
├── files/
│ ├── nginx.conf
│ └── ssl/
│ ├── cert.pem
│ └── key.pem
├── handlers/
│ └── main.yml
├── meta/
│ └── main.yml
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ ├── configure.yml
│ └── security.yml
├── templates/
│ ├── nginx.conf.j2
│ └── site.conf.j2
├── tests/
│ ├── inventory
│ └── test.yml
└── vars/
└── main.yml
基本角色示例
tasks/main.yml
---
# 主要任务文件,用于webserver角色
- name: 包含操作系统特定变量
include_vars: "{{ ansible_os_family }}.yml"
- name: 导入安装任务
import_tasks: install.yml
tags:
- install
- webserver
- name: 导入配置任务
import_tasks: configure.yml
tags:
- configure
- webserver
- name: 导入安全任务
import_tasks: security.yml
tags:
- security
- webserver
- name: 确保nginx正在运行
service:
name: "{{ nginx_service_name }}"
state: started
enabled: yes
tags:
- service
- webserver
tasks/install.yml
---
# 用于webserver角色的安装任务
- name: 安装nginx及其依赖(Debian/Ubuntu)
apt:
name:
- nginx
- nginx-extras
- python3-passlib
state: present
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: 安装nginx及其依赖(RedHat/CentOS)
yum:
name:
- nginx
- nginx-mod-stream
- python3-passlib
state: present
update_cache: yes
when: ansible_os_family == "RedHat"
- name: 创建nginx目录
file:
path: "{{ item }}"
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_group }}"
mode: '0755'
loop:
- "{{ nginx_conf_dir }}/sites-available"
- "{{ nginx_conf_dir }}/sites-enabled"
- "{{ nginx_log_dir }}"
- "{{ nginx_cache_dir }}"
- /var/www/html
- name: 安装certbot以支持SSL
apt:
name: certbot
state: present
when:
- nginx_ssl_enabled
- ansible_os_family == "Debian"
tasks/configure.yml
---
# 用于webserver角色的配置任务
- name: 部署主要nginx配置
template:
src: nginx.conf.j2
dest: "{{ nginx_conf_dir }}/nginx.conf"
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c %s'
backup: yes
notify:
- Reload nginx
tags:
- config
- name: 部署站点配置
template:
src: site.conf.j2
dest: "{{ nginx_conf_dir }}/sites-available/{{ item.name }}.conf"
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c {{ nginx_conf_dir }}/nginx.conf'
loop: "{{ nginx_sites }}"
when: nginx_sites is defined
notify:
- Reload nginx
- name: 启用站点
file:
src: "{{ nginx_conf_dir }}/sites-available/{{ item.name }}.conf"
dest: "{{ nginx_conf_dir }}/sites-enabled/{{ item.name }}.conf"
state: link
loop: "{{ nginx_sites }}"
when:
- nginx_sites is defined
- item.enabled | default(true)
notify:
- Reload nginx
- name: 禁用默认站点
file:
path: "{{ nginx_conf_dir }}/sites-enabled/default"
state: absent
when: nginx_disable_default_site
notify:
- Reload nginx
- name: 配置日志轮转
template:
src: logrotate.j2
dest: /etc/logrotate.d/nginx
owner: root
group: root
mode: '0644'
tasks/security.yml
---
# 用于webserver角色的安全任务
- name: 生成dhparam文件
command: openssl dhparam -out {{ nginx_conf_dir }}/dhparam.pem 2048
args:
creates: "{{ nginx_conf_dir }}/dhparam.pem"
when: nginx_ssl_enabled
- name: 设置dhparam的安全权限
file:
path: "{{ nginx_conf_dir }}/dhparam.pem"
owner: root
group: root
mode: '0600'
when: nginx_ssl_enabled
- name: 配置防火墙规则(ufw)
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "80"
- "443"
when:
- nginx_configure_firewall
- ansible_os_family == "Debian"
- name: 配置防火墙规则(firewalld)
firewalld:
service: "{{ item }}"
permanent: yes
state: enabled
immediate: yes
loop:
- http
- https
when:
- nginx_configure_firewall
- ansible_os_family == "RedHat"
- name: 创建基本认证文件
htpasswd:
path: "{{ nginx_conf_dir }}/.htpasswd"
name: "{{ item.username }}"
password: "{{ item.password }}"
owner: root
group: "{{ nginx_group }}"
mode: '0640'
loop: "{{ nginx_basic_auth_users }}"
when: nginx_basic_auth_users is defined
no_log: yes
角色变量
defaults/main.yml
---
# 用于webserver角色的默认变量
# 这些变量可以在playbooks或inventory中被覆盖
# 包和服务名称
nginx_package_name: nginx
nginx_service_name: nginx
# 用户和组
nginx_user: www-data
nginx_group: www-data
# 目录
nginx_conf_dir: /etc/nginx
nginx_log_dir: /var/log/nginx
nginx_cache_dir: /var/cache/nginx
nginx_pid_file: /var/run/nginx.pid
# 主要配置
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 10m
# 性能调优
nginx_sendfile: on
nginx_tcp_nopush: on
nginx_tcp_nodelay: on
nginx_gzip: on
nginx_gzip_types:
- text/plain
- text/css
- application/json
- application/javascript
- text/xml
- application/xml
# 安全
nginx_ssl_enabled: no
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
nginx_ssl_prefer_server_ciphers: on
nginx_disable_default_site: yes
nginx_configure_firewall: yes
nginx_server_tokens: off
# 站点配置
nginx_sites: []
# 示例:
# nginx_sites:
# - name: example.com
# server_name: example.com www.example.com
# root: /var/www/example.com
# enabled: yes
# ssl: yes
# 基本认证
# nginx_basic_auth_users:
# - username: admin
# password: secretpassword
vars/main.yml
---
# 不应被覆盖的变量
nginx_conf_path: "{{ nginx_conf_dir }}/nginx.conf"
nginx_error_log: "{{ nginx_log_dir }}/error.log"
nginx_access_log: "{{ nginx_log_dir }}/access.log"
# 通过include_vars加载的操作系统特定覆盖
vars/Debian.yml
---
nginx_user: www-data
nginx_group: www-data
nginx_conf_dir: /etc/nginx
nginx_service_name: nginx
vars/RedHat.yml
---
nginx_user: nginx
nginx_group: nginx
nginx_conf_dir: /etc/nginx
nginx_service_name: nginx
角色处理器
handlers/main.yml
---
# 用于webserver角色的处理器
- name: 重新加载nginx
service:
name: "{{ nginx_service_name }}"
state: reloaded
listen: "reload nginx"
- name: 重启nginx
service:
name: "{{ nginx_service_name }}"
state: restarted
listen: "restart nginx"
- name: 验证nginx配置
command: nginx -t
changed_when: false
listen: "validate nginx"
- name: 重新加载防火墙
service:
name: ufw
state: reloaded
when: ansible_os_family == "Debian"
listen: "reload firewall"
角色模板
templates/nginx.conf.j2
# {{ ansible_managed }}
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid {{ nginx_pid_file }};
events {
worker_connections {{ nginx_worker_connections }};
use epoll;
multi_accept on;
}
http {
##
# 基本设置
##
sendfile {{ nginx_sendfile | ternary('on', 'off') }};
tcp_nopush {{ nginx_tcp_nopush | ternary('on', 'off') }};
tcp_nodelay {{ nginx_tcp_nodelay | ternary('on', 'off') }};
keepalive_timeout {{ nginx_keepalive_timeout }};
types_hash_max_size 2048;
server_tokens {{ nginx_server_tokens | ternary('on', 'off') }};
client_max_body_size {{ nginx_client_max_body_size }};
include {{ nginx_conf_dir }}/mime.types;
default_type application/octet-stream;
##
# SSL 设置
##
{% if nginx_ssl_enabled %}
ssl_protocols {{ nginx_ssl_protocols }};
ssl_ciphers {{ nginx_ssl_ciphers }};
ssl_prefer_server_ciphers {{ nginx_ssl_prefer_server_ciphers | ternary('on', 'off') }};
ssl_dhparam {{ nginx_conf_dir }}/dhparam.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
{% endif %}
##
# 日志设置
##
access_log {{ nginx_access_log }};
error_log {{ nginx_error_log }};
##
# Gzip 设置
##
gzip {{ nginx_gzip | ternary('on', 'off') }};
{% if nginx_gzip %}
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types {{ nginx_gzip_types | join(' ') }};
{% endif %}
##
# 虚拟主机配置
##
include {{ nginx_conf_dir }}/sites-enabled/*;
}
templates/site.conf.j2
# {{ ansible_managed }}
# 站点: {{ item.name }}
{% if item.ssl | default(false) %}
# 重定向HTTP到HTTPS
server {
listen 80;
listen [::]:80;
server_name {{ item.server_name }};
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {{ item.server_name }};
ssl_certificate {{ item.ssl_cert | default('/etc/letsencrypt/live/' + item.name + '/fullchain.pem') }};
ssl_certificate_key {{ item.ssl_key | default('/etc/letsencrypt/live/' + item.name + '/privkey.pem') }};
{% else %}
server {
listen 80;
listen [::]:80;
server_name {{ item.server_name }};
{% endif %}
root {{ item.root | default('/var/www/' + item.name) }};
index {{ item.index | default('index.html index.htm') }};
{% if item.access_log is defined %}
access_log {{ item.access_log }};
{% endif %}
{% if item.error_log is defined %}
error_log {{ item.error_log }};
{% endif %}
{% if item.basic_auth | default(false) %}
auth_basic "限制访问";
auth_basic_user_file {{ nginx_conf_dir }}/.htpasswd;
{% endif %}
location / {
{% if item.proxy_pass is defined %}
proxy_pass {{ item.proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
{% else %}
try_files $uri $uri/ =404;
{% endif %}
}
{% if item.locations is defined %}
{% for location in item.locations %}
location {{ location.path }} {
{% if location.proxy_pass is defined %}
proxy_pass {{ location.proxy_pass }};
{% endif %}
{% if location.alias is defined %}
alias {{ location.alias }};
{% endif %}
{% if location.return is defined %}
return {{ location.return }};
{% endif %}
}
{% endfor %}
{% endif %}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
角色依赖
meta/main.yml
---
galaxy_info:
role_name: webserver
author: 您的组织
description: 安装和配置nginx web服务器
company: 您的公司
license: MIT
min_ansible_version: 2.9
platforms:
- name: Ubuntu
versions:
- focal
- jammy
- name: Debian
versions:
- buster
- bullseye
- name: EL
versions:
- 7
- 8
- 9
galaxy_tags:
- web
- nginx
- webserver
- http
dependencies:
- role: common
vars:
common_packages:
- curl
- wget
- role: firewall
when: nginx_configure_firewall
allow_duplicates: no
在Playbooks中使用角色
简单角色使用
---
- name: 配置web服务器
hosts: webservers
become: yes
roles:
- webserver
带变量的角色
---
- name: 使用自定义设置配置web服务器
hosts: webservers
become: yes
roles:
- role: webserver
vars:
nginx_worker_processes: 4
nginx_ssl_enabled: yes
nginx_sites:
- name: example.com
server_name: example.com www.example.com
root: /var/www/example.com
ssl: yes
ssl_cert: /etc/ssl/certs/example.com.crt
ssl_key: /etc/ssl/private/example.com.key
带标签的角色
---
- name: 配置基础设施
hosts: all
become: yes
roles:
- role: webserver
tags:
- web
- nginx
- role: database
tags:
- db
- postgres
前和后任务
---
- name: 部署web应用程序
hosts: webservers
become: yes
pre_tasks:
- name: 宣布部署
debug:
msg: "开始部署到 {{ inventory_hostname }}"
- name: 检查磁盘空间
command: df -h /
register: disk_space
changed_when: false
roles:
- webserver
- monitoring
post_tasks:
- name: 验证nginx是否响应
uri:
url: http://localhost
status_code: 200
retries: 3
delay: 5
- name: 通知完成
debug:
msg: "部署成功完成"
角色包含方法
静态导入
---
- name: 配置服务器
hosts: all
tasks:
- name: 导入webserver角色
import_role:
name: webserver
vars:
nginx_worker_processes: 2
tags:
- webserver
动态包含
---
- name: 基于条件配置服务器
hosts: all
tasks:
- name: 为web服务器包含webserver角色
include_role:
name: webserver
when: "'webservers' in group_names"
- name: 为数据库服务器包含database角色
include_role:
name: database
when: "'databases' in group_names"
使用任务文件导入
---
- name: 自定义安装工作流
hosts: webservers
tasks:
- name: 运行预安装检查
import_role:
name: webserver
tasks_from: preflight
- name: 安装nginx
import_role:
name: webserver
tasks_from: install
- name: 配置nginx
import_role:
name: webserver
tasks_from: configure
使用Ansible Galaxy创建角色
初始化新角色
# 创建角色结构
ansible-galaxy init roles/myapp
# 使用自定义模板创建角色
ansible-galaxy init --init-path roles/ myapp
# 列出角色文件
tree roles/myapp
从Galaxy安装角色
# 安装角色
ansible-galaxy install geerlingguy.nginx
# 安装特定版本
ansible-galaxy install geerlingguy.nginx,2.8.0
# 从requirements文件安装
ansible-galaxy install -r requirements.yml
requirements.yml
---
# 从Ansible Galaxy安装
- name: geerlingguy.nginx
version: 2.8.0
- name: geerlingguy.postgresql
version: 3.4.0
# 从Git仓库安装
- name: custom-app
src: https://github.com/yourorg/ansible-role-custom-app.git
scm: git
version: main
# 从本地路径安装
- name: internal-role
src: /path/to/roles/internal-role
高级角色模式
带多个入口点的角色
# roles/webserver/tasks/main.yml
---
- name: 默认任务流
import_tasks: "{{ webserver_task_flow | default('standard') }}.yml"
# roles/webserver/tasks/standard.yml
---
- import_tasks: install.yml
- import_tasks: configure.yml
- import_tasks: security.yml
# roles/webserver/tasks/minimal.yml
---
- import_tasks: install.yml
- import_tasks: configure.yml
条件角色执行
---
- name: 使用条件角色配置服务器
hosts: all
become: yes
roles:
- role: webserver
when:
- ansible_os_family == "Debian"
- inventory_hostname in groups['webservers']
- role: webserver-nginx
when: webserver_type == "nginx"
- role: webserver-apache
when: webserver_type == "apache"
嵌套角色依赖
# roles/application/meta/main.yml
---
dependencies:
- role: webserver
vars:
nginx_sites:
- name: "{{ app_name }}"
server_name: "{{ app_domain }}"
proxy_pass: "http://localhost:{{ app_port }}"
- role: database
vars:
db_name: "{{ app_db_name }}"
db_user: "{{ app_db_user }}"
- role: monitoring
vars:
monitor_services:
- nginx
- "{{ app_name }}"
何时使用此技能
使用ansible-roles技能当您需要:
- 在多个playbooks和项目之间结构化和重用自动化代码
- 实现模块化基础设施即代码,具有清晰的职责分离
- 在团队或项目之间共享自动化逻辑
- 为通用基础设施模式创建可分发的自动化包
- 将复杂的playbooks组织成可管理、可测试的组件
- 使用变量优先级实现基于角色的配置管理
- 构建具有角色依赖的分层基础设施
- 独立于playbooks进行版本控制自动化逻辑
- 为一致性创建标准化的基础设施组件
- 通过可重用角色实现安全和合规要求
- 为您的组织构建内部自动化库
- 从Ansible Galaxy贡献或使用社区自动化
- 在集成之前独立测试基础设施组件
- 为开发、测试和生产实现不同的配置
- 通过角色元数据创建自文档化的基础设施
最佳实践
- 遵循标准目录结构 - 使用ansible-galaxy init以适当的组织创建角色
- 明智地使用默认值 - 将可覆盖的变量放在defaults/main.yml中,不可覆盖的放在vars/main.yml中
- 充分文档化 - 包括全面的README.md,带有使用示例和变量文档
- 保持角色专注 - 每个角色应具有单一、明确定义的目的
- 使用角色依赖 - 在meta/main.yml中声明依赖,而不是在playbooks中
- 适当标记 - 为任务应用有意义的标签,以便选择性执行
- 实现幂等性 - 确保角色可以安全运行多次
- 版本化您的角色 - 使用语义版本控制进行角色发布
- 独立测试角色 - 在tests/目录中包含测试playbooks
- 使用模板进行配置 - 为提高灵活性,优先使用Jinja2模板而非静态文件
- 实现操作系统检测 - 使用ansible_os_family实现跨平台兼容性
- 保护敏感数据 - 在角色变量中使用ansible-vault存储密码和秘密
- 正确使用处理器 - 仅当配置更改时通知处理器
- 验证配置 - 在template/copy模块中使用validate参数
- 清晰命名任务 - 使用描述性名称,解释每个任务的作用
常见陷阱
- 过于复杂的角色 - 试图让一个角色做太多事情
- 变量命名不当 - 使用与其他角色冲突的通用名称
- 缺少角色前缀 - 未在角色变量前添加角色名称
- 忽略变量优先级 - 不理解Ansible如何解决变量冲突
- 硬编码值 - 嵌入环境特定的值,而不是使用变量
- 缺少依赖声明 - 未在meta/main.yml中声明角色依赖
- 无验证 - 部署配置时没有验证检查
- 跳过测试 - 未包括测试playbooks或场景
- 处理器设计不当 - 不必要地或不重启服务
- 缺少操作系统支持 - 假设所有目标系统相同
- 无备份策略 - 更改前不备份配置
- 忽略幂等性 - 使用command/shell模块时没有适当的防护
- 缺少标签 - 未标记任务以便选择性执行
- 模板实践不当 - 在模板中使用复杂逻辑而非变量
- 无版本控制 - 不进行角色版本控制或跟踪更改