适用版本: 7.13-8.x
1. 错误异常的基本描述 #
failed to start watching service_tokens file [path] 表示 Elasticsearch 安全模块在尝试为 service_tokens 文件注册文件变更监控时失败。该文件用于存放服务账户(Service Account)对应的令牌信息,Elasticsearch 通过文件监控机制实现令牌的热加载,无需重启节点即可感知令牌变更。
当监控注册失败时,节点无法自动感知 service_tokens 文件的内容变化,这意味着新增、修改或删除的服务令牌不会被实时生效,可能影响依赖服务账户令牌的客户端请求、API 调用或跨集群访问场景。
常见现象 #
- 节点启动日志或运行日志中出现
failed to start watching service_tokens file异常信息,伴随IOException或ElasticsearchException。 - 修改
service_tokens文件后,新的服务令牌无法被节点识别,相关请求返回401 Unauthorized或403 Forbidden。 - 在容器化部署或 Kubernetes 环境中,该错误出现频率较高,尤其是在使用 ConfigMap、Secret 或网络文件系统挂载令牌文件时。
- 部分节点正常而部分节点报错,通常指向特定节点的文件系统或挂载配置差异。
典型报错与异常栈 #
日志中常见的异常形态如下:
ElasticsearchException: failed to start watching service_tokens file [/path/to/service_tokens]
Caused by: java.io.IOException: Unable to register watch for file
at org.elasticsearch.watcher.FileWatcher...
Caused by: java.nio.file.WatchException: Too many open files
2. 为什么会发生这个错误 #
service_tokens 文件的监控依赖底层操作系统的文件通知机制(Linux 下为 inotify,macOS 下为 FSEvents,Windows 下为 ReadDirectoryChangesW)。当 Elasticsearch 调用 resourceWatcherService.add() 为文件父目录注册监控时,若底层机制不可用或资源不足,就会抛出此异常。
常见原因通常包括:
- 目录不存在或路径错误:
service_tokens文件所在的父目录尚未创建,或xpack.security.authc.service.token_hashes_file.path配置指向了不存在的路径。 - 文件权限不足:Elasticsearch 运行用户(如
elasticsearch)对service_tokens文件或其父目录缺少读取、执行(遍历目录)权限,导致无法在该目录上注册监控。 - 容器/网络文件系统限制:Docker、Kubernetes 的某些卷类型(如
emptyDir、hostPath的某些挂载方式)、NFS、CIFS 等网络文件系统不支持inotify文件监控,或监控行为存在延迟和异常。 - 系统 inotify 资源耗尽:Linux 系统下
fs.inotify.max_user_watches或fs.inotify.max_user_instances达到上限,无法为新目录注册监控。 - 文件句柄耗尽:节点打开的文件句柄数达到
ulimit -n限制,间接导致文件监控注册失败。 - 只读文件系统:令牌文件被放置在只读挂载点(如某些容器镜像的只读层),文件监控机制无法正常工作。
- 安全配置错误:
service_tokens文件路径配置错误,指向了非常规位置,而该位置的文件系统类型不支持监控。
3. 如何排查和解决这个异常 #
建议按以下顺序逐步排查:
- 确认文件路径与存在性:检查
service_tokens文件的绝对路径,确认文件及其父目录真实存在。可通过ls -la查看文件状态。 - 检查目录权限:确认 Elasticsearch 运行用户对文件父目录拥有
r和x权限(读取和遍历),对文件本身拥有r权限。 - 检查系统 inotify 配置(Linux):执行
cat /proc/sys/fs/inotify/max_user_watches和cat /proc/sys/fs/inotify/max_user_instances,确认未达上限。 - 检查文件句柄限制:执行
ulimit -n查看当前限制,结合lsof -p <es_pid> | wc -l确认是否接近上限。 - 确认文件系统类型:在容器或特殊挂载场景下,使用
df -T或mount | grep <path>确认文件所在文件系统类型是否支持inotify。 - 区分异常类型:注意区分
failed to start watching service_tokens file(监控注册失败)和Failed to load service_tokens file(文件内容加载失败),两者根因不同。
排查时需要注意的问题 #
- 不要将监控失败直接等同于令牌内容错误,前者是文件系统/权限问题,后者是文件格式问题。
- 在 Kubernetes 环境中,若使用 ConfigMap 或 Secret 挂载
service_tokens文件,需注意某些版本中 ConfigMap 的更新不会触发 inotify 事件,或存在显著延迟。 - 若仅部分节点报错,优先对比各节点的配置文件、挂载方式和系统参数,而非直接怀疑 Elasticsearch 版本问题。
4. 如何解决这个错误 #
常用修复思路 #
修正文件路径与权限:确保
service_tokens文件放置在 Elasticsearch 可访问的本地目录,并赋予正确的权限。推荐将文件放在$ES_PATH_CONF目录下(如config/service_tokens),由 Elasticsearch 自动管理。# 确认文件与目录权限 ls -la $ES_PATH_CONF/service_tokens chown elasticsearch:elasticsearch $ES_PATH_CONF/service_tokens chmod 600 $ES_PATH_CONF/service_tokens调整系统 inotify 限制(Linux):若 inotify 资源不足,可适当调大内核参数。
# 临时调整 sysctl -w fs.inotify.max_user_watches=524288 sysctl -w fs.inotify.max_user_instances=1024 # 持久化配置 echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.conf echo "fs.inotify.max_user_instances=1024" >> /etc/sysctl.conf sysctl -p调整文件句柄限制:修改系统
ulimit配置,确保 Elasticsearch 进程有足够的文件句柄。# 在 systemd service 文件中添加 LimitNOFILE=65536避免不支持监控的挂载方式:将
service_tokens文件放在节点本地文件系统(如ext4、xfs)上,而非 NFS、CIFS 或某些容器特殊卷上。若必须在容器中管理令牌文件,考虑使用hostPath挂载本地目录。重启节点:在修复上述配置后,重启 Elasticsearch 节点使文件监控重新注册。
后续注意事项与推荐建议 #
- 将
service_tokens文件纳入配置管理,避免手动修改后忘记同步到所有节点。 - 建立对节点日志中
failed to start watching关键字的告警规则,尽早发现监控异常。 - 在 Kubernetes 环境中,若使用 Secret 管理令牌文件,建议结合
reloader等工具主动触发令牌重载,而非依赖 inotify。 - 定期审查服务账户令牌的使用情况,清理不再使用的令牌,减少令牌文件大小和监控压力。
借助 INFINI 产品提升排障效率 #
- INFINI Console 适合查看集群健康度、节点日志、安全配置状态和错误趋势,帮助快速判断异常是局部节点问题还是全局配置问题。
- INFINI Gateway 可部署在 Elasticsearch 前面做请求观测和流量治理,当服务令牌失效导致 401/403 错误时,可通过 Gateway 的日志和指标快速定位是令牌问题还是其他权限问题。
5. 小结 #
failed to start watching service_tokens file 指向的是文件监控注册失败,而非令牌内容本身的问题。排查时应优先关注目录存在性、文件权限、文件系统类型以及系统 inotify/文件句柄资源,而非直接修改令牌文件内容。在容器化和云原生部署场景下,尤其需要注意挂载方式和文件系统对 inotify 的支持情况。
只要把文件路径、权限、系统参数和部署方式四个维度检查到位,大多数此类异常都可以快速定位并修复。
相关错误 #
- Failed to load service_tokens file:加载服务令牌文件失败
- security exception:安全异常
- unauthorized:未授权访问
- all shards failed:所有分片失败
附:日志上下文 #
下面保留当前页面中的源码片段,便于结合异常调用栈定位问题:
FileWatcher watcher = new FileWatcher(file.getParent());
watcher.addListener(new FileReloadListener(file, this::tryReload));
try {
resourceWatcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
} catch (IOException e) {
throw new ElasticsearchException("failed to start watching service_tokens file [{}]", e, file.toAbsolutePath());
}
try {
tokenHashes = parseFile(file, logger);
} catch (IOException e) {
throw new IllegalStateException("Failed to load service_tokens file [" + file + "]", e);
}





