news 2026/9/30 8:43:25

Ansible Playbook实战:20个场景化运维案例从入门到排错

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ansible Playbook实战:20个场景化运维案例从入门到排错

做运维这些年,有一个体会越来越深:能用一条命令解决的问题,绝不用十次手工操作去重复。Ansible就是我日常批量操作里最顺手的那把刀——没有agent、只走SSH、写好一个Playbook就能同时搞定几十台机器。这个系列的002篇,我把实战中真正写过的Playbook按场景做了拆解,挑出20个最常用的案例,从服务器初始化到应用部署、再到日常巡检,每一个都附上可复用的play片段和关键点的解释。如果你看过001篇(讲了安装、inventory和基础命令),这篇可以直接上手抄作业;如果刚接触Ansible,建议先把前几节的基础回顾一遍再往下写。

1. 动手前的准备:目录规划与配置文件

写Playbook之前有件事我经常提醒自己:目录结构比Playbook本身更能决定项目的生命力。一个乱七八糟的目录,三个月后连自己都不想看;一个清晰的目录,就算换人接手,半天就能看懂全貌。项目结构、配置文件、变量库各司其职,这比急于写第一个play重要得多。

1.1 标准目录结构从哪开始

我常用的顶层目录是这样:

ansible-project/ ├── ansible.cfg ├── inventory/ │ ├── production │ └── staging ├── playbooks/ ├── roles/ ├── group_vars/ └── host_vars/

初学阶段不必一上来就套Roles(roles),但至少要区分playbooks/和inventory/。等Playbook写到第10个以后,你会发现很多处理逻辑是重复的,那时候再把公共部分抽成roles,一次抽一个,不要重构上瘾一次全改。

ansible.cfg是整个项目的“默认行为说明书”,我一般会配这几项:

[defaults] inventory = ./inventory/production host_key_checking = False timeout = 30 stdout_callback = yaml

host_key_checking = False不是偷懒,而是自动化环境里首次连接时SSH指纹确认会卡住流程。如果你在意安全,可以改成ssh_args = -o StrictHostKeyChecking=accept-new,既保留首次信任提示,又不会互相阻塞。stdout_callback = yaml会让输出变成折叠的YAML格式,排错时一眼能看出哪个环节返回了什么。

1.2 inventory的写法不是只有IP列表

很多人写inventory就是一排IP,其实Ansible的inventory远不止主机列表,它还承担了变量分组的职责:

[web] web-01 ansible_host=192.168.1.11 web-02 ansible_host=192.168.1.12 [db] db-01 ansible_host=192.168.2.21 [nginx:vars] worker_processes=auto

组名后面的:vars可以直接给整组主机注入变量,在playbook里用{{ worker_processes }}引用。更高级的做法是用group_vars/和host_vars/目录里对应组名/主机名的YAML文件来管理变量,适合“不同环境不同参数”的场景:

group_vars/ ├── web.yml └── db.yml

生产环境密码、非生产环境密码就不需要反复改playbook了,只改变量文件即可。

1.3 YAML与Playbook语法的几个关键点

写Playbook的核心语法其实不多,但几个坑必须提前避:

  • 缩进只能用空格,且层级一致。我统一用2空格。
  • 模块名后的参数有两种写法,推荐用key: value的映射形式:
- name: 安装nginx ansible.builtin.yum: name: nginx state: present
  • 一个名字只对应一个play,不要图省事把多个动作塞进一个tasks里。
  • handlers(处理程序)只在任务真正发生变更后触发,适合重启服务这类“有变化才做”的操作。

2. 服务器基础初始化的6个Playbook

新环境收到一台裸机(或新开的云主机),第一件事永远是“初始化”:改主机名、调时区、关SELinux、建用户、放SSH公钥、装基础工具。这6个例子是整个批量运维的地基,也是新手练手的最佳入口。

2.1 例1:关闭SELinux与设置时区

SELinux不关掉,后面部署Nginx、MySQL时权限问题会让你怀疑人生。虽然不推荐无脑关闭,但在内网非强合规环境的机器上,关闭SELinux就是最大的省事。这个play我使用了selinux模块,而不是shell里执行setenforce:

- name: 基础安全配置 hosts: all gather_facts: yes tasks: - name: 确保selinux为disabled ansible.posix.selinux: state: disabled when: ansible_selinux.status == "enabled" - name: 设置时区为Asia/Shanghai community.general.timezone: name: Asia/Shanghai - name: 写入/etc/hosts中的本机解析 ansible.builtin.lineinfile: path: /etc/hosts line: "{{ ansible_default_ipv4.address }} {{ inventory_hostname }}"

