Ansible 自动化运维实战:一套 Playbook 批量管理你所有的 VPS


当你有了三台、五台、落到十台 VPS 的时候,问题就从"怎么部署一台"变成了"怎么一次性在十台上做同样的事还不出岔子"。手工 SSH 登录一台台改配置,改到第五台就忘了第二台改没改;换一个人来操作又可能把参数写错。Ansible 解决的正是"同一样的状态、批量地、可重复、可审计"地施加到多台机器上,而且它是无代理的——不需要在目标机装 agent,只依赖 SSH。这篇从它的执行模型讲起,落到 inventory、module、Playbook、变量与 role、幂等与安全这几个关键点上。

一、先理解 Ansible 是怎么工作的:Push 模型与幂等

Ansible 采用 Push 模型:在一台控制机(通常是你本地或一台管理机)上执行命令,它通过 SSH 连到目标机,把 Playbook 里声明的一组任务依次下发执行。和 Puppet/Chef 那种"被管节点定期去拉取并上报"的模式相反,Ansible 是"控制机主动推下去"。最大的好处就是不用在被管机上装任何常驻 agent,也不依赖目标机上有没有 agent 对应的版本——只要目标机有 Python 和 SSH,就能被管理。

理解 Ansible,绕不开"幂等(idempotent)"这个词。它指的是:同一个 Playbook 对着同一台机器跑一遍和跑十遍,最终状态是一致的。好的 Ansible 模块会先"探测当前状态"再"决定要不要改",而不是无脑把命令再执行一次。举个例子,官方模块 package 装软件:如果软件已经在、版本也对,它就什么都不做并返回 ok;只有确实没装或版本不符才去装。这种"先看再说"的设计,让重复运行变成安全操作,也让"哪些机器改过了"可以从执行结果(changed / ok / failed)直接读出来。

二、Inventory:先告诉它"管哪些机器"

Inventory 是 Ansible 的"机器清单",定义要管理哪些主机、怎样分组、每台有什么变量。默认存在 /etc/ansible/hosts 或项目里的 hosts.ini,格式是 INI 或 YAML。一个典型的清单长这样:

# hosts.ini
[web]
web1.example.com ansible_host=192.168.1.10 ansible_user=deploy
web2.example.com ansible_host=192.168.1.11 ansible_user=deploy

[db]
db1.example.com ansible_user=deploy ansible_port=22

[backup]
synology.home ansible_connection=ssh

[all:vars]
ansible_python_interpreter=/usr/bin/python3

分组的好处在于可以把"策略"作用到组而不是单台:[web] 组的机器一起装 Nginx,[db] 组一起配数据库。组名和变量能嵌套继承(组套组),用 ansible-inventory --list 可以随时查看解析后的完整清单。给连接的设置放在主机变量里,比在每个命令重复 -u、-k 干净得多。

三、最常用的两个命令:先跑 Ad-hoc 再上 Playbook

还没到写 Playbook 阶段,先学会用 ad-hoc 命令试探目标机是否通了、跑一个简单操作验证配置。ad-hoc 是单条命令模式的 Ansible:

# 测试连通性(返回 pong 表示可以管理)
ansible all -i hosts.ini -m ping

# 给 web 组的所有机器装 ntp,并启动开机自启
ansible web -i hosts.ini -m package -a "name=ntp state=present"

# 对 backup 组的机器执行一条任意 shell
ansible backup -i hosts.ini -m shell -a "df -h /"

-m 指定模块(ping、package、shell、copy、service 等),-a 传模块参数。ad-hoc 适合"批量看状态、批量装个包"这种即兴操作;一旦要管理多个步骤或需要复用,就该写成 Playbook,加进版本库,让"这台机器是什么状态"成为可追溯的代码。

四、Playbook:把"一堆操作"变成一段可读的剧本

Playbook 是 YAML 文件,描述"对谁(hosts)做什么(tasks)"以及执行策略。一个最小可用的例子,把 web 机器的 Nginx 从"装-配-起"三件事一次做掉:

---
- name: 部署 Nginx 基础配置
  hosts: web
  become: true
  tasks:
    - name: 安装 nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: 覆盖默认站点配置
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: 重载 nginx

    - name: 启用并启动 nginx
      ansible.builtin.service:
        name: nginx
        enabled: true
        state: started

  handlers:
    - name: 重载 nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

