次の方法で共有


TCP キープアライブを使用して Azure Kubernetes Service のネットワークフォールト トレランスを向上させる

標準の伝送制御プロトコル (TCP) 接続では、接続がアイドル状態のとき、ピア間でデータフローは行われません。 そのため、TCP を使用して実行時間の長い要求を処理するサーバーと通信するアプリケーションまたは API 要求は、接続の終了または切断を認識するために接続タイムアウトに依存する必要があります。 この記事では、AZURE Kubernetes Service (AKS) でホストされているアプリケーションでのフォールト トレランスを強化するために TCP キープアライブを使用する方法について説明します。

TCP キープアライブについて

Azure Load Balancer (ALB) など、いくつかの Azure ネットワーク サービスを使用すると、アイドル状態の TCP 接続が終了するまでタイムアウト期間を構成できます。 TCP 接続が、ネットワーク サービスで構成されているタイムアウト時間よりも長くアイドル状態のままである場合、どちらの方向に送信された後続の TCP パケットも破棄される可能性があります。 または、サービスで TCP リセットが有効になっているかどうかに応じて、ネットワーク サービスから TCP リセット (RST) パケットを受信する場合があります。

ALB のアイドル タイムアウト機能は、クライアントとサーバー アプリケーションの両方のリソース使用率を最適化するように設計されています。 このタイムアウトは、ALB によって管理されるイングレスとエグレス トラフィックの両方に適用されます。 タイムアウトが発生すると、クライアントとサーバー アプリケーションは、接続に関連付けられている要求の処理を停止し、リソースを解放できます。 その後、これらのリソースを他の要求に再利用して、アプリケーションの全体的なパフォーマンスを向上させることができます。

AKS では、アイドル時の TCP リセットは、既定で Load Balancer で有効になっており、アイドル タイムアウト期間は 30 分です。 このタイムアウト期間は、az aks update コマンドを使用して調整できます。 次の例では、タイムアウト期間を 45 分に設定します。

az aks update \
    --resource-group myResourceGroup \
    --name myAKSCluster \
    --load-balancer-idle-timeout 45

調整する前に、タイムアウト時間を慎重に検討してください:

  • 期間が短すぎると、実行時間の長い操作が途中で終了し、要求が失敗し、ユーザー エクスペリエンスが低下する可能性があります。 また、タイムアウトが頻繁に発生し、エラー率が上がり、アプリケーションの信頼性が低下する可能性もあります。
  • 時間が長すぎると、アイドル状態の接続を開いたままにして、新しい要求の処理に使用できる容量を減らすことで、サーバー リソースをドレインする可能性があります。 また、サーバーの問題の検出を遅らせ、ダウンタイムが長くなり、負荷分散が非効率的になります。

AKS では、ALB を通過する南北トラフィック (イングレスとエグレス) とは別に、クラスター ネットワークで一般的に動作する東西トラフィック (ポッドからポッド) もあります。 このような場合のタイムアウト期間は、kube-proxy TCP 設定とポッドの TCP sysctl 設定によって定義されます。 既定では、kube-proxy は iptables モードで実行され、kube-proxy 仕様で定義されている既定の TCP タイムアウト設定が使用されます。 kube-proxy の既定の TCP タイムアウト設定は次のとおりです:

  • CLOSE_WAIT 状態の TCP 接続のネットワーク アドレス変換 (NAT) タイムアウトは 1 時間です。
  • 確立された TCP 接続のアイドル タイムアウトは 24 時間です。

クライアントとサーバーの両方が AKS クラスター内にある、またはそのうちの 1 つが外部にある特定の実行時間の長い操作では、Azure Load Balancer や Azure NAT Gateway などのネットワーク サービスに対して構成された期間よりも長いタイムアウトが必要になる場合があります。 ネットワーク サービスで構成された期間を超えて接続がアイドル状態を維持しないようにするには、TCP キープアライブ機能の使用を検討してください。 この機能により、応答の待機中にサーバーとクライアントの両方を使用でき、接続タイムアウトが発生する代わりに操作を再試行できます。

