Ansible 自动化运维实战教程

15 阅读26分钟

🎓 Ansible 自动化运维实战教程

零基础到实战,10节课完全掌握 Ansible

👨‍🏫 适合人群:零基础小白
🎯 学习目标:从零掌握 Ansible,独立完成自动化部署项目
📅 建议学习节奏:每天1课,10天完成


📚 课程大纲

课次主题难度
第1课Ansible 是什么?为什么要用它?
第2课环境搭建:安装 Ansible + 配置实验环境
第3课Inventory 主机清单:管理你的"鸡群"⭐⭐
第4课Ad-hoc 命令:Ansible 的"遥控器"⭐⭐
第5课Playbook 入门:写你的第一个剧本⭐⭐
第6课Variables 变量:让剧本活起来⭐⭐⭐
第7课Templates 模板:批量生成配置文件⭐⭐⭐
第8课Roles 角色:组织复杂项目⭐⭐⭐⭐
第9课实战项目:自动化部署 LAMP 环境⭐⭐⭐⭐
第10课进阶技巧:Vault 加密 + 调试 + 最佳实践⭐⭐⭐⭐⭐


🎬 第 1 课:Ansible 是什么?为什么要用它?

🌰 先讲个故事

想象你是一个餐厅老板,刚开始只有 1 个服务员(1台服务器)。

你跑过去亲口告诉他:"去把3号桌的菜端上去"。完全没问题,很轻松。✅

但是有一天,你的餐厅火了,你有了 100 个服务员(100台服务器)!

你需要让他们同时做同一件事:

  • 📋 背新菜单(更新配置文件)
  • 👔 换新制服(升级软件版本)
  • 🔄 调整服务流程(重启服务)

你怎么办?一个一个跑过去说?😱 累死你!


🦸 Ansible 就是你的"大堂经理"

你只需要对大堂经理说一次

"让所有服务员换制服、背菜单、调整流程!"

大堂经理会自动找到每一个服务员、同时下达指令、完成后汇报结果。

这就是 Ansible! 你只需写好"指令",它帮你同时搞定 N 台服务器。


🔍 Ansible 的三大核心特点

① 无需安装 Agent(免客户端)

🎯 类比:大堂经理不需要给每个服务员装对讲机,直接走过去用 SSH 说就行。

传统自动化工具(如 Puppet、Chef)需要在每台被管服务器上装"客户端程序",Ansible 完全不需要

你的电脑(控制节点,装了 Ansible)
           ↓ SSH
  ┌────────┼────────┐
  ↓        ↓        ↓
服务器A  服务器B  服务器C
(被管节点——什么都不用装!)

② 幂等性(Idempotent)

🎯 类比:你让服务员"把椅子摆好",椅子已经摆好了,他不会再搬一遍。

执行一次和执行一百次,结果完全相同。 不会因为重复执行而出问题。

第1次执行:nginx 未安装 → 安装 nginx ✅
第2次执行:nginx 已安装 → 跳过,不重复安装 ✅
第3次执行:nginx 已安装 → 跳过,不重复安装 ✅

③ YAML 语法,人类可读

# Ansible 任务示例,非常接近自然语言
- name: 安装 nginx
  apt:
    name: nginx
    state: present

🏗️ Ansible 的整体架构

┌──────────────────────────────────────────┐
│            控制节点 Control Node           │
│     (你的电脑 / 跳板机,安装了 Ansible)   │
│                                          │
│   ┌─────────────┐   ┌─────────────────┐  │
│   │  Inventory  │   │    Playbooks    │  │
│   │  主机清单    │   │    任务剧本      │  │
│   └─────────────┘   └─────────────────┘  │
└──────────────────┬───────────────────────┘
                   │ SSH
         ┌─────────┼─────────┐
         ↓         ↓         ↓
    ┌─────────┐ ┌─────────┐ ┌─────────┐
    │  Web01  │ │  Web02  │ │  DB01   │
    └─────────┘ └─────────┘ └─────────┘
              被管节点 Managed Nodes
组件作用类比
Control Node(控制节点)安装 Ansible,发出指令餐厅老板
Managed Nodes(被管节点)被管理的服务器服务员们
Inventory(主机清单)记录有哪些被管节点花名册
Playbook(剧本)描述要做什么任务的 YAML 文件工作指令书
Module(模块)执行具体操作的功能单元具体技能

📦 Ansible 能干什么?

使用场景具体举例
🖥️ 批量安装软件100 台机器同时安装 Nginx、MySQL
⚙️ 批量配置系统统一修改时区、hostname、内核参数
🚀 应用部署自动拉取代码、构建、重启服务
🔍 系统巡检批量检查磁盘、CPU、内存状态
🔒 安全加固批量关闭危险端口、更新密码策略
🔄 定时运维日志清理、备份、证书续签

🎯 第1课 小测验

  1. Ansible 控制目标机器用的是什么协议?
  2. 用自己的话解释一下什么是"幂等性"?
  3. Ansible 需要在被管节点安装客户端吗?
👆 点击查看答案
  1. SSH 协议
  2. 多次执行同一任务,结果和执行一次完全相同,不会重复操作
  3. 不需要,被管节点只需要开启 SSH 服务 + 有 Python 环境即可


🔧 第 2 课:环境搭建——安装 Ansible + 配置实验环境

🧱 实验环境规划

用 3 台机器模拟真实场景:

角色主机名IP 地址
控制节点ansible-master192.168.1.10
被管节点1web01192.168.1.11
被管节点2web02192.168.1.12

💡 没有多台机器?替代方案:

  • Multipass 在本机快速起 Linux 虚拟机(免费轻量)
  • 用 Docker 启动几个容器模拟被管节点
  • 买云服务器(学生机几块钱一个月)
  • 只用 localhost 练习也能完成大多数实验

