上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:23小时前 阅读量:20
用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-enabledconf.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,可能是 roottry_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 的 PATHHOME、当前目录和权限通常不同。脚本应先由目标用户手动执行一次,再通过日志确认定时执行结果。

如果服务器同时使用 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 但服务未更新只写入了文件,没有可靠通知或重载检查服务时间、日志和实际生效配置
回滚后仍然失败旧配置本身已损坏,或故障不在网站配置停止继续自动变更,保留现场并人工处理

回滚条件:什么时候必须停止并恢复

建议在以下情况触发网站配置回滚:

  1. 新配置的 nginx -t 失败。
  2. Nginx reload 返回失败,或服务进入非 active 状态。
  3. 变更后本机健康检查出现预期之外的状态码,且能够确认与本次配置直接相关。
  4. 错误日志出现持续增加的配置解析错误、权限错误或上游地址错误。
  5. 变更超出原定范围,例如额外修改了未纳入本次任务的站点或监听端口。

定时任务应独立回滚,不要因为网站配置失败就自动删除任务。需要恢复任务时,使用原来的唯一名称和版本库中的上一版规则:

- 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 输出和服务日志,转入人工处理。

目录结构
全文