这里用community.general.timezone模块而不是timedatectl命令,因为模块能保证幂等:即使重复执行,也不会重复报错或重复执行命令。when条件是Ansible的“防呆设计”,只有满足条件才执行,避免不必要的报错。

2.2 例2:创建系统用户并部署SSH公钥

批量建用户是运维高频操作。用user模块加authorized_key模块组合起来非常标准:

- name: 创建部署用户并配置免密登录 hosts: all vars: deploy_user: deploy deploy_key: "ssh-rsa AAAA... deploy@ansible" tasks: - name: 创建deploy用户 ansible.builtin.user: name: "{{ deploy_user }}" shell: /bin/bash groups: wheel append: yes create_home: yes state: present - name: 写入公钥 ansible.builtin.authorized_key: user: "{{ deploy_user }}" key: "{{ deploy_key }}" state: present

这块有个小细节:groups: wheel加append: yes是追加用户组,而不覆盖用户原有组。不写append会导致用户被移出其他组,这是个很隐蔽的问题,我在生产环境踩过一次。

2.3 例3-6:内核参数、主机名与基础工具包

  • 例3:修改内核参数(如net.core.somaxconn):
- name: 设置内核参数 ansible.posix.sysctl: name: net.core.somaxconn value: "1024" state: present reload: yes

reload: yes表示修改后立即刷新内核参数。如果你用command: sysctl -p,则每次运行都会执行一次,并不是幂等操作。

  • 例4:修改主机名:
- name: 设置主机名 ansible.builtin.hostname: name: "{{ inventory_hostname }}"

这个要注意:inventory里的主机名和实际规划的主机名必须一致,否则DNS、监控系统会乱套。

  • 例5:同步hosts文件:用template模块配合hosts.j2模板,适合管理一批主机时统一维护映射关系。
  • 例6:安装基础工具集:
- name: 安装常用基础包 ansible.builtin.yum: name: - vim - net-tools - lsof - tcpdump - bind-utils - wget state: present

包列表适合放变量文件,新主机要加什么包就不用改playbook了。

3. 应用部署的6个典型场景

初始化搞定,接下来是重头戏:应用部署。入门阶段最值得学的不是复杂的编排,而是“把一台机器从零装成一个能对外提供服务的节点”这个过程。这里我选了6个最常遇到的场景。

3.1 例7:Nginx站点部署与虚拟主机配置

Nginx部署是应用上线的第一步。这里的play我用了template来渲染虚拟主机配置,好处是每台机器的域名、端口都能通过变量控制:

- name: 部署nginx并配置虚拟主机 hosts: web vars: nginx_port: 8080 server_name: www.example.com tasks: - name: 安装nginx ansible.builtin.yum: name: nginx state: present - name: 渲染虚拟主机配置 ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/conf.d/{{ server_name }}.conf notify: reload nginx - name: 启动nginx并设置开机自启 ansible.builtin.service: name: nginx state: started enabled: yes handlers: - name: reload nginx ansible.builtin.service: name: nginx state: reloaded

模板文件nginx.conf.j2长这样:

server { listen {{ nginx_port }}; server_name {{ server_name }}; location / { proxy_pass http://127.0.0.1:8081; } }

notify是Playbook的精髓:只有模板内容真正变化,才会去reload nginx,否则配置没变就不重启服务,避免无谓中断线上连接。

3.2 例8:MySQL安装与初始化

数据库部署重点在“初始化”,而不只是“装上”。我一般分三步走:

- name: 安装并初始化mysql hosts: db vars: mysql_root_password: "{{ vault_mysql_root_password }}" tasks: - name: 安装mysql-server ansible.builtin.yum: name: mysql-server state: present - name: 修改root密码并允许本地登录 ansible.builtin.command: > mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_root_password }}';" changed_when: false - name: 创建应用数据库 community.mysql.mysql_db: name: "{{ app_db_name }}" state: present login_user: root login_password: "{{ mysql_root_password }}"

密码这类敏感信息建议放进Ansible Vault加密的变量文件里用vault_mysql_root_password引用,而不是明写在playbook中。这里有朋友会问:直接用mysql_db模块能不能初始化密码?模块参数不同版本略有差异,用command方式虽然“不够Ansible”,但胜在直观可控。