这里有两个值得说明的设计:一是 template 使用 Jinja2 模板(nginx.conf.j2)把配置文件的动态部分写成变量,一个模板能套用到几十台机器;二是 notify/handlers——当任务确实产生了变更(changed)时才触发 handler 去重载服务,如果状态没变就不动它,避免"每次跑一遍都把服务重载一遍"这种多余的跳动。执行时用 ansible-playbook -i hosts.ini deploy.yml,加上 --check 可以只做"预演"(dry-run)不改真机,是上线前校验的标准动作。

五、变量、模板与 Jinja2:别把"魔法数字"写死在任务里

任务写死具体端口、用户名、版本号,换一台环境就要改 Playbook,违背了"一份剧本管所有机器"的初衷。Ansible 提供多层变量来源,优先级从低到高大致是 inventory 变量、组的 group_vars、项目里的 vars、命令行 -e 注入。常用组织方式是 group_vars/web.yml、host_vars/web1.yml,让"这台机器自己的差异"独立出来:

# group_vars/web.yml
nginx_listen_port: 8080
nginx_domain: blog.example.com
ssl_enable: yes
# template 里用 {{ }} 引用变量
server {
    listen {{ nginx_listen_port }};
    server_name {{ nginx_domain }};
    {% if ssl_enable %}
    listen 443 ssl;
    {% endif %}
}

变量拿来放"差异",模板拿来做"形状",两者配合,Playbook 本身就能保持"一台机器 = 一套剧本 + 若干变量"的干净结构。想掌握每台机器最终用了什么值,用 ansible -m debug -a "var=hostvars['web1']" 直接查运行时解析结果,比肉眼猜可靠。

六、用 Role 组织大型运维:把剧本拆成可复用组件

当一个项目从几十行涨到几百行,把同族任务堆在一个文件里就乱了。Ansible 提供的答案是 Role——按"用途"组织的目录结构:

roles/
  nginx/
    tasks/main.yml    # 这个 role 要做的任务
    handlers/main.yml # 变更后触发的事件
    templates/        # .j2 模板
    vars/main.yml     # role 专属变量
    defaults/main.yml # 低优先级的默认变量
    files/            # 需要原样拷贝的静态文件
  postgres/
    tasks/main.yml
    templates/

Playbook 里引用 roles: 和 role: nginx, role: postgres,就能把"装 Nginx""装 PostgreSQL"这类通用能力做成可复用的积木,不同项目、不同机器共享同一套。Role + 变量默认值的设计,意味着你可以直接拖一个社区成熟的 role 进来,覆盖 defaults 里的默认值即可适配自己的环境,不必重写逻辑。这是 Ansible 生态能够"一个角色复用多年"的基础。

七、在配置管理里放好安全护栏:Vault 与最小授权

写得越自动化,"密钥放哪"就越要小心。Playbook 里直接写 SSH 密码、API token、数据库口令是常见事故源。Ansible Vault 可以对文件做 AES 加密,运行时用口令解密:用 ansible-vault encrypt group_vars/db.yml 把含敏感变量的文件加密,平时配合 --ask-vault-pass 或 vault key 文件解密。加密后的文件可以直接进 git 而不会泄露明文,解密只发生在执行的那几秒。

与密钥配套的是授权边界。别用 root 账号做整套管理;建议控制机用普通用户 + sudo 提权(Playbook 顶部的 become: true 控制),并把 SSH 设为密钥登录、禁用密码。这样即便控制机失守,攻击者拿到的也只是"能管这些机器"的钥匙,而不是所有机器的 root 明文口令。配置管理的"便利"必须建立在"默认安全"之上,否则自动化反而把问题的爆炸半径放大了。

结语

Ansible 的价值不是"写了一次就不用手工",而是把"这几台机器应该是什么状态"沉淀成可读、可版本化、可重复执行的定义。它的执行模型(无代理、基于 SSH、本身就是幂等的模块)决定了学习曲线很平:先玩 ad-hoc 摸清连通性,再把常用操作用 Playbook 固化,最后用 Role 与变量把剧本组织到能跨机器复用的规模。配合 Vault 管密钥、--check 做预演,一套自动化运维的底座就成型了。真正有效的不是一次跑通,而是让它成为每次改动都走同一条可审计路径的默认动作。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Redis 缓存与高可用实战:持久化、主从复制与 Sentinel 哨兵

Typecho 性能优化实战:从 PHP-FPM 到 CDN 的整条提速链路

评 论
更换验证码