第4章:Ansible入门:架构与原理、Inventory管理、Ad-Hoc命令、Playbook基础语法
说实话,刚接触自动化运维那会儿,我试过不少工具。Puppet、Chef、SaltStack都折腾过。但最后让我真正觉得「嗯,就是它了」的,还是Ansible。为什么?因为它简单。不需要装客户端,不需要搞复杂的证书体系,只要SSH能通,就能干活。
这一章,咱们就从头捋一遍Ansible的核心东西。我会把我在生产环境里踩过的坑、总结的经验,都揉进去讲。
4.1 Ansible架构与原理
先看一张图,这是我画的一个Ansible核心架构图。你一看就明白它怎么工作的了。
Ansible的架构,说白了就一句话:控制节点通过SSH把模块推送到被管理节点上执行,执行完就撤,不留痕迹。这和那些需要装Agent的工具完全不一样。
我个人习惯把Ansible的核心原理总结成三点:
- 无Agent架构:被管理节点只需要有Python环境和SSH服务。我刚开始用的时候,最担心的就是「没装Agent会不会出问题?」后来发现,这恰恰是它最稳的地方。
- 幂等性:同一个Playbook跑一次和跑一百次,结果是一样的。我在项目中遇到过有人反复跑同一个任务,系统状态始终一致,这就是幂等性的好处。
- 模块化:每个操作都封装成模块。比如copy模块负责文件传输,yum模块负责包管理。你想想看,这就像搭积木一样,组合起来就能完成复杂任务。
核心理解:Ansible本质上是一个「声明式」的配置管理工具。你告诉它「最终状态应该是什么样」,它自己会想办法达到那个状态。而不是像脚本那样,一步一步告诉它「先做A,再做B」。
4.2 Inventory管理
Inventory就是Ansible的「通讯录」。它告诉Ansible:你要管理哪些机器?这些机器怎么连接?
默认的Inventory文件是 /etc/ansible/hosts,但说实话,我从来不用那个路径。我习惯在每个项目目录下自己建一个 inventory 文件,这样不同项目之间不会互相干扰。
4.2.1 静态Inventory
最简单的Inventory长这样:
# inventory文件
web01 ansible_host=192.168.1.10 ansible_user=root
web02 ansible_host=192.168.1.11 ansible_user=root
[webservers]
web01
web02
[dbservers]
db01 ansible_host=192.168.1.20 ansible_user=admin
这里有几个要点:
ansible_host:指定IP地址。如果不写,默认用主机名当IP。ansible_user:SSH登录用户。我曾经因为忘记指定用户,用root去连一个禁止root登录的机器,卡了半天。- 组名:用方括号定义组。比如
[webservers],之后就可以针对这个组执行操作。
小技巧:我习惯在Inventory里用变量来管理密码或密钥路径。比如 ansible_ssh_private_key_file=/path/to/key。这样就不用每次都在命令行里指定了。
4.2.2 动态Inventory
如果你的机器是动态变化的——比如云上的ECS实例,每次重启IP都会变——那静态Inventory就不够用了。这时候需要动态Inventory。
动态Inventory本质上是一个可执行脚本(Python、Shell都行),它返回JSON格式的机器列表。Ansible官方提供了AWS、阿里云等云厂商的插件。我当年在阿里云上管理几百台ECS时,就是用动态Inventory自动拉取机器列表的。
# 使用阿里云动态Inventory插件示例
# 需要安装:pip install ansible-alicloud
ansible-inventory -i alicloud.yml --list
嗯,这里要注意:动态Inventory的调试比静态的麻烦。我建议先在测试环境用 --list 参数看看返回的JSON结构对不对,再正式使用。
4.3 Ad-Hoc命令
Ad-Hoc命令,说白了就是「一次性命令」。你不想写Playbook,就想快速干一件事,比如重启服务、查看磁盘空间,那就用Ad-Hoc。
基本格式:
ansible <主机组> -m <模块名> -a <模块参数>
几个我常用的例子:
# 检查所有web服务器的连通性
ansible webservers -m ping
# 查看所有机器的磁盘使用情况
ansible all -m shell -a "df -h"
# 批量安装nginx
ansible webservers -m yum -a "name=nginx state=present"
# 复制配置文件
ansible webservers -m copy -a "src=/etc/nginx/nginx.conf dest=/etc/nginx/nginx.conf owner=root group=root mode=0644"
避坑指南:我曾经在生产环境上执行 ansible all -m shell -a "reboot",结果所有机器同时重启了。嗯,那场面……所以用Ad-Hoc执行危险操作时,一定要加 --limit 限制范围,或者先用 --check 做模拟运行。
Ad-Hoc命令虽然方便,但有个缺点:不可重复。你今天敲了一串命令把环境配好了,下周再来一次,可能就忘了当时怎么配的。这时候就需要Playbook了。
4.4 Playbook基础语法
Playbook是Ansible的「剧本」。它用YAML格式描述了一系列任务,告诉Ansible「先做什么,再做什么,最终要达到什么状态」。
4.4.1 YAML基础
写Playbook之前,得先懂YAML。YAML的规则很简单:
- 用缩进表示层级关系(只能用空格,不能用Tab)
- 键值对用
key: value格式(冒号后面要加空格) - 列表用
- item表示
我见过太多新手因为缩进问题卡半天。记住:YAML对缩进极其敏感。一个空格不对,整个Playbook就废了。
4.4.2 第一个Playbook
来看一个最简单的Playbook:
---
- name: 安装并启动Nginx
hosts: webservers
become: yes
tasks:
- name: 安装nginx
yum:
name: nginx
state: present
- name: 启动nginx服务
service:
name: nginx
state: started
enabled: yes
逐行解释一下:
---:YAML文件的开头标记name:Playbook的描述,方便你以后知道这个剧本是干嘛的hosts:指定在哪些机器上执行become: yes:提权执行(相当于sudo)。我习惯在需要root权限的任务里加上这个tasks:任务列表,每个任务都有name和具体的模块调用
个人经验:我写Playbook时,每个任务都会写一个清晰的 name。为什么?因为当Playbook执行失败时,Ansible会告诉你「第几个任务失败了」,但不会告诉你「哪个name的任务失败了」。有了name,你一眼就能定位问题。
4.4.3 变量与模板
真实场景中,不同机器的配置可能不一样。比如Nginx的端口,测试环境用8080,生产环境用80。这时候就需要变量。
---
- name: 配置Nginx端口
hosts: webservers
vars:
nginx_port: 8080
tasks:
- name: 使用模板配置nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
模板文件 nginx.conf.j2 里可以这样写:
server {
listen {{ nginx_port }};
server_name example.com;
...
}
变量可以用 {{ }} 引用。Ansible会把它替换成实际的值。我当年做多环境部署时,就是靠变量和模板,一套Playbook通吃开发、测试、生产三个环境。
4.4.4 条件与循环
有时候需要根据条件决定是否执行某个任务。比如只在CentOS 7上执行某个操作:
tasks:
- name: 仅在CentOS 7上执行
yum:
name: some-package
when: ansible_os_family == "RedHat" and ansible_distribution_major_version == "7"
循环也很常用。比如批量创建用户:
tasks:
- name: 创建多个用户
user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- charlie
你想想看,如果没有循环,你得写三个重复的任务。有了循环,一行搞定。
4.5 本章小结
这一章我们走了一遍Ansible的入门路线。从架构原理到Inventory管理,从Ad-Hoc命令到Playbook基础语法。说白了,Ansible的核心就两件事:知道要管哪些机器(Inventory),知道要做什么操作(Playbook)。
我个人建议,刚开始学的时候,先别急着写复杂的Playbook。先用Ad-Hoc命令熟悉各个模块的用法,等摸熟了,再慢慢把命令组合成Playbook。就像学做饭一样,先学会切菜、炒菜,再学做满汉全席。
下一章我们会深入Playbook的高级用法,包括角色(Roles)、变量优先级、以及如何组织大型项目。但那是后话了,先把今天的内容消化掉。