3.3 例9-12:Redis、Docker、Node.js 与 JDK

  • 例9:Redis安装加常用配置:安装包后修改/etc/redis.conf,把bind 127.0.0.1改成实际网卡地址,并设置requirepass。修改redis.conf我优先用lineinfile,它对“配置文件中单行替换”很友好。

  • 例10:Docker安装与镜像加速:

- name: 安装docker ansible.builtin.yum: name: docker-ce state: present - name: 配置镜像加速 ansible.builtin.copy: content: | {"registry-mirrors": ["https://docker.mirrors.example.com"]} dest: /etc/docker/daemon.json notify: restart docker

这里copy直接写内容比template更轻量,适合一小段JSON。能少一个文件就少一个文件。

  • 例11:Node.js环境安装:用get_url下载官方二进制包,unarchive解压到/opt/node/,再通过alternatives设置软链或直接丢/usr/local/bin。
  • 例12:JDK安装与环境变量:JDK我习惯用tar.gz包手动解压放到/usr/local/java,再写/etc/profile.d/java.sh。注意:/etc/profile.d下的脚本比修改/etc/profile更干净,卸载时只删一个文件即可。

4. 配置管理与日常维护的4个Playbook

配置管理部分的Playbook不是“一次性部署”,而是“持续可重复执行”的。用Ansible做日常变更,价值在于每一次执行都有记录、可回滚、可审计。

4.1 例13:环境变量与profile.d管理

批量给所有服务器设置全局环境变量,用blockinfile这个模块再方便不过。它能管理一个文件里由标记包裹的“块”,执行两遍不会重复插入:

- name: 写入全局代理与路径配置 ansible.builtin.blockinfile: path: /etc/profile.d/global_env.sh create: yes block: | export JAVA_HOME=/usr/local/java export PATH=$PATH:$JAVA_HOME/bin export APP_ENV=production

我见过不少用人肉命令一行行echo追加的方式,重复跑几次就会出现一堆重复配置。blockinfile自带“标记块”,用{{ ansible_managed }}标记后,再次执行只会更新这个块,不会往文件里无限追加。

4.2 例14:日志轮转与清理

日志一多,磁盘早晚被占满。Ansible里最常用的做法是配置logrotate,并创建一个只删除7天前日志的定时任务:

