Ansible 自动化运维入门指南

一、为什么选择 Ansible

1.1 自动化运维的痛点

在传统运维工作中,我们经常面临以下问题:

  • 重复劳动:在多台服务器上执行相同的配置任务
  • 人为错误:手动操作容易遗漏步骤或输入错误命令
  • 效率低下:上百台服务器逐台操作耗时耗力
  • 难以追溯:操作记录分散,故障复盘困难
  • 环境不一致:不同服务器配置存在差异导致问题

1.2 Ansible 的优势

特性 说明
无代理架构 无需在目标机器安装客户端,SSH 即可
幂等性 多次执行结果一致,安全可重复
简单易学 YAML 语法,Playbook 可读性强
模块化 丰富的内置模块覆盖常见运维场景
生态完善 大量社区 Role,加速开发

二、基础概念

2.1 核心术语

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────┐
│ Control Node (控制节点) │
│ - 安装 Ansible │
│ - 编写 Playbook │
│ - 执行任务 │
└────────────────────┬────────────────────────────────────┘
│ SSH

┌─────────────────────────────────────────────────────────┐
│ Managed Nodes (被管节点) │
│ - 无需安装 Ansible │
│ - 需要 Python 环境 │
│ - 接受 SSH 连接 │
└─────────────────────────────────────────────────────────┘

Inventory (清单) → 定义被管节点分组
Playbook (剧本) → 定义任务执行顺序
Module (模块) → 执行具体操作
Task (任务) → 调用模块完成单一操作
Role (角色) → 可复用的任务集合

2.2 目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ansible-project/
├── ansible.cfg # 配置文件
├── inventory/ # 主机清单
│ ├── production # 生产环境
│ └── staging # 测试环境
├── playbooks/ # 剧本目录
│ ├── deploy.yml
│ └── backup.yml
├── roles/ # 角色目录
│ ├── nginx/
│ ├── mysql/
│ └── common/
├── group_vars/ # 组变量
│ ├── webservers.yml
│ └── dbservers.yml
├── host_vars/ # 主机变量
│ └── web01.yml
└── files/ # 静态文件
└── nginx.conf

三、快速开始

3.1 安装 Ansible

1
2
3
4
5
6
7
8
9
10
11
12
# Ubuntu/Debian
apt update && apt install -y ansible

# CentOS/RHEL
yum install -y epel-release
yum install -y ansible

# pip 安装(推荐,版本更新)
pip3 install ansible

# 验证安装
ansible --version

3.2 配置 SSH 免密

1
2
3
4
5
6
7
8
# 生成 SSH 密钥
ssh-keygen -t ed25519 -C "ansible@control"

# 分发公钥到目标机器
ssh-copy-id user@target-server

# 测试连接
ssh user@target-server

3.3 创建 Inventory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# inventory/production

[webservers]
web01.example.com ansible_user=deploy
web02.example.com ansible_user=deploy

[dbservers]
db01.example.com ansible_user=deploy
db02.example.com ansible_user=deploy

[loadbalancers]
lb01.example.com ansible_user=deploy

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

3.4 第一个 Playbook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# playbooks/hello.yml
---
- name: Hello World Playbook
hosts: all
become: true
gather_facts: true

tasks:
- name: Print system information
debug:
msg: "Hostname: {{ ansible_hostname }}, OS: {{ ansible_distribution }}"

- name: Ensure NTP is installed
apt:
name: ntp
state: present
update_cache: yes

- name: Start NTP service
service:
name: ntp
state: started
enabled: yes

3.5 执行 Playbook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 语法检查
ansible-playbook playbooks/hello.yml --syntax-check

# dry-run(检查会做什么但不执行)
ansible-playbook playbooks/hello.yml --check

# 执行
ansible-playbook playbooks/hello.yml -i inventory/production

# 限制执行特定主机
ansible-playbook playbooks/hello.yml -i inventory/production -l web01.example.com

