Ansible 从入门到精通实战运维手册

Ansible 从入门到精通实战运维手册

适用版本:ansible-core 2.16+(多数内容同样适用于更新版本)
典型环境:Linux 控制节点,通过 SSH 管理 Linux 主机
原则:先保证幂等、可审计、可回滚,再追求执行速度


目录

  1. 认识 Ansible
  2. 安装与实验环境
  3. 配置文件与项目结构
  4. Inventory 主机清单
  5. Ad-hoc 临时命令
  6. Playbook 基础
  7. 变量、Facts 与优先级
  8. 条件、循环、注册变量与重试
  9. 文件、模板与 Jinja2
  10. Handlers、Blocks 与错误控制
  11. Role 工程化
  12. Collections 与 Galaxy
  13. Vault 与敏感信息
  14. Privilege Escalation 与安全
  15. 批次、委派与滚动发布
  16. Tag、Check Mode 与 Diff
  17. 生产实战一:部署 Nginx
  18. 生产实战二:滚动发布应用
  19. 生产实战三:系统基线与补丁
  20. 测试、质量门禁与 CI
  21. 性能优化
  22. 调试与故障排查
  23. AWX、Automation Controller 与执行环境
  24. 生产规范与检查清单
  25. 常用命令速查
  26. 进阶路线与练习

1. 认识 Ansible

Ansible 是无代理自动化工具。控制节点读取 Inventory 和 Playbook,通过 SSH/WinRM 连接受管节点,传输并执行模块,然后收集结果。

1
2
3
4
5
6
7
8
9
控制节点
├── Inventory:管理谁
├── Playbook:做什么
├── Variables:不同环境的参数
├── Roles/Collections:复用单元
└── Vault:敏感数据
│ SSH / WinRM

受管节点:Linux、Windows、网络设备、云服务

核心特性:

  • Agentless:受管 Linux 一般只需 SSH 与 Python。
  • 声明式与幂等:描述目标状态,重复执行不应产生额外变化。
  • Push 模式:由控制端主动执行,便于集中审计。
  • 模块化:优先使用模块,不把自动化退化为 Shell 脚本集合。

术语:

术语 含义
Control node 安装并运行 Ansible 的控制节点
Managed node 被管理主机
Inventory 主机及分组清单
Module 执行具体动作的单元,如 packageservice
Task 一次模块调用
Play 对一组主机执行的一组任务
Playbook 一个或多个 Play 构成的 YAML 文件
Role 按约定目录组织的可复用自动化单元
Collection 模块、插件、Role 等内容的发布包

1.1 Ansible 适合与不适合的场景

适合:配置管理、批量变更、应用部署、补丁升级、账户与密钥管理、云资源编排、网络自动化、灾备演练。

不宜直接承担:毫秒级实时控制、持续高频状态同步、大规模数据搬运、复杂业务工作流引擎。此类场景应由专用系统负责,Ansible 用于安装、配置和编排它们。


2. 安装与实验环境

2.1 控制节点要求

  • Linux 或 WSL;Windows 原生环境通常使用 WSL。
  • Python 3.10+。
  • 能通过 SSH 到达受管主机。
  • 受管 Linux 需可用的 Python;少数初始化任务可用 raw

2.2 使用虚拟环境安装

1
2
3
4
5
6
7
8
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "ansible-core>=2.16,<2.18" ansible-lint

ansible --version
ansible-playbook --version
ansible-lint --version

生产环境应固定版本:

1
2
3
# requirements.txt
ansible-core==2.17.7
ansible-lint==24.12.2
1
python -m pip install -r requirements.txt

不要在同一项目中依赖“机器上碰巧安装的版本”。控制节点、CI 和 AWX 应使用一致版本。

2.3 SSH 准备

1
2
3
ssh-keygen -t ed25519 -C "ansible-control"
ssh-copy-id ops@192.168.56.11
ssh ops@192.168.56.11

推荐为自动化使用独立账户,并通过最小化的 sudo 规则授权。不要把个人账户和自动化身份混用。

2.4 首次连通

创建最小 Inventory:

1
2
3
4
5
6
7
8
# inventory.ini
[web]
web01 ansible_host=192.168.56.11
web02 ansible_host=192.168.56.12

[all:vars]
ansible_user=ops
ansible_python_interpreter=/usr/bin/python3

测试:

1
ansible -i inventory.ini all -m ansible.builtin.ping

ping 模块不是 ICMP Ping;它验证 SSH、Python 和 Ansible 模块执行链路。网络连通只能说明目标端口可达,不能替代该测试。

受管机尚无 Python 时:

1
2
3
ansible -i inventory.ini all -m ansible.builtin.raw \
-a 'test -e /usr/bin/python3 || (apt-get update && apt-get install -y python3)' \
--become

3. 配置文件与项目结构

3.1 配置查找顺序

Ansible 使用找到的第一个配置文件:

  1. ANSIBLE_CONFIG 环境变量指定的文件
  2. 当前目录的 ansible.cfg
  3. ~/.ansible.cfg
  4. /etc/ansible/ansible.cfg

检查实际配置:

1
2
3
ansible --version
ansible-config dump --only-changed
ansible-config view

3.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
ansible-project/
├── ansible.cfg
├── requirements.yml
├── inventories/
│ ├── dev/
│ │ ├── hosts.yml
│ │ ├── group_vars/
│ │ │ ├── all.yml
│ │ │ └── web.yml
│ │ └── host_vars/
│ └── prod/
│ ├── hosts.yml
│ ├── group_vars/
│ └── host_vars/
├── playbooks/
│ ├── site.yml
│ ├── deploy.yml
│ └── patch.yml
├── roles/
│ ├── common/
│ └── nginx/
├── files/
├── templates/
└── .ansible-lint

示例 ansible.cfg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[defaults]
inventory = inventories/dev/hosts.yml
roles_path = roles
collections_path = collections
host_key_checking = True
retry_files_enabled = False
forks = 20
timeout = 20
interpreter_python = auto_silent
stdout_callback = default

[privilege_escalation]
become = False

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

安全提醒:

  • 生产环境不应为了方便关闭 host_key_checking
  • 预先维护 known_hosts,避免首次连接被中间人攻击。
  • pipelining=True 可提高速度,但要验证目标机 sudo 策略兼容。

4. Inventory 主机清单

4.1 YAML Inventory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# inventories/prod/hosts.yml
all:
children:
web:
hosts:
web01:
ansible_host: 10.10.1.11
web02:
ansible_host: 10.10.1.12
db:
hosts:
db01:
ansible_host: 10.10.2.11
prod:
children:
web:
db:
vars:
ansible_user: ops
ansible_python_interpreter: /usr/bin/python3

4.2 INI Inventory

1
2
3
4
5
6
7
8
9
10
11
12
[web]
web[01:02] ansible_host=10.10.1.[11:12]

[db]
db01 ansible_host=10.10.2.11

[prod:children]
web
db

[prod:vars]
ansible_user=ops

INI 中复杂变量容易产生类型歧义,生产项目更推荐 YAML 与 group_vars

