Ansible 自动化运维:从入门到实战
日期 : 2026-05-31
适用读者 : 运维工程师 / DevOps / SRE
前置知识 : Linux 基础、SSH、YAML 语法
实战环境: 4台华为云ECS (Ubuntu 24.04, Kernel 6.8, 2vCPU/3.3GB)
📖 目录
- [一、Ansible 基础篇](#一、Ansible 基础篇)
- [1.1 什么是 Ansible](#1.1 什么是 Ansible)
- [1.2 核心架构与设计哲学](#1.2 核心架构与设计哲学)
- [1.3 安装与环境搭建](#1.3 安装与环境搭建)
- [1.4 目录结构与配置文件](#1.4 目录结构与配置文件)
- [1.5 Inventory 主机清单](#1.5 Inventory 主机清单)
- [1.6 Ad-hoc 命令实战](#1.6 Ad-hoc 命令实战)
- 二、核心能力进阶篇
- [2.1 Playbook 编写规范](#2.1 Playbook 编写规范)
- [2.2 Variables 变量体系](#2.2 Variables 变量体系)
- [2.3 Jinja2 模板引擎](#2.3 Jinja2 模板引擎)
- [2.4 Roles 角色与复用](#2.4 Roles 角色与复用)
- [2.5 模块深入详解](#2.5 模块深入详解)
- [2.6 错误处理与调试](#2.6 错误处理与调试)
- [三、高级与 CI/CD 集成篇](#三、高级与 CI/CD 集成篇)
- [3.1 Ansible Vault 加密](#3.1 Ansible Vault 加密)
- [3.2 动态 Inventory](#3.2 动态 Inventory)
- [3.3 复杂工作流](#3.3 复杂工作流)
- [3.4 CI/CD 集成](#3.4 CI/CD 集成)
- [3.5 Ansible Collection](#3.5 Ansible Collection)
- 四、实战部署案例
- 五、变量优先级速查
- 六、常见问题与排查
- 附录
一、Ansible 基础篇
1.1 什么是 Ansible
Ansible 是一个开源的 IT 自动化工具,由 Michael DeHaan 于 2012 年创建,2015 年被 Red Hat 收购。它的名字来源于科幻小说《安德的游戏》中一种超光速通讯装置,寓意着"即时、无延迟的通信"。
Ansible 能做什么:
- 配置管理:批量管理服务器配置(替代手动 SSH)
- 应用部署:自动化部署 Web 应用、数据库等
- 任务编排:多步骤复杂操作的工作流编排
- 云资源管理:通过模块管理 AWS/Azure/阿里云等资源
1.2 核心架构与设计哲学
架构图
┌──────────────────────────────────────────────────────┐
│ Ansible 控制节点 (Control Node) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Inventory │ │ Playbook │ │ ansible.cfg │ │
│ │ (主机清单) │ │ (剧本) │ │ (配置文件) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │ │
│ SSH (默认) / WinRM / API │
│ 无 Agent!无需在被控端安装 │
│ │ │
└────────────────────────┼──────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Web-01 │ │ Web-02 │ │ DB-01 │
│ (被控端) │ │ (被控端) │ │ (被控端) │
│ Python │ │ Python │ │ Python │
└─────────┘ └─────────┘ └─────────┘
Ansible vs 同类工具对比
| 特性 | Ansible | SaltStack | Puppet | Chef |
|---|---|---|---|---|
| 架构 | 无Agent(Agentless) | Master/Minion | Master/Agent | Server/Client |
| 语言 | YAML | YAML/Jinja2 | Puppet DSL | Ruby DSL |
| 学习曲线 | ⭐ 简单 | ⭐⭐ 中等 | ⭐⭐⭐ 较陡 | ⭐⭐⭐ 较陡 |
| 通信协议 | SSH (Push) | ZeroMQ (Pull/Push) | HTTPS (Pull) | HTTPS (Pull) |
| 性能 | 中等(适合百台级) | 高(万级) | 中(千级) | 中 |
| 幂等性 | ✅ 内置 | ✅ 内置 | ✅ 内置 | ✅ 内置 |
| 社区生态 | 最大(Galaxy) | 中等 | 大(Forge) | 大 |
💡 核心设计哲学:
- 无代理(Agentless):只需在被控端安装 Python,通过 SSH 通信,无需额外守护进程
- 幂等性(Idempotence):同一操作重复多次,结果不变。已安装的包不会重复安装
- 声明式(Declarative):描述"期望状态"而非"执行步骤",Ansible 负责收敛到目标状态
- 可读性(Readability):使用 YAML 语法,配置文件即文档
1.3 安装与环境搭建
控制节点安装
bash
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y ansible
# CentOS/RHEL 8+
sudo dnf install -y epel-release
sudo dnf install -y ansible
# 验证安装
ansible --version
💡 命令详解:
apt-get update:刷新软件包索引,确保获取最新版本信息-y:自动确认所有提示,非交互模式(CI/CD 必备)ansible --version:显示 Ansible 版本、配置文件路径、Python 版本、模块搜索路径
实战环境信息
控制节点: ecs-ab79-0001 (192.168.0.120)
├── web-01: ecs-ab79-0002 (192.168.0.213) --- Nginx 8080端口
├── web-02: ecs-ab79-0003 (192.168.0.198) --- Nginx 80端口
└── db-01: ecs-ab79-0004 (192.168.0.203) --- Nginx 8088端口
全部: Ubuntu 24.04 / Kernel 6.8.0-106 / Python 3.12
Ansible版本: core 2.16.3
SSH 免密登录配置
bash
# 1. 在控制节点生成 SSH 密钥(推荐 Ed25519 算法)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N '' -q
# 2. 将公钥分发到所有被控节点
ssh-copy-id root@192.168.0.213
ssh-copy-id root@192.168.0.198
ssh-copy-id root@192.168.0.203
# 3. 测试连通性
ssh root@192.168.0.213 'hostname'
💡 命令详解:
-t ed25519:使用 Ed25519 椭圆曲线算法,比 RSA 更安全且密钥更短-f ~/.ssh/id_ed25519:指定密钥文件路径-N '':不设置密码短语(passphrase),方便自动化(生产环境建议加密码+ssh-agent)-q:静默模式,减少输出- 为什么用 Ed25519 而不是 RSA:Ed25519 密钥仅 256 位,安全性却与 3072 位 RSA 相当,签名/验证速度更快
1.4 目录结构与配置文件
ansible.cfg 配置文件
ini
[defaults]
inventory = /ansible-project/inventory/hosts.ini # 主机清单路径
host_key_checking = False # 首次连接不提示确认
retry_files_enabled = False # 不生成 .retry 重试文件
gathering = smart # 智能收集facts(仅当需要时)
fact_caching = jsonfile # facts缓存(避免重复收集)
fact_caching_connection = /tmp/ansible_cache
fact_caching_timeout = 3600
stdout_callback = yaml # 输出格式化为YAML
display_skipped_hosts = False # 不显示跳过的host
roles_path = /ansible-project/roles # 角色搜索路径
[privilege_escalation]
become = True # 默认提权
become_method = sudo # 使用sudo
become_user = root # 提权到root
become_ask_pass = False # 不询问密码
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s # SSH连接复用
pipelining = True # 管道模式(减少SSH连接数)
💡 参数详解:
host_key_checking = False:生产环境建议保持True,手动ssh-keyscan收集 host keygathering = smart:implicit始终收集,explicit仅当gather_facts: true时收集,smart根据缓存决定fact_caching = jsonfile:将收集到的 facts 缓存到 JSON 文件,避免每次 Play 都重新收集,显著加速stdout_callback = yaml:将输出格式化为 YAML 缩进风格,比默认的平铺格式更易读pipelining = True:将一个 Python 脚本通过 SSH 管道传送到远端执行,省去多次 SSH 连接的 SCP 操作。注意:需要远端/etc/sudoers中禁用requirettyControlMaster=auto:SSH 连接复用,同一会话共享 SSH 连接,减少握手开销。ControlPersist=60s表示空闲 60 秒后关闭隧道
项目目录结构最佳实践
ansible-project/
├── ansible.cfg # Ansible 配置文件
├── inventory/ # 主机清单
│ ├── hosts.ini # 静态清单
│ ├── group_vars/ # 组变量
│ │ ├── all.yml # 所有主机共用
│ │ ├── web_servers.yml # Web服务器组
│ │ └── db_servers.yml # DB服务器组
│ └── host_vars/ # 主机变量(覆盖组变量)
│ ├── web-01.yml
│ └── web-02.yml
├── playbooks/ # 剧本目录
│ ├── deploy_web.yml # 部署Web服务
│ └── site.yml # 入口Playbook(调用Roles)
├── roles/ # 角色目录
│ ├── common/ # 通用配置角色
│ │ ├── defaults/main.yml # 默认变量(最低优先级)
│ │ ├── vars/main.yml # 角色变量(高优先级)
│ │ ├── tasks/main.yml # 任务列表
│ │ ├── handlers/main.yml # 事件处理器
│ │ ├── templates/ # Jinja2模板
│ │ ├── files/ # 静态文件
│ │ └── meta/main.yml # 元数据+依赖
│ └── tasks/ # Web部署角色
│ ├── defaults/main.yml
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ ├── templates/
│ └── meta/main.yml
├── templates/ # 全局模板
│ ├── nginx.conf.j2
│ └── index.html.j2
└── files/ # 全局静态文件
1.5 Inventory 主机清单
Inventory 是 Ansible 管理主机的"花名册",定义了哪些主机被管理以及如何组织它们。
静态 Inventory (hosts.ini)
ini
# [组名] 定义主机组
[web_servers]
web-01 ansible_host=192.168.0.213
web-02 ansible_host=192.168.0.198
[db_servers]
db-01 ansible_host=192.168.0.203
# [组:children] 定义父子关系
[all_servers:children]
web_servers
db_servers
# [组:vars] 定义组级别变量
[all:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3
💡 参数详解:
ansible_host:目标主机的 IP 地址或域名。如果不指定,默认使用 Inventory 名称(如web-01)作为主机名ansible_user:SSH 连接使用的用户名,默认使用执行 Ansible 的当前用户ansible_python_interpreter:指定远端 Python 解释器路径。Ubuntu 24.04 中默认的/usr/bin/python可能不存在,需要明确指向/usr/bin/python3[组:children]:组嵌套 语法,定义多个组的并集。all_servers包含web_servers和db_servers中的所有主机[组:vars]:组级别变量,作用于该组内所有主机。可被host_vars覆盖
变量优先级可视化
高 ▲
│ extra vars (-e) 命令行传入,最高优先级
│ host_vars/ 主机变量
│ group_vars/all.yml 组变量
│ play vars Play级别变量
│ role vars Role变量
│ role defaults Role默认值
低 ▼ inventory vars Inventory内置变量
1.6 Ad-hoc 命令实战
Ad-hoc 命令是 Ansible 的"一次性"命令,适合快速查询和简单操作,无需编写 Playbook。
基本语法
bash
ansible <目标> -m <模块> -a <参数>
💡
-m指定模块名(如ping、command、shell、setup),-a传递模块参数
实战演示
bash
# 1. ping 模块 --- 测试连通性(不是 ICMP ping!)
ansible all_servers -m ping
💡 模块详解 ---
ping:Ansible 的
ping模块不是 ICMP 协议的网络 ping,而是应用层的心跳检测:
- 它通过 SSH 连接到目标主机
- 在远端执行一个最小的 Python 脚本
- 验证 Python 环境可用、SSH 连接正常、权限足够
- 返回
"ping": "pong"表示一切正常- 这是测试 Ansible 环境是否可用的最快方式
实测输出:
yaml
web-01 | SUCCESS => {
"changed": false, # 幂等性:ping 不会改变系统状态
"ping": "pong"
}
db-01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
bash
# 2. setup 模块 --- 收集系统信息(Facts)
ansible web_servers -m setup -a 'filter=ansible_distribution*'
💡 模块详解 ---
setup:
- 收集目标主机的大量系统信息 (OS、CPU、内存、网络、磁盘等),称为 Facts
filter参数支持通配符过滤,ansible_distribution*只返回发行版相关信息- Facts 数据可在 Playbook 中通过
{``{ ansible_facts.xxx }}或直接{``{ ansible_distribution }}引用- 生产环境建议开启
fact_caching减少重复收集
实测输出:
yaml
web-01 | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "Ubuntu",
"ansible_distribution_major_version": "24",
"ansible_distribution_release": "noble",
"ansible_distribution_version": "24.04"
}
}
bash
# 3. shell 模块 --- 执行任意 Shell 命令
ansible all_servers -m shell -a 'free -h | head -2'
💡 模块详解 ---
shellvscommand:
command:直接执行命令,不支持管道、重定向、变量展开等 Shell 特性shell:通过/bin/sh执行,支持完整 Shell 语法(管道、重定向、通配符)- 安全提示 :
shell存在命令注入风险,优先使用专用模块(如apt、service、file)command更安全:不经过 Shell 解析,参数直接传给可执行文件
bash
# 4. 幂等性验证 --- 多次执行相同命令
ansible all_servers -m apt -a 'name=nginx state=present'
# 第一次: changed=true (执行安装)
# 第二次: changed=false (已安装,跳过)
💡 幂等性(Idempotence):Ansible 模块的核心特性。执行前会先检查当前状态是否等于目标状态:
apt name=nginx state=present→ 先检查dpkg -l nginx,已安装则changed: falsefile state=directory→ 先检查目录是否存在,已存在则changed: false- 幂等性保证了安全重复执行,不会因为重跑而产生副作用
二、核心能力进阶篇
2.1 Playbook 编写规范
Playbook 是 Ansible 的"剧本"(剧本),定义了要在目标主机上执行的一系列任务。它使用 YAML 格式编写,以 --- 开头。
Playbook 结构层次
Playbook (一个YAML文件)
├── Play (一个 - hosts: 块)
│ ├── pre_tasks (预处理,始终执行)
│ ├── roles (角色调用)
│ ├── tasks (核心任务列表)
│ │ ├── Task 1 (name + module + params + notify)
│ │ ├── Task 2 (conditions/loops/tags)
│ │ └── Task N
│ ├── handlers (事件处理器,被notify触发)
│ └── post_tasks (后处理,始终执行)
└── Play (另一个目标组) ...
完整实战 Playbook 示例
以下是一个覆盖全部核心特性的 Playbook,在 2 台 Web 服务器(Nginx 8080 + 80 端口)上实战部署:
yaml
---
- name: 部署 Web 服务
hosts: web_servers
gather_facts: true # 收集系统信息
vars: # Play级别变量(优先级高)
app_version: "1.0.0"
pre_tasks: # 预处理(始终执行)
- name: 显示部署信息
debug:
msg:
- "部署目标: {{ inventory_hostname }} ({{ ansible_host }})"
- "Nginx端口: {{ nginx_port }}"
- "节点角色: {{ node_role | default('worker') }}"
tags: [always]
tasks: # 核心任务
# Task 1: 条件执行 (when)
- name: 更新apt缓存 (仅Ubuntu)
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_distribution == "Ubuntu"
# Task 2: 循环安装 (loop)
- name: 安装基础软件包
apt:
name: "{{ item }}"
state: present
loop: "{{ common_packages }}"
notify: log_packages_installed
# Task 3: 模板渲染 (Jinja2)
- name: 生成Nginx站点配置
template:
src: /ansible-project/templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}.conf
mode: '0644'
notify: reload_nginx
# Task 4: 文件管理
- name: 启用站点 (符号链接)
file:
src: /etc/nginx/sites-available/{{ app_name }}.conf
dest: /etc/nginx/sites-enabled/{{ app_name }}.conf
state: link
# Task 5: Block + Rescue 错误处理
- name: 健康检查
block:
- name: 验证Nginx配置
command: nginx -t
- name: 启动Nginx服务
service:
name: nginx
state: started
enabled: yes
- name: HTTP健康检查 (重试5次)
uri:
url: "http://localhost:{{ nginx_port }}/health"
return_content: yes
register: health_result
until: health_result.status == 200
retries: 5
delay: 2
rescue: # 失败时的回滚逻辑
- name: 健康检查失败
debug:
msg: "{{ inventory_hostname }} 部署失败!"
# Task 6: 条件执行(仅主节点)
- name: 主节点特殊配置
debug:
msg: "{{ inventory_hostname }} 是主节点"
when: node_role == "primary"
handlers: # 事件处理器(被 notify 触发)
- name: reload_nginx
service:
name: nginx
state: reloaded
- name: log_packages_installed
debug:
msg: "{{ common_packages | join(', ') }} 已安装完毕"
post_tasks: # 后处理(始终执行)
- name: 部署完成摘要
debug:
msg:
- "========================================"
- "{{ inventory_hostname }} 部署完成"
- "访问: http://{{ ansible_host }}:{{ nginx_port }}"
- "========================================"
tags: [always]
💡 关键概念详解:
pre_tasks vs tasks vs post_tasks:
pre_tasks:在 roles 之前执行,且不受 tags 影响 (taggedalways)tasks:核心任务,可以被 tags 选择性执行post_tasks:在 roles 之后执行,同样不受 tags 影响- 三者按
pre_tasks → roles → tasks → post_tasks → handlers顺序执行handlers 触发机制:
- Handler 仅在被
notify通知且对应 task 状态为changed时才执行- 即使被多个 task 通知,handler 只执行一次(去重)
- 所有 tasks 执行完毕后才统一冲刷(flush) handlers
meta: flush_handlers可以在 tasks 中间提前触发 handlerstags 标签系统:
- 为 task 打上标签,运行时可选择执行特定标签的任务
--tags nginx:只运行带有nginx标签的 task--skip-tags packages:跳过带有packages标签的 tasktags: [always]:特殊标签,始终执行,不受--tags/--skip-tags影响
常用执行命令
bash
# 语法检查(不执行)
ansible-playbook playbook.yml --syntax-check
# 列出将影响的主机
ansible-playbook playbook.yml --list-hosts
# 列出所有任务
ansible-playbook playbook.yml --list-tasks
# Dry-run(模拟执行,显示变化但不实际修改)
ansible-playbook playbook.yml --check --diff
# 逐步执行(每个任务都询问确认)
ansible-playbook playbook.yml --step
# 只执行特定标签
ansible-playbook playbook.yml --tags nginx
# 跳过特定标签
ansible-playbook playbook.yml --skip-tags packages
# 从失败点重试(使用 .retry 文件)
ansible-playbook playbook.yml --limit @/path/to/retry
2.2 Variables 变量体系
Ansible 的变量系统极其灵活,支持多种来源和优先级。
变量来源与优先级(从低到高)
| 优先级 | 变量来源 | 示例 | 说明 |
|---|---|---|---|
| 1 (最低) | Role defaults | roles/tasks/defaults/main.yml |
角色默认值,最容易被覆盖 |
| 2 | Inventory vars | hosts.ini 中的 key=value |
文件内直接的变量赋值 |
| 3 | Inventory group_vars/all | group_vars/all.yml |
全局生效 |
| 4 | Inventory group_vars/组名 | group_vars/web_servers.yml |
组级别 |
| 5 | Inventory host_vars/主机 | host_vars/web-01.yml |
主机级别(覆盖组变量) |
| 6 | Playbook group_vars/all | Playbook 同级目录的 group_vars | 项目内全局变量 |
| 7 | Play vars | Play 中的 vars: 块 |
Play 级别 |
| 8 | Play vars_files | Play 中的 vars_files: |
外部变量文件 |
| 9 | Host facts | ansible_facts |
自动收集的系统信息 |
| 10 | Registered vars | register: |
任务执行结果 |
| 11 | Set facts | set_fact: |
运行时动态设置 |
| 12 (最高) | Extra vars (-e) |
-e "version=2.0" |
命令行传入,无人可挡 |
实战变量配置
group_vars/all.yml(全局):
yaml
app_name: ansible-demo
app_user: ubuntu
timezone: Asia/Shanghai
common_packages:
- htop
- curl
- vim
- git
- unzip
group_vars/web_servers.yml(Web 组):
yaml
nginx_port: 80
nginx_server_name: demo.example.com
web_root: /var/www/html
enable_ssl: false
host_vars/web-01.yml(主机覆盖):
yaml
nginx_port: 8080 # web-01 用 8080 而非 80
node_role: primary
💡 变量覆盖实战效果:
web-01的nginx_port最终为 8080(被 host_vars 覆盖)web-02的nginx_port最终为 80(使用 group_vars 默认值)- 同一变量名,
host_vars总是覆盖group_vars
Jinja2 过滤器(Filter)
Ansible 内置 Jinja2 模板引擎,变量可以使用过滤器:
yaml
{{ variable | default('default_value') }} # 默认值
{{ list_var | join(', ') }} # 列表→字符串
{{ 'hello' | upper }} # HELLO
{{ variable | bool }} # 转布尔
{{ dict_var | to_json }} # 转JSON
{{ path | basename }} # 取文件名
{{ string | regex_replace('old', 'new') }} # 正则替换
2.3 Jinja2 模板引擎
Jinja2 是 Python 的模板引擎,Ansible 使用它将变量动态注入到配置文件中。
Nginx 配置模板示例
nginx
# {{ ansible_managed }} # Ansible 自动添加的注释
# 生成时间: {{ ansible_date_time.date }}
# 主机: {{ inventory_hostname }}
server {
listen {{ nginx_port }}; # 变量替换
server_name {{ nginx_server_name }};
root {{ web_root }};
location / {
try_files $uri $uri/ =404; # $ 需要转义为 \$
}
location /health {
access_log off;
return 200 '{"status":"ok","node":"{{ node_role | default("worker") }}"}\n';
}
}
💡 模板要点:
{``{ ansible_managed }}:Ansible 的 "ansible_managed" 变量,自动替换为 "Ansible managed" 标识{``{ ansible_date_time.date }}:Ansible 自动收集的 Facts 变量,当前日期$符号需要写成\$:因为 Jinja2 用{``{ }}语法,$在 Nginx 配置中是变量前缀,需要转义防止被 Jinja2 误解析{``{ node_role | default("worker") }}:使用default过滤器提供回退值
HTML 模板示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>{{ app_name }} - {{ inventory_hostname }}</title></head>
<body>
<h1>{{ app_name }}</h1>
<p>节点: {{ inventory_hostname }} ({{ ansible_hostname }})</p>
<p>IP: {{ ansible_default_ipv4.address }}</p>
<p>系统: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
<p><em>由 Ansible 自动化部署 --- {{ ansible_date_time.iso8601 }}</em></p>
</body>
</html>
💡 常用 Facts 变量:
ansible_hostname:主机名(短名称)ansible_fqdn:完全限定域名ansible_default_ipv4.address:默认 IPv4 地址ansible_distribution:发行版名称(Ubuntu/CentOS)ansible_distribution_version:发行版版本号(24.04)ansible_processor_cores:CPU 核心数ansible_memtotal_mb:总内存(MB)ansible_date_time.iso8601:ISO 8601 格式时间戳
2.4 Roles 角色与复用
Roles 是 Ansible 的"乐高积木"------将 Playbook 拆分为可复用的组件。
Roles 标准目录结构
roles/
└── <role-name>/
├── defaults/main.yml # 默认变量(最低优先级)
├── vars/main.yml # 角色变量(高优先级,不易被覆盖)
├── tasks/main.yml # 任务入口
├── handlers/main.yml # 事件处理器
├── templates/ # Jinja2 模板文件
├── files/ # 静态文件(copy模块使用)
├── meta/main.yml # 元数据 + 依赖声明
└── README.md # 角色说明文档
实战:common 角色(基础系统配置)
defaults/main.yml:
yaml
common_timezone: Asia/Shanghai
common_packages:
- htop
- curl
- vim
- git
- unzip
tasks/main.yml:
yaml
- name: 设置时区
timezone:
name: "{{ common_timezone }}"
- name: 安装基础软件包
apt:
name: "{{ common_packages }}"
state: present
- name: 创建MOTD提示
copy:
content: "Managed by Ansible - Do not manually edit"
dest: /etc/motd
mode: '0644'
- name: 设置系统限制 (nofile)
pam_limits:
domain: '*'
limit_type: '-'
limit_item: nofile
value: 65535
实战:tasks 角色(Web 部署,依赖 common)
meta/main.yml(声明依赖):
yaml
---
dependencies:
- { role: common } # tasks 角色依赖 common 角色
基于 Roles 的 Playbook
yaml
---
- name: 基础系统配置
hosts: all_servers
roles:
- common # common 角色(设置时区+基础包+nofile)
- name: 部署Web应用
hosts: db_servers
roles:
- role: tasks # tasks 角色(部署Nginx)
vars:
app_name: ansible-roles-demo
nginx_port: 8088 # 覆盖角色默认的80端口
💡 Roles 设计原则:
- 单一职责 :每个角色只做一件事(如
common只管基础配置,tasks只管 Web 部署)- 依赖声明 :通过
meta/main.yml声明依赖,Ansible 自动按依赖顺序执行- defaults vs vars :
defaults可被外部覆盖,vars不可(保护角色内部约定)- 变量命名空间 :给变量加角色前缀避免冲突(如
common_timezone而非timezone)
2.5 模块深入详解
Ansible 拥有 3000+ 内置模块,以下是最常用的 10 类:
系统管理模块
| 模块 | 功能 | 示例 |
|---|---|---|
apt / yum / dnf |
包管理 | apt: name=nginx state=present |
service / systemd |
服务管理 | service: name=nginx state=started enabled=yes |
user / group |
用户/组管理 | user: name=app shell=/bin/bash |
cron |
定时任务 | cron: name="backup" minute=0 hour=2 job="/script.sh" |
timezone |
时区设置 | timezone: name=Asia/Shanghai |
文件管理模块
| 模块 | 功能 | 示例 |
|---|---|---|
copy |
拷贝文件 | copy: src=local.txt dest=/remote/path/ |
template |
Jinja2 模板渲染 | template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf |
file |
文件属性管理 | file: path=/dir state=directory mode=0755 |
lineinfile |
行级编辑 | lineinfile: path=/etc/hosts line="10.0.0.1 app" |
blockinfile |
块级编辑 | blockinfile: path=/etc/hosts block="..." marker="# {mark} ANSIBLE" |
💡 copy vs template 区别:
copy:原样拷贝文件,不做任何处理template:将文件作为 Jinja2 模板渲染,变量{``{ }}会被替换,适合配置文件💡 lineinfile vs blockinfile:
lineinfile:确保文件中某一行存在或不存在(添加/删除单行)blockinfile:确保文件中一个代码块存在(标记包围的多行内容,如配置文件段落)
命令执行模块
| 模块 | 功能 | 安全性 |
|---|---|---|
command |
执行命令(无Shell) | ⭐⭐⭐ 安全(无注入风险) |
shell |
执行命令(有Shell) | ⭐ 注意注入 |
raw |
无需Python的原始命令 | ⭐ 注意注入 |
script |
执行本地脚本 | ⭐⭐ 需注意脚本内容 |
💡 command vs shell 深度对比:
command直接exec()执行,不经过/bin/sh,绝对安全但功能受限shell通过/bin/sh -c执行,支持管道|、重定向>、环境变量$VAR- 安全最佳实践 :能用
command就不用shell;必须用shell时,避免用户输入直接拼接- 幂等性缺失 :
command和shell模块不检查状态 ,每次都返回changed: true。需要手动添加creates/removes参数实现幂等
2.6 错误处理与调试
Block + Rescue + Always 结构
yaml
- name: 带容错的部署
block:
- name: 执行可能有风险的操作
command: /some/risky/operation
rescue:
- name: 出错时的补偿操作
debug:
msg: "操作失败!执行回滚。"
failed_when: true # 仍然标记为失败
always:
- name: 无论成败都执行
debug:
msg: "清理临时文件"
💡 Block/Rescue/Always 类比编程:
block=try { ... }rescue=catch { ... }always=finally { ... }- 区别:
rescue中任何 task 失败都会导致整个 play 失败
调试技巧
yaml
# 1. debug 模块输出变量
- debug:
var: health_result # 输出整个变量
- debug:
msg: "端口: {{ nginx_port }}" # 输出格式化消息
# 2. 条件失败
- command: some_check
register: result
failed_when: # 自定义失败条件
- result.rc != 0
- "'ERROR' in result.stdout"
# 3. 忽略错误(谨慎使用)
- command: non_critical_task
ignore_errors: yes # 出错不中断整个Play
# 4. 变更控制
- command: nginx -t
changed_when: false # 始终标记为未变更(只读操作)
# 5. 重试机制
- uri:
url: http://localhost:8080/health
register: result
until: result.status == 200
retries: 10 # 最多重试10次
delay: 5 # 每次间隔5秒
# 6. 详细输出
ansible-playbook playbook.yml -v # 基础信息
ansible-playbook playbook.yml -vv # 任务详情
ansible-playbook playbook.yml -vvv # SSH连接详情
ansible-playbook playbook.yml -vvvv # 完整调试信息
三、高级与 CI/CD 集成篇
3.1 Ansible Vault 加密
Ansible Vault 用于加密 Playbook 中的敏感信息(密码、API Key、证书等)。
基本操作
bash
# 1. 加密已有文件
ansible-vault encrypt secrets.yml
# 交互式输入密码
# 2. 使用密码文件(CI/CD友好)
ansible-vault encrypt secrets.yml --vault-password-file=/path/to/passfile
# 3. 查看加密文件内容
ansible-vault view secrets.yml --vault-password-file=/path/to/passfile
# 4. 编辑加密文件
ansible-vault edit secrets.yml
# 5. 解密文件
ansible-vault decrypt secrets.yml
# 6. 加密字符串(适用于单变量)
ansible-vault encrypt_string 'SuperSecret123!' --name 'db_password'
加密文件示例
加密前 (secrets.yml):
yaml
my_db_password: SuperSecret123!
加密后:
yaml
$ANSIBLE_VAULT;1.1;AES256
61666535363065323562333766656266343466633661363138633331373630346537643736346136
6565613937353662643662316132393464623934326164650a303135363165663633333663643166
...
💡 Vault 最佳实践:
- 加密粒度:推荐一个环境一个加密文件,而非一个大文件
- 密码管理:生产环境使用
--vault-password-file,从密钥管理服务(如 HashiCorp Vault)读取- 版本控制:加密文件可以安全地提交到 Git(AES256 加密)
- 不要提交明文密码文件!将 vault 密码文件加入
.gitignore
Playbook 中使用加密变量
bash
# 运行时提供密码
ansible-playbook site.yml --vault-password-file=/secure/passfile
# 或交互式输入
ansible-playbook site.yml --ask-vault-pass
3.2 动态 Inventory
静态 Inventory 适用于固定服务器,但对于云环境(AWS/Azure/GCP),服务器 IP 是动态的,需要动态 Inventory。
动态 Inventory 原理
┌──────────┐ API调用 ┌─────────────┐
│ Ansible │ ───────────────> │ 云平台 API │
│ Control │ <─────────────── │ (AWS EC2等) │
└──────────┘ 返回JSON └─────────────┘
bash
# 使用动态Inventory脚本
ansible-playbook -i aws_ec2.yml playbook.yml
aws_ec2.yml 示例:
yaml
plugin: amazon.aws.aws_ec2
regions:
- ap-southeast-1
keyed_groups:
- key: tags.Name
prefix: tag_Name_
- key: instance_type
prefix: type_
💡 动态 Inventory 插件 :Ansible 内置了对 AWS、Azure、GCP、VMware 等主流云平台的支持,只需安装对应的 Collection(如
amazon.aws)即可使用。
3.3 复杂工作流
条件执行(Conditionals)
yaml
# 基于操作系统
- name: 只在Ubuntu上执行
apt: name=nginx
when: ansible_distribution == "Ubuntu"
# 基于变量值
- name: 仅主节点
debug: msg="我是主节点"
when: node_role == "primary"
# 多条件 (AND)
- name: 符合条件才执行
apt: name=nginx
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('22.04', '>=')
# 多条件 (OR)
- name: 任一条件满足
debug: msg="匹配"
when: ansible_distribution == "Ubuntu" or ansible_distribution == "Debian"
循环(Loops)
yaml
# 基础循环
- name: 安装多个包
apt:
name: "{{ item }}"
state: present
loop:
- htop
- curl
- vim
# 字典循环
- name: 创建多个用户
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: 'app', groups: 'www-data' }
- { name: 'deploy', groups: 'docker' }
# 循环 + 条件
- name: 按条件循环
debug:
msg: "{{ item }} 会安装"
loop: "{{ packages }}"
when: item != 'unwanted-pkg'
Import vs Include
| 特性 | import_* |
include_* |
|---|---|---|
| 处理时机 | Playbook 解析时(静态) | 任务执行时(动态) |
| 变量 | 不能使用循环变量 | 可以使用循环变量 |
| Tags | 不能继承 tags | 可以继承 tags |
| 性能 | 快(只解析一次) | 慢(每次执行解析) |
| 适用场景 | 固定的文件引用 | 动态的条件文件加载 |
yaml
# import: 静态导入(解析时展开)
- import_tasks: common-tasks.yml
# include: 动态导入(执行时展开,支持条件)
- include_tasks: "{{ os_family }}-setup.yml"
3.4 CI/CD 集成
GitLab CI 示例
yaml
# .gitlab-ci.yml
stages:
- lint
- deploy
ansible-lint:
stage: lint
script:
- pip install ansible-lint
- ansible-lint playbooks/
deploy-staging:
stage: deploy
only:
- develop
script:
- ansible-playbook -i inventory/staging site.yml --check --diff
- ansible-playbook -i inventory/staging site.yml
Jenkins Pipeline 示例
groovy
pipeline {
agent any
stages {
stage('Lint') {
steps {
sh 'ansible-lint playbooks/'
}
}
stage('Dry-run') {
steps {
sh 'ansible-playbook -i inventory/prod site.yml --check --diff'
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh 'ansible-playbook -i inventory/prod site.yml'
}
}
}
}
ansible-lint 代码质量检查
bash
# 安装
pip install ansible-lint
# 运行检查
ansible-lint playbooks/
# 常见规则:
# - 所有 task 必须有 name
# - 不要使用 command/shell 替代专用模块
# - 变量命名使用小写+下划线
# - 使用 FQCN (Fully Qualified Collection Name)
3.5 Ansible Collection
Collection 是 Ansible 的"包管理器",打包 Roles、模块、插件为一个可分发的单元。
bash
# 安装Collection
ansible-galaxy collection install community.general
# 列出已安装
ansible-galaxy collection list
# 从 Galaxy 搜索
ansible-galaxy collection search nginx
# 创建自己的Collection
ansible-galaxy collection init myorg.mycollection
四、实战部署案例
4.1 实战环境一览
控制节点: ecs-ab79-0001 (192.168.0.120)
Ansible: core 2.16.3
被控节点:
web-01 (192.168.0.213): Nginx 8080, 角色 primary ✅ 部署成功
web-02 (192.168.0.198): Nginx 80, 角色 secondary ✅ 部署成功
db-01 (192.168.0.203): Nginx 8088, 角色 worker ✅ 部署成功
4.2 部署验证结果
bash
# web-01 (8080端口)
$ curl -s http://192.168.0.213:8080/health
{"status":"ok","node":"primary","app":"ansible-demo"}
# web-02 (80端口)
$ curl -s http://192.168.0.198:80/health
{"status":"ok","node":"secondary","app":"ansible-demo"}
# db-01 (8088端口, 通过Roles部署)
$ curl -s http://192.168.0.203:8088/health
{"status":"ok","node":"worker","app":"ansible-roles-demo"}
4.3 完整项目文件
本次实战的完整 Ansible 项目结构:
/ansible-project/
├── ansible.cfg # Ansible配置
├── inventory/
│ ├── hosts.ini # 主机清单(3个被控节点)
│ ├── group_vars/
│ │ ├── all.yml # 全局变量
│ │ ├── db_servers.yml # DB组变量
│ │ ├── secrets.yml # Vault加密文件
│ │ └── web_servers.yml # Web组变量
│ └── host_vars/
│ ├── web-01.yml # web-01覆盖变量
│ └── web-02.yml # web-02覆盖变量
├── playbooks/
│ ├── deploy_web.yml # 核心Playbook
│ └── site.yml # Roles入口
├── roles/
│ ├── common/ # 基础系统角色
│ │ ├── defaults/main.yml
│ │ ├── vars/main.yml
│ │ ├── tasks/main.yml
│ │ └── meta/main.yml
│ └── tasks/ # Web部署角色
│ ├── defaults/main.yml
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ ├── templates/
│ │ ├── nginx.conf.j2
│ │ └── index.html.j2
│ └── meta/main.yml
└── templates/
├── nginx.conf.j2 # Nginx模板
└── index.html.j2 # HTML模板
五、变量优先级速查
─────────────────────────────────────────────────
优先级 (高→低) 来源 说明
─────────────────────────────────────────────────
1. 命令行 -e "key=value" 无人可挡
2. set_fact 运行时动态 仅当前Play
3. register 任务返回值 仅当前Play
4. host_vars 主机变量 覆盖所有组变量
5. group_vars 组变量 覆盖play vas
6. play vars vars: {} 覆盖role vas
7. role vars roles/X/vars role内部变量
8. role defaults roles/X/defaults 易于被覆盖
9. inventory hosts.ini内的 最低优先级
─────────────────────────────────────────────────
六、常见问题与排查
错误 1:Handler 未执行
现象 :notify: reload_nginx 设置了但 Nginx 没有重载
根因 :Handler 在 Play 末尾才冲刷。如果 Play 中途因失败中断(如 rescue 触发),Handler 不会执行
解决:
yaml
# 方法1: 在 task 中间手动触发
- meta: flush_handlers
# 方法2: 失败后手动执行
- command: systemctl reload nginx
when: nginx_config.changed
错误 2:role not found
现象 :ERROR! the role 'common' was not found
根因 :roles_path 配置不在 [defaults] section,或路径错误
解决:
ini
# ansible.cfg
[defaults]
roles_path = /ansible-project/roles # 必须在 [defaults] 下
错误 3:模板变量未定义
现象 :'nginx_server_name' is undefined
根因:Jinja2 模板中引用了一个未在任何地方定义的变量
解决:
yaml
# 方法1: 使用default过滤器
{{ nginx_server_name | default('localhost') }}
# 方法2: 在group_vars中定义
# group_vars/all.yml
nginx_server_name: demo.example.com
错误 4:validate 导致模板渲染失败
现象 :msg: failed to validate --- Nginx 配置语法检查失败
根因 :template 模块的 validate 参数使用 nginx -t -c %s 来验证 Nginx 配置,但 -c 要求文件是完整的主配置文件 (包含 http {} 块),而站点配置文件(server {} 块)不满足这个要求。
解决:
yaml
# 站点配置文件不要使用 validate,或改用:
- name: 生成站点配置
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/site.conf
# 去掉 validate 参数
错误 5:SSH 连接被拒绝
现象 :Connection refused / Permission denied
排查步骤:
bash
# 1. 测试SSH连通性
ssh -v root@192.168.0.213
# 2. 确认公钥已分发
cat ~/.ssh/authorized_keys # 在目标主机上
# 3. 检查ansible_user配置
grep ansible_user inventory/hosts.ini
# 4. 使用密码方式(仅测试)
ansible all -m ping -k # -k 参数交互式输入密码
附录
A. Ansible 命令速查表
| 命令 | 功能 |
|---|---|
ansible --version |
查看版本和配置 |
ansible all -m ping |
测试所有主机连通性 |
ansible web -m setup |
收集系统信息 |
ansible all -m shell -a 'uptime' |
执行远程命令 |
ansible-playbook site.yml --check |
Dry-run 模式 |
ansible-playbook site.yml --diff |
显示差异 |
ansible-playbook site.yml --tags nginx |
按标签执行 |
ansible-playbook site.yml --start-at-task="安装Nginx" |
从指定任务开始 |
ansible-playbook site.yml -l web-01 |
限制到特定主机 |
ansible-vault encrypt file.yml |
加密文件 |
ansible-vault view file.yml |
查看加密文件 |
ansible-galaxy collection install x.y |
安装 Collection |
ansible-lint playbooks/ |
代码静态检查 |
ansible-inventory --list |
列出所有主机(含变量) |
ansible-doc -l |
列出所有模块 |
ansible-doc apt |
查看模块帮助 |
B. 推荐学习资源
| 类型 | 资源 |
|---|---|
| 官方文档 | docs.ansible.com |
| Galaxy 社区 | galaxy.ansible.com --- 数千个现成 Role |
| 最佳实践 | ansible-best-practices |
| 认证路径 | Red Hat Certified Specialist in Ansible Automation (EX407) |
| 书籍 | 《Ansible for DevOps》(Jeff Geerling)、《Ansible: Up and Running》 |
| 练习环境 | Vagrant + VirtualBox 搭建本地多节点环境 |
C. Ansible vs SaltStack vs Puppet 速查表
| 对比维度 | Ansible | SaltStack | Puppet |
|---|---|---|---|
| 架构 | 无Agent (Push) | Master/Minion (Pull+Push) | Master/Agent (Pull) |
| 配置语言 | YAML | YAML + Jinja2 | Puppet DSL (Ruby-like) |
| 学习曲线 | ⭐ 低 | ⭐⭐ 中 | ⭐⭐⭐ 高 |
| 扩展性 | 百台级 | 万级(ZeroMQ) | 千级 |
| 执行速度 | 中等 | 快(异步+事件) | 慢(30分钟间隔Pull) |
| 社区规模 | 最大 | 中等 | 大(Forge) |
| 商业版 | Red Hat Ansible Automation Platform | SaltStack Enterprise | Puppet Enterprise |
| 最适合 | 快速上手、中小规模 | 大规模、事件驱动 | 企业合规、严格管控 |
💡 选型建议:
- 快速落地、团队YAML友好 → Ansible
- 大规模集群(1000+)、实时事件响应 → SaltStack
- 企业合规要求高、需要强一致性保障 → Puppet
D. 实战服务器信息
控制节点: ecs-ab79-0001
公网IP: 1.92.65.149
私网IP: 192.168.0.120
系统: Ubuntu 24.04 / Kernel 6.8.0-106
Ansible: core 2.16.3
被控节点:
web-01: ecs-ab79-0002 (120.46.23.135 / 192.168.0.213)
web-02: ecs-ab79-0003 (123.249.82.6 / 192.168.0.198)
db-01: ecs-ab79-0004 (1.94.217.17 / 192.168.0.203)
统一规格: Ubuntu 24.04, 2vCPU, 3.3GB RAM
📝 版本记录
v1.0 --- 2026-05-31 --- 初版,覆盖 Ansible 三个学习阶段全部核心内容
实战环境:4台华为云ECS,Ansible 2.16.3,所有代码均经过实战验证