扫描上传失败,报错 IOException: The temporary upload location is not valid

问题描述

当 CI pipeline(或手动运行 sonar-scanner)向 SonarQube 提交扫描报告时,上传会在 /api/ce/submit 端点返回 HTTP 500,且 SonarQube 的 web.log 会显示:

java.io.IOException: The temporary upload location [/opt/sonarqube/temp/tc/work/Tomcat/localhost/ROOT] is not valid
        at org.apache.catalina.connector.Request.parseParts(Request.java:2530)
        ...

该分析不会进入 Background Tasks 页面中的队列。SonarQube UI 本身仍可用于其他操作——此失败仅针对包含 report multipart 文件的新扫描报告摄取。

在较旧的 SonarQube 版本中,相同的根本原因有时会表现为 HTTP 400 响应,并带有 ERROR: The report parameter is missing。下面的恢复步骤在这两种情况下都相同。

根本原因

SonarQube 的 web 层嵌入了 Tomcat(org.sonar.server.app.EmbeddedTomcat),并会在将上传的报告文件交给 Compute Engine 之前,将其作为 multipart 临时上传文件存储。临时位置位于 Tomcat 的工作目录下:

/opt/sonarqube/temp/tc/work/Tomcat/localhost/ROOT/

如果该目录不存在(通常是在 Pod 内手动修改之后、部分卷恢复之后,或者由于错误的卷挂载替换了 temp 树的一部分),Tomcat 会拒绝解析 multipart 请求,并在 SonarQube 控制器被调用之前 抛出 IOException: The temporary upload location [...] is not valid。随后 500 错误会传回给 scanner。

故障排查

第 1 步 — 确认 web.log 中的症状

kubectl -n <NAMESPACE> exec <RELEASE>-sonarqube-xxxxx -- \
  tail -200 /opt/sonarqube/logs/web.log

包含 The temporary upload locationRequest.parseParts 的堆栈跟踪是该问题的诊断特征。如果日志显示的是其他错误(认证失败、未知项目、scanner 版本不匹配),则本文不适用。

第 2 步 — 确认 Pod 内的目录状态

kubectl -n <NAMESPACE> exec <RELEASE>-sonarqube-xxxxx -- \
  ls -ld /opt/sonarqube/temp/tc/work/Tomcat/localhost/ROOT/
  • 如果该路径不存在,则该问题适用。
  • 如果该路径存在,但其所有者与 SonarQube 进程的运行时 UID/GID 不匹配,则该问题同样适用。在标准镜像中,Pod 以 runAsUser: 1000 / runAsGroup: 1000 启动,temp/tc 下现有文件的所有者为 1000:1000(由于镜像的 /etc/group 中没有 gid 1000 的条目,因此组会以数字形式显示)。

解决方案

以下两种方案任选其一。

选项 A — 在原位置重新创建目录

在 Pod 内重新创建缺失的目录,并恢复所有权,以便 SonarQube 进程可以向其中写入:

kubectl -n <NAMESPACE> exec <RELEASE>-sonarqube-xxxxx -- bash -lc '
  mkdir -p /opt/sonarqube/temp/tc/work/Tomcat/localhost/ROOT/
  chown -R 1000:1000 /opt/sonarqube/temp/tc
'

重新运行失败的扫描。此时上传应返回带有 taskId 的 HTTP 200,分析结果应出现在 Background Tasks 页面中。

选项 B — 重启 SonarQube Pod

由于 SonarQube 启动时会从头重新创建 /opt/sonarqube/temp,重启 Pod 会产生与选项 A 相同的效果,并避免在运行中的容器内手动编辑文件:

kubectl -n <NAMESPACE> rollout restart deployment <RELEASE>-sonarqube

等待新的 Pod 变为 Ready,然后重新运行扫描。

注意事项

  • 不要在 /opt/sonarqube/temp 上方挂载。 该路径是运行时临时区域,不应由持久卷提供支持,也不应被来自其他容器的 emptyDir 替换。如果在其上叠加了自定义挂载,那么移除该挂载才是持久性的修复;选项 A 和 B 只能暂时掩盖症状,直到下一次 Pod 重启。
  • 磁盘压力。 如果即使目录存在,temp 目录仍持续消失或写入尝试继续失败,请检查 Pod 的可写层(或挂载到 /opt/sonarqube/temp 的卷,如有)是否已耗尽空间。