Amazon Web Services ブログ

VPC ネットワーク内での長時間稼働 TCP 接続の実装

 本投稿は Implementing long-running TCP Connections within VPC networking (記事公開日: 2022 年 11 月 28 日) を翻訳したものです。

多くのネットワークアプライアンスは、アイドル接続タイムアウトを定義して、非アクティブ期間が経過すると接続を終了します。たとえば、NAT GatewayAmazon Virtual Private Cloud (Amazon VPC) エンドポイント、Network Load Balancer (NLB) などのアプライアンスのアイドルタイムアウトは、現在 350 秒に固定されています。アイドルタイムアウトの期限が切れた後に送信されたパケットは、ターゲットに配信されません。

データベースへの同期呼び出しなど、一部のアプリケーションまたは API リクエストでは、非アクティブ期間が長くなることがあります。アイドル状態のTCP接続を維持しなければならないシナリオがある場合は、この記事が役に立ちます。この記事では、アプリケーションが接続の両端でトラフィックを生成せずにTCP接続を開いたままにする必要がある場合に、アイドル状態でもTCP接続を存続させるテクニックを紹介します。

概要

Amazon VPC は、NLB、Gateway Load Balancer (GWLB)インターフェイス VPC エンドポイント、NAT Gatewayなど、さまざまなネットワーキングサービスを提供します。さらに、ファイアウォールなど、セルフマネージドまたはオンプレミスのネットワークアプライアンスを所有している場合があります。ほとんどの場合、これらのネットワークアプライアンスではパケットが行き来しています。ただし、場合によっては、パケットが送信されないまま接続がしばらくアイドル状態になることがあります。

Amazon VPC でセキュアなクラウド環境を構築する場合は、プライベートサブネットでワークロードをホストすることをお勧めします。いくつかの AWS サービスを使用してこれを実現できます。

  1. アウトバウンド接続には NAT Gatewayを使用できます。プライベートサブネットのインスタンスは VPC 外の宛先に送信できますが、外部装置から NAT Gatewayの背後にあるインスタンスには送信できません。プライベート NAT Gateway をプライベート通信に活用することもできます。たとえば、統合の目的で単一 IP を使用して VPC 外部のリソースと通信する場合などです。
  2. インターフェイス VPC エンドポイントは、サービスへの接続が AWS プライベートネットワーク内にとどまるようにします。
  3. NLB では、パブリックまたはプライベート NLB サービスを作成し、複数のクライアントに公開できます。そして、クライアントからNLB 経由でプライベートサブネットのターゲットにトラフィックを送信します。
  4. GWLBでは、透過的な bump-in-the-wire (BITW) 装置がトラフィックをファイアウォールインスタンスに送信し、インスペクションとファイアウォール処理を行います。

上記のすべてのサービスのアイドルタイムアウトは、現在 350 秒に設定されています。TCP 接続を介して継続的にパケット交換を行い、パケットが 350 秒以内に送信される場合は、期待どおりに機能します。詳細については、次の図を参照してください。

(図 1. ネットワーク接続が 250 秒後にレスポンスを正常に送信します)

ご利用の環境で使用されている他のネットワークアプライアンスは、上記のサービスとは異なるアイドルタイムアウト値を使用する場合があることに注意してください。

アイドルタイムアウト

一定時間アイドル状態の TCP 接続はタイムアウトする可能性があります。タイムアウトが発生すると、ネットワークアプライアンスはこの接続をアクティブとは見なさなくなり、どちらの方向にもパケットを配信しなくなります。この場合に何が起こるかは、そのネットワークアプライアンスの仕様によって異なります。NAT Gateway、NLB、およびインターフェイス VPC エンドポイントでアイドルタイムアウトが発生すると、接続の送信側はTCP RST パケットをタイムアウトしたアプライアンスから受信します。ただし、受信側は TCP パケットを受信しません。そのため、受信側では、接続が使用できなくなっても、自身のタイムアウトになるまでは接続が確立された状態にあると見なされることがあります。