📦 安装 Ansible(只在控制节点操作!)

Ubuntu / Debian

sudo apt update
sudo apt install ansible -y
ansible --version

CentOS / RHEL 7

sudo yum install epel-release -y
sudo yum install ansible -y
ansible --version

CentOS 8+ / Rocky Linux

sudo dnf install epel-release -y
sudo dnf install ansible -y
ansible --version

pip 安装(推荐,版本最新)

pip3 install ansible
ansible --version

安装成功后看到如下输出表示成功:

ansible [core 2.16.0]
  config file = /etc/ansible/ansible.cfg
  python version = 3.10.12

🔑 配置 SSH 免密登录

🌰 类比:配置免密就是给控制节点一把"万能钥匙",以后进门不用再敲密码。

Step 1:在控制节点生成 SSH 密钥对

# 三次回车跳过即可
ssh-keygen -t rsa -b 4096

生成两个文件:

文件名称能不能给别人?
~/.ssh/id_rsa私钥(钥匙)❌ 绝对不能
~/.ssh/id_rsa.pub公钥(锁)✅ 可以公开

Step 2:把公钥发到被管节点

ssh-copy-id root@192.168.1.11
ssh-copy-id root@192.168.1.12

Step 3:测试免密是否成功

ssh root@192.168.1.11
# 直接进去,不用输密码就对了!

⚙️ Ansible 配置文件说明

配置文件路径:/etc/ansible/ansible.cfg

[defaults]
inventory         = /etc/ansible/hosts   # 主机清单路径
remote_user       = root                 # 默认 SSH 用户
timeout           = 30                   # 连接超时(秒)
host_key_checking = False                # 关闭指纹检查(实验用)
forks             = 5                    # 并发主机数

配置文件查找优先级(从高到低):

1. ANSIBLE_CONFIG 环境变量
2. 当前目录 ./ansible.cfg
3. 用户目录 ~/.ansible.cfg
4. 全局默认 /etc/ansible/ansible.cfg

🚀 执行第一条 Ansible 命令!

ansible all -i "192.168.1.11,192.168.1.12," -m ping

看到以下输出,恭喜成功!🎉

192.168.1.11 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.1.12 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

🔧 常见报错排查

错误信息原因解决方法
UNREACHABLE! Connection refusedSSH 未启动systemctl start sshd
Permission denied (publickey)免密未配置重新执行 ssh-copy-id
Python not found被管节点缺 Pythonapt install python3 -y
Host key verification failedSSH 指纹检查配置文件设 host_key_checking = False

🎯 第2课 小测验

  1. SSH 密钥对中,哪个发给被管节点,公钥还是私钥?
  2. ansible all -m ping-m 代表什么?
  3. Ansible 配置文件的优先级顺序是什么?
👆 点击查看答案
  1. 公钥(id_rsa.pub),私钥自己保管
  2. module(模块),指定使用哪个 Ansible 模块
  3. 环境变量 > 当前目录 > 用户目录 > 全局默认


📋 第 3 课:Inventory 主机清单——管理你的"鸡群"

🌰 生动类比

Inventory 就是你的员工花名册

你有100台服务器,总得有个本子记着它们是谁、分哪个组、有什么特殊信息吧?

这本花名册就是 Inventory —— 告诉 Ansible:我要管理哪些机器,它们属于哪个组。


📄 INI 格式(最常见)

默认文件:/etc/ansible/hosts

# 直接写 IP 或主机名(不分组)
192.168.1.11
192.168.1.12

# 用 [组名] 定义分组
[webservers]
192.168.1.11
192.168.1.12

[dbservers]
192.168.1.20
192.168.1.21

[cacheservers]
192.168.1.30

带参数写法

[webservers]
web01 ansible_host=192.168.1.11 ansible_user=root ansible_port=22
web02 ansible_host=192.168.1.12 ansible_user=root ansible_port=2222

常用主机参数:

参数含义
ansible_host实际 IP 地址
ansible_userSSH 登录用户名
ansible_portSSH 端口(默认22)
ansible_ssh_private_key_file私钥文件路径

批量主机名简写

[webservers]
web[01:03]     # 等价于 web01, web02, web03

[dbservers]
db-[a:c]       # 等价于 db-a, db-b, db-c

组的嵌套

[webservers]
web01
web02

[dbservers]
db01

# 父组,包含多个子组
[production:children]
webservers
dbservers

🗂️ YAML 格式(推荐,结构更清晰)

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.11
          ansible_user: root
        web02:
          ansible_host: 192.168.1.12
          ansible_user: root
    dbservers:
      hosts:
        db01:
          ansible_host: 192.168.1.20
          ansible_user: root

🔧 组变量和主机变量

方式一:写在 inventory 文件里

[webservers]
web01 ansible_host=192.168.1.11

# 给整个 webservers 组设置变量
[webservers:vars]
http_port=80
max_clients=200

方式二:用目录文件管理(推荐!)

project/
├── inventory.ini
├── group_vars/           # 组变量目录
│   ├── webservers.yml    # webservers 组的变量
│   └── dbservers.yml     # dbservers 组的变量
└── host_vars/            # 主机变量目录
    ├── web01.yml         # web01 的变量
    └── db01.yml          # db01 的变量
# group_vars/webservers.yml
http_port: 80
max_clients: 200
nginx_version: "1.24"

🔍 常用查询命令

# 列出 inventory 中所有主机
ansible all --list-hosts

# 列出某个组的主机
ansible webservers --list-hosts

# 使用自定义 inventory 文件
ansible all -i inventory.yml --list-hosts

# 查看主机的所有变量
ansible web01 -m debug -a "var=hostvars[inventory_hostname]"

