为什么选择 Ansible?
在自托管和 VPS 运维领域,随着服务器数量增长,手动 SSH 登录每台机器执行命令的方式越来越不可持续。Ansible 提供了一个简洁而强大的解决方案——无需在被管理节点安装代理,只需通过 SSH 连接,就能批量执行配置、部署应用、管理资产。
与 Puppet、Chef、SaltStack 等竞争对手相比,Ansible 的核心优势在于:
- 无代理架构:被管理节点只需 SSH 和 Python,无需额外守护进程
- 声明式语法:Playbook 使用 YAML,可读性极强,新手友好
- 幂等性保证:重复执行相同 Playbook 不会产生副作用
- 丰富的模块生态:6000+ 内置模块覆盖几乎所有常见运维场景
- 渐进式采用:从单台服务器开始,逐步扩展到集群规模
一、Ansible 快速入门
1.1 安装 Ansible
Ansible 支持所有主流 Linux 发行版、macOS 和 Windows(通过 WSL)。
# Ubuntu/Debian
sudo apt update
sudo apt install -y ansible
# CentOS/RHEL/Fedora
sudo dnf install -y ansible
# macOS (Homebrew)
brew install ansible
# 使用 pip 安装(推荐,版本最新)
pip install ansible-core
验证安装:
ansible --version
1.2 配置库存文件(Inventory)
库存文件定义了你需要管理的所有主机。创建 inventory.ini:
# 主机组划分
[webservers]
web1.example.com ansible_host=192.168.1.10
web2.example.com ansible_host=192.168.1.11
web3.example.com ansible_host=192.168.1.12
[dbservers]
db1.example.com ansible_host=192.168.1.20
[monitoring]
monitor1.example.com ansible_host=192.168.1.30
# 组变量:所有 web 服务器共享的配置
[webservers:vars]
http_port=80
nginx_version=1.24
# 全部主机组
[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
1.3 首次连通测试
# 测试所有主机连通性
ansible all -i inventory.ini -m ping
# 测试特定组
ansible webservers -i inventory.ini -m ping
# 查看组内主机列表
ansible webservers --list-hosts -i inventory.ini
预期输出:
web1.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
web2.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
二、Playbook 核心概念
Playbook 是 Ansible 的配置描述文件,使用 YAML 格式。一个典型的 Playbook 包含以下结构:
---
- name: 配置 Web 服务器
hosts: webservers
become: true # 使用 sudo 执行
gather_facts: true # 收集目标主机事实
vars:
nginx_version: "1.24"
app_port: 8080
tasks:
# 任务列表...
handlers:
# 处理器(由任务触发)...
2.1 常用模块速查
| 模块 | 用途 | 示例 |
|---|---|---|
apt / yum / dnf | 包管理 | apt: name=nginx state=present |
service / systemd | 服务管理 | systemd: name=nginx state=restarted |
copy | 复制文件 | copy: src=nginx.conf dest=/etc/nginx/ |
template | Jinja2 模板 | template: src=app.conf.j2 dest=/etc/app/ |
git | 代码拉取 | git: repo=https://... dest=/opt/app |
user | 用户管理 | user: name=deploy groups=sudo |
file | 文件操作 | file: path=/data mode=0755 state=directory |
lineinfile | 文件行编辑 | lineinfile: path=/etc/hosts line="10.0.0.1 db" |
shell / command | 执行命令 | shell: docker ps --format json |
debug | 调试输出 | debug: msg="变量值为 {{ my_var }}" |
2.2 编写第一个 Playbook
创建一个基础系统加固 Playbook:
---
- name: VPS 基础安全加固
hosts: all
become: true
vars:
ntp_server: "pool.ntp.org"
fail2ban_enabled: true
tasks:
- name: 更新系统软件包
apt:
update_cache: true
upgrade: dist
tags: ['update']
- name: 安装必要工具
apt:
name:
- curl
- git
- htop
- fail2ban
- ufw
state: present
tags: ['packages']
- name: 配置防火墙规则
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: [22, 80, 443]
tags: ['firewall']
- name: 禁用 root 远程登录
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
notify: 重启 SSH 服务
tags: ['ssh']
- name: 启动并启用 fail2ban
systemd:
name: fail2ban
state: started
enabled: true
tags: ['fail2ban']
handlers:
- name: 重启 SSH 服务
systemd:
name: sshd
state: restarted
运行 Playbook:
ansible-playbook -i inventory.ini harden.yml --check --diff
ansible-playbook -i inventory.ini harden.yml
--check 模式(干跑)不会实际修改系统,--diff 显示具体变更内容。
三、进阶技巧与最佳实践
3.1 条件判断与循环
- name: 根据系统类型安装包
package:
name: "{{ item }}"
state: present
loop:
- curl
- git
when: ansible_os_family == "Debian"
- name: 创建多个用户
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: /bin/bash
loop: "{{ users_list }}"
3.2 Jinja2 模板
模板文件 templates/nginx.conf.j2:
server {
listen {{ http_port }};
server_name {{ inventory_hostname }};
root {{ app_root }};
index index.html;
{% if ssl_enabled %}
ssl_certificate /etc/ssl/certs/{{ domain }}.crt;
ssl_certificate_key /etc/ssl/private/{{ domain }}.key;
{% endif %}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
在 Playbook 中引用:
- name: 部署 Nginx 配置
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
mode: '0644'
notify: 重载 Nginx
3.3 角色(Roles)复用
角色是 Ansible 代码复用的核心机制。目录结构:
roles/
└── webserver/
├── tasks/
│ ├── main.yml
│ └── nginx.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── vars/
│ └── main.yml
├── defaults/
│ └── main.yml
└── meta/
└── main.yml
roles/webserver/tasks/main.yml:
---
- name: 安装 Nginx
import_tasks: nginx.yml
- name: 部署应用
copy:
src: app/
dest: /var/www/app
在 Playbook 中使用角色:
---
- name: 部署 Web 服务
hosts: webservers
roles:
- webserver
vars:
app_version: "2.1.0"
3.4 变量优先级
Ansible 变量按以下优先级从低到高:
- Role defaults(最低)
- Inventory 变量
- Play vars
- Host facts(gather_facts)
- Role vars
- Task vars
- Extra vars(
-e命令行参数,最高)
四、批量部署实战场景
4.1 一键部署 Docker Compose 服务
---
- name: 部署 Docker Compose 应用栈
hosts: webservers
become: true
vars:
compose_dir: /opt/apps/{{ app_name }}
apps:
- name: traefik
stack: traefik
- name: monitoring
stack: monitoring
tasks:
- name: 确保 Docker 已安装
ansible.builtin.package:
name: docker.io
state: present
- name: 将用户加入 docker 组
user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: 创建应用目录
file:
path: "{{ compose_dir }}"
state: directory
mode: '0755'
- name: 部署 docker-compose 文件
template:
src: "{{ item.stack }}.yml.j2"
dest: "{{ compose_dir }}/docker-compose.yml"
loop: "{{ apps }}"
- name: 启动服务
community.docker.docker_compose_v2:
project_src: "{{ compose_dir }}"
state: present
pull: always
4.2 批量系统升级与 reboot 控制
---
- name: 批量系统升级并安全重启
hosts: all
serial: 3 # 每次只处理 3 台主机
gather_facts: false
tasks:
- name: 升级系统包
apt:
upgrade: dist
update_cache: true
when: ansible_distribution == "Ubuntu"
- name: 检查是否需要重启
command: needs-restarting -r
register: needs_reboot
changed_when: false
failed_when: false
- name: 重启需要重启的主机
reboot:
reboot_timeout: 300
when: needs_reboot.rc == 1
serial: 3 确保每次只升级 3 台服务器,避免全部宕机导致服务中断。
4.3 配置 drift 检测与修复
---
- name: 检测并修复配置漂移
hosts: all
become: true
tasks:
- name: 确保 SSH 配置正确
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^X11Forwarding', line: 'X11Forwarding no' }
- name: 确保关键文件权限正确
file:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
loop:
- { path: /etc/ssh/sshd_config, mode: '0600' }
- { path: /etc/ssh/ssh_known_hosts, mode: '0644' }
- { path: /etc/crontab, mode: '0600' }
五、性能优化与大规模部署
5.1 连接优化
# ansible.cfg
[defaults]
forks = 50 # 并行 50 个任务
timeout = 30
retry_files_enabled = false
inventory = ./inventory.ini
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
pipelining = true # SSH 管道化,大幅提升性能
5.2 分片执行
对于超大集群,使用 --limit 和 --tags 分片:
# 只执行特定标签的任务
ansible-playbook deploy.yml --tags 'nginx,ssl'
# 限制到特定主机
ansible-playbook deploy.yml --limit 'web[1:5]'
# 跳过特定标签
ansible-playbook deploy.yml --skip-tags 'database'
5.3 Ansible Vault 加密敏感信息
# 创建加密变量文件
ansible-vault create group_vars/all/secrets.yml
# 编辑现有加密文件
ansible-vault edit group_vars/all/secrets.yml
# 运行时解密
ansible-playbook deploy.yml --ask-vault-pass
加密文件示例:
# group_vars/all/secrets.yml(加密存储)
db_password: "s3cur3P@ssw0rd"
api_key: "ak_live_xxxxxxxx"
ssl_cert_key: "{{ vault_ssl_key }}"
六、常见故障排查
6.1 连接失败
# 详细调试输出
ansible all -m ping -vvv
# 测试 SSH 连接
ansible all -m debug -a "msg='SSH test'" -vvv
# 检查 Python 路径
ansible all -m setup -a "filter=ansible_python*"
常见问题及解决:
| 症状 | 原因 | 解决方案 |
|---|---|---|
SSH: hostname could not be resolved | DNS 问题 | 检查 /etc/hosts 或使用 ansible_host |
Permission denied (publickey) | SSH 密钥问题 | 确认 ansible_ssh_private_key_file 路径正确 |
Python not found | 缺少 Python | 指定 ansible_python_interpreter 路径 |
MODULE FAILURE | 模块依赖缺失 | 在被管理节点安装对应依赖 |
6.2 幂等性问题
# ❌ 非幂等操作:每次执行都会追加
- shell: echo "new_line" >> /etc/config
# ✅ 幂等操作:使用 lineinfile
- lineinfile:
path: /etc/config
line: "new_line"
state: present
七、总结
Ansible 是自托管 VPS 运维中不可或缺的自动化工具。通过本文,你学到了:
- 基础:安装、库存配置、连通测试
- 核心:Playbook 编写、模块使用、条件循环
- 进阶:角色复用、模板引擎、变量管理
- 实战:批量部署、升级控制、配置漂移修复
- 优化:连接调优、分片执行、Vault 加密
- 排错:常见问题诊断与解决方案
从单台 VPS 到百台集群,Ansible 都能提供一致、可重复、可审计的配置管理体验。记住关键原则:声明式优于命令式,幂等性至关重要,角色化促进复用。
立即开始吧——在你的第一台 VPS 上运行 ansible all -m ping,自动化之旅就此开启。
