安装显示完成,代理却一启动就崩溃了
目标
制作一个预检脚本:检查客户主机的安装要求,以 JSON 报告和退出码给出 pass、warn、fail。在评分器搭建的多种环境中,判定必须正确,而且检查不得修改任何东西。
为什么重要
客户现场的安装窗口往往只有一次。上一次安装以“完成”结束,但端口其实被内部代理占着,证书也早已过期。预检就是在安装之前,连同依据一起把这类事实摆出来的工具。 不仅要有判定,还要留下观测值,客户负责人才能再次确认;退出码必须保持固定,安装自动化才能区分继续与中止。 评分器不会相信你的措辞。它会在临时目录里亲自制造被占用的端口、TIME_WAIT 端口、即将到期或已过期的证书、不存在的目录,并在每次运行时改变端口号、基准值和路径后执行你的脚本。
步骤
- 阅读客户备忘录,用契约中的键名把 7 类要求写入 /root/preflight/spec.json。区分旧文档中的值和支持策略。
- 在 /root/preflight/preflight.py 中实现 python_min 和 ports 检查、报告(summary、exit_code、checks)以及退出码 0、1、2。规格中没有的键,不做对应检查。
- 修改 preflight.py,使端口检查不会把只剩 TIME_WAIT 的端口误报为正在使用。正在监听的端口仍然必须是 fail。
- 在 preflight.py 中加入 disk 检查。把剩余 MiB 写入 observed,把所测路径写入 measured_path,对尚不存在的路径不要创建,而是测量最近的父目录。
- 在 preflight.py 中加入 certs 检查。把 notAfter 以 epoch 秒写入 observed,把剩余天数写入 days_left;过期或无法读取为 fail,不足 warn_days 为 warn。
- 在 preflight.py 中加入 files、hosts、writable_dirs 检查。observed 使用契约中的状态名称,写入测试文件要删除,不存在的目录不要创建。
- 确认能区分正常、警告、失败三种主机的完整规格,以及损坏的规格。退出码为 0、1、2;无法读取规格时不生成报告,退出码为 3。
- 用 spec.json 检查这台主机,留下 /root/preflight/report.json,并在 /root/preflight/decision.json 中写入报告哈希、判定(go、go-with-warnings、no-go)、blockers 和 warnings。
参考
- 材料:客户备忘录 /opt/lab/p1a-preflight/customer-host.md,执行契约 /opt/lab/p1a-preflight/CONTRACT.md(规格键、检查 id、observed、退出码),客户提供的配置和证书 /opt/lab/p1a-preflight/host/etc/stocksync/
- 直接测试:python3 /root/preflight/preflight.py --spec /root/preflight/spec.json --report /tmp/r.json; echo $?
- 模拟被占用的端口:先用 python3 -m http.server 18080 & 启动,再把该端口写进规格试试。结束后只挑出该任务的 PID 来终止。
- 生成证书:openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout /tmp/k.pem -out /tmp/c.crt -days 5 -subj /CN=t
- 常见错误:把版本当字符串比较,不加 SO_REUSEADDR 就 bind,替用户创建不存在的目录,留下写入测试文件。
- 预检通过并不保证安装成功。它只检查规格中写明的条件。
把客户备忘录转写成规格
把客户备忘录中的 7 类要求,用契约中的键名写入 /root/preflight/spec.json。
备忘录里混有旧文档中的值,以及“当时能跑起来”的经验之谈。检查的基准是这一版和供应商的支持策略。端口是整数数组,disk、certs 是对象数组。
先判定 Python 版本和端口
让 /root/preflight/preflight.py 检查 python_min 和 ports,并给出报告和退出码 0、1、2。
端口是否空闲,实际在 0.0.0.0 上 bind 一下就知道。版本比较要用 sys.version_info 和整数元组。只要有一项 fail,summary 就是 fail。评分器每次都会用不同的端口和版本基准运行。
不要把已结束的连接误报为占用
让 /root/preflight/preflight.py 的端口检查把只剩 TIME_WAIT 的端口判为 free,把正在监听的端口判为 in_use。
服务端先关闭的连接会在该端口上留下 TIME_WAIT。不加选项的 bind 连这个也会被挡住。用一个套接字选项,就能让 TIME_WAIT 通过,而真正的监听者依然被挡住。
把剩余空间分成警告和失败
在 /root/preflight/preflight.py 中加入 disk 检查,给出 observed(剩余 MiB)、measured_path 以及 pass/warn/fail。
就是 shutil.disk_usage 的 free 除以 1024*1024 取整的商。路径不存在时,用 os.path.dirname 往上找到存在的位置,但不要创建。评分器会以当前剩余空间为基准不断改变基准值,来模拟磁盘不足。
用 notAfter 测量证书剩余天数
在 /root/preflight/preflight.py 中加入 certs 检查,给出 observed(notAfter 的 epoch 秒)、days_left 和判定。
把 openssl x509 -enddate -noout -in 文件 输出中 notAfter= 之后的部分,用 ssl.cert_time_to_seconds 转换。无法读取的文件,observed 为 null 且为 fail。评分器会生成并混用剩余充足、即将到期、已过期、已损坏的证书。
检查配置、主机名和写权限
在 /root/preflight/preflight.py 中加入 files、hosts、writable_dirs 检查。不要创建不存在的目录,写入测试文件要删除。
文件要区分不存在、不是文件、空文件。名称解析在 socket.getaddrinfo 失败时会抛出 socket.gaierror。写入不要用 os.access,而是用 tempfile.mkstemp 实际创建再删除。
区分三台主机和损坏的规格
确认 /root/preflight/preflight.py 在完整规格下,用退出码 0、1、2 分别对应正常、警告、失败;对无法读取的规格,不生成报告并以 3 结束。
即使规格键的顺序变了,检查 id 也必须与规格中所写的完全一致。规格 JSON 已损坏时,就没有可判定的依据,所以不要写报告。
对客户主机做出 go / no-go 判定
用 spec.json 检查并留下 /root/preflight/report.json,再在 /root/preflight/decision.json 中写入 report_sha256、decision、blockers、warnings。
decision 由 summary 得出(pass→go,warn→go-with-warnings,fail→no-go)。blockers 是 fail 检查的 id,warnings 是 warn 检查的 id。评分器会现在重新检查,核对报告是否已过期以及哈希。这一步不是修复阻塞项并使其通过的环节。