👋

k8s.io/apimachinery/pkg/util/wait の使い道

に公開

wait が便利なので使いましょう!

https://pkg.go.dev/k8s.io/apimachinery/pkg/util/wait

Kubernetes に関連する開発を行っていると Reconcile で実行される処理を待つ必要があるケースがよくあります。
その際に wait を使います。

Overview にも以下のように記載されています、Condition の変更を polling / listening するためのパッケージです。

Package wait provides tools for polling or listening for changes to a condition.

基本的に wait は backoff を行うための関数と polling 行うための関数に分かれています。

Polling

Polling は簡単にいうと一定間隔で何かの状態を確認し続けることです。
condition が true になるまで待ち続ける無限ループみたいなものです。
実際の例だと Pod が Running になるまで待つみたいなことになります。

for {
  if condition {
    break // finish polling
  }
  time.Sleep(5 * time.Second) // wait 5 sec and retry
}

PollUntilContextCancel

func PollUntilContextCancel(ctx context.Context, interval time.Duration, immediate bool, condition ConditionWithContextFunc) error

名前の通り渡された Context が cancel されるまで、もしくは condition の関数が true を返すまで待ち続ける関数です。
interval は実行間隔、immediate は最初の interval を待つかどうかを設定できます。
また immediate が true の場合は condition は最低でも一度は実行されます。

Pod が削除されるまで待つようなケースでは普通に書くと select で ctx.Done をハンドリングするコードを書く必要があると思います。

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

for {
  select {
  case <-ctx.Done():
    err := ctx.Err()
    if err != nil {
      fmt.Printf("Polling failed: %v\n", err)
    }
    return
  default:
    pod, err := k8sClient.Get(ctx, client.ObjectKey{ Name:podName, Namespace:namespace })
    if apierrors.IsNotFound(err) {
      fmt.Println("Pod has been deleted")
      return
    }
    if err != nil {
      fmt.Printf("Polling failed: %v\n", err)
      return
    }
    time.Sleep(5 * time.Second)
  }
}

PollUntilContextCancel を使用するとこんな感じで書けます。

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

_ := wait.PollUntilContextCancel(ctx, 5*time.Second, func(ctx context.Context) (bool, error) {
  _, err := k8sClient.Get(ctx, client.ObjectKey{ Name:podName, Namespace:namespace })
  if apierrors.IsNotFound(err) {
    fmt.Println("Pod has been deleted")
    return true, nil
  }
  if err != nil {
    fmt.Printf("Polling failed: %v\n", err)
    return false, err
  }
  return false, nil
})

PollUntilContextTimeout

func PollUntilContextTimeout(ctx context.Context, interval, timeout time.Duration, immediate bool, condition ConditionWithContextFunc) error

似たような関数で Timeout をハンドルするものもありますが、これは context.WithTimeout を使っていない人のためのもので以下のコードと等価なので基本的には PollUntilContextCancel を使用するのが良いと思います。

deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout)
err := PollUntilContextCancel(deadlineCtx, interval, immediate, condition)

まとめ

wait を使うことで Polling の処理を簡潔に書くことができます、ぜひ使ってみてください。

とりあえず気力で Polling の部分だけ書きました、Backoff や Until も後ほど追記します。

Discussion