例を見てみましょう。クライアントはサーバーにリクエストを送信し、サーバーのレスポンスを待っています。サーバーはこのリクエストを処理していますが、長期間クライアントに何も送り返していません。接続タイムアウトは、クライアントとサーバの間で使用されるネットワークアプライアンスで発生しますが、どちら側もそれを認識していません。サーバー側は、接続がすでに閉じられていることを知らずにリクエストの処理を続けます。サーバーが処理を完了してレスポンスの送り返しを開始すると、ネットワークアプライアンスから RST パケットを受信し、接続が閉じられたことが通知されます。結果的に、サーバー側にはリソースが無駄に消費され、余分な負荷がかかったことになります。

一方、クライアントは引き続きサーバーからのレスポンスを待ちます。接続はすでにタイムアウトしているため、クライアントはサーバーからのレスポンスを受け取ることはありません。仮に、クライアント側でリクエストのタイムアウトが明示的に定義されている場合は、リクエストは最終的にタイムアウトします。ただし、それが起こるまで、クライアントはレスポンスを待ち続け、リソースを消費する可能性があります。

(図 2. ネットワーク接続は 350 秒後に終了します。レスポンスは送信できません)

アイドルタイムアウトは、古い接続が適切なタイミングで確実に破棄される為に重要です。ただし、サーバーのレスポンスを長時間待つ必要がある場合があります。これに対処するには、アクティブのままにしておきたい接続のアイドル状態が、ネットワークアプライアンスのアイドルタイムアウト値よりも長くならないようにする必要があります。これは TCP キープアライブで実現できます。

GWLBの場合、TCPフローがアイドルタイムアウトより長くアイドル状態になると、GWLBの接続状態テーブルから接続が削除されることに注意してください。その結果、そのフローの後続のパケットは新しいフローとして扱われ、別の正常なファイアウォールインスタンスに送信されます。これによって GWLB 側から TCP がリセットされるわけではありませんが、他のファイアウォールインスタンスがトラフィックを予期していないため、後続のパケットはすべてドロップされる可能性があります。結果的に、フローもタイムアウトが発生する可能性があります。このシナリオでも TCP キープアライブが役立ちます。また、要件によっては、特定の送信元と宛先からのすべてのパケットが常に同じアプライアンスに配信されるように、GWLB フロースティッキー設定の変更を検討することもできます。これはタイムアウトとは無関係に動作します。GWLB フロースティッキーの詳細は、GWLB のマニュアルを参照してください。

TCP キープアライブ

TCP キープアライブは、アイドル状態の TCP 接続を存続させることができるメカニズムです。このメカニズムは RFC 1122 に従って実装されています。キープアライブプローブは、ペイロード長が 0 (Len=0)、受信側が期待しているシーケンス番号から 1 を引いた状態 (SEG.SEQ = SND.NXT-1) で送信されます。このセグメントは、以前に確認された別のセグメントの再送信を模倣していたため、受信側は 、TCP ACK を返信することでパケットを認識できたことを示します。

(図 3. TCP キープアライブは、アイドルタイムアウトによる接続の終了を防ぎます。レスポンスは 400 秒後に正常に送信されます)

デフォルトでは、LinuxベースのOSでは、TCPキープアライブパケットが7200秒(2時間)後に送信されるように構成されています。これは、クラウドで実行されているネットワークアプライアンスのアイドルタイムアウト設定よりも大幅に長くなります。

次のガイドでは、TCP キープアライブを有効にして設定する方法について説明します。これには、次の 2 つの更新を行う必要があります。

  1. ノード OS で TCP キープアライブ設定を行うーこの方法の対象サービスは Amazon Elastic Compute Cloud (Amazon EC2)Amazon Elastic Kubernetes Service (Amazon EKS) です。
  2. アプリケーションで TCP キープアライブを有効にする — この方法の対象サービスは AWS Command Line Interface (AWS CLI)AWS SDK for PythonAWS SDK for Java です。

Linux ベース OS での TCP キープアライブ

TCP キープアライブは、アイドル状態を検出したときに、接続の反対側を「プロービング」することで機能します。

  • 各 TCP 接続は、カーネル内の一連の設定に関連付けられています。SO_KEEPALIVE オプションで新しいソケットが作成されるたびに、カーネル設定が適用されます。
  • ESTABLISHED 状態の各接続は追跡され、タイマーに関連付けられます。接続が設定された期間より長くアイドル状態になると、OS はリモートノードに、データなし及びACK フラグがオンの状態のキープアライブプローブパケットを送信します。
  • TCP/IP 仕様上、これは重複した ACK と同様に処理されます。リモートノードは、単純にデータなしの ACK パケットを返信します。リモートノード側で何も実装する必要はなく、このロジックは TCP プロトコルの一部として実装されます。