🎯 第3课 小测验

  1. 如何在 Inventory 中定义一个"父组",让它包含多个子组?
  2. group_vars 目录的作用是什么?
  3. 主机名 web[01:05] 代表哪些主机?
👆 点击查看答案
  1. 使用 [父组名:children] 语法,下面列出子组名
  2. 存放组变量,该目录下的 webservers.yml 会自动应用到 webservers 组所有主机
  3. web01, web02, web03, web04, web05 共5台


⚡ 第 4 课:Ad-hoc 命令——Ansible 的"遥控器"

🌰 生动类比

Ad-hoc 命令就像电视遥控器

拿起来就能用,不需要提前写脚本:换台、调音量、关机,一键搞定临时需求。

而下节课的 Playbook 则是精心编排的电影剧本,用于复杂的、可重复的正式任务。


📖 基本语法

ansible <主机/组> -m <模块名> -a "<参数>" [其他选项]
参数含义
-m指定模块(module)
-a指定模块参数(argument)
-i指定 inventory 文件
--become提权(相当于 sudo)
-f并发数(默认5)
-v / -vvv输出详细信息,v越多越详细

🧰 常用模块实战

ping 模块——测试连通性

ansible all -m ping
ansible webservers -m ping

command 模块——执行命令

# 查看服务器运行时间
ansible webservers -m command -a "uptime"

# 查看磁盘使用情况
ansible all -m command -a "df -h"

# 查看内存
ansible all -m command -a "free -m"

shell 模块——支持管道和重定向

# command 不支持管道,shell 支持
ansible all -m shell -a "ps aux | grep nginx"

# 写入文件
ansible all -m shell -a "echo 'hello ansible' > /tmp/test.txt"

⚠️ command 更安全,shell 功能更强但有注入风险,优先用 command。

copy 模块——传输文件

# 发送本地文件到远程
ansible webservers -m copy -a "src=/local/nginx.conf dest=/etc/nginx/nginx.conf"

# 直接写入内容
ansible all -m copy -a "content='Hello Ansible\n' dest=/tmp/hello.txt"

# 设置权限
ansible all -m copy -a "src=app.conf dest=/etc/app.conf mode=0644 owner=root"

apt / yum 模块——软件管理

# Ubuntu 安装 nginx
ansible webservers -m apt -a "name=nginx state=present" --become

# Ubuntu 卸载 nginx
ansible webservers -m apt -a "name=nginx state=absent" --become

# CentOS 安装 nginx
ansible webservers -m yum -a "name=nginx state=present" --become

# 更新所有包
ansible all -m apt -a "upgrade=dist" --become

service 模块——服务管理

# 启动 nginx
ansible webservers -m service -a "name=nginx state=started" --become

# 重启 nginx
ansible webservers -m service -a "name=nginx state=restarted" --become

# 停止 nginx
ansible webservers -m service -a "name=nginx state=stopped" --become

# 设置开机自启
ansible webservers -m service -a "name=nginx enabled=yes" --become

file 模块——文件/目录管理

# 创建目录
ansible all -m file -a "path=/data/app state=directory mode=0755" --become

# 创建文件
ansible all -m file -a "path=/tmp/test.txt state=touch"

# 删除文件
ansible all -m file -a "path=/tmp/test.txt state=absent"

# 创建软链接
ansible all -m file -a "src=/etc/nginx/nginx.conf dest=/tmp/nginx.conf state=link"

user 模块——用户管理

# 创建用户
ansible all -m user -a "name=deploy shell=/bin/bash" --become

# 删除用户
ansible all -m user -a "name=deploy state=absent" --become

cron 模块——定时任务

# 添加定时任务:每天凌晨2点执行备份
ansible all -m cron -a "name='daily backup' hour=2 minute=0 job='/scripts/backup.sh'" --become

📊 Ad-hoc 实战案例

# 批量查看所有机器的系统信息
ansible all -m setup -a "filter=ansible_distribution"

# 批量检查 nginx 是否在运行
ansible webservers -m shell -a "systemctl is-active nginx"

# 批量修改 DNS 配置
ansible all -m copy -a "content='nameserver 8.8.8.8\n' dest=/etc/resolv.conf" --become

# 紧急:批量杀掉某个进程
ansible all -m shell -a "pkill -f suspicious_process" --become

🎯 第4课 小测验

  1. commandshell 模块的区别是什么?
  2. 如何用一条 Ad-hoc 命令在所有 webservers 上安装 git?
  3. -f 10 参数代表什么意思?
👆 点击查看答案
  1. command 不支持管道、重定向等 shell 特性,更安全;shell 通过 /bin/sh 执行,支持所有 shell 语法,但有注入风险
  2. ansible webservers -m apt -a "name=git state=present" --become
  3. 同时对 10 台主机并发执行任务(默认是5)


📜 第 5 课:Playbook 入门——写你的第一个剧本

🌰 生动类比

如果说 Ad-hoc 是即兴表演,那 Playbook 就是精心编排的电影剧本

  • 有情节(任务步骤)
  • 有角色(谁来执行)
  • 有顺序(先做什么后做什么)
  • 可以反复拍(重复执行)

📝 Playbook 基本结构

---                          # YAML 文件开头标志
- name: 这个 Play 的描述      # Play 名称
  hosts: webservers          # 在哪些主机上执行
  become: yes                # 是否提权(sudo)
  
  tasks:                     # 任务列表
    - name: 第一个任务的描述   # 任务名称(必写!方便排错)
      模块名:                 # 使用哪个模块
        参数1: 值1
        参数2: 值2
        
    - name: 第二个任务
      模块名:
        参数: 

🎬 第一个完整 Playbook