TCP 接続では、いずれかのピアが接続の側のキープアライブを要求できます。 キープアライブは、クライアント、サーバー、両方、またはどちらもなしで構成できす。 キープアライブ機構は、RFC1122 で定義されている標準仕様に従います。 キープアライブ プローブは、空のセグメントまたは 1 バイトのみを含むセグメントです。 これは、ピアからこれまでに受信した最大受信確認 (ACK) 番号より 1 つ小さいシーケンス番号を備えています。 プローブ パケットは、受信されたパケットを模倣します。 応答として、受信側は別の ACK パケットを送信します。 これは、接続がまだアクティブであることを送信者に示します。

RFC1122 仕様では、プローブまたは ACK のいずれかが失われた場合は再送信されません。 したがって、1 つのキープアライブ プローブに対する応答がない場合、接続が動作を停止したとは限りません。 この場合、送信側は、接続を終了する前に、プローブの送信をもう数回試行する必要があります。 接続のアイドル時間は、プローブに対して ACK が受信され、プロセスが繰り返されるとリセットされます。 キープアライブ プローブを使用すると、次のパラメーターを構成して動作を制御できます。 AKS では、Linux ベースのノードには、標準の Linux オペレーティング システムと同じ、次の既定の TCP キープアライブ設定があります:

  • キープアライブ時間 (秒単位): 最初のキープアライブ プローブが送信されるまでの非アクティブ時間。 既定の期間は 7200 秒または 2 時間です。
  • キープアライブ間隔 (秒単位): 受信確認が受信されない場合の後続のキープアライブ プローブ間の間隔。 既定の間隔は 75 秒です。
  • キープアライブ プローブ: 接続が使用できないと見なされるまでの未確認プローブの最大数。 既定値は 9 です。

キープアライブ プローブは TCP 層で管理されます。 有効にすると、プローブによってリクエスタ アプリケーションに対して次の結果が得られます。

  • 通常の操作: キープアライブ プローブはリクエスタ アプリケーションに影響しません。
  • ピアの再起動またはクラッシュ (プローブが確認されていません): アプリケーションが "接続タイムアウト" エラーを受け取ります。
  • ピアの再起動またはクラッシュ (RESET RST 応答): アプリケーションが "ピアによる接続のリセット" エラーを受け取ります。
  • ピア ホストに関するネットワークの問題: アプリケーションが "接続タイムアウト" または別の関連エラーを受け取る可能性があります。

次のセクションでは、クラスターとアプリケーション ポッドの sysctl 設定を変更して TCP キープアライブを設定する方法について説明します。

AKS での TCP キープアライブの構成

AKS を使用すると、クラスター管理者は、ノードのオペレーティング システムと kubelet パラメーターを、ワークロードの要件に合わせて調整できます。 クラスターまたは新しいノード プールを設定するときに、管理者はワークロードに関連する sysctl を有効にすることができます。 Kubernetes は sysctl を 2 つのグループに分類します: 安全安全でない

安全な sysctl は、名前空間が設定され、同じノード上のポッド間で適切に分離された sysctl です。 この分離は、1 つのポッドに対して安全な sysctl を構成しても、ノード上の他のポッド、ノードの正常性、またはポッドの CPU またはメモリ リソースの制限を超えないようにすることを意味します。 Kubernetes では、既定で安全な sysctl が有効になります。 Kubernetes 1.29 の時点では、すべての TCP キープアライブ sysctl は安全と見なされます。

  • net.ipv4.tcp_keepalive_time
  • net.ipv4.tcp_fin_timeout
  • net.ipv4.tcp_keepalive_intvl
  • net.ipv4.tcp_keepalive_probes

安全と安全でない sysctl とその構成の詳細については、「Azure Kubernetes Service (AKS) ノード プールのノード構成をカスタマイズする」を参照してください。

