DevOps ·
Ansible 入门:安装、Playbook 编写与日常排障
Ansible 是运维自动化必备工具,基于 SSH 无 Agent 架构。本文从安装、Inventory、模块、Playbook 开始,到 Roles、常用场景、常见报错排查,系统整理一套日常能用的学习与排障手册。
Ansible 入门:安装、Playbook 编写与日常排障
Ansible 是一款基于 SSH、无 Agent、声明式的自动化配置管理工具,用 Python 编写,用 YAML 描述任务。相比 Puppet/Chef/Salt,它最大的优势是被控端不用装任何东西——只要能 SSH 上去、有 Python 就能管。
一、学习路线与核心概念
1.1 核心组件速览
| 组件 | 作用 | 说明 |
|---|---|---|
| Control Node | 控制节点 | 执行 ansible / ansible-playbook 的机器,一般是你的 Mac / 跳板机 |
| Managed Node | 被控节点 | 被管理的服务器,只需要 SSH + Python 2.7/3.5+ |
| Inventory | 主机清单 | 写哪些机器要管,支持分组、变量 |
| Module | 模块 | 单步操作的最小单元,如 yum、copy、service、shell |
| Playbook | 剧本 | YAML 文件,把多个模块按顺序编排成一套任务 |
| Role | 角色 | 把 Playbook + 模板 + 文件 + 默认变量按目录结构打包,便于复用 |
| Task | 任务 | Playbook 里的一步,通常调用一个模块 |
| Handler | 处理器 | Task 通知触发(比如改完配置才重启服务) |
| Facts | 事实 | 被控端自动采集的变量(OS、网卡、CPU、磁盘等) |
1.2 学习路线建议
- 单模块先跑通
ansiblead-hoc(临时命令)→ 理解 Inventory + SSH 连通 - 会写 10–20 行的小 Playbook(装包→写配置→启服务)→ 掌握
copy/template/service/package四大基础模块 - 学会变量优先级、条件
when、循环loop、block错误处理 - 把 Playbook 拆成 Roles,理解
ansible-galaxy与社区角色 - 进阶:
ansible-lint、Vault 加密密码、AWX/Tower Web 界面、Molecule 测试
二、安装与第一个命令
2.1 安装控制节点
# macOS
brew install ansible
# Debian / Ubuntu
sudo apt update && sudo apt install -y ansible
# pip(版本最新)
python3 -m pip install --user ansible
# 验证
ansible --version
# 关注:ansible [core x.y.z],config file,python version
2.2 准备 Inventory
最简单写一个 hosts 文件(INI 格式):
[web]
web01 ansible_host=192.168.1.10 ansible_user=root
web02 ansible_host=192.168.1.11 ansible_user=deploy
[db]
db01 ansible_host=192.168.1.20 ansible_port=2222 ansible_user=root
[prod:children]
web
db
[prod:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519
分组支持 [group:children] 嵌套、[group:vars] 变量。更推荐生产用 YAML 格式或 Dynamic Inventory(从 CMDB / 云平台拉)。
2.3 第一个 ad-hoc 命令
# -i 指定 inventory,all = 所有主机,-m ping 模块(不是 ICMP,是 SSH+Python 连通测试)
ansible all -i hosts -m ping
成功返回 pong 就说明 SSH 认证 + Python 环境 OK。
# 收集 Facts
ansible web -i hosts -m gather_facts
# 批量执行 shell
ansible web -i hosts -m shell -a "uptime && free -h"
# 批量安装包(幂等:装过就跳过)
ansible web -i hosts -m apt -a "name=nginx state=present update_cache=yes" -b
# -b = --become,相当于 sudo
三、写第一个 Playbook
建一个 nginx.yml:
---
- name: 配置并启动 Nginx
hosts: web
become: true
gather_facts: true
vars:
nginx_worker_connections: 1024
nginx_port: 80
tasks:
- name: 安装 Nginx
ansible.builtin.package:
name: nginx
state: present
- name: 部署 nginx.conf 模板
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: "nginx -t -c %s" # 保存前先校验配置语法
notify: 重启 Nginx # 文件变了才触发 handler
- name: 确保 Nginx 启动并开机自启
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: 放通防火墙
ansible.builtin.firewalld:
port: "{{ nginx_port }}/tcp"
permanent: true
immediate: true
state: enabled
when: ansible_os_family == 'RedHat' # 条件:只有 RHEL/CentOS/Rocky 才执行
handlers:
- name: 重启 Nginx
ansible.builtin.service:
name: nginx
state: restarted
然后建模板 templates/nginx.conf.j2(Jinja2):
worker_processes auto;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen {{ nginx_port }};
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}
执行:
# 先 DRY RUN 看会改什么(推荐每次先跑)
ansible-playbook -i hosts nginx.yml --check --diff
# 真的执行
ansible-playbook -i hosts nginx.yml
# 限制只跑某台主机,加 -l
ansible-playbook -i hosts nginx.yml -l web01
# 只跑叫"部署配置"的 task,加 --tags
ansible-playbook -i hosts nginx.yml --tags config
四、常用基础模块 Top 10
| 模块 | 场景 | 示例 |
|---|---|---|
ansible.builtin.package | 装包(跨发行版自动选 yum/apt/dnf) | package: name=vim state=present |
ansible.builtin.copy | 拷贝本地文件到远端 | copy: src=app.properties dest=/opt/app/ mode=0600 |
ansible.builtin.template | 拷贝 Jinja2 模板并渲染 | 见上面 Playbook 例子 |
ansible.builtin.service / systemd | 管理服务启停、开机自启 | service: name=crond state=started enabled=yes |
ansible.builtin.file | 建目录/软链/改权限 | file: path=/data/logs state=directory mode=0755 |
ansible.builtin.lineinfile | 改配置文件的某一行 | lineinfile: path=/etc/sysctl.conf regexp='^fs.file-max' line='fs.file-max=65535' |
ansible.builtin.replace | 正则替换多行 | replace: path=... regexp='old' replace='new' |
ansible.builtin.user / group | 建用户组 | user: name=deploy shell=/bin/bash groups=sudo append=yes |
ansible.builtin.get_url | 下载文件 | get_url: url=https://.../app.tar.gz dest=/tmp/ checksum=sha256:xxx |
ansible.builtin.shell / command | 跑命令(尽量少用) | shell: "cat /var/log/nginx/*.log | wc -l" |
小原则:能用声明式模块解决的就不要用 shell,shell 会破坏幂等性。
五、变量优先级速记(从低到高)
越后面优先级越高:
- Role defaults(
roles/x/defaults/main.yml) - Inventory
group_vars/all - Inventory
group_vars/<group> - Inventory
host_vars/<host> - Playbook
vars - Playbook
vars_prompt/vars_files - Role
vars(roles/x/vars/main.yml) --extra-vars/-e命令行(最高,-e "foo=bar")
生产建议:敏感变量(密码、Token)用 Ansible Vault,别明文写:
# 加密一个文件
ansible-vault encrypt group_vars/prod/vault.yml
# 执行时加 --ask-vault-pass 或 --vault-password-file
ansible-playbook site.yml --ask-vault-pass
六、Role 目录结构
roles/
nginx/
defaults/main.yml # 最低优先级变量(可被覆盖)
vars/main.yml # 高优先级变量
tasks/main.yml # 任务入口
handlers/main.yml # handlers
templates/ # .j2 模板
files/ # 直接拷贝的文件
meta/main.yml # 角色依赖
在 Playbook 里引用:
- hosts: web
roles:
- nginx
七、日常排障手册
7.1 调试手段清单
| 需求 | 命令 / 做法 |
|---|---|
| 最 verbose 输出,看到每个模块传参和 SSH 细节 | -vvvv |
| 只看会改什么,不动真实环境 | --check --diff |
| 打印变量值 | - debug: var=myvar 或 `- debug: msg=”{{ myvar |
| 停止在第一个失败任务处调试 | --step(每步问)或 --start-at-task='Task名' |
| 单步快速验证某个模块 | 用 ad-hoc ansible host -m xxx -a '...' |
| 验证 Playbook 语法 | ansible-playbook yml --syntax-check |
| 静态扫描最佳实践 | ansible-lint playbook.yml |
7.2 常见报错对照表
| 报错关键字 | 根因 / 排查方式 |
|---|---|
FAILED! => {"msg": "Failed to connect to the host via ssh: ... Permission denied (publickey,password)."} | SSH 不通:检查 ansible_user / 私钥路径 / 目标主机 authorized_keys / 本地 ssh-add -l 是否有 Key,先手动 ssh user@host 测 |
MODULE FAILURE\nSee stdout/stderr for the exact error + /bin/sh: /usr/bin/python: No such file or directory | 目标没装 Python(Ubuntu 最小化安装常见)。装 python3,或 inventory 加 ansible_python_interpreter=/usr/bin/python3 |
Missing sudo password | 需要 -K / --ask-become-pass 输入 sudo 密码,或确认 become 配置是否正确 |
Syntax Error while loading YAML | YAML 缩进错、Tab 代替空格、冒号后面没空格。用 yamllint 或 VS Code YAML 插件先查 |
The task includes an undefined variable | 变量没定义 / 打错名字。加 defaults 过滤器兜底:`{{ myvar |
FAILED! => {"changed": false, "msg": "Destination /etc/xxx is not writable"} | 没 become: true 或 become 用户权限不足 |
Ansible received a broken pipe | 长任务中途 SSH 断了。开 SSH keepalive(ansible.cfg 里 ssh_args = -o ServerAliveInterval=30)或改 async / poll |
ERROR! conflicting action statements | 同一个 task 里写了两个模块名。一个 task 只能调用一个模块 |
| handler 不执行 | handler 是”通知触发”——必须有 task changed=true 并 notify: 才会跑;且整个 play 结束前只执行一次 |
FAILED – RETRYING (until/retry 超时) | 用了 retries + until,条件始终不满足。把 register 的结果 debug 出来看真实返回 |
7.3 一条命令:用最短路径复现环境
出问题时,先从最短链路逐步放大:
# 1. SSH 本身通吗?
ssh -o PreferredAuthentications=publickey deploy@192.168.1.10 echo OK
# 2. Python 能跑吗?
ssh deploy@192.168.1.10 python3 --version
# 3. ansible ad-hoc 单模块
ansible web01 -i hosts -m ping -vvv
# 4. playbook check mode
ansible-playbook -i hosts nginx.yml -l web01 -C -D -vvv
7.4 性能慢怎么办
- 开启 SSH 长连接(ControlMaster):在
ansible.cfg加[ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30 gather_facts: false:不需要 Facts 就关掉strategy: free:主机间不再互相等步骤--forks 20:加大并行度(默认 5)- 用
ansible.posix.firewalld/ 原生模块,别写一堆 shell
八、进阶学习方向
- Ansible Collections:
ansible-galaxy collection install community.general community.mysql,现代版本几乎所有非核心模块都在 Collections 里。 - Molecule:给 Role 写测试(默认用 Docker/Driver + Ansible Verifier)。
- AWX / Ansible Tower / Semaphore:把 Playbook 放到 Web 上定时跑、接审批、审计日志、RBAC。
- Ansible Pull:反过来——节点自己定时
ansible-pull拉 Git 上的 Playbook 本地执行,更适合几千台规模。 - Filter & Lookup:Jinja2 filters(
selectattr、map、combine、regex_replace)是写 Playbook 的瑞士军刀。