引言
在管理多台 Linux 服务器时,手动登录每台机器执行命令的方式不仅效率低下,而且容易出错。Ansible 作为一款开源的自动化运维工具,凭借其无需 Agent、基于 SSH、声明式语法的三大优势,已成为运维工程师的必备技能。本文将全面介绍 Ansible 的安装配置、核心概念与实战应用。
Ansible 核心架构
Ansible 采用无主从架构(Agentless),通过 SSH 协议直接管理远程主机,核心组件包括:
| 组件 | 说明 |
| Control Node | 控制节点,安装 Ansible 的机器(支持 Linux/macOS/WSL) |
| Managed Node | 被管理主机,通过 SSH 被控制节点操作 |
| Inventory | 主机清单,定义被管理主机的列表和分组 |
| Module | 执行具体任务的模块(如 copy、yum、service) |
| Playbook | YAML 格式的剧本文件,编排多个任务的执行流程 |
| Role | 可复用的任务、变量、文件等资源的组织单元 |
一、Ansible 安装与配置
1.1 控制节点安装
Ubuntu / Debian
1 2 3 4 5 6 7 8
| sudo apt update sudo apt install -y software-properties-common sudo add-apt-repository --yes --update ppa:ansible/ansible sudo apt install -y ansible
ansible --version
|
CentOS / RHEL
1 2 3 4 5 6
| sudo dnf install -y epel-release sudo dnf install -y ansible
ansible --version
|
使用 pip 安装(跨平台通用)
1 2 3 4 5 6 7
| python3 -m venv ~/ansible-env source ~/ansible-env/bin/activate pip install ansible
pip install ansible==9.5.0
|
1.2 SSH 免密登录配置
Ansible 通过 SSH 管理远程主机,因此需要配置 SSH 密钥认证:
1 2 3 4 5 6 7
| ssh-keygen -t ed25519 -f ~/.ssh/ansible_key -N ""
ssh-copy-id -i ~/.ssh/ansible_key.pub user@host1 ssh-copy-id -i ~/.ssh/ansible_key.pub user@host2 ssh-copy-id -i ~/.ssh/ansible_key.pub user@host3
|
配置 SSH Config 简化连接:
1 2 3 4 5 6 7 8 9 10 11 12
| Host server-* IdentityFile ~/.ssh/ansible_key User ubuntu StrictHostKeyChecking no UserKnownHostsFile /dev/null
Host server-web HostName 192.168.1.10
Host server-db HostName 192.168.1.11
|
二、主机清单(Inventory)
2.1 静态 Inventory
默认位置 /etc/ansible/hosts 或自定义文件 inventory.ini:
1 2 3 4 5 6 7 8 9 10
| [webservers] web1 ansible_host=192.168.1.10 ansible_user=ubuntu web2 ansible_host=192.168.1.11 ansible_user=ubuntu
[dbservers] db1 ansible_host=192.168.1.20
[all:vars] ansible_ssh_private_key_file=~/.ssh/ansible_key ansible_python_interpreter=/usr/bin/python3
|
2.2 Inventory 变量定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| all: children: webservers: hosts: web1: ansible_host: 192.168.1.10 http_port: 80 web2: ansible_host: 192.168.1.11 http_port: 8080 vars: ansible_user: ubuntu nginx_version: 1.26
|
2.3 动态 Inventory
AWS、GCP、阿里云等云平台提供动态 Inventory 脚本,自动从云 API 获取主机列表:
1 2 3 4 5 6 7 8 9 10 11 12
| ansible-inventory -i aws_ec2.yaml --list
plugin: aws_ec2 regions: - ap-northeast-1 filters: tag:Environment: production keyed_groups: - key: tags.Role prefix: role
|
三、Ad-Hoc 命令快速上手
在编写 Playbook 之前,先通过 Ad-Hoc 命令熟悉 Ansible 的模块化操作:
3.1 连通性测试
1 2 3 4 5
| ansible all -i inventory.ini -m ping
ansible webservers -i inventory.yml -m ping
|
3.2 常用 Ad-Hoc 命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ansible all -m shell -a "uptime && free -h"
ansible webservers -m copy -a "src=/etc/nginx/nginx.conf dest=/etc/nginx/nginx.conf backup=yes"
ansible webservers -m apt -a "name=nginx state=present" -b
ansible webservers -m service -a "name=nginx state=restarted enabled=yes" -b
ansible all -m setup -a "filter=ansible_distribution*"
|
3.3 常用模块速查
| 模块 | 功能 | 常用参数 |
| command/shell | 执行命令 | cmd (shell 支持管道重定向) |
| copy | 复制文件 | src, dest, owner, mode, backup |
| template | Jinja2 模板 | src, dest, vars |
| file | 文件/目录管理 | path, state (directory/touch/link/absent) |
| apt/yum/dnf | 包管理 | name, state (present/latest/absent) |
| service/systemd | 服务管理 | name, state (started/stopped/restarted), enabled |
| user | 用户管理 | name, groups, shell, state |
| lineinfile | 行编辑 | path, line, regexp, insertafter |
| firewalld/iptables | 防火墙规则 | service, port, permanent, state |
| docker_container | Docker 容器 | name, image, state, ports, env |
四、编写 Playbook
Playbook 是 Ansible 的核心,使用 YAML 格式定义任务编排。
4.1 基础 Playbook 结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| --- - name: 配置 Nginx Web 服务器 hosts: webservers become: yes vars: nginx_port: 80 server_name: example.com
tasks: - name: 更新 apt 缓存 apt: update_cache: yes cache_valid_time: 3600
- name: 安装 Nginx apt: name: nginx state: present
- name: 配置 Nginx 站点 template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/{{ server_name }} notify: restart nginx
- name: 启用站点 file: src: /etc/nginx/sites-available/{{ server_name }} dest: /etc/nginx/sites-enabled/{{ server_name }} state: link notify: restart nginx
- name: 开放防火墙端口 ufw: rule: allow port: "{{ nginx_port }}" proto: tcp
handlers: - name: restart nginx service: name: nginx state: restarted
|
4.2 执行 Playbook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ansible-playbook -i inventory.yml nginx-setup.yml
ansible-playbook -i inventory.yml nginx-setup.yml --check
ansible-playbook -i inventory.yml nginx-setup.yml -vvv
ansible-playbook -i inventory.yml nginx-setup.yml -u ubuntu
ansible-playbook -i inventory.yml nginx-setup.yml --start-at-task="配置 Nginx 站点"
ansible-playbook -i inventory.yml nginx-setup.yml --syntax-check
|
4.3 条件判断与循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| - name: 根据发行版安装不同包 apt: name: "{{ item }}" state: present when: ansible_facts['os_family'] == "Debian" loop: - nginx - python3-pip - htop
- name: 创建多个用户 user: name: "{{ item.username }}" groups: "{{ item.groups | default('users') }}" shell: /bin/bash loop: - { username: "alice", groups: "sudo" } - { username: "bob", groups: "docker" } - { username: "carol" }
|
4.4 文件模板(Jinja2)
模板文件使用 .j2 扩展名,存放在 templates/ 目录下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| {# templates/nginx.conf.j2 #} server { listen {{ nginx_port }}; server_name {{ server_name }};
root /var/www/{{ server_name }}; index index.html;
location / { try_files $uri $uri/ =404; }
location /api { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
|
五、Roles 角色编排
当 Playbook 变得复杂时,使用 Role 将任务、变量、模板和文件组织为可复用的单元。
5.1 Role 目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| site.yml roles/ common/ tasks/main.yml handlers/main.yml templates/ files/ vars/main.yml defaults/main.yml meta/main.yml nginx/ tasks/main.yml templates/nginx.conf.j2 vars/main.yml postgresql/ tasks/main.yml vars/main.yml
|
5.2 编写 Role 任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| --- - name: 确保 Nginx 已安装 apt: name: nginx state: "{{ nginx_state | default('present') }}"
- name: 创建站点目录 file: path: "/var/www/{{ nginx_server_name }}" state: directory mode: '0755'
- name: 部署 Nginx 配置 template: src: nginx.conf.j2 dest: "/etc/nginx/sites-available/{{ nginx_server_name }}" notify: restart nginx
- name: 启用站点 file: src: "/etc/nginx/sites-available/{{ nginx_server_name }}" dest: "/etc/nginx/sites-enabled/{{ nginx_server_name }}" state: link notify: restart nginx
- name: 部署 index.html copy: src: index.html dest: "/var/www/{{ nginx_server_name }}/index.html" mode: '0644'
|
5.3 主 Playbook 引用 Roles
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| --- - name: 所有服务器通用配置 hosts: all roles: - common
- name: Web 服务器配置 hosts: webservers roles: - role: nginx vars: nginx_server_name: "example.com" nginx_port: 443
- name: 数据库服务器配置 hosts: dbservers roles: - role: postgresql vars: postgresql_version: 16 postgresql_max_connections: 200
|
5.4 Role 依赖与 Galaxy
1 2 3 4 5 6 7 8
| --- dependencies: - role: common - role: geerlingguy.security vars: security_ssh_permit_root_login: "no" security_fail2ban_enabled: true
|
使用 Ansible Galaxy 下载社区 Role:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ansible-galaxy search nginx
ansible-galaxy install geerlingguy.nginx ansible-galaxy install geerlingguy.postgresql
cat << 'EOF' > requirements.yml --- roles: - name: geerlingguy.nginx version: 3.2.0 - name: geerlingguy.postgresql version: 3.5.0 - name: geerlingguy.security version: 2.3.0 EOF
ansible-galaxy install -r requirements.yml
|
六、Ansible Vault 加密敏感数据
保护密码、密钥等敏感信息:
6.1 加密与解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault encrypt group_vars/all/vault.yml
ansible-vault decrypt secrets.yml
ansible-vault rekey secrets.yml
|
6.2 使用加密变量
1 2 3 4 5 6 7
| vault_db_password: "MySecureP@ssw0rd!2026" vault_api_key: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
db_password: "{{ vault_db_password }}" api_key: "{{ vault_api_key }}"
|
6.3 执行时提供密码
1 2 3 4 5 6 7 8 9 10
| ansible-playbook site.yml --ask-vault-pass
echo "my-vault-password" > .vault_pass chmod 600 .vault_pass ansible-playbook site.yml --vault-password-file .vault_pass
ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass ansible-playbook site.yml
|
七、Ansible 配置优化
7.1 主配置文件 ansible.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| [defaults]
inventory = inventory.yml
forks = 20
timeout = 30
host_key_checking = False
log_path = /var/log/ansible.log
gathering = smart fact_caching = jsonfile fact_caching_connection = /tmp/ansible_facts fact_caching_timeout = 3600
interpreter_python = /usr/bin/python3
[ssh_connection]
pipelining = True ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
7.2 性能优化技巧
1 2 3 4 5 6 7 8 9 10 11
| ansible-playbook site.yml --gathering=explicit
ansible-playbook site.yml --limit web1
ansible-playbook site.yml --skip-tags "firewall"
ansible-playbook site.yml --tags "deploy"
|
八、实战案例:完整部署 LEMP 栈
综合运用上述知识,编写完整 Playbook 部署 LEMP 栈(Nginx + MariaDB + PHP):
8.1 目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| lemp-deployment/ ├── ansible.cfg ├── inventory.yml ├── site.yml ├── requirements.yml ├── group_vars/ │ └── all/ │ └── main.yml ├── roles/ │ ├── common/ │ │ └── tasks/main.yml │ ├── nginx/ │ │ ├── tasks/main.yml │ │ └── templates/nginx.conf.j2 │ ├── mariadb/ │ │ ├── tasks/main.yml │ │ ├── vars/main.yml │ │ └── templates/my.cnf.j2 │ └── php/ │ ├── tasks/main.yml │ └── templates/www.conf.j2 └── vault.yml
|
8.2 部署命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ansible-galaxy install -r requirements.yml
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml --check -v
ansible-playbook site.yml -v
ansible all -m shell -a "curl -sI http://localhost | head -5"
|
8.3 部署后验证脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| - name: 验证 LEMP 部署 hosts: all tasks: - name: 检查 Nginx 进程 shell: "pgrep -c nginx" register: nginx_count failed_when: nginx_count.stdout | int == 0
- name: 检查 MariaDB 进程 shell: "pgrep -c mariadbd" register: mariadb_count failed_when: mariadb_count.stdout | int == 0
- name: 检查 PHP-FPM 进程 shell: "pgrep -c php-fpm" register: php_count failed_when: php_count.stdout | int == 0
- name: 测试 Nginx 响应 uri: url: http://localhost/health.php return_content: yes register: health failed_when: "'OK' not in health.content"
- name: 输出部署摘要 debug: msg: - "Nginx 进程数: {{ nginx_count.stdout }}" - "MariaDB 进程数: {{ mariadb_count.stdout }}" - "PHP-FPM 进程数: {{ php_count.stdout }}" - "Web 健康检查: {{ '通过' if health.status == 200 else '失败' }}"
|
九、常见问题排查
| 问题 | 可能原因 | 解决方案 |
| SSH 连接超时 | 防火墙规则、SSH 配置 | 检查 ssh-T user@host 连通性;确认 ControlPath 文件无冲突 |
| 权限被拒绝 | become 未启用或密码错误 | 添加 -b/-K 参数;检查 /etc/sudoers 配置 |
| Python 解释器错误 | 远程主机无 Python3 | 设置 ansible_python_interpreter=/usr/bin/python3 |
| 事实(Fact)超时 | 网络延迟或主机负载高 | 启用 fact_caching;减少 for 数量 |
| 幂等性失败 | 任务设计未考虑幂等性 | 使用声明式模块而非 shell/command |
| 依赖 Role 冲突 | 多个 Role 修改同一文件 | 使用 lineinfile 替代文件覆盖;明确 Role 执行顺序 |
| 模板语法错误 | Jinja2 变量未定义 | 使用 default() 过滤器;--syntax-check 提前检查 |
| 并发写入冲突 | 多节点同时操作 | 使用 serial 限制并行度;throttle 控制并发 |
十、最佳实践总结
- 目录结构规范化:严格遵循 Role 目录结构,Playbook 和 Role 分离
- 变量分离:加密变量放 vault,默认值放 defaults,环境特化变量放 group_vars
- 幂等性优先:优先使用声明式模块,避免 shell/command 模块
- 版本控制:所有 Playbook、Role 和配置文件纳入 Git 管理
- 标签管理:通过
tags 精确控制 Playbook 执行范围
- 测试先行:使用
--check 和 --diff 预览变更影响
- 日志记录:开启 ansible.log,便于故障追溯
- 定期验证:通过 cron 定时执行验证 Playbook 确保配置一致性
总结
Ansible 以其简洁的 YAML 语法、无 Agent 的架构设计和强大的模块生态,大大降低了服务器批量管理的学习门槛。从单条 Ad-Hoc 命令到复杂的多 Role 编排,Ansible 能够满足从小型项目到大规模集群的自动化运维需求。掌握 Inventory 清单管理、Playbook 编写、Role 复用和 Vault 加密等核心技能,是每一位运维工程师迈向高效自动化管理的关键一步。
本文由AI辅助生成,内容仅供参考