Ansible 常用命令与 Playbook 速查:从 ad-hoc 到幂等编排

一、结论先给

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 命令速查。

十、常见误区

  1. 把 Playbook 当 shell 脚本写:全篇 shell 模块,跑第二次就出问题,且没有任何可读性优势。
  2. 不分批(serial):100 台一起重启,服务直接不可用。生产必须 serial。
  3. 跳过 --check 直接上:配置语法错就是全站事故。
  4. handler 里写多个任务还期望顺序:handler 按定义顺序执行,但别依赖隐式顺序,需要顺序就拆成 listen 分组。
  5. 密码明文进 Git:一旦推上去就算删了历史里也有,必须 vault 加密。
  6. gather_facts 从不关:多数 playbook 根本用不到 facts,白花时间。

十一、几条纪律

  1. 一切变更进 Playbook,进 Git,可评审可回滚——ad-hoc 只用于查询,不用于变更。
  2. 上线前 --check --diff,再 --limit 灰度一台,再全量。
  3. 生产环境必须配 serial 和 max_fail_percentage。
  4. 敏感信息一律 vault,密码文件不入库。
  5. 优先专用模块,shell 是最后手段且必须配幂等条件。
  6. 变更要有校验任务(如 uri 探活),失败自动中止而不是继续执行。

十二、延伸阅读

👉 相关在线工具:Ansible 命令速查 · SSH 连通检测 · Linux 命令速查,免登录、数据不上传。

还有 65 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具