وقتی کلاسترهای Kubernetes به ده‌ها هزار نود می‌رسند، کنترلرهایی که منابع high-cardinality مثل Pods را پایش می‌کنند با یک دیوار مقیاس‌پذیری روبرو می‌شوند. هر replica از یک کنترلر که به‌صورت افقی مقیاس شده، کامل جریان رویدادها را از API server دریافت می‌کند و هزینه‌های CPU، حافظه و شبکه را برای deserialize کردن همه چیز می‌پردازد تا در نهایت اشیائی که مسئول‌شان نیست را دور بیندازد. افزایش تعداد replicaها هزینه را برای هر replica کمتر نمی‌کند؛ در واقع آن را چند برابر می‌کند. ⚠️

Kubernetes v1.36 قابلیت server-side sharded list and watch را به‌صورت alpha معرفی کرده است (KEP-5866).

با فعال‌سازی این قابلیت، فیلتر کردن رویدادها در مبدأ یعنی همان API server انجام می‌شود تا هر replica از کنترلر تنها آن برشی از مجموعه منابع را دریافت کند که مالک آن است — و از هزینه‌های غیرضروری شبکه و CPU جلوگیری شود.

مشکل شاردینگ در سمت کلاینت

برخی کنترلرها مثل kube-state-metrics قبلاً از شاردینگ افقی پشتیبانی می‌کردند: به هر replica بخشی از keyspace اختصاص داده می‌شود و replica اشیائی را که به آن تعلق ندارد دور می‌ریزد. این راهکار از نظر عملکردی کار می‌کند اما حجم داده‌ای که از API server عبور می‌کند کاهش پیدا نمی‌کند؛ یعنی:

هر replica تمام جریان رویداد کامل را دریافت و deserialize می‌کند و سپس رویدادهایی را که لازم ندارد حذف می‌کند. پهنای باند شبکه با تعداد replicaها رشد می‌کند، نه با اندازه shard. و CPU صرف deserialize کردن بخش‌های حذف‌شده هدر می‌رود.

Server-side sharded list and watch این مشکل را با جابه‌جایی فیلترینگ به سمت API server حل می‌کند: هر replica به API server می‌گوید که کدام بازه hash را مالک است و API server فقط رویدادهای مطابق با آن بازه را ارسال می‌کند.

نحوه کار

این ویژگی یک فیلد جدید shardSelector را به ListOptions اضافه می‌کند. کلاینت‌ها با استفاده از تابع shardRange()، بازه هش را مشخص می‌کنند:

shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')

API server روی فیلد مشخص‌شده یک هش ۶۴ بیتی قطعی از نوع FNV-1a محاسبه می‌کند و فقط اشیائی را برمی‌گرداند که هش آن‌ها در بازه [start, end) قرار دارد. این رفتار هم برای پاسخ لیست و هم برای جریان رویدادهای watch اعمال می‌شود. تابع هش بین همهٔ نمونه‌های API server یکسان است، بنابراین استفاده از این قابلیت در مقابل چند replica از API server ایمن است. مسیرهای فیلدی که فعلاً پشتیبانی می‌شوند عبارتند از object.metadata.uid و object.metadata.namespace.

استفاده از sharded watches در کنترلرها

کنترلرها معمولاً از informers برای لیست و واچ منابع استفاده می‌کنند. برای شارد کردن کار، هر replica مقدار shardSelector را به ListOptions که informers استفاده می‌کنند تزریق می‌کند — معمولاً از طریق WithTweakListOptions. نمونه کد زیر نحوهٔ انجام را نشان می‌دهد:

import (
 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 "k8s.io/client-go/informers"
)

shardSelector := "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')"

factory := informers.NewSharedInformerFactoryWithOptions(client, resyncPeriod,
 informers.WithTweakListOptions(func(opts *metav1.ListOptions) {
 opts.ShardSelector = shardSelector
 }),
)

برای یک deployment با ۲ replica، selectors فضای هش را به دو نیم تقسیم می‌کنند:

// Replica 0: lower half of the hash space
"shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')";

// Replica 1: upper half of the hash space
"shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')"

-- یک replica واحد هم می‌تواند بازه‌های غیرپیوسته را با || پوشش دهد:
"shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000') || " +
 "shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')"

بررسی پشتیبانی سرور

اگر API server یک shard selector را رعایت کند، پاسخ لیست در metadata خود یک فیلد shardInfo شامل selector اعمال‌شده را بازتاب می‌دهد:

{
 "kind": "PodList",
 "apiVersion": "v1",
 "metadata": {
  "resourceVersion": "10245",
  "shardInfo": {
   "selector": "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')"
  }
 },
 "items": [...]
}

اگر shardInfo وجود نداشته باشد یعنی سرور selector را اعمال نکرده و کلاینت مجموعهٔ کامل و فیلترنشده را دریافت کرده است. در این حالت کلاینت باید برای پردازش مجموعهٔ کامل آماده باشد — مثلاً با اعمال فیلترینگ در سمت کلاینت تا اشیاء خارج از بازهٔ shard را حذف کند.

مشارکت و وضعیت فعلی 🔧

این ویژگی در حالت alpha است و نیاز به فعال‌سازی feature gate با نام ShardedListAndWatch روی API server دارد. تیم توسعه دنبال بازخورد از نویسندگان کنترلر و اپراتورهایی است که کلاسترهای بزرگ را مدیریت می‌کنند — نظرات و تجربیات واقعی شما برای کارکردن بهتر این قابلیت حیاتی است. 🙏

KEP-5866: Server-Side Sharded List and Watch
API Concepts: Sharded list and watch
SIG API Machinery

برای پرسش یا بازخورد می‌توانید به کانال #sig-api-machinery در Kubernetes Slack بپیوندید. 📣