Ansible04-实战与安全
Ansible 实战、安全与滚动发布
目录
- 生产环境怎么用 Ansible
- 验证、排错与调试
- 安全:become 受控提权
- 安全:ansible-vault 加密敏感信息
- 端到端实战:部署一个完整服务
- 滚动发布与本机执行:serial 与 delegate_to
- 进阶:ansible-navigator 与执行环境
- 总结与延伸
- 参考资料
0. 生产环境怎么用 Ansible
生产环境里,写一个能跑的 Playbook 只是开始,更重要的是让它安全、可控地跑在真实机器上。围绕「一次安全的生产发布」,本篇覆盖以下环节:
- 先验证再上线:跑真环境前用
--syntax-check、--check、--limit小范围试跑,把风险留在上线前; - 安全两件事:become 受控提权、ansible-vault 加密敏感变量;
- 端到端实战:把这些手段全部用在一个完整的部署案例里,形成闭环;
- 滚动发布:从「能部署」升级到「坏了一批也不至于全挂」的发布节奏;
- 进阶工具:ansible-navigator 容器化运行,属于可选方向。
一、验证、排错与调试
生产环境第一原则是别把自己没把握的东西直接推到线上。这一章讲如何在运行前验证、运行中排错。它是后面所有章节的地基——不懂怎么确认「这次改动是安全的」,后面部署、发布都无从谈起。
1.1 运行前的检查
① 语法检查
# 语法检查(不执行)
ansible-playbook -i inventory.ini playbook.yml --syntax-check
# 用 yamllint 检查 YAML 语法(可选)
yamllint playbook.yml
② 试运行(dry-run / check mode)
# --check / -C:只模拟,不实际修改(对支持 check 的模块有效)
ansible-playbook -i inventory.ini playbook.yml --check
--check会显示每个任务「如果执行会怎样」,但不真正改动。注意command/shell等无法预测结果的模块在 check 模式下会直接报skipped。
③ 连通性预检
ansible -i inventory.ini all -m ping
1.2 提高输出详细度(排错核心)
ansible-playbook -i inventory.ini playbook.yml -v
ansible-playbook -i inventory.ini playbook.yml -vv
ansible-playbook -i inventory.ini playbook.yml -vvv
ansible-playbook -i inventory.ini playbook.yml -vvvv
-v:任务和主机状态,不含变量值;-vv:加变量值;-vvv:变量展开和任务详细信息;-vvvv:所有调试信息,连 HTTP 请求都显示。
1.3 用 debug 模块打印
- name: 打印某变量
debug:
var: app_port
- name: 打印自定义消息
debug:
msg: '当前主机 {{ inventory_hostname }},系统 {{ ansible_os_family }}'
- name: 打印上一步结果
debug:
var: result.stdout_lines
when: result is defined
1.4 常见错误与解决
| 现象 | 原因与解决 |
|---|---|
fatal: [host]: UNREACHABLE! |
SSH 连不上:查网络、ansible_user/ansible_host/私钥配置、能否免密 |
FAILED! ... Permission denied |
权限不足:加 become: true 或 -b 提权 |
ERROR! Syntax Error while loading YAML |
YAML 缩进/语法错:是否用了 Tab、冒号后少空格、引号未闭合 |
no action detected in task |
任务缺模块动作或缩进错误,Ansible 不认这个任务 |
variable is undefined |
变量不存在/拼错/只在别的组定义,用 debug 打印核实 |
排错次序:先
--syntax-check排除语法 → 检查 become 提权 →debug打印关键变量 →-v看完整报错 →--limit单台缩小范围。这套次序本身就是「先验证、再定位、后动手」在排错时的体现。
1.5 十个常见坑清单
- 缩进用了 Tab 或对齐错乱——YAML 只认空格,报 Syntax Error;
notify与 handler 的name不一致——差一个字都不触发或报错;command/shell以为有幂等性——它们每次执行、每次 changed;能用专用模块就用;- 变量同名被高优先级覆盖——group_vars 被 vars 又被 -e 覆盖,按优先级链排查;
- 模板渲染成空或
undefined——模板里的变量不存在或没定义; - 权限不足——任务要 root 却没加 become;
- Debian 系忘了
update_cache: true——apt 缓存过期导致装包失败/装到旧版; - 手写 IP/主机名而没用事实变量——换个环境就跑错,用 facts 尽量替代;
- 把密码/密钥明文写进 Inventory——用 vault 加密(见第 3 章);
- 大批量主机并行跑导致资源耗尽——默认 5 个并行,用
forks调整或--limit分批。
第 9、10 条对应生产中的两类高频问题:一是敏感数据需要加密存放,二是变更需要分批发布。这几条坑是最常碰到的。
二、安全:become 受控提权
验证过关后,下一步是权限安全。很多任务需要 root,但怎么提权、提权到哪、给哪些任务提权,都有讲究。这一章讲 become 的受控使用。
2.1 become:受控提权
Ansible 默认以登录用户执行。需要 root 时用 become(类似 sudo)。更安全的做法是配置 sudo 无密码并配合 become,避免每次输入密码:
sudo visudo -f /etc/sudoers.d/ansible
# 写入:deploy ALL=(ALL) NOPASSWD: ALL
然后在 Playbook 中启用:
---
- name: 提权执行
hosts: webservers
become: true
become_user: root
become_method: sudo
tasks:
- name: 安装软件
yum:
name: git
state: present
2.2 三个 become 参数的区别
| 参数 | 回答的问题 | 常见取值 |
|---|---|---|
become |
是否启用提权 | true / false |
become_user |
提权到哪个用户 | 默认 root;也可 www-data、postgres 等 |
become_method |
用什么机制提权 | 默认 sudo;也支持 su、doas、pbrun 等 |
三者的关系:
become: true打开开关后,Ansible 通过become_method指定方式,把执行身份切换成become_user指定用户。默认组合就是「用 sudo 切到 root」,等同于在目标机上执行sudo su - root后再跑任务。
何时需要提权? 装/卸软件(yum/apt)、修改 /etc 下配置、管理系统服务、写系统目录等。判断标准:若该命令在目标机上需要 sudo 才能执行,则任务应启用 become。
2.3 最小权限原则
become可以写在 play 级(整批提权)或单个 task 级(只这个任务提权);- 不要所有任务都
become: true,只给确实需要的任务提权,缩小安全面; - 不要把 root 密码或明文密钥写进 Inventory,优先密钥免密 + 指定 sudo 用户提权,敏感信息用 vault 加密。
become 管的是权限怎么给,vault 管的是敏感数据怎么藏,二者共同构成 Ansible 在生产环境的安全基线,端到端实战中会同时用上。
三、安全:ansible-vault 加密敏感信息
3.1 为什么需要 vault
密码、密钥、令牌等敏感信息如果明文写在 Inventory/Playbook 里,仓库泄露即全暴露。ansible-vault 用于加密这些内容,只有持有 vault 密码的人能解密。这是安全基线的另一半——become 管权限,vault 管数据。
3.2 基本用法
# 创建加密文件(会用编辑器录入明文,保存后自动加密)
ansible-vault create secrets.yml
# 对已有文件加密
ansible-vault encrypt vars/prod.yml
# 查看加密文件内容(需密码)
ansible-vault view vars/prod.yml
# 编辑加密文件
ansible-vault edit vars/prod.yml
# 解密(恢复明文)
ansible-vault decrypt vars/prod.yml
# 加密单个字符串(适合直接在 Playbook 里用)
ansible-vault encrypt_string 'rootpass' --name 'db_password'
3.3 在 Playbook 中引用加密文件
vars_files:
- vars/prod.yml
3.4 运行含加密文件的 Playbook
# 交互输入 vault 密码
ansible-playbook -i inventory.ini playbook.yml --ask-vault-pass
# 从文件读 vault 密码(适合 CI/CD)
ansible-playbook -i inventory.ini playbook.yml --vault-password-file ~/.vault_pass
ansible-vault让密码、密钥等敏感数据以密文存放,只有持有 vault 密码的人能解密,是 Ansible 安全管理的关键手段。配合--vault-password-file可接入 CI/CD 实现自动化。第四章实战将使用 vault 存放站点的敏感配置。
四、端到端实战:部署一个完整服务
现在把前三章的手段(验证、become、vault)和前三篇的知识全部串起来,完成一个端到端批量部署:用 Ansible 在一组服务器上部署一个 Nginx + 静态站点。这个案例会特意用上:
- Inventory 分组 + 组变量(第 1 篇)
- 角色 Roles + 模板 template + handler(第 2、3 篇)
- become 提权(本章第 2 章)
- ansible-vault 加密敏感配置(本章第 3 章)
- 验证手段(本章第 1 章:先
--check再正式跑)
4.1 目录结构
deploy-lab/
|-- inventory.ini
|-- site.yml
|-- vars/
| |-- webservers.yml # 常规组配置(明文)
| |-- webservers.secrets.yml # 敏感配置(用 vault 加密)
|-- roles/
| |-- nginx/
| | |-- tasks/main.yml
| | |-- handlers/main.yml
| | |-- templates/nginx.conf.j2
| | |-- defaults/main.yml
| | |-- files/index.html
|-- group_vars/
|-- webservers.yml
与前三篇的实战例子相比,这里多了两个关键设计:一是敏感配置单独放在 vault 加密的
webservers.secrets.yml;二是运行前先验证。这就是「实战」与「会用」的区别。
4.2 Inventory(inventory.ini)
[webservers]
web1 ansible_host=192.168.1.21
web2 ansible_host=192.168.1.22
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
4.3 组变量(明文:group_vars/webservers.yml)
app_port: 8080
app_name: demo
docroot: /usr/share/nginx/html
普通、非敏感配置放这里,随仓库走没问题。
4.4 敏感配置(vault 加密:vars/webservers.secrets.yml)
这里存的是不能明文入库的敏感信息(演示用:站点管理员的邮箱和口令)。先创建并加密:
ansible-vault create vars/webservers.secrets.yml
加密后的文件内容(编辑器录入明文,保存后自动加密):
site_admin_email: admin@example.com
site_admin_password: "S3cr3t-Passw0rd!"
ansible-vault view vars/webservers.secrets.yml需要 vault 密码才能看到明文;直接cat只能看到一堆密文。这正是 vault 的意义。
4.5 角色默认变量(roles/nginx/defaults/main.yml)
nginx_listen_port: 80
nginx_server_name: localhost
4.6 角色任务(roles/nginx/tasks/main.yml)
---
- name: 安装 nginx(按系统类型)
yum:
name: nginx
state: present
when: ansible_os_family == 'RedHat'
become: true
- name: 安装 nginx(Debian)
apt:
name: nginx
state: present
update_cache: true
when: ansible_os_family == 'Debian'
become: true
- name: 部署 nginx 主配置
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
mode: '0644'
notify: restart nginx
become: true
- name: 部署站点页面
copy:
src: index.html
dest: '{{ docroot }}/index.html'
mode: '0644'
become: true
- name: 启动并开机自启
service:
name: nginx
state: started
enabled: true
become: true
4.7 角色模板(roles/nginx/templates/nginx.conf.j2)
server {
listen {{ nginx_listen_port }};
server_name {{ nginx_server_name }};
root {{ docroot }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
这里的 {{ docroot }} 等变量来自 group_vars(明文)——演示「模板能用任何当前主机可见的变量」。敏感变量(如 site_admin_password)则只用于渲染到站点的管理后台配置,不直接出现在这个公共模板里。
4.8 角色处理器(roles/nginx/handlers/main.yml)
---
- name: restart nginx
service:
name: nginx
state: restarted
4.9 顶层 Playbook(site.yml)
---
- name: 批量部署 Nginx + 站点
hosts: webservers
become: true
vars_files:
- vars/webservers.secrets.yml # 引入 vault 加密的敏感配置
roles:
- nginx
4.10 运行与验证
① 先语法检查 + 试运行(第 1 章的手段):
ansible-playbook -i inventory.ini site.yml --syntax-check
ansible-playbook -i inventory.ini site.yml --check --ask-vault-pass
因为用到了 vault 加密文件,
--check也要带--ask-vault-pass(或--vault-password-file),否则解密不了site_admin_*。
② 正式部署(需要 vault 密码):
ansible-playbook -i inventory.ini site.yml --ask-vault-pass
③ 验证被管端 nginx 是否正常监听:
curl -sI http://192.168.1.21/ | head -n1
curl -sI http://192.168.1.22/ | head -n1
当两台 web 服务器都返回 HTTP/1.1 200 OK,说明批量部署成功——一次 Playbook 运行,多台机器被一致地安装、配置并启动了服务。
回顾本实战:其使用了 Inventory 分组、组变量、角色 Roles、默认变量 defaults、幂等模块、模板 template + Jinja2、handler/notify、条件 when、提权 become(仅对需要的任务启用)、vault 加密(敏感配置单独加密),以及运行前的
--syntax-check/--check——前三篇与本篇前三章的核心机制在此完整衔接。能够独立运行并改造该案例,即具备独立生产部署的基础。
五、滚动发布与本机执行:serial 与 delegate_to
部署成功后,下一个问题是发布的安全性:新版本若存在缺陷,几十台同时更换会导致全量故障。本章介绍两个控制发布节奏的机制,二者常搭配使用,是生产发布的标准做法。
5.1 serial:滚动发布,避免「一次全挂」
不带任何控制时,Playbook 会对清单里所有主机**同时(并行)**执行。对应用发布来说很危险:新版本有缺陷,几十台会同时坏掉,连回滚都来不及。
serial: N 让 Playbook 分批跑:每批 N 台,这一批全部成功后再跑下一批:
---
- name: 滚动发布 nginx
hosts: webservers
serial: 2 # 每批只动 2 台,全部成功后推进下一批
remote_user: devops
become: true
tasks:
- name: 拉取最新代码
git:
repo: https://example.com/app.git
dest: /srv/app
- name: 重启服务
service:
name: nginx
state: restarted
发布时若第一批就失败,Ansible 会停止并报告,而不是继续把后面几批也弄坏——这正是滚动发布的意义:把故障半径限制在一批之内。
serial 还支持百分比写法(serial: "30%")或列表(serial: [1, 3, 5],常用于「先 1 台金丝雀验证,再放量」)。
5.2 delegate_to:让某台机器「代为执行」
Ansible 默认在当前这台受管主机上执行任务。但有些操作必须在别的地方做,典型场景:
- 清空负载均衡(nginx 反代)里的某台后端,再动那台机器本身,避免发布期间流量打到正在重启的服务上;
- 向中心监控/告警平台发请求,让所有主机上报状态;
- 在一台共享机器上执行数据库迁移,而不是每台都跑。
delegate_to 把当前这个任务交给指定主机执行,但 Ansible 仍把它当作「当前主机」在循环里处理。下面的例子:发布前先摘除后端,做完再挂回:
- name: 滚动发布前摘除后端
hosts: webservers
serial: 1
tasks:
- name: 从负载均衡摘除当前主机
uri:
url: "http://lb.example.com/api/remove/{{ inventory_hostname }}"
method: POST
delegate_to: lb # 在 lb 这台机器上执行
- name: 更新并重启本机服务
yum:
name: nginx
state: latest
become: true
- name: 发布完成后挂回负载均衡
uri:
url: "http://lb.example.com/api/add/{{ inventory_hostname }}"
method: POST
delegate_to: lb
注意
{{ inventory_hostname }}仍然是当前正在处理的那台 web 主机的名字,不会因为 delegate_to 变成 lb——delegate 只改变「在哪台机器上执行」,不改变「循环针对的是谁」。配合serial: 1就实现了「逐台摘除→升级→挂回」的优雅发布。
local_action 是 delegate_to: 127.0.0.1(控制节点本机)的简写,常用于在控制端本地生成备份文件名、记录日志、调用本机脚本。
一句话区分:serial 控制「按什么节奏推进多台主机」,delegate_to 控制「某个任务具体在哪台机器上执行」。二者常搭配,构成生产里最常见的「分批 + 摘除/挂回」发布套路,解决「发布时怎么不把全量搞挂」的问题。
六、进阶:ansible-navigator 与执行环境
这一章属于可选的进阶方向。对于个人学习、单机运维场景,传统 ansible-playbook 已足够;当团队协作、CI/CD 需要环境一致时,再考虑容器化运行。将其置于系列末尾,是为了避免过早干扰基础内容的掌握。
6.1 什么是执行环境(EE)与 ansible-navigator
传统方式是在控制端安装 Ansible 后运行。但团队各成员的运行环境、Python 版本、已安装集合可能不一致,导致同一 Playbook 在不同机器上表现各异。
执行环境(Execution Environment,EE) 把 Ansible 运行环境容器化——把 ansible-core、Python、所有集合、依赖打进一个容器镜像,任何人拉同一个镜像运行,结果一致。
ansible-navigator 就是「用容器跑 Ansible」的官方工具。它调用底层的 ansible-runner,负责构建/启动容器、准备 artifacts 目录、执行任务。
ansible-navigator
|-- 读取 .ansible-navigator.yml / 命令行参数
|-- 构建执行上下文
|-- 调用 ansible-runner API
| |-- 构建 podman/docker 命令,启动 EE 容器
| |-- 容器内运行 ansible-playbook,连接被管端
| |-- 把输出写入 artifacts/ 目录
6.2 安装 ansible-navigator
pip install ansible-navigator # 建议在 venv 里装
ansible-navigator --version
6.3 常用子命令
| 子命令 | 作用 |
|---|---|
run |
运行 playbook |
exec |
在 EE 容器里执行单条命令 |
images |
查看本地 EE 镜像 |
builder |
构建 EE 镜像(调用 ansible-builder) |
inventory |
浏览/测试 inventory |
doc |
查看模块文档 |
collections |
查看可用集合 |
config |
查看当前 ansible 配置 |
6.4 运行一个 Playbook
# 用默认 EE 镜像,非交互(stdout)模式跑 playbook
ansible-navigator run site.yml -m stdout
# 指定 EE 镜像
ansible-navigator run site.yml \
--eei quay.io/ansible/awx-ee:24.6.1 -m stdout
# 挂载密钥/目录进容器
ansible-navigator run site.yml \
--eev ~/.ssh:/home/runner/.ssh:Z \
--eev "$PWD":/work:Z -m stdout
6.5 关键选项
| 选项 | 含义 | 默认 |
|---|---|---|
-m, --mode |
交互(interactive)/ 非交互(stdout) | interactive |
--ee, --execution-environment |
是否启用 EE 容器 | true |
--eei, --execution-environment-image |
指定 EE 镜像 | community-ansible-dev-tools:latest |
--ce, --container-engine |
容器引擎 auto/podman/docker | auto |
--pp, --pull-policy |
拉取策略 always/missing/never/tag | tag |
--eev, --execution-environment-volume-mounts |
挂载卷(可多次) | - |
--lf, --log-file |
日志文件路径 | ansible-navigator.log |
--pull-policy:always总是拉取;missing本地没有才拉;never从不拉取;tag标签为 latest 时总是拉取、其它标签只在本地没有时拉取。
6.6 持久化配置
可以用 ansible-navigator.yml 持久化常用参数,避免每次命令行写一长串:
# 导出默认配置
ansible-navigator settings export > ansible-navigator.yml
什么时候值得用 ansible-navigator/EE? 团队多人协作、CI/CD 里要保证环境一致、需要预装一堆集合/依赖时。个人学习、单机运维用传统
ansible-playbook完全够。它属于「进阶但越来越主流」的方向,值得了解,不必急着在入门阶段就用。
七、总结与延伸
7.1 本篇要点回顾
- 验证排错:
--syntax-check、--check、--limit先行,debug打印关键变量,按「语法→提权→变量→日志」次序排错; - 受控提权(become):
become: true+become_method+become_user的权限切换模型; - 敏感数据加密(ansible-vault):以密文存放密码/密钥,配合
--ask-vault-pass/--vault-password-file解密; - 端到端实战:Inventory 分组 + 组变量 + Roles + 模板 + vault 的组合用法;
- 滚动发布:
serial控制分批节奏、delegate_to指定执行位置,把故障半径限制在一批之内; - 进阶工具:ansible-navigator 与执行环境(EE)的容器化运行方式。
7.2 延伸方向
- Ansible Tower / AWX:企业级 Web UI、作业调度、权限管理,适合团队协作;
- 动态 Inventory:从云平台或 CMDB 动态拉取主机清单,配合
--limit做灰度; - CI/CD 集成:把 Ansible 接入 Jenkins/GitLab CI,实现配置变更的自动化发布;
- ansible-lint 与测试:用
ansible-lint做规范检查,molecule做角色测试; - 与其他工具结合:Ansible + 监控(Prometheus)、Ansible + 容器编排(K8s)等,是云原生自动化的常见组合。
7.3 写在最后
Ansible 是把「逐台手工运维」升级为「声明式批量自动化」的关键工具,也是迈向 DevOps、云原生的重要基础。生产中最常见的「批量部署 + 滚动发布 + 安全加密」组合,是实际运维中需要熟练掌握的场景。
参考资料
- Ansible 官方文档(Getting Started):https://docs.ansible.com/ansible/latest/getting_started/index.html
- Ansible Vault 指南:https://docs.ansible.com/ansible/latest/vault_guide/index.html
- Playbook 提权 become:https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_privilege_escalation.html
- Playbook 滚动(serial):https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_serial.html
- Playbook 委托 delegate_to:https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_delegation.html
- ansible-navigator 官方文档:https://docs.ansible.com/projects/navigator/
- 执行环境(EE):https://docs.ansible.com/ansible/latest/getting_started_ee/index.html
- Ansible-lint:https://docs.ansible.com/projects/lint/
- Molecule(角色测试):https://docs.ansible.com/projects/molecule/
