Ansible 自动化运维:从入门到实战

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 key
  • gathering = smartimplicit 始终收集,explicit 仅当 gather_facts: true 时收集,smart 根据缓存决定
  • fact_caching = jsonfile:将收集到的 facts 缓存到 JSON 文件,避免每次 Play 都重新收集,显著加速
  • stdout_callback = yaml:将输出格式化为 YAML 缩进风格,比默认的平铺格式更易读
  • pipelining = True:将一个 Python 脚本通过 SSH 管道传送到远端执行,省去多次 SSH 连接的 SCP 操作。注意:需要远端 /etc/sudoers 中禁用 requiretty
  • ControlMaster=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_serversdb_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 指定模块名(如 pingcommandshellsetup),-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'

💡 模块详解 --- shell vs command

  • command:直接执行命令,不支持管道、重定向、变量展开等 Shell 特性
  • shell:通过 /bin/sh 执行,支持完整 Shell 语法(管道、重定向、通配符)
  • 安全提示shell 存在命令注入风险,优先使用专用模块(如 aptservicefile
  • 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: false
  • file 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 影响 (tagged always
  • 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 中间提前触发 handlers

tags 标签系统

  • 为 task 打上标签,运行时可选择执行特定标签的任务
  • --tags nginx:只运行带有 nginx 标签的 task
  • --skip-tags packages:跳过带有 packages 标签的 task
  • tags: [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-01nginx_port 最终为 8080(被 host_vars 覆盖)
  • web-02nginx_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 varsdefaults 可被外部覆盖,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 时,避免用户输入直接拼接
  • 幂等性缺失commandshell 模块不检查状态 ,每次都返回 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,所有代码均经过实战验证

相关推荐
摆烂大大王2 小时前
玩转 OpenClaw:用 TaskFlow + Heartbeat 打造自动化工作流
前端·人工智能·自动化
宋浮檀s3 小时前
应急响应——Web漏洞:命令执行+SSRF+弱口令
运维·数据库·sql·网络安全·oracle·应急响应
日取其半万世不竭3 小时前
iftop、nethogs 和 nload:Linux 服务器网络流量实时监控工具介绍
linux·运维·服务器
mounter6253 小时前
Linux 内核资源管理:控制组(cgroup)的演进与“策略组”新提案
linux·运维·服务器·cgroup·kernel
bksczm3 小时前
文件在磁盘中的存储方式
linux·运维·服务器
Wpa.wk4 小时前
win环境本地文件上传远程服务器(scp/远程连接工具)
运维·服务器
Soari4 小时前
SSH 主机密钥冲突
运维·网络·ssh
黑泽明Coding5 小时前
使用密钥登录ssh
运维·ssh
着迷不白5 小时前
五、文本处理工具+正则表达式
linux·运维·服务器