4.3 分组变量和主机变量

1
2
3
4
5
# inventories/prod/group_vars/all.yml
timezone: Asia/Shanghai
ntp_servers:
- ntp1.example.com
- ntp2.example.com
1
2
3
# inventories/prod/group_vars/web.yml
nginx_worker_connections: 4096
app_port: 8080
1
2
# inventories/prod/host_vars/web01.yml
nginx_worker_connections: 8192

原则:

  • Inventory 只描述环境差异,不存放任务逻辑。
  • 主机名使用稳定资产标识,连接地址放 ansible_host
  • 密码、Token、私钥等必须通过 Vault 或外部密钥系统管理。

4.4 主机模式

1
2
3
4
5
6
ansible all --list-hosts
ansible web --list-hosts
ansible 'web:&prod' --list-hosts # 交集
ansible 'prod:!db' --list-hosts # 排除
ansible 'web[0]' --list-hosts # 分组中的第一台
ansible 'web01,db01' --list-hosts # 多台,末尾逗号也可表示主机列表

在执行变更前先用 --list-hosts 验证范围,尤其是 --limit 与复杂模式。

4.5 动态 Inventory

云环境应使用 Inventory 插件,避免手工维护易过期 IP。以 AWS 为例:

1
2
3
4
5
6
7
8
9
10
11
# inventories/prod/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- cn-north-1
filters:
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
compose:
ansible_host: private_ip_address
1
2
3
ansible-galaxy collection install amazon.aws
ansible-inventory -i inventories/prod/aws_ec2.yml --graph
ansible-inventory -i inventories/prod/aws_ec2.yml --host i-xxxxxxxx

动态 Inventory 插件配置文件名通常必须符合插件要求,例如以 aws_ec2.yml 结尾。


5. Ad-hoc 临时命令

Ad-hoc 适合查询、诊断和一次性低风险操作;需要审计、复用或回滚的变更应写入 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
# 连通测试
ansible all -m ansible.builtin.ping

# 查看系统信息
ansible all -m ansible.builtin.setup -a 'filter=ansible_distribution*'

# 执行不经过 Shell 的命令
ansible web -m ansible.builtin.command -a 'uptime'

# 查看磁盘;管道需要 shell 模块
ansible web -m ansible.builtin.shell -a 'df -h | sort -k5 -h'

# 复制文件
ansible web -m ansible.builtin.copy \
-a 'src=files/banner dest=/etc/motd owner=root group=root mode=0644' \
--become

# 安装软件包
ansible web -m ansible.builtin.package -a 'name=nginx state=present' --become

# 管理服务
ansible web -m ansible.builtin.service \
-a 'name=nginx state=started enabled=true' --become

# 并发与超时
ansible all -m ansible.builtin.command -a 'uptime' -f 30 -T 20

commandshell 的选择:

  • 默认使用 command,参数不会交给 Shell 解释。
  • 只有确实需要管道、重定向、通配符、变量展开时才用 shell
  • shell 输入中包含变量时必须正确引用;不要拼接不可信输入。
  • 系统存在对应模块时优先使用模块,如 userpackageserviceuri

6. Playbook 基础

6.1 第一个 Playbook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
- name: Configure web servers
hosts: web
become: true
gather_facts: true

tasks:
- name: Install Nginx
ansible.builtin.package:
name: nginx
state: present

- name: Start and enable Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true

执行:

1
2
3
ansible-playbook playbooks/site.yml --syntax-check
ansible-playbook playbooks/site.yml --check --diff
ansible-playbook playbooks/site.yml

6.2 YAML 要点

  • 使用空格缩进,禁止 Tab。
  • 布尔值统一写 true / false
  • 权限模式写成字符串,如 '0644',避免 YAML 数值解析差异。
  • 每个 Play 和 Task 使用清晰的 name
  • 模块名使用完全限定集合名(FQCN),如 ansible.builtin.copy

6.3 幂等性

幂等意味着执行一次和重复执行多次得到相同目标状态。第二次运行理想结果应为 changed=0

不推荐:

1
2
- name: Add configuration line
ansible.builtin.shell: echo 'vm.swappiness=10' >> /etc/sysctl.conf

推荐:

1
2
3
4
5
6
7
- name: Configure swappiness
ansible.posix.sysctl:
name: vm.swappiness
value: '10'
state: present
sysctl_set: true
reload: true

如果不得不执行命令,使用 createsremoveschanged_when 等准确描述变化:

1
2
3
4
- name: Initialize application database
ansible.builtin.command:
cmd: /opt/app/bin/init-db
creates: /var/lib/app/.initialized

7. 变量、Facts 与优先级

7.1 定义和引用变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- name: Variable example
hosts: web
vars:
app_name: demo
app_port: 8080
app_config:
log_level: info
workers: 4

tasks:
- name: Show application settings
ansible.builtin.debug:
msg: "{{ app_name }} listens on {{ app_port }}"

- name: Show worker count
ansible.builtin.debug:
var: app_config.workers

仅由变量构成的值应保留原始类型:

1
enabled: "{{ feature_enabled }}"

不要为了“看起来像字符串”在变量内部再次嵌套 {{ }}

7.2 常见变量来源

  • Role defaults:roles/x/defaults/main.yml,适合可被覆盖的默认值。
  • Inventory 的 group_varshost_vars
  • Play 的 varsvars_files
  • Role vars:roles/x/vars/main.yml,优先级高,不适合普通默认配置。
  • Task/block vars。
  • 注册变量和 set_fact
  • 命令行 -e 额外变量,优先级最高。

完整优先级规则较多,遇到冲突应以当前版本官方文档和下面命令为准:

1
2
ansible-inventory -i inventories/prod/hosts.yml --host web01
ansible-config dump

实践建议:同一个业务变量尽量只在 Role defaults、环境 group vars、主机特例三个层次定义。不要依赖复杂优先级“技巧”。

7.3 Facts

1
2
3
4
5
6
- name: Print selected facts
ansible.builtin.debug:
msg: >-
{{ inventory_hostname }} runs
{{ ansible_facts.distribution }}
{{ ansible_facts.distribution_version }}

查看 Facts:

1
2
ansible vm101 -m ansible.builtin.setup
ansible vm101 -m ansible.builtin.setup -a 'filter=ansible_default_ipv4'

Facts 成本较高。不需要时可关闭:

1
2
3
- name: API-only operation
hosts: localhost
gather_facts: false

