You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fabric8 Kubernetes Client无法连接集群:unexpected end of stream问题排查

Fabric8 Kubernetes Client执行get pods失败(kubectl正常):tls-server-name配置问题排查

问题背景

使用Fabric8 Kubernetes Client 6.7.2执行简单的get pods请求时失败,相同KUBECONFIG配置下kubectl可正常执行操作。本地通过隧道连接Kubernetes 1.24集群,无隧道的集群配置下代码可正常运行。

环境配置

本地kubectl配置如下:

apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.0.0.1:6443
    tls-server-name: xxx.<redacted>NNNN.us-west-2.aws.host
  name: mycluster
contexts:
- context:
    cluster: mycluster
    namespace: default
    user: kube-aws-admin
  name: mycluster
current-context: mycluster
kind: Config
preferences: {}
users:
- name: kube-aws-admin
  user:
    token: REDACTED

设置环境变量KUBECONFIG=$HOME/.kube/mycluster.conf后,kubectl -n myns get pods可正常输出,但使用以下Fabric8 Client代码执行时失败:

new KubernetesClientBuilder().build().pods().inNamespace("myns").list().getItems().... // 常规调用逻辑

错误信息

运行代码时抛出以下异常:

Exception in thread "main" io.fabric8.kubernetes.client.KubernetesClientException: Operation: [list]  for kind: [Pod]  with name: [null]  in namespace: [myns]  failed.
    at io.fabric8.kubernetes.client.KubernetesClientException.launderThrowable(KubernetesClientException.java:159)
    at io.fabric8.kubernetes.client.dsl.internal.BaseOperation.list(BaseOperation.java:422)
    at io.fabric8.kubernetes.client.dsl.internal.BaseOperation.list(BaseOperation.java:388)
    at io.fabric8.kubernetes.client.dsl.internal.BaseOperation.list(BaseOperation.java:92)
    ...
Caused by: java.io.IOException: unexpected end of stream on Connection{127.0.0.1:6443, proxy=DIRECT hostAddress=/127.0.0.1:6443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1}
    at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.waitForResult(OperationSupport.java:515)
    at io.fabric8.kubernetes.client.dsl.internal.BaseOperation.list(BaseOperation.java:420)
    ... 4 more
Caused by: java.io.IOException: unexpected end of stream on Connection{127.0.0.1:6443, proxy=DIRECT hostAddress=/127.0.0.1:6443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 protocol=http/1.1}
    at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:208)
    at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:88)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:127)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:257)
    at okhttp3.RealCall$AsyncCall.execute(RealCall.java:201)
    at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.EOFException: \n not found: limit=0 content=…
    at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:236)
    at okhttp3.internal.http1.Http1Codec.readHeaderLine(Http1Codec.java:215)
    at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:189)
    ... 19 more

排查过程与尝试

  • 确认客户端访问地址127.0.0.1:6443及TLS使用符合配置要求
  • 移除kubeconfig中的tls-server-name字段后,kubectl也会出现Unable to connect to the server: EOF错误,说明该字段是连接成功的关键
  • 已尝试设置KUBERNETES_MASTER、NO_PROXY等环境变量,指定context,更换JDK及OkHttp版本,问题仍存在;官方Java Kubernetes Client也出现相同错误

疑问解答

1. 是否Fabric8 Kubernetes Client忽略了tls-server-name配置?

是的,Fabric8 Kubernetes Client 6.7.2版本确实存在未正确读取并使用kubeconfig中tls-server-name字段的问题。该字段用于TLS握手时指定SNI(Server Name Indication),而kubectl/client-go内部会正确处理该配置,确保TLS握手时传递正确的服务器名称,从而匹配隧道后端的Kubernetes API服务器证书。

2. 问题是否出在OkHttp而非Fabric8?

不是。错误的直接表现是OkHttp连接出现EOF,但根源是Fabric8未将tls-server-name配置传递到OkHttp的TLS握手参数中,导致SNI未设置,隧道后端的API服务器无法完成TLS握手,提前关闭连接,最终触发EOF错误。

解决方案

方案一:手动配置Fabric8 Client的SNI参数

通过自定义OkHttp客户端,显式从kubeconfig中读取tls-server-name并设置到TLS握手的SNI中:

import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.KubeConfigBuilder;
import okhttp3.OkHttpClient;

import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class K8sClientExample {
    public static void main(String[] args) throws Exception {
        // 加载kubeconfig配置
        Config config = (Config) new KubeConfigBuilder().build();
        String tlsServerName = config.getTlsServerName();

        // 构建自定义OkHttp客户端
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .sslSocketFactory(createSslSocketFactory(tlsServerName), new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {}

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {}

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                })
                .hostnameVerifier((hostname, session) -> true) // 对应insecure-skip-tls-verify=true
                .build();

        // 创建Fabric8客户端
        try (KubernetesClient client = new KubernetesClientBuilder()
                .withConfig(config)
                .withHttpClient(okHttpClient)
                .build()) {
            // 执行pod列表查询
            client.pods().inNamespace("myns").list().getItems().forEach(pod ->
                    System.out.println(pod.getMetadata().getName()));
        }
    }

    private static SSLSocketFactory createSslSocketFactory(String tlsServerName) throws Exception {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, null, new SecureRandom());

        return new SSLSocketFactory() {
            private final SSLSocketFactory delegate = sslContext.getSocketFactory();

            @Override
            public String[] getDefaultCipherSuites() {
                return delegate.getDefaultCipherSuites();
            }

            @Override
            public String[] getSupportedCipherSuites() {
                return delegate.getSupportedCipherSuites();
            }

            @Override
            public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
                Socket socket = delegate.createSocket(s, host, port, autoClose);
                if (socket instanceof SSLSocket && tlsServerName != null) {
                    ((SSLSocket) socket).setHostname(tlsServerName);
                }
                return socket;
            }

            @Override
            public Socket createSocket(String host, int port) throws IOException {
                return delegate.createSocket(host, port);
            }

            @Override
            public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
                return delegate.createSocket(host, port, localHost, localPort);
            }

            @Override
            public Socket createSocket(InetAddress host, int port) throws IOException {
                return delegate.createSocket(host, port);
            }

            @Override
            public Socket createSocket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException {
                return delegate.createSocket(host, port, localAddress, localPort);
            }
        };
    }
}

方案二:升级Fabric8 Kubernetes Client版本

升级到7.x及以上版本,该版本已修复tls-server-name字段的读取和使用问题,会自动从kubeconfig中读取该字段并设置到TLS握手的SNI参数中,无需手动配置。

内容的提问来源于stack exchange,提问作者winitzki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 10:32:39