K8s Java客户端listNode报ApiException: Gone异常解决方法
问题背景
使用Kubernetes Java客户端拉取集群全量节点信息时,抛出io.kubernetes.client.openapi.ApiException: Gone异常,异常栈如下:
io.kubernetes.client.openapi.ApiException: Gone at io.kubernetes.client.openapi.ApiClient.handleResponse(ApiClient.java:971) at io.kubernetes.client.openapi.ApiClient.execute(ApiClient.java:883) at io.kubernetes.client.openapi.apis.CoreV1Api.listNodeWithHttpInfo(CoreV1Api.java:33661) at io.kubernetes.client.openapi.apis.CoreV1Api.listNode(CoreV1Api.java:33559)
业务实现代码:
public V1NodeList continueQueryNodes(String _continue) { try { V1NodeList v1NodeList = coreV1Api.listNode(null, true, _continue, null, null, LIMIT, null, CommonUtils.REQUEST_TIME_OUT, null); return v1NodeList; } catch (ApiException e) { logger.error("request k8s node api error", e); return null; } }
已知环境信息:
- 集群总节点规模2000
- 初始分页LIMIT设为200,调整为500后异常仍复现
- 直接使用token调用api-server的
/api/v1/nodes接口可正常返回结果
根因分析
这个异常对应HTTP 410状态码,是Kubernetes分页查询场景的典型错误,核心原因是请求携带的_continue分页令牌失效,常见触发场景有三个:
- 参数错位/不一致:当前代码存在参数传值不规范问题,且分页查询要求除
_continue和limit外,其余所有查询参数(标签选择器、字段选择器、watch标记、资源版本等)在多次分页请求中必须完全一致,任意参数不匹配都会触发410。 - 分页令牌过期:Kubernetes的
_continue令牌是绑定首次查询时的etcd资源快照生成的,默认有效期和etcd压缩周期一致(通常为5分钟),如果分页拉取间隔过长、单次请求超时时间设置过长导致响应返回时快照已被压缩,令牌就会失效。 - 令牌非法复用:不同查询条件生成的
_continue令牌不能交叉使用,否则也会返回410。
解决方案
- 修正传参逻辑:分页拉取普通资源列表时,显式给
watch、allowWatchBookmarks参数传false,首次请求_continue传空字符串而非null,后续分页请求除_continue和limit外所有参数和首次请求完全保持一致。 - 增加410容错处理:捕获ApiException时判断状态码,若为410则清空当前持有的
_continue令牌,重新从第一页开始拉取,这是Kubernetes API官方推荐的分页容错处理逻辑,官方客户端内置的Informer组件也采用该逻辑处理令牌失效场景。 - 简化拉取逻辑:2000节点规模属于较小规模,可直接将
limit参数设为0关闭分页,一次拉取全量节点列表,避免多次分页带来的令牌过期问题,只要将请求超时设置在30秒以内即可正常返回。 - 调整超时配置:不要把请求超时时间设置过长,建议控制在10-30秒区间,避免请求等待过程中etcd压缩了对应快照导致令牌失效。
修正后的参考代码:
public List<V1Node> listAllNodes() throws ApiException { List<V1Node> result = new ArrayList<>(); String continueToken = ""; final Integer LIMIT = 500; final Integer TIMEOUT = 30; while (true) { try { V1NodeList nodeList = coreV1Api.listNode( null, // pretty false, // allowWatchBookmarks continueToken, // _continue null, // fieldSelector null, // labelSelector LIMIT, // limit null, // resourceVersion TIMEOUT, // timeoutSeconds false // watch ); result.addAll(nodeList.getItems()); continueToken = nodeList.getMetadata().getContinue(); // 没有剩余数据直接退出 if (continueToken == null || continueToken.isEmpty()) { break; } } catch (ApiException e) { // 捕获410异常,重置令牌重新拉取 if (e.getCode() == 410) { continueToken = ""; result.clear(); continue; } throw e; } } return result; }
内容的提问来源于stack exchange,提问作者zilong
相关产品推荐
相关产品推荐

