1. Ansible到底是什么东西?先把它讲明白
干运维这么多年,我一直有个很深的体会:日常最耗时间的并不是那些高难度的故障排查,恰恰是“重复劳动”——几十台服务器挨个做同样的事情。以前我管理几十台机器的时候,写一堆shell脚本,写好之后先拷到每台机器,再用循环ssh执行,脚本本身又得维护不同系统的差异,每次批量改配置都提心吊胆。后来接触到Ansible,才终于从这种痛苦里解放出来。
Ansible是一个自动化运维工具,简单地说,你在一台机器上装好它,就能通过SSH协议同时操作成百上千台服务器,批量执行命令、批量部署服务、批量修改配置,通通靠它搞定。它跟Puppet、SaltStack这类同类工具最大的不一样有两点:第一,受管机器上不需要装任何客户端程序,只要开着SSH、有Python环境就行;第二,它的配置和任务描述用的是人类容易读懂的YAML格式,写起来、念起来都像在读说明书。
我总结过,Ansible最适合这么几类人:刚接触自动化的运维新手,想找一件不折腾的批量操作工具;天天被重复部署折磨的开发、测试同学,想自己动手把环境初始化搞定;以及已经用脚本但维护成本越来越高,想转向更规范、更结构化的配置管理方式的团队。接下来我围绕实际使用过程中的关键点,把这个工具从安装到实战,分成几个环节讲清楚,内容偏实操,我把我踩过的坑也一并放进去。
2. 从零开始:Ansible的安装部署
2.1 在线安装的常用方案
控制端(也就是你执行命令的那台机器)装Ansible非常直接,主流的两种方式:系统包管理器安装,或者用pip安装。
在CentOS/RHEL/Fedora系的系统上,从官方仓库装需要先配置EPEL源,一般我会先执行:
yum install -y epel-release yum update -y yum install -y ansible装完检查一下版本:
ansible --version用pip装同样简单,在Python环境里执行:
pip install ansible有一次我在CentOS 8上用yum装,结果装出来的版本有点老,后来嫌折腾,直接用pip装新版本了。个人建议:如果你对版本没有特殊要求,系统包管理器装的好处是依赖都是配套打好的,基本不用操心;如果你想要比较新、或者想固定某个版本,尤其在有虚拟环境需求的场景下,用pip更灵活。在Ubuntu/Debian上,输入apt install ansible也能装,但版本相对更新慢一些,我一般还是习惯pip。
2.2 离线安装才是项目里的重头戏
这里得说个现实情况:真正的生产环境,很多服务器是内网机器,根本连不上公网的yum源或PyPI。所以“离线安装Ansible”这个操作,是实际项目里的高频需求。我在外网机器上准备好了安装包和依赖,然后通过U盘或者内部跳板机,把文件传递到内网机器上离线安装。
离线安装的原理很简单:先在一台能上网的同系统机器上,把所有rpm包或者pip包下载好,再拷贝到目标机器上安装。如果用yumdownloader方式离线装,步骤是这样:
# 在一台与外网连通的CentOS机器上执行 yumdownloader --resolve --destdir=/opt/ansible-packages ansible # 也可以加上 epel-release 等相关依赖包 # 然后把 /opt/ansible-packages 整个目录拷贝到内网机器上按这个方式把包下载下来,拷贝到内网机器之后,使用yum install结合本地目录的方式安装,或者更省事的方法是先临时搭一个本地yum源。需要注意,这个方案时效性强,因为rpm包下载时依赖的仓库配置和系统版本要跟目标机一致,否则直接装很可能报依赖缺失的错。
如果目标机定位为“用Python环境跑”,可以走pip离线安装方式,含依赖一起下载下来:
# 在外网机器上下载 mkdir -p /opt/ansible-wheel pip download ansible -d /opt/ansible-wheel # 把整个 /opt/ansible-wheel 目录拷到目标机器 # 在目标机器上,离线安装 pip install --no-index --find-links=/opt/ansible-wheel ansible不要小看这个细节,我踩过“忘了下载依赖”这种坑,单独只下ansible这一个包,结果到内网一装报一堆依赖缺失。所以要特别明确:离线安装的核心是把所有依赖一并下载全,不只是主包。
2.3 装完之后必须验证的基础项
安装完成后,先不要急着写剧本,先把环境验证一下。我习惯用一套三步走:
- 执行
ansible --version,确认版本、python解释器路径、配置文件位置是否正常。 - 查看配置文件,执行
ansible-config dump查看实际生效的配置项,确认默认的inventory路径,方便后续把自定义配置写到正确位置。 - 检查control主机到受管主机的基本连接,用最简单的ping模块测试:
ansible all -i 192.168.200.11, -m ping -k-k参数表示使用SSH密码登录,安全考虑我一般只在连通性测试时用。生产环境建议直接配置好SSH密钥,避免明文密码出现在命令行历史里。如果这一步能看见pong的返回,就说明控制主机和受管主机之间的基础链路已经通了。
3. 上手Ansible绕不开的四个核心概念
3.1 控制节点与受管节点
这个概念新手绕不过去。控制节点就是装了Ansible、执行命令的主机,也就是你键盘面前那台或跳板机;受管节点是被控制的普通服务器,它们不需要安装Ansible,但需要能通过SSH连接到,并且有可用的Python环境。
这里面有个容易踩的坑:Ansible的老版本对远程Python版本有要求,虽然现在要求宽松了,但你所用的模块如果在受管节点上编译,可能还需要gcc等基础包。我在接手一堆老旧的CentOS 6机器时,远程python只有2.6,Ansible直接各种报错。遇到老系统,你可以在inventory里为每个主机单独指定python解释器路径,比如ansible_python_interpreter=/usr/bin/python3。
控制节点对Python的要求自不必说,3.x版本是基本环境。受管节点的Python版本越高越好,至少得支持你所要用的模块语法。
3.2 Inventory(主机清单)的写法与细节
Inventory文件就是Ansible认识哪些机器、怎么给机器分组的地方。默认路径在/etc/ansible/hosts,但实际项目里我更推荐放到每个项目的目录下,比如project/hosts,这样不同的项目之间环境隔离更清晰,不会污染全局配置。
ini格式是最传统也最直观的一种写法:
[web] 192.168.200.11 192.168.200.12 ansible_port=2222 [db] db-server ansible_host=192.168.200.20 [local] 127.0.0.1 ansible_connection=local这里有不少隐性的参数可以在inventory里直接设置,比如:ansible_user=root表示连接使用哪个用户,ansible_ssh_pass可以指定SSH密码(实际生产环境不建议写明文,建议用ansible-vault加密),ansible_become_pass指定sudo的密码。如果你想临时覆盖默认连接账号,可以直接在inventory里写:
[web:vars] ansible_user=deploy ansible_become=true ansible_become_method=sudo如果用YAML格式写inventory,效果也一样,但工程里我更习惯ini格式——短、直观、一眼能看清分组。建议至少掌握一种,能看懂另一种。分组支持子组和组合变量,[web:children]可以把多个组套进一个父组,这在批量管理大集群时特别适用。
3.3 模块(Module)和临时命令
Ansible最有意思的地方,也在于它的模块化设计。一条临时命令,本质上就是调用了Ansible内置的某个模块去干活。比如刚才测试用的ping,实际是检查受管节点的连通性并收集基础信息。
看几个实际高频的模块:
| 模块名 | 作用 | 使用示意 |
|---|---|---|
| ping | 测试连接 | 上文已演示 |
| command | 执行简单命令(不支持shell特性如管道、通配符) | ansible web -m command -a "uptime" |
| shell | 通过/bin/sh执行命令(支持管道、重定向) | `ansible web -m shell -a "df -h |
| copy | 把本机文件拷贝到受管节点 | ansible web -m copy -a "src=/opt/a.conf dest=/etc/a.conf mode=0644" |
| file | 管理文件属性、创建目录/软链接等 | ansible web -m file -a "path=/data state=directory mode=0755" |
| yum | 包管理(CentOS系) | ansible web -m yum -a "name=nginx state=present" |
| service | 管理系统服务启停 | ansible web -m service -a "name=httpd state=started enabled=yes" |
临时命令适合快速验证、单次操作。比如我平时临时要看一批机器的磁盘使用情况:
ansible web -m shell -a 'df -h | grep "^/dev"'但要注意,临时命令只适合简单场景,涉及多步骤、条件判断、安装+配置+启动一连串操作时,就该用Playbook了。
3.4 Playbook(剧本)到底怎么理解
热词里专门有“ansible剧本”,说的就是Playbook。Playbook本质上就是一个YAML格式的“工作流程文件”,把要执行的任务按顺序组织起来。它比裸命令强在哪?强在可复用、可版本管理、可读性强,而且天然具备幂等性——一条任务执行过之后,再跑一遍不会重复改内容。
我举一个最简的例子。在项目目录创建playbook.yaml:
--- - name: 确保 Nginx 已安装 hosts: web become: true tasks: - name: 安装 nginx yum: name: nginx state: present - name: 启动 nginx service: name: nginx state: started执行:
ansible-playbook -i hosts playbook.yaml第一次执行能看到每个task都变成changed或者ok,第二次执行再跑一遍,你看到的状态会变成ok,而changed不会增加,这就是幂等性的直观体现。
Playbook里面还能细分出更丰富的语法,比如vars定义变量、handlers做变更后的触发动作、template使用Jinja2模板引擎渲染配置文件、when做条件判断,更复杂的还包括include_tasks、roles等组织方式。但说到底,基础就是“主机、任务、模块”这三个维度。
4. 实战:一个完整的Ansible剧本拆解
4.1 场景设计
我拿一个真实工作里很常见的场景来拆解:批量给几台web服务器部署Nginx,创建一个静态站点,并让服务处于开机自启状态。这个场景覆盖了Playbook最核心的几个能力点:变量、模板、处理程序、幂等性,而且步骤够清晰,适合当作练习。
假设inventory文件hosts内容如下:
[web] 192.168.200.11 192.168.200.12 [web:vars] ansible_user=root场景目标:在这两台机器上安装nginx;把我们本地写好的一个index.html模板文件上传到web目录;配置一个虚拟主机配置文件;启动nginx并设为开机自启。
4.2 目录结构的组织方式
规范的Ansible项目目录结构,对后期维护有决定性影响。我推荐用这种布局:
playbook-demo/ ├── hosts ├── playbook.yaml ├── vars.yml └── files/ └── index.html.j2vars.yml单独管理变量,files目录存放模板或静态文件,主剧本playbook.yaml只做任务编排。这样目录一多,找东西也方便。如果项目变大,再引入roles目录结构也不迟,初次上手先保持简单。
4.3 一步步编写剧本
先定义变量文件vars.yml:
--- web_root: /usr/share/nginx/html server_port: 8080 server_name: demo.example.com再写主剧本playbook.yaml:
--- - name: 部署 Nginx 静态站点 hosts: web become: true vars_files: - vars.yml tasks: - name: 安装 nginx yum: name: nginx state: present - name: 写入 index.html 模板 template: src: files/index.html.j2 dest: "{{ web_root }}/index.html" mode: '0644' - name: 配置 Nginx 虚拟主机 template: src: files/demo.conf.j2 dest: /etc/nginx/conf.d/demo.conf notify: reload nginx - name: 启动并设置开机自启 service: name: nginx state: started enabled: yes handlers: - name: reload nginx service: name: nginx state: reloaded这里notify和handlers是一对固定搭配:前面三个任务,只要“配置Nginx虚拟主机”这一步实际发生了内容变更(即changed状态),就会通知handler来重载nginx;如果配置文件没变化,就不会触发重载。这样可以大幅减少无意义的服务重启。
我还需要写模板文件。files/index.html.j2里面可以用到变量:
<html> <head><title>Welcome</title></head> <body> <h1>Hello from {{ ansible_facts['hostname'] }}</h1> <p>Server port: {{ server_port }}</p> </body> </html>这里ansible_facts['hostname']不需要我特意定义,Ansible连接上去后会自动去收集受管节点的事实信息,比如主机名、内存、磁盘、IP等,这个“自动收集”机制非常有用,特别在需要根据机器情况生成不同内容的场景里。
files/demo.conf.j2虚拟主机配置:
server { listen {{ server_port }}; server_name {{ server_name }}; root {{ web_root }}; index index.html; }4.4 执行剧本和验证结果
项目目录下的执行命令:
ansible-playbook -i hosts playbook.yaml首次执行,输出里会出现每个host下的每个task结果,一般有changed、ok;如果中途失败,会直接显示failed,同时告诉你卡在哪个task和错误信息。执行到服务这一步,一般就是changed,因为服务肯定是从未启动到启动了。
紧接着再执行一次同样的命令:
ansible-playbook -i hosts playbook.yaml这次你会看到所有的task几乎都是ok,没有changed,说明没有做重复修改,这正是幂等性的演示:配置没有变动,服务不会瞎重启。理解了这点,你以后给生产环境跑Playbook时心里就有底多了。
最后到浏览器或者curl里验证一下:
curl http://192.168.200.11:8080 curl http://192.168.200.12:8080看到各自的hostname出现在页面上,这个过程就算完整跑通了。
5. 我踩过的坑:常见问题与排查技巧实录
5.1 SSH主机密钥检查导致的失败
初次连接时,SSH经常会提示是否信任目标机器的指纹。但Ansible默认是启用严格校验的,如果目标机器的指纹不在known_hosts里,会有报错。比较省事的处理方式是在ansible.cfg里关掉校验:
[defaults] host_key_checking = False不过我要提醒你,这种方式在测试环境里省事,但生产环境最好还是先预先把known_hosts库里所有主机指纹都收集好,保证安全性。对于安全性要求很严格的金融行业项目,这个方法一定要谨慎,提前跟安全团队确认可用性,不要为了图方便给自己埋雷。
5.2 SSH连接超时或密码认证失败
这类问题和网络连通性、认证配置直接挂钩。先检查ansible -m ping是否能通过,如果不通过,从这几个方向排查:
- 受管节点的sshd服务是否正常监听22端口。
- 控制节点到受管节点的网络是否能通,试试
ssh命令手动连接一次。 - inventory里指定的用户名、密码或密钥是否正确。
- 如果使用sudo提权,
ansible_become_password是否正确配置。
我见过一个特别隐蔽的坑:受管节点上某个用户的shell被改成了nologin,SSH登录本身没问题,但Ansible执行命令却始终报错。排查半天,最后发现是目标机器上该用户的.bashrc文件里有非交互式shell会报错的内容,导致进程非正常结束。按顺序排查非常关键。
5.3 Playbook重复执行却不幂等
理想情况下重复执行应该都是changed不增加,但很多刚上手的人会发现,明明什么都没改,每次执行还是显示changed。这多半是任务里用了shell或command模块去执行那些每次都会输出不同结果的命令,或者写配置文件时模板里的内容因为时间戳、随机数等原因每次渲染出的结果都不一样。
这种情况下,可以给模块指明“该结果达到什么状态才需要变更”,比如command模块里有creates参数:
- name: 初始化数据库 command: /usr/local/bin/init_db.sh args: creates: /etc/myapp/.db_initialized只有目标文件不存在时,这条命令才会执行,一旦文件生成了,任务就直接判为ok,不重复执行。
5.4 中文编码问题
处理中文环境的时候,我遇到最多的问题是剧本文件或模板文件里的中文在受管节点上显示乱码。这主要是因为控制节点和受管节点之前的locale环境不一致。我的建议是:所有剧本和模板文件统一使用UTF-8编码;在playbook里可以通过environment给任务设置指定的环境变量:
- name: 执行某个中文相关命令 shell: /usr/bin/some_cmd environment: LANG: en_US.UTF-8 LC_ALL: en_US.UTF-8这样受管节点执行命令时环境变量、locale都按你指定的来,乱码问题会少很多。还有一点,写模板时尽量避免把中文写死在配置文件里,而应通过变量引用,这样排查编码问题时定位更快。
5.5 权限配置不当造成提权失败
Ansible默认很多管理任务是root才能做,比如yum安装包、改系统配置文件。如果你用的连接账号不是root,就要借助become机制做提权。最经典的坑是,become方法写错、密码没配,或者受管节点sudo规则里不允许当前账号免密执行。
我的建议是:inventory里明确指定:
[web:vars] ansible_user=deploy ansible_become=true ansible_become_method=sudo ansible_become_password=your_secure_password更稳妥一点的做法,是在受管节点上给deploy用户配置NOPASSWD的sudo权限,减少密码交互带来的麻烦。注意ansible_become_password不要在明文的inventory文件里长期保留,可以配合ansible-vault encrypt对文件进行整体加密。
6. 写在最后:我用Ansible的一些个人体会
工具到手上之后,怎么让它真正发挥价值,我觉得核心还在于“先小后大、由简入繁”。刚接触Ansible的新手,别急着把现有所有运维操作都塞进剧本里,先从一两个高频、风险低的小任务开始,比如批量同步ntp配置、批量修改dns解析,跑顺了,对它的行为特性真正理解了,再慢慢往关键业务上推,这样踩坑有缓冲,不会在你的核心组件上直接爆雷。
从学习和参考资料角度看,官方文档质量很高,多看多练比什么都强。搜索“ansible菜鸟教程”也能找到很多适合新手的基础文章,但网上的教程质量参差不齐,还是要以官方文档为准。另外,把常用的操作整理成自己的角色(roles),沉淀成团队公共资产,你后面用起来会特别舒服。
我在实际使用中还有一个习惯:把所有Playbook纳入Git仓库管理,每次变更都带着清晰的提交说明。配置管理一旦版本化,回滚、审计、多人协作就全都顺理成章了。这虽然跟Ansible本身无关,但做运维的都知道,出问题的时候,能快速知道“上一次正常配置是哪一版”,比什么都值钱。
Ansible的价值不是“写一条命令执行一下”,而是让运维工作变得可预期、可重复、可追踪。希望这篇从安装到实战再到排错的内容,能帮你把第一把自动化运维的钥匙攥到手里。