# install_nginx.yml
---
- name: 安装并启动 Nginx
  hosts: webservers
  become: yes

  tasks:
    - name: 安装 nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: 启动 nginx 并设置开机自启
      service:
        name: nginx
        state: started
        enabled: yes

    - name: 创建网站目录
      file:
        path: /var/www/mysite
        state: directory
        mode: '0755'

    - name: 部署首页
      copy:
        content: "<h1>Hello from Ansible!</h1>"
        dest: /var/www/mysite/index.html

执行 Playbook

# 基本执行
ansible-playbook install_nginx.yml

# 指定 inventory
ansible-playbook -i inventory.yml install_nginx.yml

# 演习模式(不真正执行,只检查)
ansible-playbook install_nginx.yml --check

# 查看详细输出
ansible-playbook install_nginx.yml -v

# 从某个任务开始执行
ansible-playbook install_nginx.yml --start-at-task="部署首页"

🔄 Handlers:触发式任务

🌰 类比:你说"如果配置文件改了,就重启 nginx"。不改就不重启,Handlers 做的就是这件事。

---
- name: 配置 Nginx
  hosts: webservers
  become: yes

  tasks:
    - name: 复制 nginx 配置文件
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
      notify: 重启 nginx        # 触发 handler

    - name: 复制网站配置
      copy:
        src: site.conf
        dest: /etc/nginx/conf.d/site.conf
      notify: 重启 nginx        # 同一个 handler,只会执行一次

  handlers:
    - name: 重启 nginx          # 名字要和 notify 一致
      service:
        name: nginx
        state: restarted

💡 重要: 即使多个 task 都 notify 同一个 handler,该 handler 只会在最后执行一次


🔁 循环(Loop)

tasks:
  # 安装多个软件包(用循环,不用写多遍)
  - name: 安装必要软件
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - nginx
      - git
      - curl
      - vim

  # 创建多个用户
  - name: 批量创建用户
    user:
      name: "{{ item.name }}"
      shell: "{{ item.shell }}"
    loop:
      - { name: alice, shell: /bin/bash }
      - { name: bob,   shell: /bin/sh }

🔀 条件判断(When)

tasks:
  # 只在 Ubuntu 上安装
  - name: 安装 nginx(Ubuntu)
    apt:
      name: nginx
      state: present
    when: ansible_os_family == "Debian"

  # 只在 CentOS 上安装
  - name: 安装 nginx(CentOS)
    yum:
      name: nginx
      state: present
    when: ansible_os_family == "RedHat"

  # 多条件组合
  - name: 只在生产环境的 web 服务器执行
    shell: /scripts/deploy.sh
    when:
      - env == "production"
      - ansible_hostname.startswith("web")

🎯 第5课 小测验

  1. Playbook 中 become: yes 是什么意思?
  2. Handlers 和普通 tasks 有什么区别?
  3. 如何让一个 task 只在 Ubuntu 系统上运行?
👆 点击查看答案
  1. 以提升的权限执行任务,等同于 sudo,默认提升到 root
  2. Handler 只有被 notify 触发时才执行,且无论被触发多少次,只在所有 tasks 结束后执行一次
  3. 使用 when: ansible_os_family == "Debian" 条件判断


🔤 第 6 课:Variables 变量——让剧本活起来

🌰 生动类比

没有变量的 Playbook 就像填死了数据的合同模板,每换一个客户都要重新打印一份。

有了变量,就像 Word 的邮件合并功能——模板不变,把名字、地址一换,批量生成!


📌 变量定义的多种方式

方式一:Playbook 内定义(vars)

---
- name: 部署应用
  hosts: webservers
  vars:
    app_name: myapp
    app_port: 8080
    app_version: "1.2.3"

  tasks:
    - name: 创建应用目录
      file:
        path: /opt/{{ app_name }}
        state: directory

    - name: 输出变量值(调试用)
      debug:
        msg: "部署 {{ app_name }} 版本 {{ app_version }} 到端口 {{ app_port }}"

方式二:vars_files(变量文件)

# vars/app_config.yml
app_name: myapp
app_port: 8080
db_host: 192.168.1.20
db_name: mydb
---
- name: 部署应用
  hosts: webservers
  vars_files:
    - vars/app_config.yml   # 引入变量文件

  tasks:
    - name: 使用变量
      debug:
        msg: "数据库地址: {{ db_host }}"

方式三:命令行传入(-e)

# 临时覆盖变量,优先级最高
ansible-playbook deploy.yml -e "app_version=1.3.0 env=production"

方式四:group_vars / host_vars(推荐!)

# group_vars/webservers.yml(自动应用到 webservers 组)
http_port: 80
max_connections: 1000

# host_vars/web01.yml(只应用到 web01)
http_port: 8080   # 覆盖组变量

📊 变量优先级(从低到高)

role defaults          ← 最低
group_vars/all
group_vars/<组名>
host_vars/<主机名>
playbook vars
vars_files
task vars
-e 命令行参数          ← 最高

💡 记住口诀:越具体优先级越高,命令行最高。


🤖 Magic Variables(内置变量)

Ansible 自动提供了很多内置变量,直接用就行:

tasks:
  - name: 显示当前主机信息
    debug:
      msg: |
        主机名: {{ inventory_hostname }}
        IP地址: {{ ansible_host }}
        操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}
        CPU核心: {{ ansible_processor_vcpus }}
        内存: {{ ansible_memtotal_mb }} MB

常用内置变量:

变量名含义
inventory_hostname当前主机在 inventory 中的名称
ansible_host当前主机的 IP 地址
ansible_user当前 SSH 用户
ansible_distribution操作系统发行版(Ubuntu、CentOS…)
ansible_os_family系统家族(Debian、RedHat…)
hostvars所有主机的变量字典
groups所有组的字典

🔧 register:保存任务结果