# 限制执行特定标签
ansible-playbook playbooks/hello.yml --tags "install"

四、常用模块详解

4.1 文件操作模块

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
# 复制文件
- name: Copy configuration file
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes

# 生成模板文件(支持变量)
- name: Generate config from template
template:
src: templates/app.conf.j2
dest: /etc/app/config.conf
owner: app
group: app
mode: '0640'

# 确保目录存在
- name: Create application directory
file:
path: /var/log/myapp
state: directory
owner: app
group: app
mode: '0755'

# 管理软链接
- name: Create symlink
file:
src: /opt/app/current
dest: /opt/app/latest
state: link

# 批量下载文件
- name: Download package
get_url:
url: https://example.com/app.tar.gz
dest: /tmp/app.tar.gz
checksum: sha256:abc123...

4.2 包管理模块

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
# APT (Debian/Ubuntu)
- name: Install packages
apt:
name:
- nginx
- python3-pip
- git
state: present
update_cache: yes

# YUM (CentOS/RHEL)
- name: Install packages
yum:
name:
- httpd
- mariadb-server
state: latest

# Pip
- name: Install Python packages
pip:
name:
- requests
- flask
version: ">=2.0"

# 添加 apt 仓库
- name: Add repository
apt_repository:
repo: "ppa:nginx/stable"
state: present

4.3 服务管理模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 管理系统服务
- name: Manage services
service:
name: nginx
state: restarted
enabled: yes

# systemd 服务
- name: Configure systemd service
systemd:
name: myapp
state: started
enabled: yes
daemon_reload: yes

4.4 用户与权限模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建用户
- name: Create application user
user:
name: app
group: app
shell: /bin/bash
home: /home/app
create_home: yes
system: yes

# 管理用户组
- name: Create group
group:
name: app
state: present

# 管理 sudo 权限
- name: Add user to sudoers
lineinfile:
path: /etc/sudoers.d/app
line: 'app ALL=(ALL) NOPASSWD: ALL'
create: yes
mode: '0440'

4.5 命令执行模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 执行简单命令
- name: Check disk space
command: df -h
register: disk_info
changed_when: false

# 执行 shell 命令(支持管道)
- name: Get memory usage
shell: free -m | grep Mem
register: mem_info
changed_when: false

# 条件执行
- name: Restart if config changed
service:
name: nginx
state: restarted
when: nginx_config.changed

五、变量与模板

5.1 变量定义

1
2
3
4
5
6
7
8
9
10
11
12
# group_vars/webservers.yml
---
nginx_port: 80
nginx_worker_processes: 4
nginx_worker_connections: 1024
app_domain: "www.example.com"
ssl_enabled: true

# host_vars/web01.yml
---
nginx_worker_processes: 8
server_role: "primary"

5.2 Jinja2 模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};

events {
worker_connections {{ nginx_worker_connections }};
}

http {
server {
listen {{ nginx_port }};
server_name {{ app_domain }};

{% if ssl_enabled %}
listen 443 ssl;
ssl_certificate /etc/ssl/certs/{{ app_domain }}.crt;
ssl_certificate_key /etc/ssl/private/{{ app_domain }}.key;
{% endif %}

location / {
proxy_pass http://127.0.0.1:8080;
}
}
}

5.3 变量优先级

1
2
3
4
5
6
1. 命令行变量 (-e "var=value")          # 最高优先级
2. Playbook 中的 vars
3. host_vars/ 主机变量
4. group_vars/ 组变量
5. inventory 文件中定义
6. role defaults # 最低优先级

六、Roles 最佳实践

6.1 Role 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
roles/
└── nginx/
├── defaults/
│ └── main.yml # 默认变量
├── vars/
│ └── main.yml # 角色变量
├── tasks/
│ └── main.yml # 任务列表
├── handlers/
│ └── main.yml # 处理器
├── templates/
│ └── *.j2 # 模板文件
├── files/
│ └── * # 静态文件
├── meta/
│ └── main.yml # 角色元数据
└── tests/
└── test.yml # 测试用例