- name: 配置logrotate ansible.builtin.copy: content: | /var/log/nginx/*.log { daily rotate 7 compress missingok notifempty sharedscripts } dest: /etc/logrotate.d/nginx - name: 创建清理临时文件的定时任务 ansible.builtin.cron: name: cleanup tmp minute: "30" hour: "2" job: "find /tmp -type f -mtime +7 -delete"

4.3 例15-16:防火墙rule与定时任务

  • 例15:firewalld端口放行:
- name: 放行服务端口 ansible.posix.firewalld: port: 8080/tcp permanent: yes immediate: yes state: enabled

permanent: yes+immediate: yes表示写入永久规则并立即生效。只写permanent不写immediate,规则要下次重载才生效;只写immediate不写permanent,重启后规则就没影了。

  • 例16:定时任务统一管理:上面例14里已有cron模块的用法,相当于把crontab变成了“可版本化的文件”。项目里所有定时任务建议只通过Ansible维护,不再允许人肉登录去加crontab,这样想排查“谁改了定时任务”时有个明确来源。

5. 巡检与状态收集的4个Playbook

巡检是运维的日常工作,但重复登录机器执行命令再贴截图,是最浪费时间的。把这4个play写好,每天巡检从半小时变成三十秒。

5.1 例17:磁盘与内存状态批量巡检

利用gather_facts已经拿到的基础信息,加上register注册变量,可以输出一份简洁的巡检报告:

- name: 巡检磁盘和内存 hosts: all gather_facts: yes tasks: - name: 收集磁盘使用率 ansible.builtin.shell: df -h --output=pcent,target | tail -n +2 register: disk_usage changed_when: false - name: 输出内存总量 ansible.builtin.debug: msg: "主机: {{ inventory_hostname }} 总内存: {{ ansible_memtotal_mb }} MB"

注意changed_when: false,这类只读命令永远不会“变更”系统,别让play在每次执行后都显示changed状态,否则别人看输出会误以为有变更发生。

5.2 例18:服务状态与端口连通性检查

  • 检查服务是否active、端口是否监听:
- name: 检查核心服务状态 hosts: all tasks: - name: 判断nginx是否运行 ansible.builtin.systemd: name: nginx state: started register: nginx_status failed_when: nginx_status.failed and nginx_status.state != 'running'

也可以直接wait_for模块探测端口:

- name: 等nginx端口连通 ansible.builtin.wait_for: port: 80 host: 127.0.0.1 timeout: 10

5.3 例19-20:系统信息采集与稳定性自检

  • 例19:采集系统信息汇总到本地文件:
- name: 采集系统信息 hosts: all gather_facts: yes tasks: - name: 生成本机信息文件 ansible.builtin.copy: content: | hostname: {{ ansible_hostname }} os: {{ ansible_distribution }} {{ ansible_distribution_version }} kernel: {{ ansible_kernel }} ip: {{ ansible_default_ipv4.address }} dest: /tmp/host_info_{{ inventory_hostname }}.txt delegate_to: localhost

用delegate_to: localhost可以把采集到的信息写到Ansible控制机上,实现“所有服务器的信息汇总到一个目录”的效果。

  • 例20:系统稳定性一句话自检:用s hell组合命令看平均负载、登录失败次数、重启记录,返回非0即报警。这种“检查型playbook”建议配合--limit参数小范围执行,先跑一台验证输出,再全量跑。

6. 平踩过的坑:写Playbook时的典型问题与解决方案

前20个例子写下来,你可能已经遇到一些Common的问题:重复执行两次结果不一样、报错信息看不懂、任务明明失败了play却显示成功。我把这些高频问题集中梳理一下。

6.1 shell模块是万能的吗

不是。我见过很多新人图省事,大量使用ansible.builtin.shell,把一个10步shell脚本原封不动丢进去。这会导致幂等性完全丧失:脚本每跑一次都会执行全部逻辑,结果却不一定一致。正确做法是优先用专用模块,比如改文件用lineinfile、template,安装包用package,管理服务用service——这些模块内部都做了“检查当前状态再变更”的逻辑。

6.2 变量作用域容易混淆

变量有四个常见作用域:play、host、role、global。写的时候一时痛快,排查的时候就可能找半天:

全局变量: 命令行 -e 或 group_vars/all.yml 组变量: group_vars/web.yml 主机变量: host_vars/web-01.yml play内变量: vars:

我建议从高层往低层逐级覆盖,并养成在play开头用debug输出变量调试的习惯:

- name: 查看变量实际值 ansible.builtin.debug: var: nginx_port

这个习惯能帮你省下大量“为什么和想象的不一样”的排查时间。

6.3 命令结果反序列化报错:no start of json char found

热词里提到的这个报错,真实场景里很常见。它的原文是:

module result deserialization failed: no start of json char found

这通常意味着:模块执行后,返回的不是标准的JSON结构,而是混入了额外文本。比如我遇过下面这种写法:

- name: 查看服务版本 ansible.builtin.shell: systemctl --version && echo done

shell把echo done的输出也带了回来,Ansible在解析模块返回结果时拿到的是“非JSON”,于是报出这个错误。排查步骤其实不复杂:

  1. 单独在目标机上手动执行这个命令,确认输出是不是以正常文本开头,有没有多余提示(比如sudo密码交互)。
  2. 检查inventory里是否配置了sudo,需要提权但没写become: yes,系统会提示“password:”,这个交互文本也会“污染”输出。
  3. 尽量用返回值收集方式而不是依赖整个原始输出:
- name: 获取服务版本 ansible.builtin.command: systemctl --version register: sysctl_version - name: 输出首行版本 ansible.builtin.debug: msg: "{{ sysctl_version.stdout_lines[0] }}"

如果必须用shell,尽量把需要捕获的输出重定向到文件,再用slurp或fetch取回。这样就从根上避免了“非JSON输出干扰解析”的问题。

6.4 任务失败但不想中断怎么办

有些场景下,某个任务是“试探性的”,失败不代表整体失败。比如检查某个旧目录是否存在,不存在就跳过。默认Ansible任务失败会中断整个play,需要显式处理:

- name: 清理旧目录(允许不存在) ansible.builtin.file: path: /opt/old_app state: absent ignore_errors: yes

但注意ignore_errors: yes不要滥用。它会让真正该暴露的问题也被吞掉。我更推荐用failed_when条件,只在真正需要失败时才失败:

- name: 检查应用健康接口 ansible.builtin.uri: url: http://127.0.0.1:8080/health failed_when: - result.status != 200 - "'ok' not in result.content"

7. 实操排错的一些个人心得

实战排错是Ansible绕不开的环节,但很多人卡在“不知道从哪查装备”。这里分享几个我自己长期用的套路。

7.1 先加 -v 看细节

Ansible的命令行详细等级从-v到-vvvv。最低挡次-v足够看到大部分模块返回状态;端口连接、脚本输出、权限问题,至少用到-vvv,它会显示SSH连接过程和每次模块执行的具体参数。写playbook后第一遍运行,我都会直接ansible-playbook -C(check模式)或者加-v运行,确认每个任务都在预期路径上。

7.2 万能的debug与register

排查“注册变量里到底有什么”,永远是register+debug的组合。先把这个变量的全部内容打印出来,再决定从里面取哪个字段。这个习惯看起来笨,却是最快定位问题的方法。

- name: 完整打印注册变量 ansible.builtin.debug: var: result

7.3 观察changed状态

如果一个任务每次运行都显示changed,说明没有做到幂等。最常见的就是shell脚本里用了“追加日志”或者“创建临时文件”,第二次执行就会看到changed。追求幂等,是我认为Playbook从“能用”到“好用”的分水岭。把所有command/shell逐步替换成专用模块,是值得花费精力做的改造。

7.4 变量文件要用Vault加密

涉及密码、密钥的变量,强烈建议不要明文放在仓库里。Ansible Vault支持部分加密:

ansible-vault encrypt group_vars/db.yml

运行playbook时加上--ask-vault-pass,让输入密码后才解密。这样既可以让playbook“程序化”,敏感信息又不至于裸奔。这也是我在团队里强制要求的一条红线。

写在最后的体会

说实话,写这20个Playbook的过程,比我预期的更考验功底。表面上是在写YAML,实际上是在梳理整个运维流程:哪些操作该自动化、哪些该留手动兜底、哪些参数需要暴露成变量、哪些状态需要被记录和报警。等到Playbook稳定下来,日常操作变成“改变量文件 + 执行一条命令”时,你会明显感觉到运维工作从“救火”变成“合规化交付”。

我个人建议新手朋友不要一上来就追求“一个playbook管理1000台机器”的大场面,先从三两台机器开始,把这20个例子按自己的环境改一遍,把每个模块的返回值输出看一遍,再考虑成规模推广。Ansible是一个需要“手感”的工具,玩得多了,自然就知道哪些坑值得提前避开,哪些功能可以留着以后再用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 8:42:59

从散装脚本到可维护工具集:Selenium UI自动化工程化实践

接手过一个项目,仓库里躺着两百多个 Selenium 脚本,全塞在一个文件里,从登录一路 if-else 写到下单。跑一遍四十分钟,失败三十多条,点进去看,一半元素找不到,另一半找到了但点不动。那天下午我干…

作者头像 李华
网站建设 2026/9/30 8:42:21

Spring Boot新闻管理系统:从源码结构到部署的完整拆解

当前不少计算机专业的同学都在做Spring Boot相关的毕业设计,新闻管理系统算是非常经典的一类选题。市面上的课程设计、毕设项目交付包通常打成一个压缩包,里面揉着源码、数据库脚本、调试部署说明、开发环境配置,偶尔还会附上一份字数可观的论…

作者头像 李华
网站建设 2026/9/30 8:41:02

MATLAB调试与性能优化实战:从断点到向量化的高效编程指南

1. 调试:先搞清楚“错在哪”,再谈优化1.1 调试工具链全景:从print到断点,一套完整的排查打法不知道你有没有过这种经历:一段MATLAB脚本跑了一半,突然蹦出一串红色报错,然后你对着命令行里的几十…

作者头像 李华
网站建设 2026/9/30 8:40:31

数据大屏零代码开发:FineReport实操与避坑指南

做数据大屏这件事,这两年几乎是所有业务团队绕不开的活儿。销售要看实时业绩,运营要盯转化漏斗,生产要监控设备状态,说白了,数据大屏就是给管理层开的“驾驶舱”。但真正动手做的时候,很多团队会卡在同一个…

作者头像 李华
网站建设 2026/9/30 8:39:03

Go Slice底层原理与避坑指南:从append扩容到底层数组共享

在Go的所有内置类型里,slice应该算是最“亲民”又最“阴险”的一个。亲民在于你翻任何Go语言速成教程,它都排在前面,写业务代码十个函数有八个在跟它打交道;阴险在于它表面上是"动态数组",里面却藏着一套“头…

作者头像 李华
网站建设 2026/9/30 8:39:01

高项论文总是字数不够,这6类内容必须补齐

项目背景写了600字,知识定义背了500字,正文刚进入管理过程,能写的内容已经用完。看着字数还差一大截,只好继续加“高度重视、积极协调、严格把控”,或者把同一项措施换几种说法重复一遍。高项论文写不够,通…

作者头像 李华