tasks:
  - name: 检查 nginx 是否安装
    command: which nginx
    register: nginx_result        # 把结果保存到变量
    ignore_errors: yes            # 出错也继续

  - name: 根据结果决定下一步
    debug:
      msg: "nginx 已安装,路径是 {{ nginx_result.stdout }}"
    when: nginx_result.rc == 0   # rc=0 表示命令执行成功

  - name: 如果没安装就装上
    apt:
      name: nginx
      state: present
    when: nginx_result.rc != 0

🎯 第6课 小测验

  1. 哪种方式定义的变量优先级最高?
  2. register 关键字的作用是什么?
  3. ansible_distribution 是什么类型的变量?
👆 点击查看答案
  1. 命令行 -e 参数,优先级最高,可以覆盖所有其他变量定义
  2. 将任务的执行结果保存到一个变量,后续任务可以根据结果做判断
  3. Magic Variable(魔法变量/内置变量),由 Ansible 自动收集系统信息后提供


📄 第 7 课:Templates 模板——批量生成配置文件

🌰 生动类比

模板就像公司的合同模板

合同主体内容不变,只是把客户名、金额、日期这些信息换一换,批量生成不同合同。

Ansible 的 Template 功能,让你用同一个配置文件模板,给100台不同服务器生成100份定制化配置


🔤 Jinja2 模板语法

Ansible 使用 Jinja2 模板引擎,文件后缀一般是 .j2

