🎓 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课 小测验
- Ansible 控制目标机器用的是什么协议?
- 用自己的话解释一下什么是"幂等性"?
- Ansible 需要在被管节点安装客户端吗?
👆 点击查看答案
- SSH 协议
- 多次执行同一任务,结果和执行一次完全相同,不会重复操作
- 不需要,被管节点只需要开启 SSH 服务 + 有 Python 环境即可
🔧 第 2 课:环境搭建——安装 Ansible + 配置实验环境
🧱 实验环境规划
用 3 台机器模拟真实场景:
| 角色 | 主机名 | IP 地址 |
|---|---|---|
| 控制节点 | ansible-master | 192.168.1.10 |
| 被管节点1 | web01 | 192.168.1.11 |
| 被管节点2 | web02 | 192.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 refused | SSH 未启动 | systemctl start sshd |
Permission denied (publickey) | 免密未配置 | 重新执行 ssh-copy-id |
Python not found | 被管节点缺 Python | apt install python3 -y |
Host key verification failed | SSH 指纹检查 | 配置文件设 host_key_checking = False |
🎯 第2课 小测验
- SSH 密钥对中,哪个发给被管节点,公钥还是私钥?
ansible all -m ping中-m代表什么?- Ansible 配置文件的优先级顺序是什么?
👆 点击查看答案
- 公钥(id_rsa.pub),私钥自己保管
- module(模块),指定使用哪个 Ansible 模块
- 环境变量 > 当前目录 > 用户目录 > 全局默认
📋 第 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_user | SSH 登录用户名 |
ansible_port | SSH 端口(默认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课 小测验
- 如何在 Inventory 中定义一个"父组",让它包含多个子组?
group_vars目录的作用是什么?- 主机名
web[01:05]代表哪些主机?
👆 点击查看答案
- 使用
[父组名:children]语法,下面列出子组名 - 存放组变量,该目录下的
webservers.yml会自动应用到 webservers 组所有主机 - 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课 小测验
command和shell模块的区别是什么?- 如何用一条 Ad-hoc 命令在所有 webservers 上安装 git?
-f 10参数代表什么意思?
👆 点击查看答案
command不支持管道、重定向等 shell 特性,更安全;shell通过 /bin/sh 执行,支持所有 shell 语法,但有注入风险ansible webservers -m apt -a "name=git state=present" --become- 同时对 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课 小测验
- Playbook 中
become: yes是什么意思? - Handlers 和普通 tasks 有什么区别?
- 如何让一个 task 只在 Ubuntu 系统上运行?
👆 点击查看答案
- 以提升的权限执行任务,等同于
sudo,默认提升到 root - Handler 只有被
notify触发时才执行,且无论被触发多少次,只在所有 tasks 结束后执行一次 - 使用
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课 小测验
- 哪种方式定义的变量优先级最高?
register关键字的作用是什么?ansible_distribution是什么类型的变量?
👆 点击查看答案
- 命令行
-e参数,优先级最高,可以覆盖所有其他变量定义 - 将任务的执行结果保存到一个变量,后续任务可以根据结果做判断
- 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课 小测验
- Ansible 使用哪个模板引擎?模板文件的常用后缀是什么?
- Jinja2 中
{{ }}和{% %}分别用来做什么? {{ app_name | default('myapp') }}这个过滤器的作用是什么?
👆 点击查看答案
- Jinja2 模板引擎,文件后缀通常为
.j2 {{ }}用于输出变量或表达式的值;{% %}用于控制结构(if、for 等逻辑语句)- 如果变量
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课 小测验
- Role 中
defaults/main.yml和vars/main.yml有什么区别? - 如何用一条命令自动创建 Role 的标准目录结构?
- Ansible Galaxy 是什么?
👆 点击查看答案
defaults/优先级低,可被 group_vars、host_vars 等覆盖,用于定义"可被用户覆盖的默认值";vars/优先级高,用于定义"不应被轻易覆盖的内部变量"ansible-galaxy role init roles/角色名- 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课 小测验
ansible-playbook site.yml --check的作用是什么?- 为什么要有
commonrole,它的作用是什么? python3-mysqldb包安装在哪台机器上?为什么?
👆 点击查看答案
- 演习模式(Dry Run),不会真正执行任何操作,只是预演并显示"如果执行会发生什么变化",用于提前检查
- Common role 做所有机器都需要的基础配置(时区、基础工具、NTP等),避免在每个 role 里重复写
- 安装在数据库服务器(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课 小测验
- 为什么不能把数据库密码明文写在 YAML 文件里提交到 Git?
ansible-playbook site.yml --tags "install"的效果是?- 在
ansible.cfg中开启pipelining = True有什么作用?
👆 点击查看答案
- Git 仓库(尤其是远程仓库)可能被多人访问,明文密码一旦泄露,服务器和数据库将面临安全风险。应使用 Ansible Vault 加密敏感信息
- 只执行 Playbook 中打了
install标签的任务,其他任务跳过,实现精准执行 - 开启 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 Galaxy:galaxy.ansible.com(社区 Role)
- 📦 Molecule:编写 Role 测试用例
- 🏢 AWX(开源版 Tower):Ansible 的 Web 管理平台
- 📗 推荐书籍:《Ansible for DevOps》- Jeff Geerling