1. 序章

このチュートリアルでは、引き続きJavaKubernetesAPIについて説明します。 今回は、ウォッチを使用してクラスターイベントを効率的に監視する方法を示します。

2. Kubernetesウォッチとは何ですか?

Kubernetes API に関する以前の記事では、特定のリソースまたはそれらのコレクションに関する情報を復元する方法を示しました。 特定の時点でこれらのリソースの状態を取得することだけが必要な場合は、これで問題ありません。 ただし、Kubernetesクラスタは本質的に非常に動的であるため、通常はこれだけでは不十分です。

ほとんどの場合、これらのリソースを監視し、発生したイベントを追跡する必要があります。 たとえば、ポッドのライフサイクルイベントや展開ステータスの変更を追跡することに関心があるかもしれません。 ポーリングを使用することはできますが、このアプローチにはいくつかの制限があります。 まず、監視するリソースの数が増えると、拡張性が低下します。 次に、ポーリングサイクルの間に発生するイベントを失うリスクがあります。

これらの問題に対処するために、Kubernetesにはウォッチの概念があります。は、watchクエリパラメーターを介してすべてのリソースコレクションAPI呼び出しで使用できます。 その値がfalseであるか省略されている場合、 GET 操作は通常どおりに動作します。サーバーは要求を処理し、指定された基準に一致するリソースインスタンスのリストを返します。 ただし、watch = trueを渡すと、動作が大幅に変わります:

  • 応答は、変更のタイプと影響を受けるオブジェクトを含む一連の変更イベントで構成されます。
  • ロングポーリングと呼ばれる手法を使用して、イベントの最初のバッチを送信した後、接続は開いたままになります

3. ウォッチの作成

JavaKubernetesAPIのサポート時計を通って時計単一の静的メソッドを持つクラス: createWatch。 このメソッドは3つの引数を取ります。

  • KubernetesAPIサーバーへの実際のREST呼び出しを処理するApiClient
  • 監視するリソースコレクションを記述するCallインスタンス
  • 予想されるリソースタイプのTypeToken

listXXXCall()メソッドのいずれかを使用して、ライブラリで使用可能なxxxApiクラスのいずれかからCallインスタンスを作成します。 たとえば、Podイベントを検出するWatchを作成するには、 listPodForAllNamespacesCall()を使用します。

CoreV1Api api = new CoreV1Api(client);
Call call = api.listPodForAllNamespacesCall(null, null, null, null, null, null, null, null, 10, true, null);
Watch<V1Pod> watch = Watch.createWatch(
  client, 
  call, 
  new TypeToken<Response<V1Pod>>(){}.getType()));

ここでは、 ヌルほとんどのパラメータでは、「デフォルト値を使用する」ことを意味しますが、2つの例外があります。 タイムアウト見る。 後者はに設定する必要があります真実ウォッチコールの場合。 それ以外の場合、これは通常のREST呼び出しになります。この場合、タイムアウトはウォッチの「存続時間」として機能します。つまり、サーバーはイベントの送信を停止し、期限切れになると接続を終了します

timeout パラメーターの適切な値を見つけるには、秒単位で表されますが、クライアントアプリケーションの正確な要件に依存するため、試行錯誤が必要です。 また、Kubernetesクラスターの構成を確認することも重要です。 通常、時計には5分の厳しい制限があるため、それを超えるものを通過しても、望ましい効果は得られません。

4. イベントの受信

Watch クラスを詳しく見ると、標準JREのIteratorIterableの両方が実装されていることがわかります。したがって、から返された値を使用できます。 createWatch() in for-eachまたはhasNext()-next()ループ:

for (Response<V1Pod> event : watch) {
    V1Pod pod = event.object;
    V1ObjectMeta meta = pod.getMetadata();
    switch (event.type) {
    case "ADDED":
    case "MODIFIED":
    case "DELETED":
        // ... process pod data
        break;
    default:
        log.warn("Unknown event type: {}", event.type);
    }
}

各イベントのtypeフィールドは、オブジェクトに発生したイベントの種類(この場合はポッド)を示します。 すべてのイベントを消費したら、 Watch.createWatch()を新たに呼び出して、イベントの受信を再開する必要があります。 サンプルコードでは、Watchの作成と結果の処理をwhileループで囲んでいます。 ExecutorService などを使用してバックグラウンドで更新を受信するなど、他のアプローチも可能です。

5. リソースバージョンとブックマークの使用

上記のコードの問題は、新しい Watchを作成するたびに、指定された種類の既存のすべてのリソースインスタンスを含む初期イベントストリームがあるという事実です。これは、サーバーがそれらに関する以前の情報がないため、すべてを送信するだけです。

ただし、これを行うと、最初のロード後にのみ新しいイベントが必要になるため、イベントを効率的に処理するという目的が損なわれます。 すべてのデータを再度受信しないようにするために、監視メカニズムは、リソースバージョンとブックマークという2つの追加の概念をサポートしています。

5.1. リソースバージョン

