本文へ移動

Kubernetes v1.37 で etcd RangeStream が beta、etcd v3.7 未満は対象外

v1.37 の kube-apiserver は etcd v3.7 以上と組むと、collection の全件読みに RangeStream を使う。etcd が古ければ旧経路のままで、現状は listStream カウンタで分かる。

v3.7 RangeStream に必要な etcd の最低バージョン kube-apiserver v1.37 の EtcdRangeStream gate は beta で既定有効。etcd がこれより古いと起動時に検出して Range(ページング)に自動で戻る。

kube-apiserver が etcd から collection を丸ごと読む経路が、v1.37 で変わる。EtcdRangeStream feature gate が beta に上がって既定で有効になり、etcd が v3.7 以上なら、ページング付きの Range の代わりに streaming の RangeStream RPC が使われる。etcd が v3.7 より古ければ API server は起動時にそれを検出し、従来の経路を使い続ける。影響があるのは etcd v3.7 以上と組んだクラスタだけで、それ以外では読み出しの挙動は何も変わらない。

変更の狙いは、Pods のように数が多いか一つ一つが大きいリソースを読むときの、API server と etcd のメモリ使用量を下げて、ピークを予測しやすくすることだ。ブログを書いたのは Google の Jeffrey Ying で、公開日は 2026年9月1日。

今の経路は listStream のカウンタで分かる

API server は streaming で読んだ操作を、etcd metrics の独自の operation ラベルで記録する。次のカウンタが 0 でなければ RangeStream が使われている。

etcd_request_duration_seconds_count{operation="listStream"}

0 のままなら、API server はまだページング付きの Range を通っている。ブログが最も可能性が高い理由として挙げるのは、etcd が v3.7 より古いことだ。v1.37 に上げた直後に見るなら、この一つで足りる。

kube-apiserveretcdetcd からの読み出し経路
v1.37 以上、gate 有効(既定)v3.7 以上RangeStream
v1.37 以上、gate 有効(既定)v3.7 未満Range(ページング)に自動で戻る
v1.37 以上、gate 無効どのバージョンでもRange(ページング)

出典:Kubernetes ブログ、2026年9月1日公開「etcd RangeStream Cuts Memory Use on Large List Reads」の Requirements 節

key 数で切ったページは、オブジェクトの大きさを知らない

API server は list と watch の大半を、メモリ上の watch cache から返す。その cache を埋めるには、起動時と再初期化のたびに、リソースの全状態を etcd から読む必要がある。オブジェクトが多いか大きいリソース、たとえば Pods では、この読み出しが高くつく。

読み出しはすでにページングされていた。collection を一度に要求せず、決まった数の key ずつ etcd に頼む形だ。ただ key 数で区切られたページはオブジェクトのサイズを見ていないので、大きなオブジェクトが並ぶページは依然として大きくなりうる。メモリ使用量が読めなくなり、オブジェクトサイズと同時読み出しの組み合わせが悪ければ OOM に至りうる。

もう一つの問題は、同じペイロードが両側に同時に載ることだ。etcd の unary な Range はページを丸ごと組み立ててから送り、API server はデコードの間それを保持する。コストの大半は etcd 側に落ちる。streaming が最も効くのもそこだ。

RangeStream は同じ RangeRequest を chunk に分けて流す

etcd v3.7 が追加した RangeStream RPC は、Range と同じ RangeRequest を受け取り、同じ結果集合を返す。違いは組み立て方で、応答を先に全部作らず、chunk に分けて順に流す。chunk のサイズは返す値に合わせて適応的に調整されるので、大きなオブジェクトの collection は key 数ではなくバイト数で区切られ、メモリはページが完成するまで抱えられずに、stream が進むにつれて解放される。

gate が有効なとき、API server は etcd から collection を丸ごと読む場面すべてで RangeStream を使う。watch cache の初期化に加え、list 要求を cache から返せず etcd を直接読むフォールバック経路も含まれる。どちらの場合も API server は chunk を届いた順にデコードし、次を取りに行く前に解放するので、collection 全体を一度に持つ側はどちらにもない。

止める手段は kube-apiserver の gate 一つで、起動引数で落とす。

--feature-gates=EtcdRangeStream=false

API server は etcd の対応を起動時に解決し、加えて呼び出しが Unimplemented を返した場合は実行時にも Range に戻る。etcd の更新が kube-apiserver より遅れているクラスタでも、手動で gate を触る必要はない。

v1.37 では Storage Version Migration も GA になった。どちらも etcd に触る変更だが、RangeStream が変えるのは読み出しの経路で、返る結果は Range と同じだ。

減る量の数字はブログにない

メモリがどれだけ減るかの計測値は、ブログに載っていない。あるのは減らす、ピークを予測しやすくするという説明だけで、削減率やベンチマークは示されていない。効果は自分のクラスタで、API server と etcd のメモリを更新前後で見るしかない。beta から GA へ上がる時期も書かれていない。

chunk サイズは適応的に調整されるとだけあり、運用者が上限を指定できるかは読み取れない。control plane をマネージドで使っていて kube-apiserver の metrics を取れているなら、上の listStream カウンタを見れば自分の etcd が v3.7 に達しているかの間接的な手掛かりになる。ただしゼロの理由を etcd のバージョンだと決めつけるのは早い。ブログ自体、それを最も可能性が高い原因と位置づけているだけだ。

etcd を自分で運用していて v3.7 未満なら、今日は何も変わらない。API server の再起動時のメモリの山が課題なら、先に動くのは etcd 側の更新だ。すでに v3.7 以上なら、v1.37 への更新で経路は勝手に切り替わる。比べるのはカウンタとメモリのピークの、更新前後の差だ。問題が出たときの退避先は gate の無効化になる。

出典 種別について