Kubernetes 1.29 以降では、TCP キープアライブ sysctl は安全と見なされ、既定で有効になっています。 クラスターで明示的に有効にする必要はありません。

ポッド定義で次のようにセキュリティ コンテキストを設定することで、目的のポッドで TCP キープアライブ sysctl を構成できます:

apiVersion: v1
kind: Pod
metadata:
  name: busybox-sysctls
spec:
  securityContext:
    sysctls:
      - name: "net.ipv4.tcp_keepalive_time"
        value: "45"
      - name: "net.ipv4.tcp_keepalive_probes"
        value: "5"
      - name: "net.ipv4.tcp_keepalive_intvl"
        value: "45"
  containers:
    - name: busybox
      image: busybox
      command: ["sleep", "3600"]

この仕様を適用すると、次の TCP キープアライブ動作が実装されます:

  • net.ipv4.tcp_keepalive_time は、接続が 45 秒間非アクティブになってから送信されるようにキープアライブ プローブを構成します。
  • net.ipv4.tcp_keepalive_probes は、接続を使用できないと見なす前に、未確認の 5 つのキープアライブ プローブを送信するようにオペレーティング システムを構成します。
  • net.ipv4.tcp_keepalive_intvl は、2 つのキープアライブ プローブのディスパッチ間の期間を 45 秒に設定します。

TCP キープアライブ sysctl は Linux カーネルで名前空間付けされています。つまり、ノード上のポッドごとに個別に設定できます。 この分離により、ポッドのセキュリティ コンテキストを使用してキープアライブ設定を構成できます。これは、同じポッド内のすべてのコンテナーに適用されます。

ポッドは、キープアライブ プローブを送信して応答する準備ができました。 設定を確認するには、次のようにポッドで sysctl コマンドを実行します:

kubectl exec -it busybox-sysctls -- sh -c "sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes"

コマンドを実行すると、次の出力が生成されます:

net.ipv4.tcp_keepalive_time = 45
net.ipv4.tcp_keepalive_intvl = 45
net.ipv4.tcp_keepalive_probes = 5

次のセクションでは、クライアントとの接続でアプリケーションで TCP キープアライブが有効になっていることを確認する方法について説明します。

アプリケーションでの TCP キープアライブの構成

TCP クライアント アプリケーションは、キープアライブ プローブがサーバーに送信されるように TCP キープアライブを有効にする必要があります。 ほとんどのプログラミング言語とフレームワークには、ソケット接続で TCP キープアライブを有効にするオプションが用意されています。 次の例では、Python の socket ライブラリを使用します:

import socket
import sys

# Create a TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Enable TCP keepalive
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

# Optional: Set TCP keepalive parameters (Linux specific).
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)    # Idle time before keepalive probes
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)   # Interval between keepalive probes
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)      # Number of keepalive probes

# Connect to the server
server_address = ('server.example.com', 12345)
print(f'Connecting to {server_address[0]} port {server_address[1]}')
sock.connect(server_address)

try:
    # Send and receive data
    message = 'This is a test message.'
    print(f'Sending: {message}')
    sock.sendall(message.encode())

    # Wait for a response
    data = sock.recv(1024)
    print(f'Received: {data.decode()}')

finally:
    print('Closing connection')
    sock.close()

次の点に注意してください。

  • アプリケーションは TCP キープアライブを有効にします。
  • キープアライブ プローブは、アイドル時間が 60 秒後に送信されます。
  • キープアライブ プローブは、10 秒間隔で送信されます。
  • 5 つの連続するプローブが失敗すると、接続が閉じます。

kubelet の sysctl を介して設定されたシステム レベルの TCP キープアライブ構成は、アプリケーションの TCP キープアライブ設定によってオーバーライドされることに注意してください。 アプリケーション全体で一貫したキープアライブ動作を維持するには、kubelet レベルでキープアライブ パラメーターを設定します。 次に、アプリケーション内で個々のパラメーターを指定せずにソケットでキープアライブ オプションを有効にして、システム レベルのキープアライブ パラメーターがアプリケーションに使用されるようにします。 前の例のように、システム レベルのパラメーター値が絶対に必要な場合は、個々のアプリケーションのみがオーバーライドできるようにします。

