生产规范与 Web 节点实战
本章把前面所有能力收束成一套可交付的 Ansible 工程:目录规范、命名约定、变更流程,以及「标准 Web 节点初始化」综合实战。完成后,你应能在 staging 上完整跑通,并具备迁到生产的检查清单。
1. 生产级目录规范
ansible-for-ops/
├── README.md
├── ansible.cfg
├── inventory/
│ ├── staging/
│ │ ├── hosts.ini
│ │ └── group_vars/ # 可选:环境局部变量
│ └── prod/
│ ├── hosts.ini
│ └── group_vars/
├── group_vars/ # 跨环境共享(谨慎)
│ └── all.yml
├── host_vars/
├── playbooks/
│ ├── site.yml # 总入口
│ ├── web.yml
│ └── adhoc/ # 临时排查 play,定期清理
├── roles/
│ ├── common/
│ └── nginx/
├── files/ # 跨角色静态资源(可选)
└── scripts/ # 辅助脚本:加密、包装 CI 调用等
ansible.cfg(生产向)
[defaults]
inventory = ./inventory/staging/hosts.ini
roles_path = ./roles
remote_user = ops
private_key_file = ~/.ssh/ansible_ed25519
host_key_checking = True
retry_files_enabled = True
interpreter_python = auto_silent
forks = 20
timeout = 30
ansible_managed = Managed by Ansible. Source in git: ansible-for-ops
stdout_callback = default
[privilege_escalation]
become = False
become_method = sudo
become_ask_pass = False
[ssh_connection]
pipelining = True
生产把 host_key_checking 打开,并只在通过云控制台或主机管理员核验指纹后,才写入 known_hosts;不要把未经核验的 ssh-keyscan 输出直接当作可信身份。默认不提权,每个需要修改系统状态的 play 明确声明 become: true。
2. 命名与协作约定
| 对象 | 约定 |
|---|---|
| 逻辑主机名 | web1、web-a-01,稳定不变 |
| 组名 | 小写,角色导向:web/db/lb |
| 变量 | 角色前缀:nginx_listen_port |
| Vault 键 | vault_ 前缀 |
Play name | 英文或中文短句,可检索 |
| Git 分支 | main 保护;变更走 MR |
README 必写:
- 如何安装依赖集合
- 如何拿到 Vault 密码(链到密码库,不写明文)
- staging / prod 的标准执行命令
- 回滚方式
3. 综合实战目标
对 web 组主机收敛到如下状态:
- 基础包就绪(curl/jq/ca-certificates 等)
- 时区
Asia/Shanghai(若集合可用) - Nginx 安装、开机自启
- 主配置由模板管理,含
/healthz - 首页展示主机名
- 二次 apply 基本无 changed
- 敏感示例变量来自 Vault(可演示假密码)
4. Inventory 示例
inventory/staging/hosts.ini:
[web]
web1 ansible_host=10.0.1.11
web2 ansible_host=10.0.1.12
[staging:children]
web
[all:vars]
ansible_user=ops
group_vars 可放在仓库根或 inventory 目录下。示例使用仓库根:
group_vars/web/vars.yml:
---
nginx_listen_port: 80
nginx_worker_processes: 2
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_healthz_enable: true
common_packages:
- curl
- jq
- ca-certificates
common_timezone: Asia/Shanghai
# 由 vault 映射
demo_secret: "{{ vault_demo_secret }}"
group_vars/web/vault.yml(加密前内容):
---
vault_demo_secret: "staging-only-not-real"
ansible-vault encrypt group_vars/web/vault.yml
5. Role:common
roles/common/defaults/main.yml:
---
common_timezone: Asia/Shanghai
common_packages:
- curl
- jq
- ca-certificates
common_manage_timezone: true
roles/common/tasks/main.yml:
---
- name: 安装基线包
ansible.builtin.package:
name: "{{ common_packages }}"
state: present
tags: [common, packages]
- name: 设置时区
community.general.timezone:
name: "{{ common_timezone }}"
when: common_manage_timezone | bool
tags: [common]
无 community.general 时可临时:
- name: 设置时区(Debian 回退)
ansible.builtin.copy:
dest: /etc/timezone
content: "{{ common_timezone }}\n"
when:
- common_manage_timezone | bool
- ansible_os_family == "Debian"
notify: Reconfigure tzdata
6. Role:nginx(完整可跑)
roles/nginx/defaults/main.yml:
---
nginx_package: nginx
nginx_service: nginx
nginx_listen_port: 80
nginx_worker_processes: 2
nginx_server_name: "_"
nginx_root: /var/www/html
nginx_healthz_enable: true
nginx_manage_main_config: true
nginx_start_service: true
nginx_index_content: |
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8"/>
<title>{{ inventory_hostname }}</title>
<body>
<h1>{{ inventory_hostname }}</h1>
<p>managed by ansible role nginx</p>
</body>
</html>
roles/nginx/templates/nginx.conf.j2:
# {{ ansible_managed }}
worker_processes {{ nginx_worker_processes }};
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
server_tokens off;
server {
listen {{ nginx_listen_port }} default_server;
server_name {{ nginx_server_name }};
root {{ nginx_root }};
location / {
try_files $uri $uri/ =404;
}
{% if nginx_healthz_enable | bool %}
location /healthz {
access_log off;
return 200 'ok';
add_header Content-Type text/plain;
}
{% endif %}
}
}
roles/nginx/tasks/main.yml:
---
- name: 安装 nginx
ansible.builtin.package:
name: "{{ nginx_package }}"
state: present
tags: [nginx, packages]
- name: 创建站点目录
ansible.builtin.file:
path: "{{ nginx_root }}"
state: directory
mode: "0755"
tags: [nginx, config]
- name: 部署首页
ansible.builtin.copy:
content: "{{ nginx_index_content }}"
dest: "{{ nginx_root }}/index.html"
mode: "0644"
tags: [nginx, content]
- name: 部署主配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
backup: true
validate: "/usr/sbin/nginx -t -c %s"
register: nginx_config
when: nginx_manage_main_config | bool
notify: Reload nginx
tags: [nginx, config]
- name: 确保服务运行
ansible.builtin.service:
name: "{{ nginx_service }}"
state: started
enabled: true
when: nginx_start_service | bool
tags: [nginx, services]
roles/nginx/handlers/main.yml:
---
- name: Reload nginx
ansible.builtin.service:
name: "{{ nginx_service }}"
state: reloaded
7. Playbook 入口
playbooks/site.yml:
---
- name: Baseline
hosts: all
become: true
serial: "50%"
roles:
- common
- name: Web nodes
hosts: web
become: true
serial: [1, "100%"]
max_fail_percentage: 50
pre_tasks:
- name: 确认 web 组非空
ansible.builtin.assert:
that:
- groups['web'] | length > 0
roles:
- nginx
tasks:
- name: 立即 flush handlers
ansible.builtin.meta: flush_handlers
- name: 健康检查
ansible.builtin.uri:
url: "http://127.0.0.1:{{ nginx_listen_port }}/healthz"
status_code: 200
retries: 8
delay: 2
register: health
until: health.status == 200
playbooks/web.yml(只动 web):
---
- name: Web only
hosts: web
become: true
serial: 1
roles:
- nginx
8. 锁定依赖、静态检查与 CI
生产项目不要让每次执行都安装“当时最新”的 collection。把已经在 staging 验证过的版本写入仓库,并在控制节点与 CI 使用同一份清单。
collections/requirements.yml:
---
collections:
- name: community.general
version: "9.5.0"
- name: ansible.posix
version: "1.6.2"
版本号应替换为团队实际验证过的版本;升级 collection 时,先在独立分支和 staging 验证,再提交这个文件。安装与基础静态检查:
ansible-galaxy collection install -r collections/requirements.yml
pipx install ansible-lint
ansible-lint
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml --syntax-check
CI 不应直接对生产 inventory 做真实变更。最小流水线可使用测试 inventory,依次安装依赖、运行 ansible-lint、--syntax-check,再执行 --check --diff;需要运行时验证的 Role 可在后续引入 Molecule 和临时虚拟机。
set -euo pipefail
ansible-galaxy collection install -r collections/requirements.yml
ansible-lint
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml --syntax-check
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml --check --diff
9. 标准执行流程(照着跑)
cd ~/ansible-for-ops
# 0. 依赖(版本由仓库锁定)
ansible-galaxy collection install -r collections/requirements.yml
# 1. 连通
ansible -i inventory/staging/hosts.ini web -m ping
# 2. 预览
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml \
--ask-vault-pass --check --diff
# 3. 灰度一台
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml \
--ask-vault-pass -l web1
# 4. 验证
ansible -i inventory/staging/hosts.ini web1 -m uri \
-a "url=http://127.0.0.1/healthz return_content=true"
# 5. 全量 staging
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml \
--ask-vault-pass
# 6. 幂等确认
ansible-playbook -i inventory/staging/hosts.ini playbooks/site.yml \
--ask-vault-pass
验收标准
-
ping全绿 - 第一次 apply 后
/healthz返回ok - 第二次 apply 无异常 changed(允许 facts 相关的可解释差异)
-
nginx -t通过;backup在变更时生成 - vault 文件为密文且 playbook 能解密引用
- 故意断掉一台健康检查时,滚动策略符合预期
10. 回滚策略(务实)
Ansible 不提供自动「state 回滚」,靠工程纪律:
| 手段 | 做法 |
|---|---|
| Git revert | 还原模板/变量,再 apply |
backup: true | 变更时生成带时间戳的备份;注册 task 结果中的 backup_file 并记录路径 |
| 分批 | serial 限制爆炸半径 |
| 蓝绿/双版本 | 应用层保留 previous release 目录 |
| 变更记录 | MR + 执行日志 + 窗口时间 |
紧急回滚示例:
# 假设 Git revert 已完成
ansible-playbook -i inventory/prod/hosts.ini playbooks/web.yml \
--vault-password-file ~/.ansible/vault_prod \
--tags config -l web
backup: true 是额外保险,不是完整的发布回滚机制。需要固定、可预测的回退文件时,采用 Playbook 章节中“先保存旧文件、再变更”的方式;Nginx task 的 nginx_config.backup_file 只有文件实际变更时才存在。
11. 生产切换检查单
从 staging 到 prod 前:
- inventory 主机与业务确认一致
- prod Vault 密码与 staging 分离
host_key_checking=True- 先
--check --diff并由第二人审 diff serial从1起,观察指标- 准备回滚 MR / 备份文件路径
- 通知相关值班
在笔记本电脑上对生产全量 forks 拉满并行;不要用个人账号长期存产线 Vault 密码;不要在未限制 -l 时试验破坏性 tags。
12. 可选下一步
本系列收束于 Web 初始化。后续可按同一模式扩展:
| 方向 | 做法 |
|---|---|
| 监控 Agent | 新增 roles/node_exporter,site.yml 挂上 |
| 应用发布 | roles/app:拉制品 → 切 symlink → 健康检查 |
| AWX | 把现有 playbook 导入,补 RBAC/审批 |
| CI | MR 跑 --check,main 部署 staging |
保持不变的是:Role 承载能力,Playbook 承载范围与节奏,Vault 承载秘密,Git 承载真相。
13. 系列回顾
| 能力 | 文档 |
|---|---|
| 环境与连通 | 01 安装与连通 |
| 主机与变量 | 02 清单与变量 |
| 常用模块 | 03 常用模块 |
| 编排语法 | 04 Playbook |
| 复用 | 05 Role |
| 变更护栏 | 06 安全变更 |
| 密钥 | 07 Vault |
| 交付 | 本文 |
返回 系列导读。