6.2 创建 Nginx Role

1
2
3
4
5
6
7
# roles/nginx/defaults/main.yml
---
nginx_version: "stable"
nginx_port: 80
nginx_root: /var/www/html
nginx_user: www-data
nginx_ssl: false
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
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
tags: install

- name: Create web root
file:
path: "{{ nginx_root }}"
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: '0755'
tags: config

- name: Deploy Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c %s'
notify: restart nginx
tags: config

- name: Ensure Nginx is running
service:
name: nginx
state: started
enabled: yes
tags: service
1
2
3
4
5
6
# roles/nginx/handlers/main.yml
---
- name: restart nginx
service:
name: nginx
state: restarted

6.3 使用 Role

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# playbooks/deploy-web.yml
---
- name: Deploy Web Servers
hosts: webservers
become: true

roles:
- common
- nginx
- app-deploy

tasks:
- name: Custom task after roles
debug:
msg: "Deployment completed on {{ ansible_hostname }}"

七、实战案例

7.1 MySQL 主从复制部署

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
44
45
46
47
48
49
50
51
# playbooks/mysql-replication.yml
---
- name: Configure MySQL Master
hosts: mysql_master
become: true

vars:
mysql_root_password: "{{ vault_mysql_root_password }}"
replication_user: "repl"
replication_password: "{{ vault_replication_password }}"

roles:
- mysql

tasks:
- name: Create replication user
mysql_user:
name: "{{ replication_user }}"
host: "%"
password: "{{ replication_password }}"
priv: "*.*:REPLICATION SLAVE"
state: present

- name: Get master status
mysql_replication:
mode: getmaster
register: master_status

- name: Configure MySQL Slave
hosts: mysql_slave
become: true

vars:
master_host: "{{ groups['mysql_master'][0] }}"

roles:
- mysql

tasks:
- name: Configure slave replication
mysql_replication:
mode: changeprimary
primary_host: "{{ master_host }}"
primary_user: repl
primary_password: "{{ vault_replication_password }}"
primary_log_file: "{{ hostvars[master_host]['master_status']['File'] }}"
primary_log_pos: "{{ hostvars[master_host]['master_status']['Position'] }}"

- name: Start slave
mysql_replication:
mode: startslave

7.2 批量服务器安全加固

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# playbooks/hardening.yml
---
- name: Security Hardening
hosts: all
become: true
gather_facts: true

tasks:
- name: Disable root SSH login
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
notify: restart sshd

- name: Disable password authentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PasswordAuthentication"
line: "PasswordAuthentication no"
notify: restart sshd

- name: Configure firewall
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "22"
- "80"
- "443"

- name: Enable UFW
ufw:
state: enabled
policy: deny

- name: Install security updates
apt:
upgrade: security
update_cache: yes

- name: Configure fail2ban
apt:
name: fail2ban
state: present

- name: Enable fail2ban
service:
name: fail2ban
state: started
enabled: yes

handlers:
- name: restart sshd
service:
name: sshd
state: restarted

7.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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# playbooks/deploy-app.yml
---
- name: Deploy Application
hosts: webservers
become: true
serial: 1 # 逐台部署,避免全部中断

vars:
app_name: myapp
app_version: "{{ lookup('env', 'APP_VERSION') | default('latest') }}"
deploy_user: deploy

pre_tasks:
- name: Disable load balancer
command: /usr/bin/disable-in-lb {{ ansible_hostname }}
delegate_to: localhost

tasks:
- name: Pull latest code
git:
repo: "git@gitlab.com:company/{{ app_name }}.git"
dest: "/opt/{{ app_name }}/repo"
version: "{{ app_version }}"
become_user: "{{ deploy_user }}"

- name: Install dependencies
pip:
requirements: "/opt/{{ app_name }}/repo/requirements.txt"
virtualenv: "/opt/{{ app_name }}/venv"
become_user: "{{ deploy_user }}"

