メインコンテンツへスキップ

kubectl の TLS handshake timeout の原因を追ったら、原因は Kubernetes ではなく MTU だった話

    はじめに

    MicroK8s 環境で突然 `kubectl` が使えなくなりました。

    実行すると:

    kubectl get nodes

    以下のエラーになります。

    couldn't get current server API group list:
    Get "https://192.168.101.13:16443/api?timeout=32s":
    net/http: TLS handshake timeout

    最初は、

    • MicroK8s の API Server が落ちた?

    • 証明書がおかしい?

    • kubeconfig が壊れた?

    • kubectl のバージョン問題?

    あたりを疑いました。

    しかし、最終的な原因は Wi-Fi 経由の MTU 問題でした。


    環境

    今回の環境は以下です。

    クライアント側

    • Ubuntu 24.04

    • ThinkCentre M75q Gen2

    • Wi-Fi 接続

    • kubectl v1.33.13

    ネットワーク:

    wlp2s0
      IP: 192.168.11.225

    Kubernetes側

    • MicroK8s

    • API Server

    https://192.168.101.13:16443

    最初の切り分け

    TCP接続確認

    まずポート自体が開いているか確認しました。

    curl -Ik https://192.168.101.13:16443/api

    結果:

    HTTP/2 401

    401 は認証エラーなので、逆に言えば:

    • TCP接続できている

    • HTTPSサーバーは応答している

    ということです。


    証明書確認

    次に TLS 証明書を疑いました。

    openssl で確認:

    openssl s_client \
    -connect 192.168.101.13:16443

    結果:

    TLSv1.3
    Cipher is TLS_CHACHA20_POLY1305_SHA256

    さらにクライアント証明書を指定すると:

    Verification: OK

    になりました。

    つまり:

    • サーバー証明書 OK

    • CA OK

    • クライアント証明書 OK

    • mTLS OK

    です。


    kubectl だけ失敗する謎

    ここで疑問が出ました。

    なぜ:

    | コマンド | 結果 |
    | ------- | -- |
    | curl | 成功 |
    | openssl | 成功 |
    | kubectl | 失敗 |

    なのか?

    kubectl は Go の TLS 実装を使っています。

    そのため、

    「OpenSSLでは成功するが、Go TLSでは失敗する」

    という状態でした。


    tcpdump で決定的な証拠

    原因特定のため tcpdump を取得しました。

    sudo tcpdump \
    -i wlp2s0 \
    host 192.168.101.13 and port 16443 \
    -nn -vv

    すると異常な通信が見えました。

    例:

    192.168.11.225 > 192.168.101.13
    length 1500
    seq 1:1449

    その後:

    SACK {1449:1472}

    が返っています。

    これはサーバー側が、

    「1449〜1472 byte のデータが届いていない」

    と言っている状態です。

    さらに同じパケットを何度も再送しています。

    つまり:

    • TCP接続は成功

    • TLS ClientHello送信

    • 途中の大きなパケットが欠落

    • TLS handshake 完了できない

    という流れでした。


    なぜ curl は成功して kubectl は失敗したのか

    原因は TLS ClientHello のサイズ差でした。

    kubectl は Kubernetes API に接続するため:

    • HTTP/2

    • ALPN

    • TLS拡張

    • 証明書情報

    などを含みます。

    その結果、最初の TLS パケットが大きくなります。

    一方:

    • curl

    • openssl

    は条件によって小さいパケットで済み、問題が表面化しませんでした。


    解決方法

    Wi-Fi インターフェースの MTU を下げました。

    変更前:

    MTU 1500

    変更:

    sudo ip link set dev wlp2s0 mtu 1400

    その後:

    kubectl get nodes

    結果:

    NAME       STATUS   ROLES    AGE   VERSION
    microk8s   Ready    <none>   15d   v1.33.13

    無事復旧しました。


    途中で試したが効果がなかったもの

    kubectl バージョン変更

    kubectl:

    v1.32.9
    ↓
    v1.33.13

    へ更新しました。

    しかし原因ではありませんでした。


    HTTP/2無効化

    GODEBUG=http2client=0 kubectl get nodes

    も試しました。

    改善なし。


    TLS 1.3無効化

    GODEBUG=tls13=0 kubectl get nodes

    も改善なし。


    NICオフロード無効化

    以下も試しました。

    sudo ethtool -K wlp2s0 tso off gso off gro off

    しかし改善しませんでした。

    これは NIC 内部の処理を変える設定であり、今回の問題だった経路 MTU には影響しませんでした。

    最終的には元に戻しました。

    sudo ethtool -K wlp2s0 tso on gso on gro on

    今回学んだこと

    TLS handshake timeout でも TLS が原因とは限らない

    エラーを見ると、

    TLS handshake timeout

    なので証明書や暗号方式を疑いました。

    しかし実際には:

    TLS開始
     ↓
    大きなパケット送信
     ↓
    MTU超過
     ↓
    パケット欠落
     ↓
    TLS完了できない

    というネットワーク問題でした。


    Kubernetes系トラブルは下位層から見る

    今回の調査順:

    1. kubectlエラー確認

    2. curl確認

    3. openssl確認

    4. 証明書確認

    5. tcpdump確認

    6. MTU変更

    という流れでした。

    Kubernetes の問題に見えても、

    • TCP

    • MTU

    • Firewall

    • Routing

    など下位レイヤーを見ることが重要だと再認識しました。


    まとめ

    MicroK8s の `kubectl TLS handshake timeout` は、

    「API Serverが死んでいる」

    とは限りません。

    今回の原因は:

    Wi-Fi経由のMTU問題により、kubectl の TLS ClientHello パケットが正常に通らなかったこと

    でした。

    解決した設定:

    sudo ip link set dev wlp2s0 mtu 1400

    でした。

    同じように、

    • curl は通る

    • openssl は通る

    • kubectl だけ TLS handshake timeout

    という症状が出た場合は、MTUを疑う価値があります。


    あなたへのおすすめ