Linux ベースの OS では、TCP キープアライブの動作を設定するための OS レベルのパラメータが 3 つあります。

  • tcp_keepalive_time — 最初のTCP キープアライブプローブ送信が開始されるまでに、接続がアイドル状態になっていた時間。
  • tcp_keepalive_intvl — キープアライブプローブの送信間隔。このパラメータは tcp_keepalive_probes と連動して、リモートピアとの接続がクローズされたと宣言します。
  • tcp_keepalive_probes ー 送信したもののACK応答を受け取っていないプローブ数がこの数を超えると、接続がクローズされたと見なされます。

TCP キープアライブは 、Linux カーネルパラメータとして/proc/sys/net/ipv4/に設定されています。

cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

Linuxベース OS のデフォルト値は上記で確認できます。最初の 2 つの値は秒単位で、3 番目の値は絶対数です。これらの設定は、2 時間アイドル状態だった接続が TCP キープアライブプローブの送信を開始し、さらに約 11 分間(9 回のリトライ x 75 秒)、リモートピアからACK返答を受け取らない場合、この接続は切断されることを意味します。

(図 4. TCP キープアライブパラメータとワークフロー)

TCP キープアライブのデフォルト値を更新すると、デフォルトのシステム動作が変更されることに注意してください。必ず影響範囲を理解し、ロールバック戦略を立ててください。TCP キープアライブの設定値のチューニングはユースケースに大きく依存します。プローブの数が多すぎると、リソースの使用率が高くなり、トラフィックが混雑する可能性があります。また、プローブが少なすぎると、たとえばパケット損失が発生した場合などに接続が早く閉じられる可能性があります。

Linux EC2 インスタンスの TCP キープアライブの設定

以下の手順は、Amazon Linux 2 を実行している EC2 インスタンスで検証されました。

以下の設定では、ソケットで SO_KEEPALIVE オプションを使用するソフトウェアは、45 秒後に TCP キープアライブプローブの送信を開始します。接続がアイドル状態のままである限り、リモートピアが応答するまで、これらのプローブは 45 秒ごとに送信され続けます。リモートピアが 9 回連続してプローブに応答しない場合、接続はクローズされたと宣言されます。接続を介してデータパケットが送信される場合は、tcp_keepalive_time もリセットされ、プロセス全体がスタート時点に戻されます。

sysctl -w net.ipv4.tcp_keepalive_time=45
sysctl -w net.ipv4.tcp_keepalive_intvl=45
sysctl -w net.ipv4.tcp_keepalive_probes=9

システムを再起動しても TCP キープアライブ設定を維持するには、/etc/sysctl.conf ファイルを更新 (存在しない場合は作成) する必要があります。次の内容をファイルに追加します。

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

/etc/sysctl.conf ファイルを保存したら、sudo sysctl -p コマンドを実行すると、変更がすぐに反映され、再起動後も保持されます。この手順を実行した後にシステムを再起動して、新しい設定が適用されたことを確認することをお勧めします。

Amazon EKS クラスターの TCP キープアライブの設定

Amazon EKS マネージドノードグループに TCP Keepalive を設定するには、クラスター管理者がノードグループの作成中に関連する sysctls の更新を明示的に許可する必要があります。eksctl CLI を使用してクラスターを作成する場合、管理対象ノードグループに下記のスニペットを追加してください。

overrideBootstrapCommand: |
    #!/bin/bash
    set -o xtrace
    /etc/eks/bootstrap.sh SET_CLUSTER_NAME_HERE \
    --kubelet-extra-args "--allowed-unsafe-sysctls=net.ipv4.tcp_keepalive_time,net.ipv4.tcp_keepalive_intvl"

クラスターがプロビジョニングされたら、ポッドが許可された sysctls を使用できるようにするポッドセキュリティポリシーを作成します。クラスター管理者でない場合、これを設定するには適切なアクセス権限が必要です。