{{ 变量名 }}          # 输出变量值
{% if 条件 %}         # 条件判断
{% for item in list %}  # 循环
{# 这是注释 #}        # 注释(不会输出)

📝 实战:Nginx 配置文件模板

创建模板文件 templates/nginx.conf.j2

# 由 Ansible 自动生成,请勿手动修改
# 生成时间: {{ ansible_date_time.date }}
# 目标主机: {{ inventory_hostname }}

user www-data;
worker_processes {{ ansible_processor_vcpus }};  {# 根据 CPU 核数自动设置 #}

events {
    worker_connections {{ nginx_worker_connections | default(1024) }};
}

http {
    sendfile on;
    keepalive_timeout 65;

    {% if enable_gzip | default(true) %}
    gzip on;
    gzip_types text/plain text/css application/json;
    {% endif %}

    server {
        listen {{ http_port | default(80) }};
        server_name {{ server_name | default(inventory_hostname) }};

        root {{ web_root | default('/var/www/html') }};
        index index.html index.htm;

        {% for location in custom_locations | default([]) %}
        location {{ location.path }} {
            proxy_pass {{ location.backend }};
        }
        {% endfor %}
    }
}

在 Playbook 中使用模板:

---
- name: 配置 Nginx
  hosts: webservers
  become: yes
  vars:
    http_port: 80
    web_root: /var/www/mysite
    enable_gzip: true
    custom_locations:
      - { path: /api, backend: "http://127.0.0.1:8080" }

  tasks:
    - name: 渲染并部署 nginx 配置
      template:
        src: templates/nginx.conf.j2    # 模板文件(本地)
        dest: /etc/nginx/nginx.conf     # 目标路径(远程)
        owner: root
        group: root
        mode: '0644'
      notify: 重启 nginx

  handlers:
    - name: 重启 nginx
      service:
        name: nginx
        state: restarted

🔧 常用 Jinja2 过滤器

# 过滤器用 | 符号调用,类似 Linux 管道

{{ app_name | upper }}           # 转大写 → MYAPP
{{ app_name | lower }}           # 转小写
{{ version | default('1.0') }}   # 没有该变量时用默认值
{{ items | length }}             # 列表长度
{{ path | basename }}            # 取文件名 /a/b/c.txt → c.txt
{{ path | dirname }}             # 取目录 /a/b/c.txt → /a/b
{{ text | replace('a','b') }}    # 字符串替换
{{ list | join(',') }}           # 列表转字符串
{{ num | int }}                  # 转整数

📋 实战:批量生成 hosts 文件

# templates/hosts.j2
127.0.0.1   localhost

# 自动把所有 webservers 组的主机写入
{% for host in groups['webservers'] %}
{{ hostvars[host]['ansible_host'] }}   {{ host }}
{% endfor %}

# 数据库服务器
{% for host in groups['dbservers'] %}
{{ hostvars[host]['ansible_host'] }}   {{ host }}
{% endfor %}

🎯 第7课 小测验

  1. Ansible 使用哪个模板引擎?模板文件的常用后缀是什么?
  2. Jinja2 中 {{ }}{% %} 分别用来做什么?
  3. {{ app_name | default('myapp') }} 这个过滤器的作用是什么?
👆 点击查看答案
  1. Jinja2 模板引擎,文件后缀通常为 .j2
  2. {{ }} 用于输出变量或表达式的值{% %} 用于控制结构(if、for 等逻辑语句)
  3. 如果变量 app_name 未定义,则使用默认值 'myapp',避免变量未定义时报错


🏗️ 第 8 课:Roles 角色——组织复杂项目

🌰 生动类比

当你的 Playbook 越写越长,所有内容堆在一个文件里,就像把所有衣服乱扔在地上,找起来要命。

Roles 就是给你的运维代码安装了衣柜——上衣放上衣格,裤子放裤子格,袜子放袜子格,整洁、好找、好复用。


📁 Role 的标准目录结构

roles/
└── nginx/                    # 角色名
    ├── tasks/
    │   └── main.yml          # 核心任务(必须有)
    ├── handlers/
    │   └── main.yml          # handlers
    ├── templates/
    │   └── nginx.conf.j2     # 模板文件
    ├── files/
    │   └── index.html        # 静态文件
    ├── vars/
    │   └── main.yml          # 变量(高优先级)
    ├── defaults/
    │   └── main.yml          # 默认变量(低优先级,可被覆盖)
    ├── meta/
    │   └── main.yml          # 角色依赖等元信息
    └── README.md             # 角色说明文档

🛠️ 创建一个完整的 nginx Role

Step 1:创建目录结构

# 使用 ansible-galaxy 命令自动创建标准目录
ansible-galaxy role init roles/nginx

Step 2:编写默认变量 roles/nginx/defaults/main.yml

---
nginx_port: 80
nginx_worker_processes: auto
web_root: /var/www/html
server_name: localhost

Step 3:编写核心任务 roles/nginx/tasks/main.yml

---
- name: 安装 nginx
  apt:
    name: nginx
    state: present
    update_cache: yes

- name: 部署 nginx 配置
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: 重启 nginx

- name: 创建网站目录
  file:
    path: "{{ web_root }}"
    state: directory
    mode: '0755'

- name: 部署默认首页
  copy:
    src: index.html
    dest: "{{ web_root }}/index.html"

- name: 启动 nginx
  service:
    name: nginx
    state: started
    enabled: yes

Step 4:编写 Handlers roles/nginx/handlers/main.yml

---
- name: 重启 nginx
  service:
    name: nginx
    state: restarted

Step 5:在 Playbook 中使用 Role

# site.yml
---
- name: 配置 Web 服务器
  hosts: webservers
  become: yes
  roles:
    - nginx              # 使用 nginx role

- name: 配置数据库服务器
  hosts: dbservers
  become: yes
  roles:
    - mysql              # 使用 mysql role

🌐 使用 Ansible Galaxy 共享 Role

Ansible Galaxy 是 Ansible 的"应用商店",里面有大量社区贡献的 Role。

# 搜索 nginx 相关的 role
ansible-galaxy search nginx

# 安装社区 nginx role(geerlingguy 是最知名的作者之一)
ansible-galaxy install geerlingguy.nginx

# 查看已安装的 role
ansible-galaxy list

# 从 requirements.yml 批量安装
ansible-galaxy install -r requirements.yml
# requirements.yml
---
roles:
  - name: geerlingguy.nginx
    version: "3.1.0"
  - name: geerlingguy.mysql
    version: "4.3.1"

📦 推荐的项目目录结构

my_ansible_project/
├── ansible.cfg            # Ansible 配置文件
├── inventory/
│   ├── production.yml     # 生产环境主机
│   └── staging.yml        # 测试环境主机
├── group_vars/
│   ├── all.yml            # 所有主机的公共变量
│   ├── webservers.yml
│   └── dbservers.yml
├── host_vars/
│   └── web01.yml
├── roles/
│   ├── common/            # 基础配置 role(所有机器都跑)
│   ├── nginx/             # nginx role
│   └── mysql/             # mysql role
├── site.yml               # 主入口 Playbook
├── webservers.yml         # 只跑 web 服务器的 Playbook
└── dbservers.yml          # 只跑数据库的 Playbook

🎯 第8课 小测验

  1. Role 中 defaults/main.ymlvars/main.yml 有什么区别?
  2. 如何用一条命令自动创建 Role 的标准目录结构?
  3. Ansible Galaxy 是什么?
👆 点击查看答案
  1. defaults/ 优先级低,可被 group_vars、host_vars 等覆盖,用于定义"可被用户覆盖的默认值";vars/ 优先级高,用于定义"不应被轻易覆盖的内部变量"
  2. ansible-galaxy role init roles/角色名
  3. Ansible 官方的 Role 共享平台(类似应用商店),可以下载社区贡献的成熟 Role 直接使用


🚀 第 9 课:实战项目——自动化部署 LAMP 环境

🎯 项目目标

用 Ansible 全自动部署一套 LAMP 环境

LAMP = Linux + Apache/Nginx + MySQL + PHP

实现效果:一条命令,从空白服务器到可访问的 PHP 网站!


📁 项目目录结构

lamp_project/
├── ansible.cfg
├── inventory.yml
├── site.yml                  # 主入口
├── group_vars/
│   ├── all.yml               # 公共变量
│   ├── webservers.yml        # Web 服务器变量
│   └── dbservers.yml         # 数据库变量
└── roles/
    ├── common/               # 基础配置
    ├── nginx/                # Web 服务器
    ├── php/                  # PHP 环境
    └── mysql/                # 数据库

📋 各文件内容

inventory.yml

all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.11
          ansible_user: root
    dbservers:
      hosts:
        db01:
          ansible_host: 192.168.1.20
          ansible_user: root

group_vars/all.yml

# 所有主机公用的变量
timezone: Asia/Shanghai
ntp_server: ntp.aliyun.com

group_vars/webservers.yml

http_port: 80
web_root: /var/www/html
server_name: mysite.example.com
php_version: "8.1"

group_vars/dbservers.yml

mysql_root_password: "Str0ng@Root#Pass"
mysql_db_name: myapp_db
mysql_db_user: myapp_user
mysql_db_password: "App@User#Pass456"

roles/common/tasks/main.yml(基础配置)

---
- name: 更新 apt 缓存
  apt:
    update_cache: yes
    cache_valid_time: 3600

- name: 安装基础工具
  apt:
    name:
      - curl
      - wget
      - vim
      - git
      - unzip
      - net-tools
    state: present

- name: 设置时区
  timezone:
    name: "{{ timezone }}"

- name: 配置 NTP 时间同步
  apt:
    name: ntp
    state: present
  notify: 重启 NTP

- name: 关闭 UFW 防火墙(实验用)
  ufw:
    state: disabled

roles/nginx/tasks/main.yml

---
- name: 安装 nginx
  apt:
    name: nginx
    state: present

- name: 部署 nginx 配置
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/mysite
  notify: 重启 nginx

- name: 启用网站配置
  file:
    src: /etc/nginx/sites-available/mysite
    dest: /etc/nginx/sites-enabled/mysite
    state: link
  notify: 重启 nginx

- name: 删除默认站点
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  notify: 重启 nginx

- name: 创建网站根目录
  file:
    path: "{{ web_root }}"
    state: directory
    owner: www-data
    group: www-data
    mode: '0755'

- name: 启动并设置自启
  service:
    name: nginx
    state: started
    enabled: yes

roles/nginx/templates/nginx.conf.j2

server {
    listen {{ http_port }};
    server_name {{ server_name }};
    root {{ web_root }};
    index index.php index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
    }
}

roles/php/tasks/main.yml

---
- name: 安装 PHP 及常用扩展
  apt:
    name:
      - "php{{ php_version }}-fpm"
      - "php{{ php_version }}-mysql"
      - "php{{ php_version }}-curl"
      - "php{{ php_version }}-mbstring"
      - "php{{ php_version }}-xml"
      - "php{{ php_version }}-zip"
    state: present

- name: 启动 PHP-FPM
  service:
    name: "php{{ php_version }}-fpm"
    state: started
    enabled: yes

- name: 部署 PHP 测试页面
  copy:
    content: "<?php phpinfo(); ?>"
    dest: "{{ web_root }}/info.php"

roles/mysql/tasks/main.yml

---
- name: 安装 MySQL
  apt:
    name:
      - mysql-server
      - python3-mysqldb     # Ansible 操作 MySQL 需要这个
    state: present

- name: 启动 MySQL
  service:
    name: mysql
    state: started
    enabled: yes

- name: 设置 root 密码
  mysql_user:
    name: root
    password: "{{ mysql_root_password }}"
    login_unix_socket: /var/run/mysqld/mysqld.sock
    state: present

- name: 创建应用数据库
  mysql_db:
    name: "{{ mysql_db_name }}"
    state: present
    login_user: root
    login_password: "{{ mysql_root_password }}"

- name: 创建应用用户并授权
  mysql_user:
    name: "{{ mysql_db_user }}"
    password: "{{ mysql_db_password }}"
    priv: "{{ mysql_db_name }}.*:ALL"
    state: present
    login_user: root
    login_password: "{{ mysql_root_password }}"

site.yml(主入口)

---
# 第一步:所有机器先做基础配置
- name: 基础环境配置
  hosts: all
  become: yes
  roles:
    - common

# 第二步:配置 Web 服务器
- name: 部署 Web 服务
  hosts: webservers
  become: yes
  roles:
    - nginx
    - php

# 第三步:配置数据库
- name: 部署数据库
  hosts: dbservers
  become: yes
  roles:
    - mysql

🚀 一键部署!

# 语法检查
ansible-playbook site.yml --syntax-check

# 演习模式(看看会执行哪些操作)
ansible-playbook site.yml --check

# 正式部署!
ansible-playbook site.yml

# 部署完成后,浏览器访问 http://192.168.1.11 验证结果

部署成功后的输出示例:

PLAY RECAP *********************************************
web01 : ok=18 changed=12 unreachable=0 failed=0
db01  : ok=10 changed=8  unreachable=0 failed=0

failed=0 = 全部成功!🎉


🎯 第9课 小测验

  1. ansible-playbook site.yml --check 的作用是什么?
  2. 为什么要有 common role,它的作用是什么?
  3. python3-mysqldb 包安装在哪台机器上?为什么?
👆 点击查看答案
  1. 演习模式(Dry Run),不会真正执行任何操作,只是预演并显示"如果执行会发生什么变化",用于提前检查
  2. Common role 做所有机器都需要的基础配置(时区、基础工具、NTP等),避免在每个 role 里重复写
  3. 安装在数据库服务器(db01) 上,因为它是 Ansible 的 mysql_* 模块在目标主机上操作 MySQL 时依赖的 Python 库


🔐 第 10 课:进阶技巧——Vault 加密 + 调试 + 最佳实践

一、🔐 Ansible Vault——加密敏感信息

🌰 为什么需要 Vault?

数据库密码、API Key、证书私钥这些敏感信息,如果直接写在 YAML 文件里提交到 Git,就像把家里的钥匙贴在大门上——非常危险!

Vault 就是给这些敏感信息上锁的工具。


加密整个文件

# 加密文件(会提示输入 Vault 密码)
ansible-vault encrypt group_vars/dbservers.yml

# 查看加密后的文件(认不出来了)
cat group_vars/dbservers.yml
# $ANSIBLE_VAULT;1.1;AES256
# 61386537363063343965636630363566...(一堆加密字符)

# 查看加密文件内容(需要输入密码)
ansible-vault view group_vars/dbservers.yml

# 编辑加密文件
ansible-vault edit group_vars/dbservers.yml

# 解密文件(还原成明文)
ansible-vault decrypt group_vars/dbservers.yml

# 修改 Vault 密码
ansible-vault rekey group_vars/dbservers.yml

只加密单个变量值(推荐!)

# 生成单个加密字符串
ansible-vault encrypt_string 'MySecret@Pass123' --name 'mysql_root_password'

输出:

mysql_root_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236306662386466353535...

把这段内容复制粘贴到你的变量文件里即可。

执行时提供 Vault 密码

# 方式一:运行时手动输入密码
ansible-playbook site.yml --ask-vault-pass

# 方式二:从密码文件读取(CI/CD 常用)
echo "my_vault_password" > ~/.vault_pass
chmod 600 ~/.vault_pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass

# 方式三:在 ansible.cfg 中配置(最方便)
# vault_password_file = ~/.vault_pass

二、🐛 调试技巧

debug 模块——打印变量

tasks:
  - name: 打印单个变量
    debug:
      var: ansible_distribution

  - name: 打印自定义消息
    debug:
      msg: "当前主机 {{ inventory_hostname }} 的 IP 是 {{ ansible_host }}"

  - name: 打印所有变量(排查时超有用)
    debug:
      var: hostvars[inventory_hostname]

详细输出模式

# -v   显示任务结果
# -vv  显示任务输入和结果
# -vvv 显示 SSH 连接信息
# -vvvv 显示所有调试信息(最详细)
ansible-playbook site.yml -vvv

从指定任务开始 / 在指定任务停止

# 从某个任务开始执行(跳过前面的)
ansible-playbook site.yml --start-at-task="安装 nginx"

# 执行到某个 tag 就停止
ansible-playbook site.yml --tags "install"

Tags 标签——精准执行

tasks:
  - name: 安装 nginx
    apt:
      name: nginx
    tags:
      - install
      - nginx

  - name: 配置 nginx
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    tags:
      - configure
      - nginx

  - name: 部署代码
    git:
      repo: https://github.com/myapp.git
      dest: /var/www/html
    tags:
      - deploy
# 只执行带 install tag 的任务
ansible-playbook site.yml --tags "install"

# 跳过带 deploy tag 的任务
ansible-playbook site.yml --skip-tags "deploy"

# 列出所有 tags(不执行)
ansible-playbook site.yml --list-tags

三、⚡ 性能优化

开启 SSH 连接复用(ControlPersist)

# ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True

这两个配置可以大幅减少 SSH 握手时间,在管理大量服务器时效果显著。

异步任务

tasks:
  - name: 后台执行耗时操作
    shell: /scripts/long_running_task.sh
    async: 300       # 最多等300秒
    poll: 10         # 每10秒检查一次

  - name: 等待任务完成
    async_status:
      jid: "{{ long_task.ansible_job_id }}"
    register: job_result
    until: job_result.finished
    retries: 30

四、✅ 最佳实践总结

代码规范

# ✅ 好的写法
- name: 安装 nginx Web 服务器          # 任务名称清晰,说明做什么
  apt:
    name: nginx
    state: present
  tags: [install, nginx]               # 打标签

# ❌ 差的写法
- apt: name=nginx state=present        # 单行写法,没有 name,不打 tag

目录结构规范

✅ 推荐:
project/
├── ansible.cfg
├── inventory/
│   ├── production/
│   └── staging/
├── group_vars/
├── host_vars/
├── roles/
└── site.yml

❌ 不推荐:
project/
└── all_in_one_giant_playbook.yml    # 所有内容堆一个文件

安全规范

✅ 所有密码、密钥用 Ansible Vault 加密
✅ 生产和测试使用不同的 inventory 和变量文件
✅ ansible.cfg 中关闭 host_key_checking 仅限实验环境
✅ 给所有任务写清晰的 name
✅ 给关键任务打 tags,方便单独执行
✅ Playbook 提交 Git 前用 --syntax-check 检查语法
✅ 在生产环境执行前先用 --check 演习

❌ 不要把密码明文写在 YAML 文件里
❌ 不要用 shell 模块做 apt/yum 能做的事
❌ 不要在 Playbook 里硬编码 IP 地址

推荐工具链

工具用途
ansible-lint检查 Playbook 代码质量
molecule对 Role 进行测试
ansible-navigator现代化的 Ansible 执行界面
AWX / Ansible Tower企业级 Ansible 管理平台(Web UI)
# 安装代码质量检查工具
pip3 install ansible-lint

# 检查 Playbook
ansible-lint site.yml

🎯 第10课 小测验

  1. 为什么不能把数据库密码明文写在 YAML 文件里提交到 Git?
  2. ansible-playbook site.yml --tags "install" 的效果是?
  3. ansible.cfg 中开启 pipelining = True 有什么作用?
👆 点击查看答案
  1. Git 仓库(尤其是远程仓库)可能被多人访问,明文密码一旦泄露,服务器和数据库将面临安全风险。应使用 Ansible Vault 加密敏感信息
  2. 只执行 Playbook 中打了 install 标签的任务,其他任务跳过,实现精准执行
  3. 开启 SSH 管道传输,减少 SSH 连接次数,大幅提升 Ansible 在大规模机器上的执行速度


🎓 课程总结与学习路线

🗺️ 知识体系回顾

Ansible 核心知识体系
│
├── 基础概念
│   ├── 控制节点 / 被管节点
│   ├── SSH 免密登录
│   └── 幂等性
│
├── Inventory(主机清单)
│   ├── INI / YAML 格式
│   ├── 主机分组
│   └── group_vars / host_vars
│
├── Ad-hoc 命令(临时任务)
│   └── ping / command / shell / copy / apt / service
│
├── Playbook(剧本)
│   ├── tasks / handlers / loop / when
│   └── ansible-playbook 命令行
│
├── Variables(变量)
│   ├── 多种定义方式
│   ├── 优先级
│   └── register / magic variables
│
├── Templates(模板)
│   ├── Jinja2 语法
│   └── 过滤器
│
├── Roles(角色)
│   ├── 标准目录结构
│   └── Ansible Galaxy
│
└── 进阶技巧
    ├── Vault 加密
    ├── Tags 标签
    ├── 调试方法
    └── 最佳实践

🚀 学完后能做什么?

能力具体体现
🖥️ 批量管理服务器100台机器一键配置,5分钟完成
🚀 自动化部署一条命令部署完整 Web 应用
🔄 持续交付结合 Jenkins/GitLab CI 实现 CI/CD
🔒 安全合规批量安全加固、审计配置
📊 系统运维自动巡检、备份、日志处理

📖 推荐进阶学习资源

  • 📚 官方文档docs.ansible.com(最权威)
  • 🌐 Ansible Galaxygalaxy.ansible.com(社区 Role)
  • 📦 Molecule:编写 Role 测试用例
  • 🏢 AWX(开源版 Tower):Ansible 的 Web 管理平台
  • 📗 推荐书籍:《Ansible for DevOps》- Jeff Geerling