一、结论先给
Ansible 的价值不在"能批量跑命令",而在幂等——同一份 Playbook 跑一百遍,结果应该一致,第二次开始不应该有任何 changed。
| 你想做什么 | 用什么 |
|---|---|
| 临时看一眼、批量执行简单命令 | ad-hoc:ansible all -m shell -a "uptime" |
| 要留下可复用、可评审、可回滚的变更 | Playbook |
| 要区分环境(生产/测试) | inventory 分组 + group_vars |
| 要存密码/密钥 | ansible-vault 加密,绝不进 Git 明文 |
第一原则:能用专用模块就不用 shell。专用模块自带幂等判断,shell 没有,每次都会 changed,重跑就可能重复追加、重复创建。
👉 命令记不全时查 Ansible 命令速查(在线)。执行前确认目标机 22 端口通不通:SSH 连通检测。
二、安装与连通性验证
# 控制端(只要装在一台机器上,被控端不需要装 agent,只要有 Python + SSH)
yum install -y ansible # CentOS/RHEL:先装 epel-release
apt install -y ansible # Ubuntu
pip3 install ansible # 通用,版本最新
ansible --version
被控端最低要求:SSH 可达 + Python 2.7 或 3.5+。CentOS 7 自带 Python 2,Ubuntu 20.04+ 只有 python3,可能要指定解释器:
[all:vars]
ansible_python_interpreter=/usr/bin/python3
2.1 配 SSH 免密(必须先做)
Ansible 走 SSH,控制端到被控端要免密,否则每次都要输密码:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_ansible
for h in 10.0.0.{11,12,13}; do ssh-copy-id -i ~/.ssh/id_ed25519_ansible.pub root@$h; done
免密配不通就先别碰 Ansible,直接看 SSH 免密登录配置与排障。
2.2 第一次连通测试
cat > /etc/ansible/hosts <<'EOF'
[web]
10.0.0.11
10.0.0.12
[db]
10.0.0.21
[prod:children]
web
db
EOF
ansible all -m ping
返回 SUCCESS => {"ping": "pong"} 才算通。
三、Ad-hoc 命令:临时任务的正确姿势
格式:ansible <主机模式> -m <模块> -a "<参数>" [选项]
# 看负载
ansible all -m command -a "uptime"
# 需要管道/重定向时必须用 shell(command 不经过 shell)
ansible web -m shell -a "df -h | grep -v tmpfs"
# 分发文件
ansible all -m copy -a "src=/tmp/nginx.conf dest=/etc/nginx/nginx.conf mode=0644 backup=yes"
# 装包(yum/apt 通用用 package)
ansible web -m package -a "name=nginx state=present"
# 服务管理
ansible web -m systemd -a "name=nginx state=reloaded enabled=yes"
# 拉文件回来
ansible db -m fetch -a "src=/var/log/mysql/error.log dest=/tmp/logs/ flat=no"
# 收集信息(不跑任务,只看 facts)
ansible web -m setup -a "filter=ansible_distribution*"
常用选项:
| 选项 | 作用 |
|---|---|
-i inventory.ini |
指定清单文件 |
-u ops |
登录用户 |
-b / --become |
提权(sudo) |
-K |
询问 sudo 密码 |
--limit web / --limit 10.0.0.11 |
只跑部分主机 |
-f 20 |
并发数(默认 5) |
-C / --check |
干跑,只报会改什么 |
--diff |
显示文件变更差异 |
-v / -vvv |
详细输出,排障加 -vvv |
上线前必做:
ansible-playbook site.yml --check --diff # 干跑 + 看差异
ansible-playbook site.yml --limit web[0] # 先跑一台灰度
四、Inventory:分组、变量与批量写法
# inventory.ini
[web]
web-[01:05].example.com # 等于 web-01 ... web-05
[web:vars]
ansible_user=ops
ansible_port=2222
nginx_worker=4
[db]
db01 ansible_host=10.0.0.21 ansible_user=root
[prod:children]
web
db
更推荐用 group_vars / host_vars 目录,结构清晰、能进 Git:
inventory/
hosts.ini
group_vars/
all.yml
web.yml
host_vars/
db01.yml
变量优先级(从低到高,常用的几档):
group_vars/all < group_vars/web < host_vars/db01 < playbook vars < 命令行 -e
命令行 -e 优先级最高,灰度时用 -e "version=1.2.3" 覆盖很方便。
五、12 个高频模块(附幂等写法)
| 模块 | 用途 | 关键点 |
|---|---|---|
ping |
连通性 | 不查 SSH,只验证 Ansible 端到端可用 |
command |
执行命令 | 不支持管道/变量,最安全 |
shell |
执行脚本 | 支持管道,但非幂等,要配 creates/removes |
copy |
传文件 | backup=yes 留备份,validate 先验配置 |
template |
传 Jinja2 模板 | 变量渲染,配置文件首选 |
file |
建目录/改权限/软链 | state: directory/link/absent |
lineinfile |
改单行 | regexp + line,宁可它也不要 sed |
blockinfile |
改多行块 | 带标记,可重复执行 |
package/yum/apt |
装包 | state: present/latest/absent |
systemd/service |
服务 | enabled: yes + daemon_reload: yes |
user |
用户 | 配合 authorized_key 分发公钥 |
get_url/unarchive |
下载/解压 | 发布包常用 |
5.1 copy + validate(改配置的标准写法)
- name: 推送 nginx 配置
ansible.builtin.copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
validate: '/usr/sbin/nginx -t -c %s' # 语法不对就不覆盖,防止把服务搞挂
notify: reload nginx
validate 是关键防线——配置语法错的时候,Ansible 直接失败而不是把坏配置推上去。
5.2 lineinfile(改单行的幂等写法)
- name: 关闭 SELinux
ansible.builtin.lineinfile:
path: /etc/selinux/config
regexp: '^SELINUX='
line: 'SELINUX=disabled'
- name: 调大文件句柄数
ansible.builtin.lineinfile:
path: /etc/security/limits.conf
regexp: '^\* soft nofile'
line: '* soft nofile 65535'
create: yes
5.3 authorized_key(批量分发公钥)
- name: 部署运维公钥
ansible.builtin.authorized_key:
user: ops
key: "{{ lookup('file', '~/.ssh/id_ed25519_ansible.pub') }}"
state: present
exclusive: yes # 清掉该用户下其他未声明的公钥,安全但注意影响
5.4 shell 的幂等补丁
- name: 初始化数据库(只跑一次)
ansible.builtin.shell: /opt/app/bin/init-db.sh
args:
creates: /opt/app/.db_initialized # 这个文件存在就跳过
六、Playbook 骨架与 handler
---
- name: 部署 Web 服务
hosts: web
become: yes
gather_facts: yes
serial: "30%" # 分批次滚动,30% 一批,出问题只影响一部分
max_fail_percentage: 20 # 失败超过 20% 就中止
vars:
app_version: "1.2.3"
tasks:
- name: 创建应用目录
ansible.builtin.file:
path: /opt/app
state: directory
mode: '0755'
- name: 推送配置
ansible.builtin.template:
src: app.conf.j2
dest: /opt/app/app.conf
notify: restart app # 只有文件真的变了才触发
- name: 启动服务
ansible.builtin.systemd:
name: app
state: started
enabled: yes
daemon_reload: yes
handlers:
- name: restart app
ansible.builtin.systemd:
name: app
state: restarted
handler 的三个特点:只在有 notify 且任务状态为 changed 时触发;不管被通知几次都只执行一次;在所有 tasks 跑完后统一执行。这就是为什么配置变更用它而不是在 task 里直接 restart。
6.1 when / loop / tags
- name: 只在 CentOS 上装 epel
ansible.builtin.yum:
name: epel-release
state: present
when: ansible_distribution == "CentOS"
- name: 建多个目录
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /data/logs
- /data/tmp
- /data/backup
- name: 灰度校验
ansible.builtin.uri:
url: http://127.0.0.1:8080/health
status_code: 200
tags: [verify]
retries: 10
delay: 3
until: result.status == 200
用 tags 只跑一部分:
ansible-playbook site.yml --tags verify
ansible-playbook site.yml --skip-tags verify
七、Vault:密码不能明文进 Git
# 加密整个变量文件
ansible-vault create group_vars/prod/vault.yml
ansible-vault edit group_vars/prod/vault.yml
# 只加密一个字符串(推荐,能直接贴进 yml)
ansible-vault encrypt_string 'P@ssw0rd' --name 'db_password'
输出可以直接贴:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
6638613565376538...
运行时提供密码:
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
纪律:.gitignore 里加 *.vault_pass,密码文件绝不入库。
八、加速:大批量时把时间砍下来
默认并发只有 5,100 台机器会很慢:
# ansible.cfg
[defaults]
host_key_checking = False # 内网批量可接受,省去首次确认
forks = 50
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400 # facts 缓存一天
[ssh_connection]
pipelining = True # 减少 SSH 往返,性能提升明显
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
不需要 facts 的 playbook 直接关掉,能省下每台机几秒:
- hosts: web
gather_facts: no
对不需要顺序依赖的任务用 free 策略:
- hosts: web
strategy: free # 谁先跑完谁继续,不被最慢的机器拖住
九、故障排查清单
| 现象 | 原因 | 处理 |
|---|---|---|
UNREACHABLE! ... Failed to connect |
SSH 不通/端口/安全组 | 先 ansible all -m ping -vvv,再用 SSH 检测 |
Using a SSH password instead of a key |
没配免密 | 配 ssh-copy-id |
Host key checking is enabled |
首次连接要确认 | host_key_checking = False 或先 ssh-keyscan |
/bin/sh: /usr/bin/python: not found |
目标机没有 python2 | 设 ansible_python_interpreter=/usr/bin/python3 |
Missing sudo password |
忘了 -K |
加 --ask-become-pass 或配 ansible_become_password |
Permission denied (publickey) |
密钥不对 | 见 SSH 免密那篇 |
| 任务每次都 changed | 用了 shell/command 无幂等 |
换专用模块或加 creates |
| 某台卡住拖慢全批 | 默认 linear 策略 | 加 strategy: free 或 timeout |
排障万能:
ansible-playbook site.yml -vvv --limit 10.0.0.11 # 单台 + 详细
ansible-playbook site.yml --step # 每步确认
ansible-playbook site.yml --start-at-task="推送配置" # 从某步继续
👉 常用模块参数记不住,查 Ansible 命令速查 和 Linux 命令速查。
十、常见误区
- 把 Playbook 当 shell 脚本写:全篇
shell模块,跑第二次就出问题,且没有任何可读性优势。 - 不分批(serial):100 台一起重启,服务直接不可用。生产必须
serial。 - 跳过
--check直接上:配置语法错就是全站事故。 - handler 里写多个任务还期望顺序:handler 按定义顺序执行,但别依赖隐式顺序,需要顺序就拆成
listen分组。 - 密码明文进 Git:一旦推上去就算删了历史里也有,必须 vault 加密。
gather_facts从不关:多数 playbook 根本用不到 facts,白花时间。
十一、几条纪律
- 一切变更进 Playbook,进 Git,可评审可回滚——ad-hoc 只用于查询,不用于变更。
- 上线前
--check --diff,再--limit灰度一台,再全量。 - 生产环境必须配
serial和max_fail_percentage。 - 敏感信息一律 vault,密码文件不入库。
- 优先专用模块,
shell是最后手段且必须配幂等条件。 - 变更要有校验任务(如
uri探活),失败自动中止而不是继续执行。
十二、延伸阅读
👉 相关在线工具:Ansible 命令速查 · SSH 连通检测 · Linux 命令速查,免登录、数据不上传。