我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Ansible自动化运维入门:从Ad-hoc命令到Playbook实战

Ansible自动化运维入门:从Ad-hoc命令到Playbook实战 1. 为什么今天开始学Ansible从重复劳动到按时下班1.1 真正让运维变忙的不是故障是“重复”接触运维工作久了你会发现日常工作里最消耗精力的往往不是某个惊天动地的故障而是那些你每天都要做、却一直靠手工完成的重复操作给几十台服务器改一行配置、批量上传一个发布包、逐个节点重启服务、再挨个检查日志确认启动正常。一遍两遍还能忍三五台服务器也能手动扛下来一旦服务器数量到了几十上百手工操作的效率和出错率就会同时变成灾难。这也是第十九天笔记里我最想先讲清楚的东西Ansible 不是某个锦上添花的工具而是一套把“重复”交给机器去跑的工作方式。自动化运维这个概念听起来很大但落到日常核心诉求就一句话——同样的操作能不能让我下一次不再敲第二遍。Ansible 恰好把这件事的门槛降到了很低控制端装一个软件包被管理的服务器只需要能通过 SSH 连上剩下的执行、分发、配置、回读结果都由 Ansible 代劳。这篇笔记会从 Ansible 的背景开始快速拆解它的核心概念然后进入实际操作。内容包括三块第一Ansible 自动化解决了什么问题、为什么适合作为自动化入门工具第二Ansible 的基本使用方式也就是不写 Playbook 时怎么用它执行命令第三编写和运行 Playbook这是把“一次性命令”变成“可复用自动化脚本”的关键一步。适合运维、开发、SRE以及所有还在手动登录服务器折腾配置的人。只要你看得懂命令行Ansible 基本上可以当天上手、当场见效。1.2 Ansible 和同类工具对比为什么我推荐拿它入门运维自动化领域的工具并不少Puppet、Chef、SaltStack 都是老牌方案但 Ansible 在近年成了很多人默认的起点原因很直接它太朴实了。Puppet 和 Chef 需要部署客户端-服务端结构管理节点、证书、agent 状态一套流程走完已经把初学者劝退了。SaltStack 性能很强但架构灵活的同时也意味着概念更多学习曲线并不友好。Ansible 的设计思路极其直白控制端通过 SSH 直接连到目标机器上执行任务。不需要在每台被管理服务器里安装 agent不需要维护复杂的服务端不需要额外的数据库或消息队列。只要 SSH 能通Ansible 就能工作。这种架构带来的好处是可以直接用你已有的服务器账号和密钥体系学习的心理负担小很多。从配置定义方式看Ansible 使用 YAML 写 Playbook而 YAML 本质上是给人看的文本格式有缩进就能读稍微懂一点英语就能猜出大概意思。相比 Puppet 的 DSL 语法和 Chef 的 Ruby 语法Ansible 的 YAML 几乎没有额外学习成本。我知道有些同行会争论“Ansible 性能比不上 SaltStack”“大规模节点下它就是慢”这话没错但那是几百上千台机器规模才需要纠结的事。对于大多数团队尤其是刚开始做自动化的团队易上手、易调试、易维护带来的长期收益远远超过那点执行效率差异。下面的表格可以直观看到几个主流工具的核心差异工具架构配置语法上手成本适用规模Ansible无 agent走 SSHYAML低中小规模适合绝大多数团队起步Puppet客户端-服务端需 agent自有 DSL高大型企业配置合规场景Chef客户端-服务端需 agentRuby高复杂基础设施偏开发背景团队SaltStack可选 master-minionYAML / Python中大规模节点性能敏感场景当时我选择 Ansible 还有一个实际原因团队里的同事不全是专业运维背景有做开发的、有做测试的大家需要共同维护一套自动化脚本。在这种混合背景的团队里工具越是容易理解越容易被真正用起来。如果一个自动化工具只有两个人会用那它本质上还是没有落地。2. Ansible 核心概念打通控制节点、清单与模块的关系2.1 免 Agent 模式并不神秘就是 SSH 加 Python很多人刚接触 Ansible 时会纠结一个问题它不是不需要装 agent 吗那它是怎么在远程机器上执行命令的原理其实很简单——Ansible 通过 SSH 连接目标主机把执行任务所需的 Python 代码片段发送过去在远端用 Python 解释器执行然后回收结果。控制端负责编排远程主机负责跑指令整个交互过程类似你手动 SSH 登录后敲命令只不过那些命令被 Ansible 的模块包装成了统一接口。所以远程主机上有 Python 是一个前提条件。Linux 服务器基本都自带 Python 环境旧一点的系统可能是 Python 2新系统一般是 Python 3。如果远程机器上找不到 PythonAnsible 会报错那时候你需要手动指定解释器路径。实际工作中我会尽量避免把环境搞得太特殊因为特殊环境意味着别人接手时也要跟着踩一遍坑。这里可以做个类比Ansible 控制端就像一个不驻场的远程指挥员它只在执行任务时通过 SSH 进场任务结束后就退出。每个命令任务都像是你在终端里下发的一次操作只不过这个操作经过了标准化封装有统一的输入输出格式并且能自动判断“这台机器是否需要做变更”。在执行层面Ansible 还有一点非常关键声明式执行。你写的是“目标状态”而不是“执行步骤”。比如你告诉 Ansible“nginx 应该处于运行状态”它会自己检查当前状态如果已经在运行就不会重复执行。这种能力和模块机制直接相关下一节详细讲。2.2 清单文件控制节点怎么知道目标机器在哪Ansible 把被管理的服务器列表称为 Inventory 清单文件。默认情况下控制端会读取 /etc/ansible/hosts如果你只想在某个项目里使用独立的机器列表也可以用 -i 参数指定自己的清单文件这样不同项目之间不会互相干扰。清单文件用的是 INI 风格格式支持分组组里可以放多台机器。一个典型的例子是这样的[web] web01 ansible_host192.168.1.11 ansible_userroot web02 ansible_host192.168.1.12 [db] db01 ansible_host192.168.1.21 [all:vars] ansible_ssh_port22 ansible_python_interpreter/usr/bin/python3第一行方括号里写的是组名下面每行是一台主机。ansible_host 指定实际连接的 IP 或域名ansible_user 指定 SSH 登录用户。如果所有机器都用同一个端口或其他共同参数可以放在 [all:vars] 这类组变量里一次声明。分组不只是为了好看它还决定了你的自动化指令作用于哪些机器。比如“只重启 web 组”和“重启所有机器”是完全不同的操作分组逻辑一定要提前规划好。建议按业务角色分组比如 web、db、cache、mq而不是按 IP 段分组因为自动化操作通常是按业务属性来发起的。清单位置还有一个实际经验用版本控制管理清单文件。尤其是把密码、密钥这类敏感信息和清单文件分离别把生产环境的 IP 和账号直接提交到公共仓库。Ansible 支持从命令行输入密码、使用 SSH 私钥也支持通过 ansible-vault 加密变量文件。安全习惯从第一天就要养成。2.3 模块Ansible 的最小动作单元Ansible 执行任务时真正干活的单元叫模块。模块是一段被封装好的功能代码比如“检查某个服务是否启动”“复制一个文件”“创建一个用户”“修改文件权限”。你不需要关心模块内部怎么实现只需要告诉它参数。因为模块封装了底层细节它天然具备一个非常重要的特性幂等性。简单说同一个模块操作执行十次结果和第一次保持一致不会因为你多跑了几次就把环境搞乱。修改配置文件的模块会先判断内容是否一致服务模块会先检查服务状态一切都以“目标状态”为准。这也是 Ansible 能安全反复执行的基础。常用模块在入门阶段最少要认识这几个模块功能典型使用场景ping测试控制端与目标主机的连接验证清单配置是否正确command / shell / script执行命令或脚本临时操作、安装包、启动服务copy将本地文件复制到远端分发配置文件、发布包file管理文件、目录、软链接创建目录、设置权限和属主service / systemd管理系统服务状态启动、停止、重启服务yum / apt管理软件包安装卸载安装 nginx、python、nfs-utilsuser / group创建和管理用户组批量创建运维账号template用模板文件生成配置根据变量生成不同环境的配置这里要提醒一件事command 和 shell 模块虽然好用但它们不具备幂等性。比如你用 shell 执行“systemctl restart nginx”每次跑都会重启一次Ansible 无法判断“这次是否需要重启”。这也是很多人写 Playbook 时会掉的坑。正确的做法是能用专用模块解决的尽量别用 shell 硬刚。第 6 部分会专门说这个坑。3. Ad-hoc 命令先上手不写 Playbook 也能自动化3.1 环境准备真的很快控制端一个包就够Ansible 的安装过程简单到让人怀疑是不是漏了什么。控制端如果是 CentOS / RHEL 系可以用 yum 直接装yum install epel-release -y yum install ansible -y如果是 Debian / Ubuntu 系通过 apt 安装或者更通用的方式是用 pip 装到 Python 环境里pip3 install ansible安装完成后验证一下版本看到版本号输出就说明控制端就绪ansible --version被管理端真的什么都不用装也不完全对——它需要 SSH 服务可用需要有 Python 解释器需要你有对应的登录账号。这些条件对于 Linux 服务器来说几乎都是现成的。如果你管理的是 Windows 主机那要额外配置 WinRM但在入门阶段建议先拿 Linux 练手等概念模式真正建立起来以后再扩展。还有一个被很多人忽略的前置步骤控制端到被管理端的 SSH 免密登录。Ansible 可以配合 SSH 私钥使用也可以临时用 -k 参数输入密码。强烈建议先配置好 SSH 密钥免密登录因为后续执行 Playbook 时需要反复连接大量机器每次都提示输密码会把人逼疯。测试连通性的机制反而很简单ansible all -i hosts -m ping如果返回的每台机器都是 pong说明控制端到目标主机的链路已经通了可以进入下一步。3.2 从 ping 到 user一条条命令跑起来Ad-hoc 命令是 Ansible 最简单的使用形态语法结构是选择目标机器指定模块传入参数。一行命令完成一个操作适合临时性、一次性任务。比如批量获取所有 web 服务器的当前负载ansible web -i hosts -m command -a uptimeweb 是目标组可以换成 all 或者清单中任意主机名-m command 指定模块-a uptime 是传给模块的参数再比如把本地配置文件分发到所有 web 节点并让目标服务器保留一份备份ansible web -i hosts -m copy -a src/etc/nginx/nginx.conf dest/etc/nginx/nginx.conf backupyes批量重启某组机器上的 nginx 服务ansible web -i hosts -m service -a namenginx staterestarted批量创建用户并加入 wheel 组提权ansible all -i hosts -m user -a namezhangsan groupswheel appendyes statepresent每条命令都像是一次“远程批量手工操作”执行结果会一屏一屏地刷出来告诉你哪些机器成功了、哪些失败了、哪台机器发生了什么变化。这样的操作方式在学习阶段特别合适因为你能直观地感受到 Ansible 的控制逻辑而不用先被 Playbook 的语法细节淹没。3.3 看懂了 OK、changed、failed你就懂了一半Ad-hoc 命令执行完屏幕上最值得关注的是三种状态ok、changed、failed。我用真实输出给你演示一次执行效果TASK [ping] ************************************************** ok: [web01] ok: [web02] TASK [command] *********************************************** changed: [web01] changed: [web02]ok 表示目标主机当前状态已经符合预期或者模块执行成功且没有产生变更。changed 表示这台机器实际发生了状态变化比如文件被复制、服务被重启、软件包被安装。failed 不用多说执行出错。理解这三种状态对后续写 Playbook 至关重要因为 Ansible 的很多高级手法都是围绕“状态”开展的。比如 handler 机制就是只在状态是 changed 的时候才触发后续动作。你可以把 ok 理解成“什么都不用做”把 changed 理解成“这次动了真格”。在 Ad-hoc 阶段有一个实测心得同一个命令执行两次第二次如果全部显示 ok 而没有 changed说明这个模块具备幂等性是安全的。如果你用 shell 模块执行“echo hello”第二次执行依然会显示 changed这不是报错而是命令型模块不检查状态的自然表现。理解这个差异后面排查 Playbook 问题时能少走很多弯路。4. 编写和运行 Playbook把自动化从“命令”变成“剧本”4.1 YAML 缩进其实是第一个门槛Playbook 是 Ansible 的核心表达方式简单说就是把一组要执行的任务按照顺序写成文件让工具可以重复、稳定地执行。Playbook 用 YAML 编写YAML 的语法本身不复杂复杂的是它严格到苛刻的格式要求。第一次写的时候大概率会踩缩进或空格的坑。YAML 最核心的三条规则--- # 这是一个键值对 name: nginx # 这是一个列表 packages: - nginx - curl - htop # 这是一个嵌套结构 tasks: - name: 安装 nginx yum: name: nginx第一不要用 Tab 缩进只能用空格而且同一个层级缩进必须对齐。第二冒号后面必须有空格写成 name:nginx 会被当成另一个键。第三列表项的短横线后面也要有空格。这些细节看起来不起眼实际跑起来任何一个错误都会让解析器直接报 YAML 语法错误。如果你想先检查 YAML 语法对不对不用急着执行 PlaybookAnsible 给你准备好了语法检查命令ansible-playbook -i hosts nginx.yaml --syntax-check返回没有任何 warning 和 error说明文件格式没问题可以继续。4.2 第一个 Playbook安装并启动 Nginx写一个最简单的 Playbook目标是在 web 组上安装 nginx 并确保服务启动。文件内容如下--- - name: 配置 web 服务器 hosts: web become: yes tasks: - name: 安装 nginx yum: name: nginx state: present - name: 启动 nginx 服务 service: name: nginx state: started enabled: yes逐行拆解一下最外层短横线代表这是一个 play一个 Playbook 文件里可以包含多个 play。name 字段给这个 play 起个名字主要方便人看日志。hosts 指定目标主机组。become: yes 表示执行任务时切换为 sudo 提权因为安装软件、启动服务都需要 root 权限。tasks 下面是具体任务列表每个任务都有 name 和模块调用。这里最关键的认知要建立起来Playbook 的任务顺序是从上到下依次执行的每两个任务之间默认存在关联。如果前面任务失败默认情况下整个 Playbook 会中止后面的任务不再执行。在实际生产场景里这通常是我们想要的行为因为后续任务很可能依赖前面任务的结果。现在的 Playbook 还有一个问题如果 nginx 配置文件后续被修改怎么能自动触发重启这就要用到 handler 机制。改造一下--- - name: 配置 web 服务器 hosts: web become: yes tasks: - name: 安装 nginx yum: name: nginx state: present - name: 写入站点配置 template: src: server.conf.j2 dest: /etc/nginx/conf.d/server.conf notify: restart nginx - name: 启动 nginx 服务 service: name: nginx state: started enabled: yes handlers: - name: restart nginx systemd: name: nginx state: restartedhandler 定义了一个名称为 restart nginx 的动作但它不会主动执行。只有当某个任务运行时状态为 changed并且通过 notify 明确喊它它才会在 Playbook 结尾被触发。这个设计很巧妙配置内容没变就不重启服务避免无谓的业务中断。4.3 运行按钮syntax-check、 --check 和真实执行正式运行 Playbook 前建议养成一个固定习惯先语法检查再试运行最后真实执行。试运行命令是ansible-playbook -i hosts nginx.yaml --check--check 模式也叫干跑模式Ansible 会模拟执行所有任务但不会真正对目标机器做任何修改。你可以在屏幕上看到每个任务预计会产生 ok 还是 changed这对于审核一个 Playbook 是否会影响业务环境非常有用。打个比方这就像是正式演出前的彩排能提前发现大部分问题。确认没问题后正式执行ansible-playbook -i hosts nginx.yaml执行过程中你会看到 PLAY、TASK 的打印信息每台机器的结果按照 ok、changed、failed 汇总呈现。跑完之后如果所有任务都是 ok 或 changed说明 Playbook 完整走通。真实工作里还有几个常用选项值得立刻掌握。想要在执行前再次确认要操作的机器范围可以加 --limitansible-playbook -i hosts nginx.yaml --limit web01只对 web01 执行其他机器不受影响。想在本地查看 Playbook 里的变量、模板可能渲染成什么样可以用 --list-tasks 和 --list-hosts 快速预览ansible-playbook -i hosts nginx.yaml --list-tasks ansible-playbook -i hosts nginx.yaml --list-hosts这些命令不产生实际变更非常适合团队协作时做变更审核。说实话我见过不少新手写完了 Playbook 直接就往生产环境跑结果发现 hosts 组指向了不该触碰的机器或者某个任务是钉死路径的错误命令。先 check 再执行这个习惯能帮你避开百分之八十的低级事故。5. 变量、模板与角色让 Playbook 从“单机脚本”变为“可复用方案”5.1 变量来源和优先级记住一条就够了现在写 Playbook 已经能完成基本任务但一个 Playbook 如果只为一种环境服务价值还是有限。比如测试环境端口是 8080生产环境端口是 80同一个配置逻辑总不能复制两份剧本吧。这时候变量就该登场了。先在 Playbook 内部定义变量最直观的方式是写 vars--- - name: 部署 nginx 站点 hosts: web become: yes vars: server_port: 8080 server_name: www.example.com tasks: - name: 写入站点配置 template: src: server.conf.j2 dest: /etc/nginx/conf.d/server.conf变量的使用方式是在模板文件或任务参数里通过 {{ server_port }} 引用。Ansible 的变量来源非常多清单文件里的主机变量和组变量、Playbook 里的 vars、vars_files 引入的外部变量文件、系统自动采集的 facts、命令行通过 -e 参数传入的变量。变量多了就会有优先级冲突入门阶段不需要背完整的优先级表记住一条经验命令行 -e 传入的变量优先级最高然后是 Playbook 里的 vars再往下才是清单文件里定义的组变量和主机变量。这很好理解——你临时在命令行里覆盖的值应该能压过文件里的默认配置。另一个重要概念是 facts。Ansible 执行 Playbook 时默认会先收集目标机器的基本信息包括操作系统类型、IP 地址、CPU 内存、磁盘等。这些信息自动存放在变量里比如 ansible_os_family 表示系统家族ansible_default_ipv4.address 是主 IP。你可以直接用它们来写条件判断- name: 根据系统家族安装不同包 yum: name: nginx state: present when: ansible_os_family RedHat这样一份剧本就能根据目标系统的差异自动选择合适动作而不是靠人肉判断。5.2 用 Jinja2 模板生成带变量的配置文件template 模块是 Ansible 配置管理的精华。它的工作方式很简单你在本地写一个 .j2 格式的模板文件里面嵌入变量引用template 模块渲染完以后再传到目标机器的目标位置。假设模板文件 server.conf.j2 内容如下server { listen {{ server_port }}; server_name {{ server_name }}; root /usr/share/nginx/html; }执行任务时Ansible 会把 {{ server_port }} 替换成 8080把 {{ server_name }} 替换成 www.example.com最终写到 /etc/nginx/conf.d/server.conf 的就是一份完整的可用配置。这是“一份配置处处安装”最实用的落地方案。模板里还能用 for 循环、if 判断这类 Jinja2 语法。比如配置上游服务器列表可以这样写upstream backend { {% for host in groups[app] %} server {{ hostvars[host][ansible_default_ipv4][address] }}:8080; {% endfor %} }这段模板会遍历清单里 app 组的所有主机把它们的 IP 写进 upstream 配置块。以后新加一台应用服务器只要更新清单文件重新跑一次 Playbook 就能生成最新配置。5.3 角色化超过两个 Playbook 就该拆目录当 Playbook 越写越多把全部任务塞在一个文件里会让文件变得臃肿团队协作时也容易冲突。这时候该引入角色的概念。角色是 Ansible 官方推荐的组织方式本质上一个标准化目录结构。比如创建一个 nginx 角色目录会长这样roles/ └── nginx/ ├── tasks/ │ └── main.yml ├── handlers/ │ └── main.yml ├── templates/ │ └── server.conf.j2 ├── files/ ├── vars/ │ └── main.yml └── defaults/ └── main.ymltasks/main.yml 放角色要执行的核心任务handlers/main.yml 放这个角色用到的处理动作templates 放模板文件vars 和 defaults 都可以放变量区别是 defaults 的优先级更低适合给使用者一个“可覆盖的默认值”。在 Playbook 里使用角色时只需这样写--- - name: 部署 nginx 角色 hosts: web become: yes roles: - nginx角色最大的收益是复用。你在一个项目里把 redis 角色、nginx 角色、mysql 角色都整理清晰以后新项目需要哪个就直接引用不用再整理一堆重复任务。这和我前面说到的“不让重复劳动消耗自己”的理念完全一致。但角色不是越早拆越好。如果整个环境加起来才十几台机器单个 Playbook 二三十行就能搞定强行拆角色反而增加维护成本。我的建议很实际当你的 Playbook 文件超过两个、或者需要给不同团队共用同一个自动化逻辑时再上角色机制那时候收益才会明显大于成本。6. 第十九天踩坑实录连接报错、幂等性和 YAML 的坑6.1 三个最影响学习效率的坑我替你先踩了第一天实操 Ansible 最容易遇到的就是连接报错。执行 ansible all -m ping 时如果出现 failed to connect to the host via ssh多半不是 Ansible 配置问题而是你的 SSH 免密没有配好。解决方法是先用 ssh 命令直接手动登录目标机器确认能免密登录后再跑 Ansible。别低估这一步我见过很多新手卡在“安装完 Ansible 却一台机器也连不上”其实问题出在 SSH 密钥不是自己这边生成的。先手动验证 SSH 通不通可以在十分钟内排除掉一大半连接类故障。第二个是 Python 解释器问题。老系统或者精简系统上默认 Python 路径可能不是 /usr/bin/python或是只有 python2 没有 python3。Ansible 连接目标机器后找不到解释器就会报 python not found。解决办法是在清单文件或组变量里显式指定解释器路径[all:vars] ansible_python_interpreter/usr/bin/python3如果是老系统只有 python2也要显式指认到 python2 的绝对路径。这个变量现在也支持自动发现但显式指定能减少很多不确定因素。第三个坑和第 2.3 部分提到的幂等性直接相关。很多人第一次写 Playbook发现每次执行某些任务都显示 changed而且服务真的被重启了。问题通常是用了 command 或 shell 模块执行了不带任何状态判断的操作。比如- name: 重启 nginx command: systemctl restart nginx这条命令每次都会重启 nginx哪怕服务本来就是正常的。正确做法是用 service 模块- name: 确保 nginx 运行 service: name: nginx state: started或者如果你确实需要检查配置变更以后才重启那就沿用 notify handler 的模式让重启动作只在配置真正变化时触发。写 Playbook 时先问自己一句话这个操作的目标状态是什么如果机器已经处于目标状态Ansible 能不能判断出来能判断的模块和写法才是生产环境敢反复跑的东西。6.2 从学习笔记到生产可用下一步往哪走到这一步你已经掌握了 Ansible 的介绍、基本使用和 Playbook 编写。接下来继续深入的话有几个方向非常值得投入。第一个是动态清单。现在清单文件是手写的但真实环境里服务器可能频繁扩缩容。让 Ansible 直接对接云厂商 API 或某个 CMDB 系统动态生成清单就不需要每次新增机器都改文件。第二个是 ansible-lint。它像一个代码审查器能检查 Playbook 里的不规范写法比如权限过大的模块、缺失的 name、潜在的不安全参数。把这些检查接入 CI 流程里团队提交代码时自动化脚本可以多一道保险。第三个是可视化运行。Ansible 有 AWX 或更轻量的 Ansible Semaphore 这类 Web 界面可以定时执行 Playbook、保存执行历史、给不同人分配不同权限。不过我不建议在还没完全掌握命令行的时候就去折腾图形界面那会把核心原理掩盖掉。最后保持一个习惯所有 Playbook、清单、模板都放进版本控制仓库并且用 ansible-vault 加密敏感信息。自动化脚本本身就是你团队的资产像管理代码一样管理它们长期收益会非常大。我从第十九天笔记里学到的最大体会是Ansible 的本质不是减少敲键盘的次数而是把运维经验固化成可执行、可审查、可回滚的资产。你今天手工改过的那条配置如果写成 Playbook明天新加十台机器也能一次完成。这才是自动化的价值所在。
返回列表