.NET を使用している場合、次のコードは前の Python の例と同じ結果を生成します:

static async Task Main()
{
    using SocketsHttpHandler handler = new SocketsHttpHandler();

    handler.ConnectCallback = async (ctx, ct) =>
    {
        var s = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
        try
        {
            s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime,60);
            s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 10);
            s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 5);
            await s.ConnectAsync(ctx.DnsEndPoint, ct);
            return new NetworkStream(s, ownsSocket: true);
        }
        catch
        {
            s.Dispose();
            throw;
        }
    };

    // Create an HttpClient object
    using HttpClient client = new HttpClient(handler);

    // Call asynchronous network methods in a try/catch block to handle exceptions
    try
    {
        HttpResponseMessage response = await client.GetAsync("<service url>");

        response.EnsureSuccessStatusCode();

        string responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Read {responseBody.Length} characters");
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine("\nException Caught!");
        Console.WriteLine($"Message: {e.Message} ");
    }
}

詳細については、「ConnectCallback ハンドラー」を参照してください。

HTTP/2 キープアライブ

gRPC などの HTTP/2 ベースの通信プロトコルを使用する場合、TCP キープアライブ設定はアプリケーションに影響しません。 HTTP/2 は RFC7540 仕様に従います。この仕様では、クライアントが PING フレームをサーバーに送信し、サーバーが PING ACK フレームで直ちに応答することが義務付けられています。 HTTP/2 はネットワーク スタックのレイヤー 7 で動作し、レイヤー 4 で動作する TCP によって提供されるデータ配信の保証の利点があります。 すべての HTTP/2 要求で応答が保証されるため、HTTP/2 トランスポートに必要なキープアライブ構成はタイムアウト設定だけです。 構成されたタイムアウト期間の前に PING ACK が受信されない場合、接続は切断されます。

アプリケーションが HTTP/2 トランスポートを使用する場合、サーバーはキープアライブをサポートし、その動作を定義する役割を担います。 クライアントのキープアライブ設定は、サーバーの設定と互換性がある必要があります。 たとえば、クライアントが PING フレームをサーバーが許可するよりも頻繁に送信する場合、サーバーは HTTP/2 GOAWAY フレーム応答を使用して接続を終了します。

gRPC アプリケーションの場合、クライアントとサーバーは、PING フレーム間の間隔、チャネルが存在できる最大時間などのキープアライブ設定と既定値をカスタマイズできます。 キープアライブを使用したクライアントおよびサーバー アプリケーションを示す構成可能なオプションと言語固有の例の完全な一覧については、「gRPC キープアライブ構成仕様」を参照してください。

ベスト プラクティス

キープアライブ プローブはアプリケーションのフォールト トレランスを向上させることができますが、帯域幅を増やすこともできます。帯域幅が増え、ネットワーク容量に影響を与え、追加料金が発生する可能性があります。 さらに、モバイル デバイスでは、ネットワーク アクティビティの増加がバッテリ寿命に影響する可能性があります。 そのため、次のベスト プラクティスに従うことが重要です:

  • パラメーターのカスタマイズ: アプリケーションの要件とネットワーク条件に基づいてキープアライブ設定を調整します。
  • アプリケーション レベルのキープアライブ: 暗号化された接続 (TLS/SSL など) の場合は、セキュリティで保護されたチャネル経由でプローブが確実に送信されるように、アプリケーション層にキープアライブ メカニズムを実装することを検討してください。
  • 監視とログ記録: ログ記録を実装して、トラブルシューティングのためにキープアライブによって誘発される接続クロージャを監視します。
  • フォールバック メカニズム: 再試行ロジックやフェールオーバー戦略など、切断を適切に処理するようにアプリケーションを設計します。