LabHub
学习 学习路径 课程

调试实战

在我们服务器上是好的呀

在 LabHub 中继续学习

一句话总结

相同代码只有在某个环境中无法运行时,原因通常来自以下几项之一:环境变量、编码、权限,或 Shell 中看到的值与进程实际使用的值并不相同。

概念图: 第一,环境变量。 · 配置变量的位置与读取变量的位置不同。 · 第二,编码。 · 依赖自动转换

为什么需要它

FDE 最常听到的一句话是:“在我们的服务器上明明可以运行。”而且这句话通常是真的。代码相同,环境却不同。

环境差异比代码缺陷更难查找。代码可以直接阅读,环境却往往没有一个统一对象可供阅读,因此必须遵循固定顺序。

它如何运作

按照生产现场中最常出现的顺序,检查以下四项。

**第一,环境变量。缺少变量时,最先告诉你的通常就是错误消息本身,例如 missing env API_TOKEN。问题在于,即使看到这条消息,也经常得到“已经配置了”的回答;几乎所有这类情况都是因为配置变量的位置与读取变量的位置不同。**在 Shell 中执行 export 设置的值,只会传给该 Shell 的子进程。由 systemd 启动的服务无法看到它。

**第二,编码。**在包含韩文的客户数据中,这类问题尤其常见。Windows 环境生成的 CSV 往往不是 UTF-8,而是 EUC-KR(CP949);如果按 UTF-8 读取,文字会乱码,或直接因解码错误终止。file 命令无法始终准确判断编码,因此发现韩文乱码时,最快的方法是尝试使用 iconv -f EUC-KR -t UTF-8 转换。

这里的陷阱是依赖自动转换。使用错误编码读取的字符串可能悄无声息地通过并存入数据库,数周后才以“无法搜索”的问题报告重新出现。必须在读取时明确确定编码。

**第三,权限。**凭据文件尤其如此。如果令牌文件的权限为 644,同一服务器上的所有其他用户都能读取它;安全检查也可能因为这一项,降低对整个项目的信任。FDE 是处理别人家钥匙的人,因此这项责任格外重大。最小权限不是礼貌,而是生存规则。

**第四,Shell 中的值与进程实际使用的值不同。**这是最常欺骗人的情况。以打开文件数限制为例,即使 Shell 中的 ulimit -n 显示 1048576,实际运行服务的限制也可能只有 1024。应该检查的不是 Shell,而是 /proc/<PID>/limits

同样的陷阱会重复出现在多个层级。/etc/security/limits.conf 只适用于 PAM 登录会话,不适用于 systemd 服务。容器内的 freenproc 会显示宿主机数值,但实际限制位于 cgroup 中。原则只有一条:不要只看配置文件,要在正在运行的进程上实测。

生产现场中的表现

预先编写一个环境检查脚本,就能代替上述所有排查。它按顺序检查所需环境变量、文件是否存在及其权限、Locale、数据是否可访问,并在第一次失败时说明原因后停止。

这类脚本的价值不只在诊断时间,更在于提高沟通质量。当交流内容从“运行不了”变为“envcheck 报告缺少 API_TOKEN”,与客户负责人的往返沟通就能从三次减少到一次。

朝消除差异的方向前进

善于发现环境差异很重要,但更好的做法是减少产生差异的位置。逐步把接手的系统推向这个方向,也是 FDE 留下的长期价值。

**列出全部依赖。**如果没有任何地方记录程序需要什么,就无法判断新环境中缺少了什么。需要哪些环境变量、必须访问哪些路径和地址、依赖哪些命令及其版本。这份清单本身就会成为前述检查脚本的内容。

可以提供默认值,但不能悄无声息地使用。值缺失时回退到合理默认值很方便,但如果日志中没有记录这一事实,日后就无法解释“为什么结果不同”。仅仅留下一行说明使用了默认值,就能显著缩短调查时间。

**启动时一次检查全部条件,失败后立即退出。**缺少必需项时,与其在运行过程中随机位置失败,不如启动后立即检查全部条件,一次列出所有缺失项并停止。这样,原本需要三次往返的问题可以一次解决。

**用代码描述环境。**手工配置无法复现,无法复现就无法解释两个环境为什么不同。无论使用容器镜像还是配置管理工具,只要创建环境的步骤保存在文件中,“只有这里运行不了”这句话就很难再成立。

同时要求完成这四项,会让客户感到负担很重。因此,最好从刚刚经历的一次事故出发。这次因为 API_TOKEN 浪费了半天,所以建议先把它加入检查脚本,通常很容易被接受。这样逐行增长的脚本,几个月后就会成为一份相当实用的文档。

下一项实验要做什么

你将从环境检查脚本失败的状态开始,保留完整失败原文,定位缺失的环境变量,把 EUC-KR 编码的韩文 CSV 恢复为 UTF-8,并为令牌文件应用最小权限,最终通过检查。