allowedUnsafeSysctls:
    - "net.ipv4.tcp_keepalive_time"
    - "net.ipv4.tcp_keepalive_intvl"

こして、ポッド定義でセキュリティコンテキストを設定することで、ポッドごとに TCP Keepalive sysctls を設定できるようになりました。

securityContext:
    sysctls:
        - name: "net.ipv4.tcp_keepalive_time"
          value: "45"
        - name: "net.ipv4.tcp_keepalive_intvl"
          value: "45"

アプリケーションで TCP キープアライブを有効にする

OS レベルの sysctls を設定するだけでは不十分です。アプリケーションでも TCP Keepalive が有効になっている必要があります。

以下の例は、AWS CLI と AWS SDK を使用して、最大 15 分 ( 900 秒) 実行できる AWS Lambda 関数を同期的に呼び出す場合に必要な変更を示しています。ユースケースによっては、使用している他のクライアントライブラリの他のシナリオでも同様の調整が必要になる場合があります。AWS SDK タイムアウトの設定情報については、この記事を参照してください。

AWS CLI

config ファイル ~/.aws/config を更新します。tcp_keepalive=true プロパティを追加します。サポートされている他の設定プロパティについては、AWS CLI ドキュメントを参照してください。

[default]
region=us-east-1
output=yaml
max_attempts=1
tcp_keepalive=true

AWS CLI でリクエストを行う場合は、cli-read-timeout パラメータを使用してソケット読み取りタイムアウト (デフォルトは 60 秒) を定義します。

aws lambda invoke --function-name my-func out.txt --cli-read-timeout 900

AWS SDK for Python

AWS SDK for Python は boto3 とも呼ばれ、~/.aws/config ファイルから TCP キープアライブ設定を取得します。詳細については、設定ファイルの使用ドキュメントを参照してください。tcp_keepalive=true~/.aws/config に追加し、botocore.config.Config インスタンスを使用してソケット読み取りタイムアウトの値を設定します。

import boto3
from botocore.config import Config

my_config = Config(
    read_timeout = 900
)

lambda_client = boto3.client('lambda', config=my_config)
response = lambda_client.invoke(FunctionName='my-func')

AWS SDK for Java

AWS SDK for Java が内部的に使用しているデフォルトの Apache HTTP クライアントでは、TCP キープアライブが無効になっています。以下のようにキープアライブを有効にしたカスタム ApacheHttpClient インスタンスを作成し、タイムアウトを設定します。

ApacheHttpClient.Builder apacheHttpClientBuilder = ApacheHttpClient.builder();
apacheHttpClientBuilder.connectionMaxIdleTime(Duration.ofSeconds(900));
apacheHttpClientBuilder.connectionTimeToLive(Duration.ofSeconds(900));
apacheHttpClientBuilder.socketTimeout(Duration.ofSeconds(900));
apacheHttpClientBuilder.tcpKeepAlive(true);
SdkHttpClient sdkHttpClient = apacheHttpClientBuilder.build();

LambdaClientBuilder lambdaClientBuilder = LambdaClient.builder();
lambdaClientBuilder.httpClient(sdkHttpClient);

LambdaClient lambdaClient = lambdaClientBuilder.build();
InvokeRequest invokeRequest = InvokeRequest.builder()
    .functionName(FUNCTION_NAME)
    .build();

InvokeResponse invokeResponse = lambdaClient.invoke(invokeRequest);

ApacheHttpClient.Builder クラスは software.amazon.awssdk: apache-client パッケージの一部です。NettyNioAsyncHttpClientUrlConnectionHttpClient を使用する場合にも同様の手法が適用できます。

結論

このブログでは、Amazon EC2 インスタンスと Amazon EKS クラスターの OS レベルの TCP キープアライブ設定を行うことと、AWS Command Line Interface (AWS CLI)AWS SDK for Python 及び AWS SDK for Java を使用してアプリケーションで TCP キープアライブを有効にする方法について説明しました。

この手法を使用すると、NAT Gatewayインターフェイス VPC エンドポイント、またはアイドル状態の接続を終了する仕組みを持つ他の同様のネットワークアプライアンスの背後にあるワークロードからの接続を長時間維持できます。

翻訳は Partner Success Solutions Architect 銭 敏娟が担当しました。