Kubernetesのすべてのリソースには、メタデータに resourceVersion フィールドが含まれています。これは、何かが変更されるたびにサーバーによって設定される不透明な文字列です。 さらに、リソースコレクションもリソースであるため、resourceVersionが関連付けられています。 コレクションから新しいリソースが追加、削除、または変更されると、それに応じてこのフィールドが変更されます。

コレクションを返し、resourceVersionパラメーターを含むAPI呼び出しを行うと、サーバーはその値をクエリの「開始点」として使用します。 Watch API呼び出しの場合、これはこれは、通知されたバージョンが作成された後に発生したイベントのみが含まれることを意味します。

しかし、どのようにして resourceVersion を呼び出しに含めることができますか? 単純:最初の同期呼び出しを実行して、コレクションの resourceVersion、を含むリソースの初期リストを取得し、それを後続のWatch呼び出しで使用します。

String resourceVersion = null;
while (true) {
    if (resourceVersion == null) {
        V1PodList podList = api.listPodForAllNamespaces(null, null, null, null, null, "false",
          resourceVersion, null, 10, null);
        resourceVersion = podList.getMetadata().getResourceVersion();
    }
    try (Watch<V1Pod> watch = Watch.createWatch(
      client,
      api.listPodForAllNamespacesCall(null, null, null, null, null, "false",
        resourceVersion, null, 10, true, null),
      new TypeToken<Response<V1Pod>>(){}.getType())) {
        
        for (Response<V1Pod> event : watch) {
            // ... process events
        }
    } catch (ApiException ex) {
        if (ex.getCode() == 504 || ex.getCode() == 410) {
            resourceVersion = extractResourceVersionFromException(ex);
        }
        else {
            resourceVersion = null;
        }
    }
}

この場合の例外処理コードはかなり重要です。 何らかの理由で、要求された resourceVersion が存在しない場合、Kubernetesサーバーは504または410エラーコードを返します。 この場合、返されるメッセージには通常、現在のバージョンが含まれています。 残念ながら、この情報は構造化された方法ではなく、エラーメッセージ自体の一部として提供されます。

抽出コード(別名 醜いハック)はこのインテントに正規表現を使用しますが、エラーメッセージは実装に依存する傾向があるため、コードはnull値にフォールバックします。 そうすることで、メインループは開始点に戻り、新しい resourceVersion を使用して新しいリストを回復し、監視操作を再開します。

とにかく、この警告があっても、重要な点は、イベントリストがすべての時計で最初から開始されるわけではないということです。

5.2. ブックマーク

ブックマークは、ウォッチコールから返されたイベントストリームで特別なBOOKMARKイベントを有効にするオプション機能です。 このイベントのメタデータには、後続のWatch呼び出しで新しい開始点として使用できるresourceVersion値が含まれています。

これはオプトイン機能であるため、API呼び出しでtrueallowWatchBookmarksに渡すことで明示的に有効にする必要があります。 このオプションは、 Watch を作成する場合にのみ有効であり、それ以外の場合は無視されます。 また、サーバーはそれを完全に無視する可能性があるため、クライアントはこれらのイベントの受信にまったく依存しないでください。

resourceVersion のみを使用する以前のアプローチと比較すると、ブックマークを使用すると、コストのかかる同期呼び出しをほとんど回避できます。

String resourceVersion = null;

while (true) {
    // Get a fresh list whenever we need to resync
    if (resourceVersion == null) {
        V1PodList podList = api.listPodForAllNamespaces(true, null, null, null, null,
          "false", resourceVersion, null, null, null);
        resourceVersion = podList.getMetadata().getResourceVersion();
    }

    while (true) {
        try (Watch<V1Pod> watch = Watch.createWatch(
          client,
          api.listPodForAllNamespacesCall(true, null, null, null, null, 
            "false", resourceVersion, null, 10, true, null),
          new TypeToken<Response<V1Pod>>(){}.getType())) {
              for (Response<V1Pod> event : watch) {
                  V1Pod pod = event.object;
                  V1ObjectMeta meta = pod.getMetadata();
                  switch (event.type) {
                      case "BOOKMARK":
                          resourceVersion = meta.getResourceVersion();
                          break;
                      case "ADDED":
                      case "MODIFIED":
                      case "DELETED":
                          // ... event processing omitted
                          break;
                      default:
                          log.warn("Unknown event type: {}", event.type);
                  }
              }
          }
        } catch (ApiException ex) {
            resourceVersion = null;
            break;
        }
    }
}

ここでは、最初のパスで、および内部ループでApiExceptionを取得するたびに、完全なリストを取得するだけで済みます。 BOOKMARK イベントは他のイベントと同じオブジェクトタイプであるため、ここで特別なキャストは必要ありません。 ただし、気になるフィールドは resourceVersion だけで、次のWatch呼び出しのために保存します。

6. 結論

この記事では、JavaAPIクライアントを使用してKubernetesWatchesを作成するさまざまな方法について説明しました。 いつものように、例の完全なソースコードはGitHubにあります。