当数据卷由 NFS 作为后端时,SonarQube Pod 在启动时卡住

问题描述

某个数据卷由 NFS export 提供后端存储的 SonarQube 实例无法变为 Ready。表现包括:

  • PostgreSQL Pod 处于健康状态,且没有重启。
  • SonarQube Pod 处于 Running 状态,或卡在启动循环中,但始终不报告 Ready。
  • Pod 日志在初始化期间会继续推进,随后在读取或写入 /opt/sonarqube/data 下文件的某一步骤卡住。不会抛出异常——进程只是长时间停止输出。
  • Pod 内部的文件模式和所有权可能看起来与非 NFS 环境不同,但修改它们并不能解决问题。

NFS export 本身已使用预期选项配置(例如 rw,async,no_root_squash,no_all_squash),并且该 export 可从集群访问。

根因

SonarQube 及其内置的 Elasticsearch 需要进行文件锁定,以协调对磁盘状态的访问。默认情况下,NFSv3 挂载通过 rpc.lockd / rpc.statd 协商锁。如果客户端无法访问这些锁守护进程(通常是因为它们未通过网络策略暴露,或者 NFS 服务器未运行它们),SonarQube 发起的文件锁操作就会无限期阻塞。Pod 仍然存活,但无法继续推进,这就表现为“启动卡住”。

要求客户端在本地处理锁、而不是联系服务器的挂载选项是 nolock。启用 nolock 后,文件锁变为进程本地锁——对于单 Pod 的 SonarQube 已经足够——因此启动可以正常继续。

故障排查

步骤 1 — 确认 Pod 是否卡在文件系统操作上

kubectl -n <NAMESPACE> logs <RELEASE>-sonarqube-xxxxx

典型特征是日志在某个步骤中途停止(最常见于 Elasticsearch 初始化期间,或在 /opt/sonarqube/data 下创建初始索引时),并且在数分钟内不再输出任何内容。

步骤 2 — 检查节点上的挂载选项

找到承载 SonarQube Pod 的节点,并检查为该 SonarQube data PVC 提供后端的 NFS export 的挂载选项:

kubectl -n <NAMESPACE> get pod <RELEASE>-sonarqube-xxxxx -o wide

# On the listed node
mount | grep nfs

如果挂载选项不包含 nolock,则很可能会出现该问题。存在诸如 local_lock=none 的选项,或者根本没有 lock 条目,都与这种故障模式一致。

步骤 3 — 先排除 export 侧配置错误

如果 export 缺少 rwno_root_squashno_all_squash,请先修复这些配置——它们属于不同的故障模式,不能通过 nolock 解决。只有在确认 export 选项正确,且 Pod 能够读取和写入文件(只是不能加锁)之后,才继续执行下面的解决方案。

解决方案

将 SonarQube 使用的 NFS PV 的挂载选项中添加 nolock,然后重启 SonarQube Pod,使其使用新选项重新挂载。

使用手动创建的 NFS PV 时

编辑 PersistentVolume,并将 nolock 添加到 spec.mountOptions

apiVersion: v1
kind: PersistentVolume
metadata:
  name: <PV_NAME>
spec:
  mountOptions:
    - nolock
    # other existing options (e.g. nfsvers=4.1, rw, ...)
  nfs:
    server: <NFS_SERVER>
    path: <EXPORT_PATH>
  # ...

使用动态 NFS StorageClass 时

编辑 StorageClass,并将 nolock 添加到其 mountOptions,然后重新创建 PVC(以及 Pod),以便新选项在新创建的 PV 上生效:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: <STORAGE_CLASS_NAME>
mountOptions:
  - nolock
provisioner: <NFS_PROVISIONER>
# ...

应用更改

更新 PV 或 StorageClass 后,重新创建 SonarQube Pod,使卷重新挂载:

kubectl -n <NAMESPACE> delete pod <RELEASE>-sonarqube-xxxxx

确认节点上已生效新的挂载选项,然后观察 Pod 启动:

# On the node now hosting the new Pod
mount | grep nfs    # expect to see nolock

kubectl -n <NAMESPACE> get pods -w

注意事项

  • nolock 对单 Pod 的 SonarQube 是安全的。 SonarQube 针对其数据卷以单 Pod 方式运行,因此进程本地锁已经足够。对于真正需要通过 NFS 锁进行跨节点协调的工作负载,不应使用 nolock
  • NFSv4。 在 NFSv4 上,文件锁定是协议本身的一部分,不需要 rpc.lockd / rpc.statd。如果该 export 可作为 NFSv4(vers=4 / vers=4.1)访问,但锁定仍然卡住,请先查找其他无关原因,再考虑应用 nolock
  • 参考。 Red Hat 关于 NFS 挂载选项的文档对 nolock 及其他选项进行了说明:Mounting NFS shares