自定义本地 Facts 可放在受管机 /etc/ansible/facts.d/*.fact,通过 ansible_local 访问。

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
一、两种格式本地 Facts
路径标准:/etc/ansible/facts.d/,目录不存在需要手动创建
1)静态文件(JSON 格式,.fact
文件:/etc/ansible/facts.d/app_info.fact
json
{
"env": "prod",
"zone": "yun-nan",
"service_name": "gateway",
"version": "2.1.0"
}
2)可执行脚本(Shell/Python,输出 JSON,.fact
ansible 会自动执行脚本,捕获 stdout JSON 作为 fact
示例 /etc/ansible/facts.d/disk_status.fact
bash
#!/bin/bash
cat <<EOF
{
"root_used_pct": $(df -P / | awk 'NR>1 {print $5}' | sed 's/%//'),
"hostname": "$(hostname)"
}
EOF
授权:
bash
chmod +x /etc/ansible/facts.d/disk_status.fact
⚠️ 重要约束
脚本必须输出标准 JSON,不能有多余日志;
必须有执行权限;
文件名后缀必须 .fact
二、如何读取自定义 facts
所有本地 facts 会收纳在变量:ansible_local
查看验证命令
bash
# 抓取所有local facts
ansible test-node -m setup -a 'filter=ansible_local'
假设文件 app_info.fact,取值方式:
jinja2
# jinja2模板 / playbook中
{{ ansible_local.app_info.env }}
{{ ansible_local.app_info.service_name }}

# 脚本fact disk_status.fact
{{ ansible_local.disk_status.root_used_pct }}

7.4 默认值、必填值和省略参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name: Use safe defaults
ansible.builtin.debug:
msg: "{{ optional_name | default('unknown') }}"

- name: Require a value
ansible.builtin.assert:
that:
- app_version is defined
- app_version | length > 0
fail_msg: app_version must be set

- name: Optionally set shell
ansible.builtin.user:
name: "{{ user_name }}"
shell: "{{ user_shell | default(omit) }}"

8. 条件、循环、注册变量与重试

8.1 条件

1
2
3
4
5
6
7
8
9
10
11
12
- name: Install package on Debian family
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
when: ansible_facts.os_family == 'Debian'

- name: Install package on Red Hat family
ansible.builtin.dnf:
name: nginx
state: present
when: ansible_facts.os_family == 'RedHat'

when 表达式不加 {{ }}

8.2 循环

1
2
3
4
5
6
7
8
9
- name: Install base packages
ansible.builtin.package:
name: "{{ base_packages }}"
state: present
vars:
base_packages:
- curl
- jq
- vim

模块支持列表时,一次传列表通常比逐项循环更快。

复杂循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- name: Create service users
ansible.builtin.user:
name: "{{ account.name }}"
uid: "{{ account.uid }}"
groups: "{{ account.groups | join(',') }}"
append: true
state: present
loop:
- name: app
uid: 2101
groups: [app, logs]
- name: deploy
uid: 2102
groups: [app]
loop_control:
loop_var: account # 默认循环变量名叫 item,这里重命名循环变量为 account。
label: "{{ account.name }}" # 日志精简关键配置,如果不加 label,Ansible 执行时会把整条字典(name/uid/groups)完整打印到屏幕,日志很长。设置 label 后,任务日志只会展示 account.name,输出简化:ok: [xxx] => (item=app)

8.3 注册结果

1
2
3
4
5
6
7
8
9
- name: Query service status
ansible.builtin.command: systemctl is-active myapp
register: app_status
changed_when: false
failed_when: app_status.rc not in [0, 3]

- name: Show status
ansible.builtin.debug:
var: app_status.stdout

常见返回字段包括 rcstdoutstderrchangedfailed。循环注册结果位于 result.results

8.4 等待与重试

1
2
3
4
5
6
7
8
9
10
11
- name: Wait for application health check
ansible.builtin.uri:
url: "http://127.0.0.1:{{ app_port }}/health"
status_code: 200
return_content: true
register: health
retries: 12
delay: 5
until:
- health.status == 200
- health.content is search('UP')

等待端口:

1
2
3
4
5
6
- name: Wait for TCP port
ansible.builtin.wait_for:
host: 127.0.0.1
port: "{{ app_port }}"
delay: 2
timeout: 60

9. 文件、模板与 Jinja2

9.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
- name: Create application directory
ansible.builtin.file:
path: /etc/myapp
state: directory
owner: root
group: myapp
mode: '0750'

- name: Copy static file
ansible.builtin.copy:
src: myapp.service
dest: /etc/systemd/system/myapp.service
owner: root
group: root
mode: '0644'
validate: /usr/bin/systemd-analyze verify %s # 这个验证可以是不同的程序
notify:
- Reload systemd
- Restart myapp

# 只有 copy 模块发生文件变更(changed=true)时,才会触发 handler
# 文件内容 / 权限 / 属主和远端一致 → 无变更 → handler 不执行
# 文件被更新 → 触发两个 handler
# handler 默认在当前 play 所有 task 执行完毕后统一运行,不是立刻执行
# 多条 notify 按顺序执行;同一个 handler 被多次 notify,只会运行一次

copysrc 默认在控制端;remote_src: true 表示源文件已经在受管机。

9.2 Jinja2 模板

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# templates/nginx.conf.j2
# Managed by Ansible. Manual changes will be overwritten.
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes | default('auto') }};

events {
worker_connections {{ nginx_worker_connections }};
}

http {
{% for backend in app_backends %}
upstream {{ backend.name }} {
{% for server in backend.servers %}
server {{ server.host }}:{{ server.port }};
{% endfor %}
}
{% endfor %}
}

任务:

1
2
3
4
5
6
7
8
9
10
- name: Render Nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
validate: /usr/sbin/nginx -t -c %s
notify: Reload Nginx

validate 会在替换正式文件前校验临时文件,是配置变更的重要防线。

9.3 常用过滤器

1
2
3
4
5
6
normalized_name: "{{ app_name | lower | replace(' ', '-') }}"
unique_ports: "{{ ports | unique | sort }}"
encoded: "{{ secret_value | b64encode }}"
json_text: "{{ payload | to_nice_json }}"
yaml_text: "{{ config | to_nice_yaml(indent=2) }}" # 配置文件不换行缩进2个字符
selected_users: "{{ users | selectattr('enabled', 'equalto', true) | list }}" # 选择enabled 等于 true的

处理未受信数据时,不要把模板输出直接拼接进 shell 命令。


10. Handlers、Blocks 与错误控制

10.1 Handlers

Handler 仅在收到通知且任务发生变化时执行,同名 Handler 每台主机每个 Play 通常只运行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
tasks:
- name: Render application configuration
ansible.builtin.template:
src: app.yml.j2 # 复制后会把模板内的变量进行替换,生成最终app.yml文件
dest: /etc/myapp/app.yml
mode: '0640'
notify: Restart myapp

handlers:
- name: Restart myapp
ansible.builtin.service:
name: myapp
state: restarted

需要立即执行已通知的 Handler(不需要等待所有的task都执行完毕采取执行handler):

1
2
- name: Flush handlers before verification
ansible.builtin.meta: flush_handlers

10.2 Block、Rescue、Always

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: Deploy with recovery
block:
# ========== 【block:正常发布流程】 ==========
- name: Install new package
ansible.builtin.package:
name: "myapp-{{ app_version }}"
state: present

- name: Verify service
ansible.builtin.uri:
url: http://127.0.0.1:8080/health
status_code: 200

rescue:
# ========== 【rescue:block 里任意任务失败才进来执行】 ==========
- name: Restore previous symlink
ansible.builtin.file:
src: "releases/{{ previous_version }}"
dest: /opt/myapp/current
state: link
force: true

- name: Stop deployment after rollback
ansible.builtin.fail:
msg: "Deployment failed on {{ inventory_hostname }}; rollback applied"

always:
# ========== 【always:不管成败,必然执行】 ==========
- name: Record deployment attempt
ansible.builtin.debug:
msg: "Deployment attempt finished on {{ inventory_hostname }}"

rescue 处理任务失败,不处理语法错误、主机不可达等所有异常。回滚逻辑必须主动演练。

10.3 失败和变化判定

1
2
3
4
5
6
7
- name: Run application migration check
ansible.builtin.command: /opt/myapp/bin/check-migration
register: migration
changed_when: "'migration required' in migration.stdout"
failed_when:
- migration.rc != 0
- "'already current' not in migration.stderr"

不要滥用:

1
ignore_errors: true

它容易掩盖真正故障。应明确允许哪些返回码,或在 rescue 中处理恢复动作。

10.4 全局失败控制

1
2
3
4
5
- name: Critical cluster change
hosts: app
serial: 20%
any_errors_fatal: true
max_fail_percentage: 10
  • any_errors_fatal:任一主机失败后终止整个 Play。
  • max_fail_percentage:当前批次失败比例超过阈值时终止。
  • 两者会显著改变发布行为,需在测试环境验证。

11. Role 工程化

11.1 创建 Role

1
ansible-galaxy role init roles/nginx
1
2
3
4
5
6
7
8
9
roles/nginx/
├── defaults/main.yml # 可覆盖默认值
├── files/ # 静态文件
├── handlers/main.yml # Handlers 任务通知执行
├── meta/main.yml # 元数据和依赖
├── tasks/main.yml # 任务入口
├── templates/ # Jinja2 模板
├── tests/ # 简单测试入口
└── vars/main.yml # 高优先级内部变量

11.2 最小 Role

1
2
3
4
# roles/nginx/defaults/main.yml
nginx_package_name: nginx
nginx_service_name: nginx
nginx_worker_connections: 2048
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
ansible.builtin.package:
name: "{{ nginx_package_name }}"
state: present

- name: Render Nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: /usr/sbin/nginx -t -c %s
notify: Reload Nginx

- name: Enable and start Nginx
ansible.builtin.service:
name: "{{ nginx_service_name }}"
state: started
enabled: true
1
2
3
4
5
6
# roles/nginx/handlers/main.yml
---
- name: Reload Nginx
ansible.builtin.service:
name: "{{ nginx_service_name }}"
state: reloaded

引用 Role:

1
2
3
4
5
6
- name: Configure web tier
hosts: web
become: true
roles:
- role: nginx
tags: [nginx]

11.3 Role 设计原则

  • 一个 Role 负责一个清晰职责。
  • 用户可配置项放 defaults/main.yml,内部常量才放 vars/main.yml
  • 变量加 Role 前缀,如 nginx_worker_connections,避免冲突。
  • Role 不应偷偷依赖 Inventory 中未说明的魔法变量。
  • 在 README 中记录输入变量、默认值、支持平台、依赖和示例。
  • 一个很短且只使用一次的 Playbook 不必强行拆成 Role。

11.4 include 与 import

1
2
3
4
5
- name: Import static task list
ansible.builtin.import_tasks: install.yml

- name: Include task list dynamically
ansible.builtin.include_tasks: "{{ ansible_facts.os_family | lower }}.yml"
  • import_* 在解析阶段静态展开,适合结构固定的内容。
  • include_* 在执行阶段动态加载,可基于运行时变量选择。
  • Tag 和条件在两者上的传播行为不同,使用前应以小例验证。

12. Collections 与 Galaxy

Collection 提供模块、插件、Role 和 Playbook。依赖应进入版本控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Galaxy = 仓库 / 平台(商店)
# Collections = 软件包(商品)
##### 日常最常用集合举例
community.general:通用大量模块(files、command、sysctl、各种零散模块)
community.docker:docker 相关模块
kubernetes.core:K8s 模块
ansible.posix:posix 系统模块(mount、firewalld、sysctl 等)
####
# requirements.yml
---
collections:
- name: ansible.posix
version: 1.6.2
- name: community.general
version: 9.5.0

roles:
- name: geerlingguy.docker
version: 7.4.3

安装:

1
2
3
ansible-galaxy collection install -r requirements.yml
ansible-galaxy role install -r requirements.yml
ansible-galaxy collection list

查看文档:

1
2
3
ansible-doc ansible.builtin.copy
ansible-doc -l
ansible-doc -t inventory amazon.aws.aws_ec2

生产要求:

  • 固定依赖版本并评审升级变更。
  • 内网环境使用私有 Automation Hub/Galaxy 镜像或离线包。
  • 外部 Role 在引入前做源码、安全和许可证审查。
  • Playbook 中使用 FQCN,避免同名模块冲突。

13. Vault 与敏感信息

13.1 加密变量文件

1
2
3
4
ansible-vault create inventories/prod/group_vars/all/vault.yml
ansible-vault edit inventories/prod/group_vars/all/vault.yml
ansible-vault view inventories/prod/group_vars/all/vault.yml
ansible-vault rekey inventories/prod/group_vars/all/vault.yml

文件内容在解密后可为:

1
2
vault_db_password: change-me
vault_api_token: change-me

普通变量文件映射业务变量:

1
2
3
# inventories/prod/group_vars/all/main.yml
db_password: "{{ vault_db_password }}"
api_token: "{{ vault_api_token }}"

执行:

1
ansible-playbook playbooks/site.yml --ask-vault-pass

13.2 Vault ID

多个环境或密钥来源:

1
2
3
4
5
6
ansible-vault encrypt --vault-id dev@prompt inventories/dev/group_vars/all/vault.yml
ansible-vault encrypt --vault-id prod@prompt inventories/prod/group_vars/all/vault.yml

ansible-playbook playbooks/site.yml \
--vault-id dev@prompt \
--vault-id prod@/secure/path/prod-vault-password

13.3 防止日志泄密

1
2
3
4
5
6
7
8
- name: Configure database password
ansible.builtin.template:
src: database.yml.j2
dest: /etc/myapp/database.yml
owner: root
group: myapp
mode: '0640'
no_log: true

no_log 会降低排障信息,应只用于真正包含敏感数据的任务。Vault 保护的是静态文件,变量解密后仍可能进入日志、命令行参数、临时文件或目标配置。

更成熟的环境应使用 HashiCorp Vault、云 Secret Manager、CyberArk 等外部密钥系统,通过 lookup 插件按需读取并实施短期凭据与轮换。


14. Privilege Escalation 与安全

14.1 become

1
2
3
4
5
6
7
8
9
- name: Configure system service
hosts: app
become: true
become_user: root
tasks:
- name: Install package
ansible.builtin.package:
name: myapp
state: present

命令行:

1
ansible-playbook playbooks/site.yml --become --ask-become-pass

不要在整个 Play 上启用 become,除非所有任务确实需要。应用 API 调用、健康检查等可用普通身份执行。

14.2 最小权限 sudoers 示例

1
2
3
4
5
6
# /etc/sudoers.d/ansible-ops
Defaults:ops !requiretty
ops ALL=(root) /usr/bin/systemctl restart myapp, \
/usr/bin/systemctl reload nginx, \
/usr/bin/apt-get, \
/usr/bin/dnf

实际模块可能调用临时脚本或其他程序,严格命令白名单需结合执行模型验证。更常见的生产做法是限制控制节点来源、SSH 密钥、目标主机范围和自动化入口,同时审计 sudo。

14.3 安全基线

  • 自动化账号禁用密码登录,使用专用短期证书或受管 SSH 密钥。
  • 使用 known_hosts 校验服务端身份。
  • CI 密钥不落盘,不输出到日志。
  • Inventory、Vault 密文、Playbook 全部纳入版本控制;Vault 密码不能入库。
  • 控制节点限制登录,执行日志集中留存。
  • 第三方 Collection 固定版本并进行供应链审查。
  • 对危险变量使用 assert 建立前置条件。
1
2
3
4
5
6
- name: Validate deployment target
ansible.builtin.assert:
that:
- deploy_environment in ['dev', 'staging', 'prod']
- app_version is match('^[0-9]+\\.[0-9]+\\.[0-9]+$')
fail_msg: Invalid deployment parameters

15. 批次、委派与滚动发布

15.1 serial

1
2
3
4
5
6
7
8
- name: Rolling update
hosts: app
serial: 2
max_fail_percentage: 0
tasks:
- name: Update application
ansible.builtin.include_role:
name: myapp

逐步扩大批次:

1
2
3
4
5
serial:
- 1
- 10%
- 25%
- 100%

先发布一台金丝雀,再逐步扩大范围。批次大小应小于负载均衡和容量允许的同时离线节点数。

15.2 delegate_to

1
2
3
4
5
6
7
8
9
- name: Remove node from load balancer
ansible.builtin.uri:
url: "https://lb-api.example.com/nodes/{{ inventory_hostname }}"
method: DELETE
headers:
Authorization: "Bearer {{ lb_api_token }}"
status_code: [200, 204, 404]
delegate_to: localhost
no_log: true

委派(把当前的task换到另外的主机上去执行)任务仍处于当前 Inventory 主机上下文。变量来自谁、结果注册到谁必须明确。

15.3 run_once

1
2
3
4
- name: Run database migration once
ansible.builtin.command: /opt/myapp/bin/migrate
run_once: true
delegate_to: "{{ groups['app'][0] }}"

注意:配合 serial 时,run_once 可能对每个批次执行一次。真正全局只运行一次的迁移更适合独立 Play,并明确指定执行节点。

15.4 strategy 与 throttle

1
2
3
4
5
6
7
8
9
10
11
- name: Independent checks
hosts: all
strategy: free
tasks:
- name: Collect service status
ansible.builtin.service_facts:

- name: Call rate-limited API
ansible.builtin.uri:
url: https://api.example.com/check
throttle: 3
  • 默认 linear:同一批主机大致按任务同步推进。
  • free:每台主机独立推进,适合互不依赖的任务。
  • throttle:限制某任务/Block 的并发数。

有共享状态、集群仲裁或严格发布顺序时,不要随意使用 free


16. Tag、Check Mode 与 Diff

16.1 Tags

1
2
3
4
5
6
7
- name: Install packages
ansible.builtin.import_tasks: install.yml
tags: [install]

- name: Configure service
ansible.builtin.import_tasks: configure.yml
tags: [config]
1
2
3
ansible-playbook playbooks/site.yml --list-tags
ansible-playbook playbooks/site.yml --tags config
ansible-playbook playbooks/site.yml --skip-tags install

保留标签:

  • always:默认总执行,除非显式跳过。
  • never:默认不执行,只有显式选择时执行,适合高风险维护动作。

16.2 Check Mode

1
ansible-playbook playbooks/site.yml --check --diff --limit web01

Check Mode 是预测,不是事务:

  • 部分模块不支持或支持不完整。
  • 依赖前序任务实际产物的后续任务可能失败。
  • 外部 API、命令和脚本未必能可靠预测变化。
  • --diff 可能暴露敏感配置,CI 日志需谨慎。

个别任务控制:

1
2
3
4
- name: Read-only check should always run
ansible.builtin.command: /opt/myapp/bin/status
check_mode: false
changed_when: false

生产发布推荐顺序:

  1. --syntax-check
  2. ansible-lint
  3. 测试环境执行
  4. 生产 --check --diff --limit 小范围预演
  5. 生产金丝雀实际执行
  6. 扩大批次并观察监控

17. 生产实战一:部署 Nginx

17.1 变量

1
2
3
4
5
6
7
8
9
# inventories/prod/group_vars/web.yml
nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 4096
nginx_server_name: app.example.com
nginx_listen_port: 80
app_upstream_servers:
- 10.10.3.11:8080
- 10.10.3.12:8080

17.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
# roles/nginx/templates/nginx.conf.j2
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};

events {
worker_connections {{ nginx_worker_connections }};
}

http {
include /etc/nginx/mime.types;

upstream app_backend {
{% for server in app_upstream_servers %}
server {{ server }} max_fails=3 fail_timeout=10s;
{% endfor %}
keepalive 32;
}

server {
listen {{ nginx_listen_port }};
server_name {{ nginx_server_name }};

access_log /var/log/nginx/app.access.log;
error_log /var/log/nginx/app.error.log warn;

location /healthz {
access_log off;
return 200 "ok\n";
}

location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
}
}
}

17.3 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
32
33
34
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
ansible.builtin.package:
name: nginx
state: present

- name: Deploy Nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
validate: /usr/sbin/nginx -t -c %s
notify: Reload Nginx

- name: Enable and start Nginx
ansible.builtin.service:
name: nginx
enabled: true
state: started

- name: Flush configuration handler
ansible.builtin.meta: flush_handlers

- name: Verify local health endpoint
ansible.builtin.uri:
url: "http://127.0.0.1:{{ nginx_listen_port }}/healthz"
status_code: 200
return_content: true
register: nginx_health
failed_when: nginx_health.status != 200 or nginx_health.content != 'ok\n'

17.4 Handler

1
2
3
4
5
6
# roles/nginx/handlers/main.yml
---
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded

17.5 Playbook

1
2
3
4
5
6
7
8
9
# playbooks/nginx.yml
---
- name: Configure Nginx servers
hosts: web
become: true
serial: 25%
max_fail_percentage: 0
roles:
- nginx

验证:

1
2
3
4
5
6
ansible-playbook playbooks/nginx.yml --syntax-check
ansible-lint playbooks/nginx.yml roles/nginx
ansible-playbook playbooks/nginx.yml --check --diff --limit web01
ansible-playbook playbooks/nginx.yml --limit web01
ansible-playbook playbooks/nginx.yml
ansible-playbook playbooks/nginx.yml # 第二次应尽量 changed=0

18. 生产实战二:滚动发布应用

假设制品已由 CI 构建并发布到制品库;Ansible 负责下载、校验、切换软链接、重启和验证,不在生产主机现场编译。

18.1 发布变量

1
2
3
4
5
6
7
app_name: myapp
app_version: 2.4.1
app_root: /opt/myapp
app_release_dir: "{{ app_root }}/releases/{{ app_version }}"
app_artifact_url: "https://artifacts.example.com/myapp/{{ app_version }}/myapp.tar.gz"
app_artifact_checksum: "sha256:0123456789abcdef..."
app_health_url: http://127.0.0.1:8080/health

版本号和校验值应来自已批准的发布单或 CI 产物元数据,不能使用不稳定的 latest

18.2 滚动发布 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
---
- name: Rolling deploy application
hosts: app
become: true
serial:
- 1
- 25%
- 100%
max_fail_percentage: 0

pre_tasks:
- name: Validate release inputs
ansible.builtin.assert:
that:
- app_version is match('^[0-9]+\\.[0-9]+\\.[0-9]+$')
- app_artifact_checksum is match('^sha256:[0-9a-f]{64}$')

- name: Disable node in load balancer
ansible.builtin.uri:
url: "{{ lb_api_url }}/nodes/{{ inventory_hostname }}/disable"
method: POST
headers:
Authorization: "Bearer {{ lb_api_token }}"
status_code: [200, 204]
delegate_to: localhost
become: false
no_log: true

- name: Wait for load balancer connections to drain
ansible.builtin.uri:
url: "{{ lb_api_url }}/nodes/{{ inventory_hostname }}/status"
method: GET
headers:
Authorization: "Bearer {{ lb_api_token }}"
status_code: 200
return_content: true
register: lb_node_status
retries: 24
delay: 5
until: lb_node_status.json.active_connections | int == 0
delegate_to: localhost
become: false
no_log: true

tasks:
- name: Read current release link
ansible.builtin.stat:
path: "{{ app_root }}/current"
register: current_link

- name: Remember previous release
ansible.builtin.set_fact:
previous_release: "{{ current_link.stat.lnk_source | default('') }}"

- name: Deploy release
block:
- name: Create release directory
ansible.builtin.file:
path: "{{ app_release_dir }}"
state: directory
owner: myapp
group: myapp
mode: '0750'

- name: Download verified artifact
ansible.builtin.get_url:
url: "{{ app_artifact_url }}"
dest: "{{ app_release_dir }}/myapp.tar.gz"
checksum: "{{ app_artifact_checksum }}"
owner: myapp
group: myapp
mode: '0640'

- name: Extract artifact
ansible.builtin.unarchive:
src: "{{ app_release_dir }}/myapp.tar.gz"
dest: "{{ app_release_dir }}"
remote_src: true
owner: myapp
group: myapp
creates: "{{ app_release_dir }}/bin/myapp"

- name: Point current link to new release
ansible.builtin.file:
src: "{{ app_release_dir }}"
dest: "{{ app_root }}/current"
state: link
force: true
notify: Restart myapp

- name: Apply service restart now
ansible.builtin.meta: flush_handlers

- name: Wait for healthy application
ansible.builtin.uri:
url: "{{ app_health_url }}"
status_code: 200
register: health
retries: 18
delay: 5
until: health.status == 200

rescue:
- name: Restore previous release link
ansible.builtin.file:
src: "{{ previous_release }}"
dest: "{{ app_root }}/current"
state: link
force: true
when: previous_release | length > 0

- name: Restart previous release
ansible.builtin.service:
name: myapp
state: restarted
when: previous_release | length > 0

- name: Fail this deployment batch
ansible.builtin.fail:
msg: "Release {{ app_version }} failed on {{ inventory_hostname }} and was rolled back"

post_tasks:
- name: Enable node in load balancer
ansible.builtin.uri:
url: "{{ lb_api_url }}/nodes/{{ inventory_hostname }}/enable"
method: POST
headers:
Authorization: "Bearer {{ lb_api_token }}"
status_code: [200, 204]
delegate_to: localhost
become: false
no_log: true

handlers:
- name: Restart myapp
ansible.builtin.service:
name: myapp
state: restarted

重要边界:如果部署在 rescue 后失败,普通 post_tasks 不一定按预期完成所有恢复流程。生产版本应将“重新加入负载均衡”设计为经过健康检查的显式恢复步骤,并测试 Ansible 进程中断、目标失联和控制节点故障。

18.3 数据库迁移

数据库迁移不要隐式混在每台应用主机的 Role 中。推荐独立 Play:

1
2
3
4
5
6
7
8
9
10
11
- name: Run approved database migration
hosts: migration_runner
become: true
serial: 1
tasks:
- name: Apply migration
ansible.builtin.command:
cmd: "/opt/myapp/releases/{{ app_version }}/bin/migrate"
register: migration
changed_when: "'applied' in migration.stdout"
tags: [never, migrate]

执行时显式启用:

1
ansible-playbook playbooks/deploy.yml --tags migrate -e app_version=2.4.1

迁移应具备备份、兼容窗口、锁策略和独立回滚方案。应用二进制回滚不等于数据库结构回滚。


19. 生产实战三:系统基线与补丁

19.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
42
43
44
---
- name: Apply Linux baseline
hosts: linux
become: true

tasks:
- name: Set timezone
community.general.timezone:
name: Asia/Shanghai

- name: Install baseline packages
ansible.builtin.package:
name:
- curl
- rsync
- chrony
state: present

- name: Enable time synchronization
ansible.builtin.service:
name: chronyd
enabled: true
state: started
when: ansible_facts.os_family == 'RedHat'

- name: Configure file descriptor limit
community.general.pam_limits:
domain: '*'
limit_type: soft
limit_item: nofile
value: '65535'

- name: Configure kernel settings
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
sysctl_set: true
reload: true
loop:
- name: net.ipv4.tcp_syncookies
value: '1'
- name: fs.file-max
value: '2097152'

基线参数必须结合内核版本、业务负载和安全标准评审,不能机械复制。

19.2 Debian/Ubuntu 安全更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- name: Apply security updates on Debian family
hosts: debian
become: true
serial: 10%
tasks:
- name: Update APT cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600

- name: Upgrade installed packages
ansible.builtin.apt:
upgrade: safe

- name: Check whether reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required

- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 900
when: reboot_required.stat.exists

19.3 RHEL 系安全更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- name: Apply security updates on Red Hat family
hosts: redhat
become: true
serial: 10%
tasks:
- name: Install security updates
ansible.builtin.dnf:
name: '*'
state: latest
security: true

- name: Check whether reboot is required
ansible.builtin.command: dnf needs-restarting -r
register: reboot_check
changed_when: false
failed_when: reboot_check.rc not in [0, 1]

- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 900
when: reboot_check.rc == 1

补丁流程至少包含:维护窗口、业务摘流、备份/快照、分批、重启、健康检查、监控观察、失败终止和回滚决策。虚拟机快照不是数据库一致性备份的替代品。


20. 测试、质量门禁与 CI

20.1 本地检查

1
2
3
4
yamllint .
ansible-playbook playbooks/site.yml --syntax-check
ansible-lint
ansible-inventory -i inventories/dev/hosts.yml --graph

.ansible-lint 示例:

1
2
3
4
5
6
---
profile: production
exclude_paths:
- .cache/
- .venv/
mock_roles: []

ansible-lint 规则不应被大面积跳过。确需跳过时,在最小范围注明原因。

20.2 幂等性测试

1
2
ansible-playbook -i inventories/test/hosts.yml playbooks/site.yml
ansible-playbook -i inventories/test/hosts.yml playbooks/site.yml

第二次运行应无变化。若必须每次变化,应明确记录原因,例如生成时间戳或轮换一次性凭据。

20.3 Molecule 思路

Molecule 可创建临时实例、执行 Role、验证结果并销毁环境。典型阶段:

1
dependency → create → prepare → converge → idempotence → verify → destroy

验证不应只检查 Playbook “没有报错”,还应检查目标状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# molecule/default/verify.yml
---
- name: Verify Nginx role
hosts: all
gather_facts: false
tasks:
- name: Request health endpoint
ansible.builtin.uri:
url: http://127.0.0.1/healthz
status_code: 200

- name: Read service facts
ansible.builtin.service_facts:

- name: Assert Nginx is running
ansible.builtin.assert:
that:
- ansible_facts.services['nginx.service'].state == 'running'

20.4 CI 流程

建议门禁:

1
2
3
4
5
6
7
8
9
10
提交
→ YAML/语法检查
→ ansible-lint
→ 单元/模板测试
→ Molecule converge + idempotence + verify
→ 测试环境部署
→ 人工审批生产参数
→ 金丝雀发布
→ 指标观察
→ 分批扩大发布

CI 中必须固定 Python、Ansible、Collection 和 Role 版本,并对实际使用的 Inventory/变量组合做验证。


21. 性能优化

21.1 首先测量

启用耗时回调(需确认当前版本与 Collection 提供情况),找出慢任务,而不是盲目调大并发。

常见瓶颈:SSH 建连、Facts 收集、包仓库、逐项循环、文件传输、串行 API、目标机 I/O。

21.2 优化手段

  1. SSH 复用与 pipelining

    1
    2
    3
    [ssh_connection]
    pipelining = True
    ssh_args = -o ControlMaster=auto -o ControlPersist=60s
  2. 合理 forks

    1
    2
    3
    4
    5
    # 每一个并发主机,在控制节点都会产生独立进程 / 线程开销
    # 默认 forks(并发数)由 ansible.cfg forks = 5 控制。

    [defaults]
    forks = 30

    并发受控制节点 CPU/内存、网络、目标资源和外部 API 限流共同约束。

  3. 减少 Facts

    1
    gather_facts: false

    或仅收集需要的子集:

    1
    2
    3
    4
    5
    gather_facts: true
    gather_subset:
    - '!all'
    - min
    - network
  4. 批量传参而非逐项循环(前提是模块支持批量传递)

    1
    2
    3
    4
    - name: Install packages in one transaction
    ansible.builtin.package:
    name: "{{ package_list }}"
    state: present
  5. 缓存 Facts

    1
    2
    3
    4
    5
    [defaults]
    gathering = smart
    fact_caching = jsonfile
    fact_caching_connection = .cache/facts
    fact_caching_timeout = 3600

    缓存会变旧,不要让关键安全决策依赖过期 Facts。

  6. 异步任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - name: Start long-running package update
    ansible.builtin.command: /usr/local/sbin/long-update
    async: 1800
    poll: 0
    register: update_job

    - name: Wait for update
    ansible.builtin.async_status:
    jid: "{{ update_job.ansible_job_id }}"
    register: update_result
    until: update_result.finished
    retries: 180
    delay: 10

异步并不自动降低目标系统负载。数据库、包仓库和共享存储仍需限流。


22. 调试与故障排查

22.1 分层定位

按以下顺序排查:

  1. 范围:Inventory 是否选中了正确主机?
  2. 网络:DNS、路由、TCP 22/WinRM 是否可达?
  3. SSH:用户、密钥、Host Key、跳板机是否正确?
  4. Python:解释器是否存在、版本是否兼容?
  5. 权限:普通用户和 become 是否有权操作目标?
  6. 变量:实际合并后的变量值是什么?
  7. 模块:模块参数、返回值和远端依赖是否正确?
  8. 业务状态:配置校验、服务日志、端口和健康检查是否正常?

22.2 常用诊断命令

1
2
3
4
5
6
7
8
9
ansible-inventory -i inventories/prod/hosts.yml --graph
ansible-inventory -i inventories/prod/hosts.yml --host web01
ansible web01 -m ansible.builtin.ping -vvvv
ansible web01 -m ansible.builtin.setup -a 'filter=ansible_python*'
ansible-playbook playbooks/site.yml --syntax-check
ansible-playbook playbooks/site.yml --list-hosts
ansible-playbook playbooks/site.yml --list-tasks
ansible-playbook playbooks/site.yml --step
ansible-playbook playbooks/site.yml --start-at-task 'Render Nginx configuration'

-vvvv 可能打印连接细节和敏感上下文,日志共享前先脱敏。

22.3 debug、assert 与 fail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name: Show safe deployment context
ansible.builtin.debug:
msg:
host: "{{ inventory_hostname }}"
version: "{{ app_version }}"
environment: "{{ deploy_environment }}"

- name: Ensure enough free space
ansible.builtin.assert:
that:
- ansible_facts.mounts
| selectattr('mount', 'equalto', '/opt')
| map(attribute='size_available')
| first > 1073741824
fail_msg: At least 1 GiB free space is required on /opt

22.4 常见故障

现象 常见原因 处理
UNREACHABLE SSH 用户/密钥、路由、Host Key 先手工 SSH,再用 -vvvv
Python not found 目标无 Python 或路径不同 raw 安装,设置解释器
Missing sudo password become 配置不完整 配置 sudo 或使用 --ask-become-pass
变量未定义 分组错误、文件位置错误、拼写错误 ansible-inventory --host 查看
YAML 解析失败 缩进、冒号、引号错误 yamllint + --syntax-check
每次都 changed 命令不幂等、模板内容波动 使用状态模块或准确的 changed_when
Handler 未执行 通知任务未 changed 或前面失败 查看 changed 状态,必要时 flush_handlers
模块找不到 Collection 未安装或名称错误 安装依赖并使用 FQCN
Check Mode 结果异常 模块不完整支持预演 查看模块文档,在隔离环境实测
模板替换后服务失败 未做语法校验 使用 validate,再通知 reload/restart

22.5 不可达主机恢复

1
2
- name: Retry after transient network recovery
ansible.builtin.meta: clear_host_errors

这只清除 Ansible 的失败状态,不会修复网络。应仅在恢复动作确实可能让主机重新可达时使用。


23. AWX、Automation Controller 与执行环境

当团队需要 RBAC、审批、凭据托管、调度、Webhook、审计和统一执行环境时,可使用 AWX 或 Red Hat Ansible Automation Platform 的 Automation Controller。

核心对象:

对象 用途
Organization/Team/User 多租户与权限
Project Git 中的自动化代码
Inventory 静态或动态主机来源
Credential SSH、Vault、云凭据等
Job Template Playbook、Inventory、凭据和参数组合
Workflow 多个 Job 的编排、审批和分支
Execution Environment 固定 Ansible 与依赖的容器镜像

23.1 执行环境

执行环境解决“本地能跑、平台不能跑”的依赖漂移问题。镜像中应固定:

  • ansible-core 版本
  • Collections
  • Python 包
  • 系统级依赖
  • 必要的 CLI 工具

execution-environment.yml 示例:

1
2
3
4
5
6
7
8
9
---
version: 3
dependencies:
galaxy: requirements.yml
python: requirements.txt
system: bindep.txt
images:
base_image:
name: quay.io/ansible/ansible-runner:stable-2.17-latest

镜像标签应在正式环境进一步固定到不可变摘要,并进行漏洞扫描和签名验证。

23.2 平台化原则

  • 普通用户只能启动批准的模板,不能读取原始凭据。
  • 生产 Inventory、凭据与 Job Template 分权管理。
  • 高风险模板加入审批节点与变更单号。
  • Survey/extra vars 建立选项约束,不接受任意命令文本。
  • 日志发送到集中平台,设置保留周期和敏感信息策略。
  • 同一份代码通过 Inventory 和变量区分环境,不复制多份 Playbook。

24. 生产规范与检查清单

24.1 编码规范

  • 所有 Task、Play、Handler 有清晰名称。
  • 使用 FQCN 模块名。
  • 权限模式使用字符串,例如 '0644'
  • 优先使用专用模块,谨慎使用 shell
  • 命令任务正确设置 changed_whenfailed_whencreates/removes
  • 配置文件通过 validate 校验后替换。
  • 变量有业务前缀,默认值与环境值职责清楚。
  • 没有无理由的 ignore_errors: true
  • 敏感任务使用 Vault/外部密钥系统和必要的 no_log
  • 第二次执行尽量为 changed=0

24.2 发布前

  • 明确目标 Inventory、主机模式和 --limit 范围。
  • 版本号、制品地址、校验和固定且可追溯。
  • YAML、语法、lint、测试和幂等检查通过。
  • 在与生产等价的环境完成验证。
  • 评估容量、批次、仲裁和最大失败比例。
  • 已准备备份、回滚步骤和回滚判据。
  • 已确认监控、告警、值班人员和维护窗口。
  • 数据库变更有独立兼容与恢复方案。
  • Check Mode 差异经过人工审核。

24.3 发布中

  • 先用 --list-hosts 再执行。
  • 金丝雀只发布一台或最小安全批次。
  • 摘流、连接排空、部署、健康检查、恢复流量顺序正确。
  • 观察错误率、延迟、吞吐、资源利用率和业务指标。
  • 任一批次异常时自动停止扩大范围。
  • 不在执行中临时修改未提交的 Playbook。

24.4 发布后

  • 全局健康检查通过。
  • 再次执行验证幂等性。
  • 记录版本、提交、操作者、时间、目标和结果。
  • 清理旧版本时保留规定数量,并确认不影响回滚。
  • 将异常和人工操作沉淀为自动检查或 Runbook。

25. 常用命令速查

Inventory

1
2
3
4
ansible-inventory --graph
ansible-inventory --list
ansible-inventory --host web01
ansible all --list-hosts

连通与信息

1
2
3
ansible all -m ansible.builtin.ping
ansible all -m ansible.builtin.setup
ansible all -m ansible.builtin.command -a 'uptime'

Playbook

1
2
3
4
5
6
7
8
9
ansible-playbook playbooks/site.yml --syntax-check
ansible-playbook playbooks/site.yml --list-hosts
ansible-playbook playbooks/site.yml --list-tasks
ansible-playbook playbooks/site.yml --check --diff
ansible-playbook playbooks/site.yml --limit web01
ansible-playbook playbooks/site.yml --tags config
ansible-playbook playbooks/site.yml --skip-tags reboot
ansible-playbook playbooks/site.yml -e app_version=2.4.1
ansible-playbook playbooks/site.yml -vvv

Vault

1
2
3
4
5
6
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault view secrets.yml
ansible-vault encrypt plain.yml
ansible-vault decrypt encrypted.yml
ansible-vault rekey secrets.yml

Galaxy 与文档

1
2
3
4
5
ansible-galaxy collection install -r requirements.yml
ansible-galaxy role install -r requirements.yml
ansible-galaxy collection list
ansible-doc ansible.builtin.template
ansible-doc -t lookup ansible.builtin.env

配置与质量

1
2
3
4
5
ansible --version
ansible-config dump --only-changed
ansible-config list
yamllint .
ansible-lint

26. 进阶路线与练习

阶段一:入门

目标:独立管理 3 台实验主机。

  1. 配置 SSH 密钥和 Inventory。
  2. 使用 pingsetupcommandpackageservice
  3. 编写安装 Nginx 的 Playbook。
  4. 加入变量、模板和 Handler。
  5. 验证第二次运行 changed=0

阶段二:熟练

目标:形成可复用、可测试的项目。

  1. 将 Nginx Playbook 重构为 Role。
  2. 创建 dev/staging/prod 三套 Inventory。
  3. 用 Vault 管理测试密码。
  4. 使用 assertblock/rescue 和配置 validate
  5. 接入 yamllintansible-lint 和 Molecule。

阶段三:生产

目标:完成可靠的滚动发布。

  1. 设计负载均衡摘流与恢复流量任务。
  2. 使用 serial 实现金丝雀和分批发布。
  3. 下载带 SHA-256 校验的固定版本制品。
  4. 实现软链接切换、健康检查和失败回滚。
  5. 演练目标失联、服务启动失败、制品损坏和控制节点中断。

阶段四:精通

目标:把自动化建设为团队平台能力。

  1. 设计 Role/Collection 边界和版本策略。
  2. 建立执行环境,固定全部依赖。
  3. 使用 AWX/Controller 建立 RBAC、审批、调度和审计。
  4. 对接 CMDB 动态 Inventory 和企业密钥系统。
  5. 建立 SLO:执行成功率、平均耗时、变更失败率、回滚耗时。
  6. 通过故障注入和恢复演练验证自动化,而不只验证成功路径。

结语

掌握 Ansible 的关键不在于记住多少模块,而在于形成一套可靠的变更方法:

1
2
3
4
5
6
7
8
9
明确目标状态
→ 缩小执行范围
→ 验证输入和前置条件
→ 使用幂等模块实施变更
→ 配置校验与健康检查
→ 分批扩大范围
→ 失败即停止并按预案恢复
→ 留下可追溯记录
→ 再次执行验证幂等性

任何进入生产的 Playbook,都应被视为软件:需要版本控制、代码评审、自动测试、依赖锁定、发布审批、监控和持续维护。


Ansible 从入门到精通实战运维手册
https://blog.t-ao.cn/2026/07/21/Ansible从入门到精通实战运维手册/
作者
TAO
发布于
2026年7月21日
许可协议