Apache 日志轮转触发 SVN 更新异常:一次 SIGHUP 与 E175002 的实战排查
结论先行: 当 Apache 同时承载 SVN HTTP 服务时,日志轮转后执行
service httpd reload可能向 Apache 主进程发送SIGHUP。如果此时 Jenkins 正在执行svn update,活跃的 SVN/DAVREPORT请求可能受到影响,并表现为svn: E175002。更稳妥的处理方式是把轮转后的操作调整为graceful,并通过时间关联、日志和复测确认效果。
一、故障背景
最近在一台提供 SVN HTTP 访问的 Apache 服务器上,Jenkins 构建任务出现 SVN 更新失败。环境大致如下:
- 服务组件: Apache HTTP Server、SVN、Jenkins
- Apache: 2.2.15
- SVN: 1.8.11
- 日志轮转:
/etc/logrotate.d/httpd - 访问方式: Jenkins 通过 HTTP 协议访问 SVN 仓库
这类故障的特殊点在于:Apache 不只是普通 Web 服务,还在承载 SVN 的 WebDAV 协议请求。一次看似常规的"日志轮转后重载",可能刚好打断构建中的 SVN 更新过程。
二、故障现象
Jenkins 在预构建 Shell 阶段依次执行:
bash
svn cleanup
svn update
svn cleanup 正常完成;svn update 已开始更新工作副本、创建版本目录并拉取资源文件,随后失败:
text
svn: E175002: REPORT of '/M_00003/!svn/me':
200 OK (http://devsvn.uuzuonline.net)
Failed build for hudson.tasks.Shell@47e96163
ERROR: pre-build step failed to setup environment
Finished: FAILURE
与此同时,在 Apache 的归档错误日志中发现:
text
[Fri Oct 16 03:46:10 2026] [notice] SIGHUP received. Attempting to restart
这意味着 Apache 在该时刻接收到了 SIGHUP,并进入重启处理逻辑。
三、为什么 200 OK 仍会让 SVN update 失败?
初看错误信息很容易产生疑问:既然服务端返回了 200 OK,为什么 SVN 还会报错?
关键在于:SVN 的 REPORT 不是一个只看 HTTP 状态码的普通请求。客户端除了要求收到 HTTP 200,还需要接收到完整且符合 SVN/WebDAV 协议预期的响应内容。
在以下情况下,客户端可能已经收到 200 OK,但仍然失败:
- Apache 重启过程影响了正在传输的响应流;
REPORT的 XML 或目录清单没有完整传输;- KeepAlive 连接在请求过程中被关闭或复用失败;
- Apache 子进程切换期间,请求被中断、截断或出现协议解析异常。
因此,下面这条错误并不表示请求完整成功:
text
E175002: REPORT ... 200 OK
它更准确的含义是:HTTP 头部可能已经正常返回,但 SVN 客户端没有拿到一个可用的完整协议响应。
四、定位日志轮转行为
查看 Apache 的 logrotate 配置:
bash
cat /etc/logrotate.d/httpd
发现其中包含:
conf
/var/log/httpd/*log {
compress
olddir oldlog
daily
rotate 7
missingok
notifempty
sharedscripts
delaycompress
postrotate
/sbin/service httpd reload > /dev/null 2>/dev/null || true
endscript
}
这里最关键的是:
bash
/sbin/service httpd reload
继续检查系统服务脚本:
bash
grep -n -A 15 -B 5 'reload' /etc/init.d/httpd
可以看到 reload() 最终执行类似逻辑:
bash
LSB=1 killproc -p ${pidfile} $httpd -HUP
也就是说,service httpd reload 并不是一个纯配置检查动作,而是会向 Apache 主进程发送 SIGHUP。
这与错误日志中的内容完全对应:
text
SIGHUP received. Attempting to restart
由此可以建立完整链路:
text
logrotate 执行
↓
postrotate 调用 service httpd reload
↓
init 脚本向 Apache 主进程发送 SIGHUP
↓
Apache 进入常规重启处理
↓
正在执行的 SVN HTTP/DAV 请求可能受影响
五、为什么不建议简单删除 postrotate?
有人看到问题后,第一反应是直接删掉 postrotate。这并不推荐。
日志轮转时,logrotate 会对旧日志重命名或移动,并创建新的活动日志文件。如果 Apache 不重新打开日志文件描述符,它可能继续写入已经被移动到 oldlog 目录的旧文件。
最终会出现两个问题:
- 新的
access_log、error_log看起来不再有日志; - 实际日志继续写入归档文件,影响排障和监控采集。
因此,正确方向不是"取消 Apache 重新打开日志",而是:
保留日志重开动作,但将对业务请求影响较大的常规重启,调整为更温和的平滑重启。
六、推荐方案:reload 改为 graceful
先备份原配置:
bash
cp -a /etc/logrotate.d/httpd /etc/logrotate.d/httpd.bak
将原配置:
conf
postrotate
/sbin/service httpd reload > /dev/null 2>/dev/null || true
endscript
修改为:
conf
postrotate
/sbin/service httpd graceful > /dev/null 2>&1 || true
endscript
完整示例:
conf
/var/log/httpd/*log {
compress
olddir oldlog
daily
rotate 7
missingok
notifempty
sharedscripts
delaycompress
postrotate
/sbin/service httpd graceful > /dev/null 2>&1 || true
endscript
}
reload 与 graceful 的区别
| 操作 | 常见信号 | 典型行为 | 对 SVN 活跃请求的风险 |
|---|---|---|---|
reload |
SIGHUP |
常规重启处理,子进程重建 | 较高 |
graceful |
SIGUSR1 |
尽量让旧请求处理完成,再平滑切换 | 较低 |
需要强调:graceful 不是"完全无影响"。它仍会触发 Apache 的平滑重启和日志重开,只是比直接以 SIGHUP 进行常规重启更适合存在长请求的服务场景。
七、实施前必须确认的一件事
不同发行版、不同版本的 /etc/init.d/httpd 实现可能不同。不要假定 graceful 一定发送 SIGUSR1,应先在服务器上确认:
bash
grep -n -A 10 -B 3 '^graceful' /etc/init.d/httpd
预期可以看到类似:
bash
killproc -p ${pidfile} $httpd -USR1
只有确认 graceful 走的是平滑重启逻辑,修改才有意义。
八、验证步骤
1. 检查 Apache 配置
bash
/sbin/service httpd configtest
预期:
text
Syntax OK
2. 检查 logrotate 规则
bash
logrotate -dv /etc/logrotate.d/httpd
-d 为调试模式,不会真正执行轮转。-v 用于输出详细过程,便于确认匹配到的日志文件和脚本逻辑。
3. 在低峰窗口验证日志持续写入
变更后,检查活动日志和归档日志:
bash
ls -lh /var/log/httpd/
ls -lh /var/log/httpd/oldlog/
观察活动日志是否继续增长:
bash
tail -f /var/log/httpd/access_log
如果日志文件命名不同,请替换为实际文件名。
4. 验证 SVN 服务
优先使用独立测试工作副本,而不是直接操作 Jenkins 正在使用的生产工作目录:
bash
svn info
svn update --non-interactive
同时观察 Apache 错误日志,确认是否再次出现:
text
E175002: REPORT of '/M_00003/!svn/me'
九、怎样确认本次故障的因果关系?
目前我们可以确认:
- 日志轮转配置会触发
httpd reload; reload会发送SIGHUP;- Apache 在对应时段记录了重启尝试;
- Jenkins 的 SVN 更新出现
E175002。
但要把"高度相关"升级为"确认根因",还需要进行时间关联。
建议核对:
text
Jenkins 中 svn update 的失败时间
↕
Apache error_log 中 SIGHUP 的时间
↕
Apache access_log 中对应 REPORT 请求的时间
可重点检查:
bash
grep -nE 'SIGHUP|restart|graceful|child pid|segfault|DAV|svn' \
/var/log/httpd/oldlog/error_log-20261016
如果已知 Jenkins 节点 IP,则定位其 REPORT 请求:
bash
grep 'JENKINS_IP' /var/log/httpd/oldlog/access_log-20261016 \
| grep '"REPORT '
如果 Jenkins 失败时间与 SIGHUP 相差数秒到几十秒,并且同一时间段存在对应的 REPORT /M_00003/!svn/me 请求,那么这条因果链就有较强证据支持。
十、一个容易忽略的运维改进
很多生产配置习惯这样写:
bash
/sbin/service httpd graceful > /dev/null 2>&1 || true
它的优点是:即使重开日志失败,也不会导致 logrotate 返回失败。
但缺点也明显:所有失败都会被吞掉。 如果 Apache 没有成功 graceful,新日志可能无法被正常打开,故障会隐藏很久。
如果运维体系允许,建议至少记录一次系统日志:
conf
postrotate
if ! /sbin/service httpd graceful; then
logger -t logrotate-httpd "httpd graceful failed after log rotation"
fi
endscript
这样既不会阻断日志轮转流程,也能在系统日志中保留异常线索。
十一、回滚方案
若变更后发现 Apache 日志未持续写入、SVN 请求异常或服务行为不符合预期,可以恢复原配置:
bash
cp -a /etc/logrotate.d/httpd.bak /etc/logrotate.d/httpd
恢复后不建议马上执行强制轮转。应先检查 Apache 服务状态、当前日志文件及文件描述符写入情况,再安排下一次维护窗口验证。
十二、总结
这次问题提醒我们:日志轮转不是纯粹的文件维护操作,它常常隐含服务重载行为。
对于只提供静态页面的 Apache,短暂 reload 可能不容易暴露问题;但对于承载 SVN、WebDAV、长连接或大文件传输的 Apache,一次不恰当的重载就可能变成构建链路中的偶发故障。
本次处理建议可以总结为四点:
- 不直接删除
postrotate,避免 Apache 持续写入旧日志; - 将
httpd reload替换为经验证的httpd graceful; - 通过 Jenkins、Apache 错误日志和访问日志建立时间关联;
- 在低峰维护窗口完成验证,确认日志轮转、日志写入与 SVN 更新均稳定。
运维中的"无感操作"往往只是没有碰巧撞上业务高峰。把重载行为、信号语义和请求链路串起来,才能真正找到偶发故障的原因。