- name: Run database migrations
command: /opt/{{ app_name }}/venv/bin/python manage.py migrate
become_user: "{{ deploy_user }}"
environment:
DJANGO_SETTINGS_MODULE: config.production

- name: Collect static files
command: /opt/{{ app_name }}/venv/bin/python manage.py collectstatic --noinput
become_user: "{{ deploy_user }}"

- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes

post_tasks:
- name: Health check
uri:
url: "http://localhost:8080/health"
status_code: 200
timeout: 30
register: health_check
retries: 5
delay: 10
until: health_check.status == 200

- name: Enable load balancer
command: /usr/bin/enable-in-lb {{ ansible_hostname }}
delegate_to: localhost
when: health_check.status == 200

八、Ansible Vault 密钥管理

8.1 创建加密文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建加密文件
ansible-vault create secrets.yml

# 加密现有文件
ansible-vault encrypt secrets.yml

# 查看加密文件
ansible-vault view secrets.yml

# 编辑加密文件
ansible-vault edit secrets.yml

# 解密文件
ansible-vault decrypt secrets.yml

8.2 在 Playbook 中使用

1
2
3
4
5
6
7
8
# 方式 1:运行时提供密码
ansible-playbook deploy.yml --ask-vault-pass

# 方式 2:使用密码文件
ansible-playbook deploy.yml --vault-password-file ~/.vault_pass

# 方式 3:多个 vault 密码
ansible-playbook deploy.yml --vault-id dev@~/.vault_dev --vault-id prod@~/.vault_prod
1
2
3
4
5
6
7
8
9
10
# playbook 引用 vault 变量
---
- hosts: all
vars_files:
- secrets.yml # 加密文件

tasks:
- name: Use secret variable
debug:
msg: "DB Password is {{ db_password }}"

九、性能优化

9.1 并行控制

1
2
3
4
5
6
7
8
9
10
11
12
13
# ansible.cfg
[defaults]
# 默认并发数
forks = 20

# 超时设置
timeout = 30

# 启用 pipelining 减少 SSH 连接
pipelining = True

# SSH 优化
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

9.2 事实缓存

1
2
3
4
5
6
7
8
9
10
11
# 启用事实缓存减少 gather_facts 开销
[defaults]
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

# 对于不需要事实的 playbook
- hosts: all
gather_facts: no
tasks:
- ...

9.3 策略插件

1
2
3
4
5
# 使用 free 策略(不等待其他主机完成)
- hosts: all
strategy: free
tasks:
- ...

十、调试与排错

10.1 调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
# 详细输出
ansible-playbook deploy.yml -v
ansible-playbook deploy.yml -vvv # 非常详细

# 只运行特定任务
ansible-playbook deploy.yml --start-at-task="Install Nginx"

# 列出所有任务
ansible-playbook deploy.yml --list-tasks

# 列出所有主机
ansible-playbook deploy.yml --list-hosts

10.2 常用调试模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- name: Debug variable
debug:
var: my_variable

- name: Debug message
debug:
msg: "Current user: {{ ansible_user }}"

- name: Pause for debugging
pause:
prompt: "Check the server and press Enter to continue"

- name: Assert condition
assert:
that:
- service_status.status == "running"
fail_msg: "Service is not running!"

十一、总结

Ansible 是自动化运维的利器,掌握以下关键点:

  1. 理解幂等性 - 确保多次执行结果一致
  2. 合理使用 Roles - 提高代码复用性
  3. 变量管理 - 灵活运用不同层级的变量
  4. 密钥安全 - 使用 Vault 保护敏感信息
  5. 错误处理 - 添加适当的错误检查和回滚

学习路线建议:

  1. 基础:Ad-Hoc 命令 → 简单 Playbook
  2. 进阶:Variables → Templates → Roles
  3. 高级:Custom Modules → Plugins → Tower/AWX

参考资源: