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,所有代码均经过实战验证

相关推荐
XIAOHEZIcode1 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220702 天前
如何搭建本地yum源(上)
运维
大树885 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠5 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质5 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工5 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智5 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_5 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉5 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造
dayuOK63075 天前
写作卡壳怎么办?我的“5分钟启动法”
人工智能·职场和发展·自动化·新媒体运营·媒体