
我最早正经用上Ansible是因为一次深夜扩容。三十台新服务器上线按老办法一台台ssh上去敲命令天亮都未必搞得完。那次之后我就把Ansible彻底用了起来再也没有回到手工运维的老路上去。如果你也正在自学Ansible或者刷到这篇但还不清楚它到底是干什么的我先用一句话说清Ansible是一套基于SSH的批量执行工具它不需要在被管机器上装任何代理程序管理机通过SSH协议连接目标机器用幂等的方式执行任务、下发配置、编排流程。这套东西能解决的问题很直接批量配置系统、统一部署软件、滚动发布、巡检上百台机器。适合的场景就是任何“需要对多台服务器做同样操作”的运维工作。不管你是刚入行的运维新手还是开发环境要自己管服务的后端这篇文章都值得看完。我准备把安装部署、基本命令、Playbook剧本编写、离线安装、常见坑都串起来讲一遍内容包括直接可复制的命令和配置希望能帮你从零开始把Ansible用起来。1. 为什么是Ansible选型思路与核心概念1.1 为什么在众多自动化工具里选它市面上自动化工具不少Puppet、Chef、SaltStack都是老牌选手但Ansible是后来居上、现在社区活跃度最高的一个。核心原因就是它的架构足够轻。Puppet和Chef在被管端必须装agent还得维护一套证书体系SaltStack也要装minion光这个前置条件就劝退了很多人。Ansible则完全不同它默认走SSH协议只要你控制机能连上目标机器就能开始干活不需要预先部署任何客户端。这意味着什么意味着引入成本极低。你不需要在被管机器上装东西不需要额外暴露端口不需要规划agent升级策略。对于有上千台服务器的环境光是省掉在每个节点安装和维护agent的功夫就值得选它。而且它的学习曲线比较友好写配置用的是YAML不是某种复杂的新编程语言团队里的同事上手很快。另一个关键点是幂等性。所谓幂等就是同一套配置反复执行多次最终结果一致。传统脚本最大的问题就是执行一遍一个样装过了再跑一遍报错配置改过了又重复追加这种不确定性在自动化场景里非常致命。Ansible的模块设计天然考虑了幂等性比如yum模块遇到已安装的包会跳过service模块遇到已启动的服务不会重复触发。你不需要自己写一堆判断条件它替你做了这件事。1.2 必须先搞懂的几个核心概念使用Ansible之前有几个概念绕不开理解它们比背命令重要得多。第一个是控制节点和被管节点。控制节点就是安装Ansible的那台机器一般是你的跳板机或者一台专门的运维机器负责下发指令。被管节点就是你要操作的目标服务器它们只需要有SSH服务和一个可用的Python环境Ansible默认要通过Python来执行模块。目前Linux发行版默认都带Python 2.7或3.x所以被管端几乎不用额外准备什么。第二个是Inventory翻译过来叫“清单”就是你要管理的机器列表。默认路径是/etc/ansible/hosts但实战中我更习惯在项目目录里自建一个hosts文件用-i参数指定。清单文件不只是列出一堆IP它还支持分组、定义变量、给不同组设置不同的连接参数。比如你可以把Web组、DB组分开对它们执行不同的Playbook。第三个是模块Module。模块是Ansible执行具体操作的单元copy模块负责复制文件yum模块负责装包service模块负责管理服务template模块负责渲染模板。掌握Ansible的过程很大程度就是掌握常用模块的过程。大多数时候你不需要自己写执行逻辑只要把模块的参数组合对了任务就完成了。第四个是Playbook也就是剧本。一个Playbook是把多个任务Task串起来执行的编排文件用YAML格式写。比如一个部署任务可以拆解为安装nginx、写入配置、启动服务、检查端口、加入开机自启。这些步骤写进Playbook后可以反复执行。Playbook还支持变量、模板、条件判断、循环已经能覆盖绝大多数自动化场景。最后是角色Role。当Playbook变得复杂你需要把代码组织成可复用的结构角色就是Ansible的组织方式。一个角色包含tasks、handlers、templates、vars等目录说白了就是把一套业务逻辑打包成积木需要的时候引用相应角色即可。新手阶段可以不急着深入角色但知道这个概念对后续进阶很重要。1.3 什么场景适合用Ansible什么场景不适合Ansible不是万能的明确边界可以少走弯路。它适合的场景包括批量执行一次性命令、批量修改配置、应用部署、配置同步、定时巡检任务。在这些场景里Ansible的表现都很稳定。但它不适合所有问题。如果你追求实时推送和秒级响应比如要在一秒内感知所有节点状态变化Ansible偏拉取式的执行模型就有些力不从心。另外它也不适合做大规模实时流式任务它定位的是配置管理和任务编排不是消息系统。至于那些被管节点无法安装Python的特殊设备虽然Ansible支持一些network模块但兼容性和表现确实不如专门的网络自动化工具。分析完这些边界你在头脑里对Ansible的定位应该就清晰了它就是那个能解决80%日常自动化需求的轻量级瑞士军刀。2. 安装部署从在线到离线的完整方案2.1 控制端环境准备Ansible控制节点推荐使用Linux系统这基本是常识。Windows虽然可以作为控制端做有限使用但我个人不建议Windows下的兼容性和体验都差。我平时用的是CentOS 7和Ubuntu 20.04这两套系统下面的操作在这两套上都能跑通。控制节点需要能通过SSH连接到目标机器连接信息主要有IP、端口、用户名、密码或密钥。如果目标机端口不是默认的22需要在inventory里指定ansible_port变量。另外控制节点需要Python环境CentOS 7系统自带的Python 2.7也能跑现在的新版Ansible但建议装Python 3环境更省心。在动手安装前先确认控制节点能够直接SSH到目标机器这是所有自动化操作的前提。用什么用户连接就以什么用户执行权限不足时要配置sudo提权。比如用root直接连接最容易但不安全生产环境通常用一个有sudo权限的普通用户。2.2 在线安装Ansible两种常用方式在线安装最直接的方式是使用系统的包管理器。以CentOS系统为例需要先安装EPELExtra Packages for Enterprise Linux仓库然后执行yum install epel-release yum install ansible这条命令装完的不是最新版但胜在方便系统仓库里有什么版本就用什么版本。如果你的环境对版本没有强要求用这个方式最快。Ubuntu/Debian系统则是apt update apt install ansibleUbuntu官方仓库里的Ansible版本常常比较旧如果想用较新的版本建议用pip方式安装。pip安装的核心命令是pip3 install ansible这里有个细节需要留意。如果你的服务器存在多个Python环境比如既有系统Python又有虚拟环境pip安装时要确认安装到控制节点实际使用的Python解释器里。我遇到过同事把Ansible装进了虚拟环境但登录时没有激活虚拟环境结果执行ansible命令时报“command not found”的窘境。装完以后可以用以下命令验证ansible --version看到版本信息就说明安装成功了。除了ansible本体pip还会自动装上它依赖的第三方库比如Jinja2模板引擎、PyYAMLYAML解析、cryptography加解密等整个过程不需要手动逐个处理依赖。2.3 离线安装Ansible内网环境的标准操作实际生产环境里很多机房的内网服务器是不能访问外网的。每次部署新机器都要一台台手动配置运维效率极低。这时候离线安装Ansible就是一个很常见的需求。我自己实现离线安装的思路是准备一台能联网的机器用pip把Ansible及其依赖包全部下载成whl文件再把整个包目录拷贝到内网机器上离线安装。具体操作分三步走。第一步在联网机器上用pip下载所有需要的包mkdir /tmp/ansible_packages pip3 download ansible -d /tmp/ansible_packages下载完成后把/tmp/ansible_packages整个目录打包传到内网控制节点上。第二步在内网机器上执行离线安装pip3 install --no-index --find-links/tmp/ansible_packages ansible--no-index表示不访问PyPI索引--find-links指定本地包目录。第三步运行ansible --version验证是否安装成功。这里有几个需要注意的坑。第一做pip download的机器Python版本最好和内网机器一致否则可能下载了不适合当前Python版本的包。第二如果是内网机器无法通过DNS解析主机名ansible连接目标机时尽量直接用IP地址。第三如果你用的是非常旧的Python 2.7环境一些新版Ansible可能不支持需要下载对应旧版本比如ansible 2.9是最后一个官方支持Python 2.7的大版本。我项目的Ansible离线安装包目录下总会保留一份2.9.x和一个新版本以备不同环境的机器使用。2.4 被管节点要做哪些准备被管节点真的不用装agent但不是说完全不用管。它至少需要满足这几个条件SSH服务正常运行、控制节点能用目标用户登录、目标机器上有Python环境。Linux上这些基本都是默认具备的所以我可以说“零准备”。有一点容易忽略新版Ansible执行模块时默认在目标机器上找/usr/bin/python。有些系统只有/usr/bin/python3而没有python命令连接时会报“Python interpreter not found”之类的问题。解决办法是在inventory里为这批机器指定Python解释器路径[all:vars] ansible_python_interpreter/usr/bin/python3这个细节不处理好你会在刚开始使用的时候就受挫。很多菜鸟教程没有讲清楚这一点实际部署时经常在这里卡住。还有一个建议如果被管机器的系统版本跨度大比如同时有CentOS 6和CentOS 7最好统一在inventory里指定合适的Python路径避免兼容性问题。2.5 编写第一个Inventory文件安装完Ansible后第一步就是准备inventory。不用花时间在复杂配置上从最简单的开始。比如我新建了一个项目目录/opt/ansible-lab在里面建了一个hosts文件内容如下[web] 192.168.1.101 192.168.1.102 [db] 192.168.1.201 ansible_port22 ansible_userroot [all:vars] ansible_python_interpreter/usr/bin/python3[web]和[db]是分组名组下面的IP就是目标机器。all:vars组用于设置对所有机器生效的变量这里我把Python解释器统一指定为python3。如果你要用普通用户连接并sudo提权还可以配置ansible_becometrue和ansible_become_pass。配置完成后用下面这条命令验证能不能连通ansible all -i hosts -m ping看到返回pong说明控制节点和所有目标机器之间的链路是通的。这里多提一句Ansible的ping模块并不是去ping目标机的网络而是在目标机上创建一个临时Python脚本并执行成功后返回pong所以它验证的是“控制端SSH能通”和“目标机Python环境正常”两个核心条件。3. 从第一条命令开始ad-hoc与模块3.1 ad-hoc是什么和Playbook什么关系刚开始接触Ansible时最直观的感受可能是“啊原来我可以不用写剧本也能干活”。这就是ad-hoc命令直接通过ansible命令行执行单条任务适合一次性操作、快速验证场景。Playbook则是一条条ad-hoc任务的有序集合适合复杂、可重复执行的编排。它们的执行逻辑相同都依赖模块。实际使用中我个人的习惯是临时查看信息、快速重启服务、检查机器状态用ad-hoc正式部署和配置变更尽量写Playbook。因为ad-hoc的好处是快但坏处是不可复现、不可审计。你在凌晨三点紧急重启个服务ad-hoc很快但你想让这个重启动作成为标准化流程的一部分那必须进Playbook。3.2 最常用的几个模块示例我从工作里长期使用的模块中挑几个典型的来说基本覆盖了日常80%的需求。第一个是command模块用于在目标机器上执行简单命令。例如批量查看被管机器的uptimeansible web -i hosts -m command -a uptimecommand模块不会经过shell解释器所以管道、重定向、通配符这些都没法用。想用这些要手动调用shell模块或重定向到/bin/bash -c。例如ansible web -i hosts -m shell -a free -m | grep Memshell模块会调用shell来执行命令能力更强但需要谨慎因为同样的命令在shell里可能有隐藏的坑比如特殊字符、通配符解析而且它打破了幂等性。能用command解决时我不建议用shell。第二个是copy模块用于把控制节点上的文件分发到目标机器。比如分发一个本地配置文件到所有web服务器ansible web -i hosts -m copy -a src/opt/ansible-lab/nginx.conf dest/etc/nginx/nginx.conf ownerroot grouproot mode0644 backupyesbackupyes是一个实用的参数目标机器上已有文件时会先备份再覆盖这对生产环境非常友好。第三个是yum模块用于安装、移除、更新软件包ansible web -i hosts -m yum -a namenginx statepresentstatepresent表示安装stateabsent表示卸载statelatest表示升级到最新。第四个是service模块用于管理系统服务ansible web -i hosts -m service -a namenginx statestarted enabledyesenabledyes表示设置开机自启动。日常巡检时我还会组合几个模块完成一次系统状态收集比如用setup模块查看系统信息ansible all -i hosts -m setup -a filteransible_os_familysetup模块返回的是被管节点大量系统信息配合filter参数可以只提取你需要的内容。这不仅是个收集工具也是排查问题时的重要来源。3.3 优化连接效率的实战参数用ad-hoc管理上百台机器时默认执行速度会让人着急。默认状态下Ansible对所有目标机是并行执行的并发数受forks参数控制默认是5。如果目标机很多可以调大ansible all -i hosts -m command -a uptime --forks20另一个影响速度的大头是SSH连接握手。每次执行一条任务都要重新建立SSH连接开销不小。通过开启SSH长连接和pipelining可以明显提升速度。你可以在ansible.cfg配置文件里加[ssh_connection] pipelining True control_path /tmp/ansible-%%h-%%p-%%rpipelining的作用是减少SSH连接时传输临时文件所需的额外round-trip对大批量执行效果很明显。配上ControlMaster长连接机制后多个任务可以复用同一个SSH连接速度快了不少。这个优化建议越早做越好否则你以后跑几十台机器会觉得每执行一次都像在等电梯。4. 核心攻坚Playbook剧本编写实战4.1 YAML语法新手问题高发区写Playbook首先要会YAML。这是一种非常容易人畜无害、但对缩进敏感的格式。我第一次写Playbook时因为用Tab缩进导致启动失败。YAML要求用空格缩进不能用Tab且同一层级缩进必须一致。很多编辑器会自动把Tab转为空格新手阶段最好统一用空格并开启显示空白字符。YAML语言的基本结构是键值对和列表。键值对写法是key: value冒号后面必须有一个空格。列表以短横线开头- name: 安装nginx yum: name: nginx state: present这里的- name只是给任务起个名字yum是模块名下面的是该模块的参数。缩进层级表达的是包含关系。我见过不少刚上手的朋友在写任务时把缩进搞错导致模块参数被当成顶层键整个Playbook报错。排查方法很简单先在在线YAML校验工具里粘贴检查或者直接用ansible-playbook --syntax-check验证语法。4.2 一个完整可用的nginx部署剧本现在来看一个真实的Playbook。假设我要在web组的两台机器上部署nginx修改监听端口为8080并启动开机自启。完整内容如下--- - hosts: web become: yes become_user: root vars: http_port: 8080 tasks: - name: 安装nginx yum: name: nginx state: present - name: 写入nginx配置模板 template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: restart nginx - name: 启动nginx并设置开机自启 service: name: nginx state: started enabled: yes handlers: - name: restart nginx service: name: nginx state: restarted一行行解释。hosts: web表示该Playbook只在web组执行。become: yes表示需要提权为root执行become_user: root是提权目标用户。vars里定义了变量http_port值8080。tasks列表里有三个任务包括安装软件、渲染模板写入配置、启动服务。关键是写入配置那一步用到了template模块src指定控制节点上的模板文件nginx.conf.j2dest指定写入目标机的路径。模板里的变量用Jinja2语法下面会细讲。notify: restart nginx表示如果模板文件内容发生了变化会触发handlers里定义的restart nginx处理器。这是Ansible实现“只在配置变化时才重启服务”的标准做法能有效避免每次都重启服务导致业务抖动。4.3 模板变量和文件组织的配合在项目目录里模板文件nginx.conf.j2的内容大概是这样server { listen {{ http_port }}; server_name _; root /usr/share/nginx/html; index index.html; }这里的{{ http_port }}会被Playbook中定义的值8080替换。这就是模板和变量配合的简单示例。实战中变量可以写在Playbook里也可以单独用vars_files引入还可以按照目录结构存放比如group_vars/web.yml是只对web组生效的变量host_vars/192.168.1.101.yml是对单台主机生效的变量。文件组织上推荐一个标准目录结构ansible-lab/ ├── hosts ├── ansible.cfg ├── group_vars/ │ └── web.yml ├── host_vars/ ├── roles/ └── playbooks/ └── deploy-nginx.yml随着项目扩大这样组织才不会乱。具体原因很简单如果你的所有功能都堆在一个大型Playbook里过一个月你自己翻起来都费劲更不用说团队协作了。4.4 条件的循环覆盖更复杂的场景在写Playbook的进阶阶段你会需要处理“不同机器执行不同操作”的情况。比如只对CentOS 7系统执行某个命令或者让一组任务循环跑多遍。Ansible用when来做条件判断用loop来做循环。- name: 在CentOS系统上执行清理 command: systemctl stop firewalld when: ansible_os_family RedHat- name: 批量创建用户 user: name: {{ item }} state: present loop: - zhangsan - lisi - wangwu掌握了条件判断和循环你的Playbook就从一个“固定步骤清单”升级成了一种真正的“逻辑程序”。这在实际部署工作中非常实用我经常用它实现灰度发布中“只对部分机器做变更”的需求。4.5 调试Playbook的几个保命技巧写Playbook不可能一次就通过调试方法是必须会的。第一语法检查。运行以下命令可以在执行前发现明显的YAML或逻辑错误ansible-playbook -i hosts playbooks/deploy-nginx.yml --syntax-check第二试运行。加--check参数会让Ansible进入“空跑模式”它只输出将要做什么不会真正修改系统ansible-playbook -i hosts playbooks/deploy-nginx.yml --check第三查看详细输出。执行时加-vvv参数可以输出SSH连接细节、模块返回结果排查问题时最能看出问题在哪一步ansible-playbook -i hosts playbooks/deploy-nginx.yml -vvv第四只跑部分任务。如果只想跑第一、二步任务可以加--start-at-taskansible-playbook -i hosts playbooks/deploy-nginx.yml --start-at-task写入nginx配置模板第五限制目标主机。多个分组在同一个Playbook里但只想现在先跑web组的话用--limit参数ansible-playbook -i hosts playbooks/deploy-nginx.yml --limit web这些参数看起来琐碎但在实际项目中能帮你节省大量时间建议熟记。5. 常见问题与排查技巧实录5.1 SSH连接与主机密钥验证失败新手刚上手时最常遇到的就是类似“Host key verification failed.”的报错。这是因为目标机器的主机密钥还没有被控制节点记录SSH第一次连接时会动态询问而Ansible的非交互式连接没法回答“yes”。最省事的方法是临时在ansible.cfg里设置[defaults] host_key_checking False这样Ansible不再校验主机密钥连接时不会弹提示。但要提醒一下这种配置在低安全环境可以接受在安全要求高的环境里不推荐。更严谨的做法是预先通过ssh-keyscan批量收集主机密钥统一写入known_hosts文件。比如ssh-keyscan 192.168.1.101 192.168.1.102 ~/.ssh/known_hosts我个人的习惯是本地测试环境直接关掉校验生产环境预先录入密钥。如果你的密码连接失败还需要检查sshpass是否安装在CentOS上执行yum install -y sshpass。5.2 权限不足和sudo提权失败用普通用户连接目标机器时很多时候执行命令需要root权限。Ansible在任务里配置become: yes以后如果还是报权限错误通常是因为没有指定提权密码。运行Playbook时加上参数ansible-playbook -i hosts playbooks/deploy-nginx.yml --ask-become-pass这样会交互式地要求输入sudo密码。如果使用同一个密码还可以在inventory里通过ansible_become_pass变量统一配置不过把它明文写在文件里我也不太建议可以用ansible-vault做加密。Vault是Ansible自带的一种加密机制用来加密敏感变量文件。我用了之后至少不用再担心密码文件被同事无意中看到后外泄。5.3 幂等性被破坏的几个典型场景Ansible强调幂等但实际使用中还是会遇到执行两次结果不一致的情况。最常见的问题出在模板渲染上。比如你写一个shell脚本模板每次渲染都会生成一个带时间戳的注释这就导致每次执行template模块都会发现文件变化并触发handlers重启服务业务就会被反复干扰。解决思路是保证模板输出是确定的不要在模板里生成动态随机内容。如果确实需要记录部署时间应该写入单独的文件而不是放进每次会被比较的内容里。另一个典型的幂等性问题是shell模块大量使用。shell模块天然黑盒Ansible不知道你执行后系统处于什么状态也就无法判断是否需要再次执行。能用专用模块时不要用shell硬扛。5.4 执行速度慢怎么优化管理上百台机器时执行速度和结果输出都会让人觉得难受。出现慢的问题后优先检查这几项。第一是forks并发数默认5调大到20甚至更高能缩短批量执行时间。第二是SSH长连接在ansible.cfg里开启ControlMaster和ControlPersist复用SSH连接能明显减少握手开销。第三是pipelining开启后减少SSH传输临时文件的次数。第四是关闭gathering facts如果跑Playbook不需要收集系统信息可以设置gather_facts: False或者配置[defaults] gathering explicit系统信息收集本身是对每台目标机执行一个setup模块开销不小。不需要的时候关掉速度提升非常明显。5.5 新手最容易犯的几个错误我见过太多刚接触Ansible的同事在同一个地方转圈这里把典型错误整理一下。YAML缩进错误是最常见的Tab和空格混用、冒号后面没空格都会导致语法错误。第二个常见错误是inventory主机组名写错比如Playbook里写hosts: web但inventory里定义的是[webserver]执行时报错说找不到主机。第三个是忘了指定inventory文件新手容易不写-i参数直接跑默认的/etc/ansible/hosts而那个文件里可能什么也没有。第四个是模块名或参数名拼写错误这个没有太好的办法多参考ansible-doc查看模块文档。第五个是盲目用shell模块执行一切操作虽然能跑但方式和可维护性都会很差。这些错误如果能在学习阶段就规避掉后面会顺很多。6. 关于Ansible的几点个人体会Ansible这个工具说起来不复杂但要真正用顺手我总结几个从实战里沉淀下来的经验。第一配置管理代码要进版本库。Playbook、Inventory、模板这些本质上都是代码放Git里做版本管理非常必要。一次线上变更如果出了故障你可以快速对比上一次成功的版本差异回滚成本大大降低。我现在每个项目都有单独的仓库分支策略和代码项目一样。第二尽量把Playbook写得小而专。一个大型Playbook被很多人不断追加任务之后会变得难以维护。我更倾向于把常用操作拆成独立的小Playbook比如deploy-nginx.yml、update-packages.yml、check-nginx-status.yml。每次执行只做一件事情出了问题也知道去哪里找。第三学会阅读模块文档。Ansible的模块数量非常多python有数千个可用模块很多任务其实不需要自己写shell现成的模块就能完成。遇到陌生的需求先查一下Ansible官方文档看看有没有对应的模块。第四有条件的话要建立一套测试环境。Ansible命令行直接跑在生产环境上是很危险的行为。我自己会在一组虚拟机上先验证Playbook没问题了再到生产环境执行。这套习惯帮我规避过很多次潜在故障。最后再分享一个小技巧。当你写Playbook时可以先用--check试运行确认执行计划再挑一台机器用--limit单机跑一遍真实执行最后再全量执行。这个流程我称为“三层验证法”它不见得能完全消灭问题但至少能把大范围的错误挡在生产环境之外是我这几年运维生涯里最常用、也最踏实的习惯。