用Ansible管理香港服务器时,网站配置、定时任务和失败回滚如何设计

在生产环境中用 Ansible 管理香港服务器,网站打不开不一定是网络问题,也可能是 Nginx 配置语法错误、服务未重新加载、站点目录权限变化,或定时任务在非交互环境下执行失败。变更负责人不能只看 ansible-playbook 是否返回成功,而应先确认故障边界,再备份、预检、分步变更,最后根据验证结果决定保留还是回滚。
建议按以下优先级排查:先确认 SSH 和提权正常,再检查磁盘、Nginx 配置语法及监听状态,然后验证本机 HTTP 请求,最后核对定时任务是否重复、是否使用了正确的用户和环境变量。前一层未通过时,不要直接修改后一层配置。
现状核对:先判断故障发生在哪一层
1. 确认目标主机和权限
先检查 Ansible 实际连接的是否是目标香港服务器,避免 inventory 变更后误操作其他主机。
ansible-inventory -i inventories/production/hosts.yml --graph
ansible web_hk -i inventories/production/hosts.yml -m ping
ansible web_hk -i inventories/production/hosts.yml -b -m command -a 'id'
结果可以这样判断:
ping成功且id显示为root,说明 SSH 和become基本正常。- SSH 失败,先处理密钥、端口、用户名或安全策略,不要进入配置变更阶段。
ping成功但提权失败,说明 Ansible 能登录,却不能修改系统文件或重载服务。- 目标组中出现不应变更的主机,应立即停止执行,修正 inventory 或使用
--limit。
单台香港服务器执行生产变更时,建议明确限制目标:
ansible-playbook -i inventories/production/hosts.yml site.yml \
--limit hk_web_01 \
--check --diff
--check 只能帮助观察模块计划,不代表真实执行结果。涉及服务重载、HTTP 探测和某些命令时,仍需在变更窗口内进行一次正式验证。
2. 检查磁盘、服务和配置语法
以下命令适用于使用 systemd 和 Nginx 的 Linux 服务器。具体配置路径应以服务器实际安装方式为准,不要直接假定一定存在 sites-enabled 或 conf.d。
ansible hk_web_01 -i inventories/production/hosts.yml -b -m shell -a \
'df -h; systemctl is-active nginx || true; nginx -t'
再检查监听端口和当前完整配置来源:
ansible hk_web_01 -i inventories/production/hosts.yml -b -m shell -a \
'ss -ltnp; nginx -T 2>/dev/null | sed -n "1,160p"'
重点看四类结果:
| 检查项 | 正常含义 | 异常含义 | 处理方向 |
|---|---|---|---|
| 磁盘空间 | 配置、日志和临时文件仍有空间 | 磁盘或 inode 接近耗尽 | 先清理可确认的日志,避免在满盘状态下部署 |
systemctl is-active nginx | 服务处于 active | 服务停止、启动失败或反复重启 | 查看 journalctl -u nginx,不要立即覆盖配置 |
nginx -t | 当前配置可被解析 | 语法、路径、权限或 include 错误 | 记录错误行号,定位最近一次变更 |
nginx -T | 能看到生效配置和 include 来源 | 配置文件未被加载或路径判断错误 | 以实际 include 路径修正 Ansible 变量 |
ss -ltnp | 目标端口由预期服务监听 | 端口未监听或被其他进程占用 | 先确认服务状态和监听配置 |
如果 nginx -t 已经失败,应先保存现有错误信息和配置副本,再决定是否恢复最近版本。此时不要直接运行带有删除或批量覆盖效果的命令。
3. 验证本机请求,而不是只看服务状态
Nginx 处于 active 并不代表站点一定可用。配置可能指向错误的站点目录,或者默认虚拟主机抢先匹配。
ansible hk_web_01 -i inventories/production/hosts.yml -b -m uri -a \
'url=http://127.0.0.1/ headers={"Host":"www.example.com"} status_code=200 return_content=no'
这里的域名、协议和预期状态码应替换为实际站点值。结果含义如下:
- 返回预期状态码,说明本机监听、虚拟主机匹配和基础响应链路通过。
- 返回
404,可能是root、try_files或 Host 匹配错误,也可能是站点本身没有该资源。 - 返回
502,通常应继续检查后端应用或上游服务,不应只回滚 Nginx。 - 连接拒绝,优先检查监听端口和服务状态。
- 外部访问失败但本机正常,应分别检查 DNS、入口策略和外部访问链路;不要仅凭外部现象覆盖网站配置。
变更准备:把可重复部署和回滚前置
目录和变量应分离
建议将主机信息、网站变量和任务逻辑分开管理,例如:
inventories/
production/
hosts.yml
group_vars/
web.yml
roles/
website/
tasks/
main.yml
templates/
site.conf.j2
maintenance.sh.j2
site.yml
主机变量中只放环境差异,不把生产域名、站点目录和定时规则散落在多个任务文件里:
site_domain: "www.example.com"
web_root: "/srv/www/example"
nginx_conf_path: "/etc/nginx/conf.d/example.conf"
healthcheck_url: "http://127.0.0.1/"
healthcheck_host: "www.example.com"
maintenance_user: "siteops"
nginx_conf_path 必须先通过 nginx -T 或人工核对确认。Debian、Ubuntu 和其他发行版的 Nginx 包可能使用不同的目录布局,路径不确定时应先探测,不要凭经验覆盖文件。
先保留配置和任务证据
网站配置、环境文件和定时任务都可能包含敏感信息。备份目录应限制权限,备份内容不应直接提交到公开代码仓库。
- name: Create restricted backup directory
ansible.builtin.file:
path: /var/backups/ansible-site
state: directory
owner: root
group: root
mode: "0700"
- name: Read current website configuration
ansible.builtin.stat:
path: "{{ nginx_conf_path }}"
register: current_nginx_conf
- name: Build unique backup path
ansible.builtin.set_fact:
nginx_backup_path: "/var/backups/ansible-site/example.conf.{{ ansible_date_time.epoch }}"
如果站点配置中包含密码、API 密钥或数据库连接信息,备份还应使用受控权限或加密存储。不要为了方便把这些内容输出到 --diff、流水线日志或 Ansible 调试日志中。
变更应小批量执行
生产变更建议同时使用以下控制手段:
- 使用
--limit hk_web_01先在单台香港服务器执行。 - 在 play 中设置
serial: 1,避免多个目标同时失去服务。 - 使用
--check --diff预览配置变化。 - 配置和定时任务拆成不同任务块,避免网站配置失败时误删有效任务。
- 使用模块代替无必要的
shell,必须使用 shell 时明确记录退出码和影响范围。 - 不把数据库删除、数据迁移和网站配置重载捆绑在同一个不可逆任务中。
分步实施:网站配置与定时任务分别管理
1. 使用模板生成网站配置
网站配置应由模板渲染,变量由 inventory 提供。下面是静态站点的简化示例,适用于已确认 Nginx 主配置会加载该文件的场景:
server {
listen 80;
server_name {{ site_domain }};
root {{ web_root }};
index index.html;
access_log /var/log/nginx/{{ site_domain }}.access.log;
error_log /var/log/nginx/{{ site_domain }}.error.log;
location / {
try_files $uri $uri/ =404;
}
}
不要把 PHP-FPM socket、反向代理地址等环境相关值硬编码到模板中。如果站点依赖上游应用,应将上游地址作为变量,并在变更前确认该地址能够连接。
推荐的实施顺序是“备份—渲染—语法检查—重载—本机验证”。以下示例使用显式备份和异常恢复,适用于已经确认配置文件路径的 systemd + Nginx 环境:
- name: Change website configuration safely
block:
- name: Backup current website configuration
ansible.builtin.copy:
src: "{{ nginx_conf_path }}"
dest: "{{ nginx_backup_path }}"
remote_src: true
owner: root
group: root
mode: "0600"
when: current_nginx_conf.stat.exists
- name: Render website configuration
ansible.builtin.template:
src: site.conf.j2
dest: "{{ nginx_conf_path }}"
owner: root
group: root
mode: "0644"
- name: Validate Nginx configuration
ansible.builtin.command: nginx -t
changed_when: false
- name: Reload Nginx after successful validation
ansible.builtin.systemd:
name: nginx
state: reloaded
- name: Check local website response
ansible.builtin.uri:
url: "{{ healthcheck_url }}"
headers:
Host: "{{ healthcheck_host }}"
status_code: 200
return_content: false
rescue:
- name: Restore previous website configuration
ansible.builtin.copy:
src: "{{ nginx_backup_path }}"
dest: "{{ nginx_conf_path }}"
remote_src: true
owner: root
group: root
mode: "0644"
when:
- current_nginx_conf.stat.exists
- nginx_backup_path is defined
- name: Validate restored configuration
ansible.builtin.command: nginx -t
register: restore_nginx_test
changed_when: false
failed_when: false
- name: Reload restored configuration
ansible.builtin.systemd:
name: nginx
state: reloaded
when: restore_nginx_test.rc == 0
- name: Stop the play after rollback
ansible.builtin.fail:
msg: >-
Website change failed. The previous configuration was restored when
possible. Check the task output and Nginx logs before retrying.
这个示例中的 nginx -t 是关键闸门。语法检查失败时,不应执行 reload;本机 HTTP 检查失败时,只有在能够合理判断问题由本次配置引起的情况下才自动回滚。如果失败来自后端应用、证书、DNS 或外部依赖,应该保留现场并单独排查,避免把无关问题误归因于 Nginx。
2. 用唯一标识管理定时任务
定时任务不要通过整段 crontab 覆盖。这样容易删除人工维护的任务,也不利于审计。使用 Ansible 的 cron 模块并设置稳定的 name,可以保证重复执行不会不断新增相同任务。
下面的示例以已有 siteops 用户、已经验证过的维护脚本为前提:
- name: Ensure maintenance log directory exists
ansible.builtin.file:
path: /var/lib/site-maintenance
state: directory
owner: "{{ maintenance_user }}"
group: "{{ maintenance_user }}"
mode: "0750"
- name: Install maintenance wrapper
ansible.builtin.template:
src: maintenance.sh.j2
dest: /usr/local/sbin/site-maintenance
owner: root
group: root
mode: "0750"
- name: Schedule site maintenance
ansible.builtin.cron:
name: "site-maintenance"
user: "{{ maintenance_user }}"
minute: "15"
hour: "3"
job: >-
/usr/bin/flock -n /var/lib/site-maintenance/site-maintenance.lock --
/usr/local/sbin/site-maintenance
>> /var/lib/site-maintenance/maintenance.log 2>&1
flock 用于避免上一次任务未结束时再次启动。如果服务器没有该命令,应先使用以下方式确认:
command -v flock
getent passwd siteops
crontab -l -u siteops
定时任务脚本应使用绝对路径、明确的工作目录和必要的环境变量。交互式 SSH 中能执行,不代表 cron 中也能执行,因为 cron 的 PATH、HOME、当前目录和权限通常不同。脚本应先由目标用户手动执行一次,再通过日志确认定时执行结果。
如果服务器同时使用 systemd timer,应将它与 cron 分开审计,不能认为“已经有一条 cron”就代表任务只会执行一次:
systemctl list-timers --all --no-pager
systemctl list-units --type=service --no-pager | grep -E 'cron|crond' || true
crontab -l -u siteops
3. 让任务具备幂等性
同一个 playbook 重复执行时,结果应趋于稳定:
- 配置内容未变时,不应反复触发模板变更和 Nginx reload。
- 定时任务应通过固定名称更新,而不是每次追加新行。
- 目录、文件权限和属主应由 Ansible 明确声明。
- 维护脚本需要重复执行时,不应重复创建数据、重复发送通知或覆盖未备份文件。
- 需要保存秘密时使用受控变量和
no_log,不要将秘密写入普通模板或命令参数。
验证观察:每一步都要有结果解释
正式执行前先做语法和差异检查:
ansible-playbook -i inventories/production/hosts.yml site.yml \
--syntax-check
ansible-playbook -i inventories/production/hosts.yml site.yml \
--limit hk_web_01 --check --diff
正式执行后,至少完成以下验证:
ansible hk_web_01 -i inventories/production/hosts.yml -b -m command -a \
'nginx -t'
ansible hk_web_01 -i inventories/production/hosts.yml -b -m shell -a \
'systemctl is-active nginx; ss -ltnp'
ansible hk_web_01 -i inventories/production/hosts.yml -b -m uri -a \
'url=http://127.0.0.1/ headers={"Host":"www.example.com"} status_code=200 return_content=no'
随后检查访问日志、错误日志和任务日志:
journalctl -u nginx --since "30 minutes ago" --no-pager
journalctl --since "30 minutes ago" --no-pager | grep -E 'cron|crond|site-maintenance' || true
tail -n 100 /var/lib/site-maintenance/maintenance.log
验证结果与修复动作可以按下面的顺序判断:
| 现象 | 主要含义 | 修复后必须再次确认 |
|---|---|---|
| 配置语法失败 | 模板、变量、include 或权限有问题 | nginx -t 返回成功,且未发生 reload |
| reload 失败 | 服务无法加载新配置或服务状态异常 | 恢复旧配置后再次 nginx -t 和 reload |
| Nginx active 但 HTTP 状态异常 | 虚拟主机、目录、权限或应用响应异常 | 带正确 Host 头的本机请求恢复 |
| 定时任务重复出现 | 使用了追加式 crontab 或名称不稳定 | crontab -l 中只保留一条受管任务 |
| 手工执行成功、cron 失败 | 用户、环境变量、工作目录或绝对路径不一致 | 以定时任务用户的最小环境重新执行 |
| Ansible 显示 changed 但服务未更新 | 只写入了文件,没有可靠通知或重载 | 检查服务时间、日志和实际生效配置 |
| 回滚后仍然失败 | 旧配置本身已损坏,或故障不在网站配置 | 停止继续自动变更,保留现场并人工处理 |
回滚条件:什么时候必须停止并恢复
建议在以下情况触发网站配置回滚:
- 新配置的
nginx -t失败。 - Nginx reload 返回失败,或服务进入非 active 状态。
- 变更后本机健康检查出现预期之外的状态码,且能够确认与本次配置直接相关。
- 错误日志出现持续增加的配置解析错误、权限错误或上游地址错误。
- 变更超出原定范围,例如额外修改了未纳入本次任务的站点或监听端口。
定时任务应独立回滚,不要因为网站配置失败就自动删除任务。需要恢复任务时,使用原来的唯一名称和版本库中的上一版规则:
- name: Remove broken managed schedule
ansible.builtin.cron:
name: "site-maintenance"
user: "{{ maintenance_user }}"
state: absent
删除任务会改变生产行为,执行前应确认该任务确实由本次变更创建,且已经保存当前 crontab 作为证据。若只是任务脚本内容错误,优先恢复脚本并手动验证,不要无条件删除整个用户的 crontab。
回滚成功的标准不是“文件恢复了”,而是:
- 旧配置文件已恢复,权限和属主正确;
nginx -t成功;- Nginx reload 成功且服务保持 active;
- 带正确 Host 头的本机请求恢复;
- 定时任务没有产生重复条目;
- 错误日志在观察窗口内不再持续出现同类错误。
变更完成后,应至少覆盖一次实际业务观察窗口;对于每日执行的任务,还要覆盖一次任务执行周期,或在相同用户、相同环境下完成一次人工验证。若健康检查持续失败、错误日志持续增长、任务重复执行,或回滚后的配置仍无法通过语法检查,应立即停止继续发布,保留当前配置、Ansible 输出和服务日志,转入人工处理。