بخش سوم: شبکه پادها، CNI و مسیر واقعی پکت
در این بخش سراغ شبکه واقعی پادها میرویم؛ از network namespace، veth و IPAM تا مرز CNI و datapath، مسیر ترافیک بین نودها، tunneling، native routing، MTU و تفاوت پیادهسازیهای رایج شبکه.
در بخش اول برای شبکه در کوبرنتیز یک مدل ذهنی ساختیم و چهار نوع IP را از هم جدا کردیم. آنجا گفتیم IP پاد معمولاً مقصد واقعیتر است؛ یعنی حتی اگر درخواست از یک سرویس شروع شود،در آخر، سازوکار شبکه سرویس باید یکی از اندپوینتها را انتخاب کند و پکت را به همان پاد برساند.
در بخش دوم سراغ DNS رفتیم. دیدیم یک درخواست ساده به my-api.default.svc.cluster.local از چند لایه رد میشود؛ از resolver داخل کانتینر تا search domain، مقدار ndots، CoreDNS، caching، TCP و UDP و حتی conntrack.
حالا لایه پایینتر برویم؛ جایی که قبل از سرویس، قبل از DNS و حتی قبل از kube-proxy قرار میگیرد: شبکه خود پادها.
خلاصه این بخش هم ساده است: کوبرنتیز نتیجهای را که از شبکه پاد انتظار دارد تعریف میکند، اما خودش یک دیتاپث(ساز و کار) واحد برای اجرای آن نمیسازد. یک پیادهسازی شبکه پاد باید اتصال پاد به شبکه، آدرسدهی و مسیر ارتباط بین نودها را آماده کند. اجرای NetworkPolicy قابلیت جداگانهای است که فقط در صورت پشتیبانی همان پیادهسازی یا یک بخش مکمل انجام میشود. در لینوکس، بیشتر runtimeها برای اتصال به این پیادهسازی از CNI استفاده میکنند؛ اما خود CNI با کل شبکه یا datapath یکی نیست.
اگر این مرز را درست نفهمیم، موقع دیباگ خیلی راحت به بیراهه میرویم(البته شاید). سرویس سالم است، EndpointSlice درست است و CoreDNS جواب میدهد، ولی ارتباط هنوز timeout میشود. دلیلش این است که بعد از انتخاب endpoint، پکت باید واقعاً به ایپی پاد مقصد برسد؛ و این دیگر کار خود آبجکت Service یا YAML آن نیست.
چرا بهتر است این لایه را بفهمیم؟
خیلی از آموزشهای کوبرنتیز از Service شروع میکنند. که مسیر درستی هم به نظر است، چون بیشتر ما در کار روزمره اول با سرویس درگیر میشویم، نه با route table روی نود. اما از نظر مسیر واقعی پکت، Service اول داستان نیست.
یک Service مجموعهای منطقی از endpointها و روش دسترسی به آنها را تعریف میکند. آبجکت Service خودش پکت را پردازش نمیکند و backend را انتخاب نمیکند.این کار را سازوکار هدایت ترافیک سرویس انجام میدهد؛ برای نمونه kube-proxy با iptables، nftables یا IPVS، یا یک پیادهسازی جایگزین مبتنی بر eBPF. حالت IPVS هنوز در کوبرنتیز ۱٫۳۶ وجود دارد، اما از نسخه ۱٫۳۵ منسوخ شده و برای طراحی جدید، nftables جایگزین پیشنهادی آن است.
بعد از اینکه این datapath بر اساس Service و EndpointSlice یک اندپوینت را انتخاب کرد، مقصد نهایی معمولاً IP یک پاد است. از اینجا سؤال اصلی شروع میشود:
نود از کجا میفهمد IP پاد مقصد کجاست و پکت را از چه مسیری به آن میرساند؟
اگر پاد مقصد روی همان نود باشد، مسیر کاملاً لوکال است. اگر روی نود دیگری باشد، پکت باید از شبکه بین نودها عبور کند. این عبور ممکن است داخل tunnel انجام شود یا با native routing پیش برود. در مدل routed نیز routeها ممکن است بهصورت static، با BGP یا از طریق integration شبکه cloud منتشر شوند(که ما کلاود نداریم عملا). مهم این است که یک دیتاپث واقعی وجود دارد و همان دیتاپث باید درست کار کند.
برای بررسی یک سرویس مشخص، بهتر است همه EndpointSliceهای همان سرویس را با label استاندارد بگیریم:
root@pedram:~# kubectl get endpointslice -n default \
-l kubernetes.io/service-name=my-api -o wide
برای دیدن conditionهای endpointها هم میتوانیم خروجی کامل را بررسی کنیم:
root@pedram:~# kubectl get endpointslice -n default \
-l kubernetes.io/service-name=my-api -o yaml
وجود ایپی داخل EndpointSlice بهتنهایی کافی نیست. conditionهای ready، serving و terminating نیز تعیین میکنند endpoint در چه وضعیتی است. حتی اگر همه اینها درست باشند، باز هم هیچ تضمینی وجود ندارد که شبکه بتواند آن ایپی را route کند.
مدل شبکه کوبرنتیز؛ قراردادی برای پیادهسازی شبکه پاد
کوبرنتیز چند نتیجه پایه از شبکه انتظار دارد، اما روش رسیدن به آنها را به یک پیادهسازی ثابت محدود نمیکند.
مهمترین بخشهای این مدل اینها هستند:
۱. هر پاد عادی یک آدرس یکتای قابل تشخیص در سطح کلاستر دارد. در کلاستر dual-stack، یک پاد میتواند یک IPv4 و یک IPv6 داشته باشد.
۲. پادها باید بتوانند، مگر در صورت segmentation عمدی مانند NetworkPolicy، مستقیم و بدون NAT یا proxy با پادهای دیگر ارتباط بگیرند؛ چه روی یک نود باشند و چه روی نودهای متفاوت.
۳. کانتینرهای داخل یک پاد نتورک نیماسپیس مشترک دارند و پروسسهایشان میتوانند از طریق localhost با یکدیگر ارتباط بگیرند.
۴. agentهای سیستمی روی یک نود، مانند kubelet و daemonهای همان نود، باید بتوانند با پادهای روی همان نود ارتباط داشته باشند.
این مدل برای NetworkPolicy، observability، tracing و troubleshooting با packet capture مهم است. بااینحال، نباید آن را با یک محصول یا plugin خاص یکی بگیریم. قرارداد را کوبرنتیز تعریف میکند و یک pod network implementation آن را اجرا میکند.
CNI چیست و چه چیزی نیست؟
نام CNI مخفف Container Network Interface است(قاعدتا میدونیم فقط جهت یاداوری گفتم). CNI یک specification و execution protocol است که runtime از طریق آن یک یا چند پلاگین را برای اتصال ورکلود به شبکه اجرا میکند.
خود CNI یک شبکه کامل، یک agent دائمی، یک route پروتکل یا دیتاپث عبور پکت نیست. پیادهسازی واقعی ممکن است علاوه بر باینریهای CNI شامل DaemonSet روی نود، controller، routeهای کرنل، bridge، tunnel interface، BPF program، state مربوط به IPAM یا APIهای شبکه cloud باشد.
روند سادهشده ساخت شبکه یک پاد معمولاً اینگونه است:
۱. kubelet از طریق CRI از container runtime میخواهد sandbox پاد را آماده کند.
۲. runtime ابتدا sandbox و network namespace مربوط به آن را میسازد.
۳. runtime تنظیمات CNI را میخواند و عملیات ADD را برای plugin یا زنجیره pluginها اجرا میکند.
۴. پلاگین میتواند اینترفیس درخواستی را داخل namespace بسازد یا یک اینترفیس موجود را تنظیم کند. تخصیص IP نیز ممکن است داخل همان پلاگین انجام شود یا به یک IPAM پلاگین واگذار شود.
۵. route، rule، firewall state یا وابستگیهای لازم برای attachment آماده میشوند. بعضی stateها نیز ممکن است توسط agentها و controllerهای دائمی پیادهسازی شبکه همگرا شوند.
۶. پس از ساختهشدن سندباکس و آمادهشدن شبکه پاد، کیوبلت شرط PodReadyToStartContainers را روی True میگذارد. این وضعیت نشان میدهد پاد یک سندباکس با شبکه آماده دارد، اما سالمبودن مسیر بین نودها، پالیسی، MTU یا ارتباط سرتاسری را ثابت نمیکند.
۷. پس از این مرحله، کیوبلت میتواند ایمیجها را دریافت کند و کانتینرهای پاد را بسازد و اجرا کند. همه کانتینرهای همان پاد از نتورک نیماسپیس مشترک استفاده میکنند.
۸. پس از پایان این آمادهسازی، پکتهای عادی برای هر بار عبور دوباره CNI را صدا نمیزنند؛ آنها از دیتاپث ساختهشده در کرنل، ایجنت، تونل، مسیرهای شبکه یا سختافزار عبور میکنند.
مشخصات CNI نسخه ۱٫۱ علاوه بر ADD و DEL، عملیاتهای CHECK، STATUS، VERSION و GC را هم تعریف میکند. عملیات CHECK وضعیت اتصال موجود را بررسی میکند، STATUS آمادگی خود پلاگین برای پذیرش درخواستهای جدید را گزارش میدهد، VERSION نسخههای پشتیبانیشده را برمیگرداند و GC منابعی که استفاده نمیشود را پاک میکند. CHECK برای درست کردن اتصال نیست و GC هم جای DEL را نمیگیرد. تعریفشدن یک عملیات در مشخصات نیز به این معنی نیست که هر runtime و پلاگینی حتماً آن را اجرا یا پشتیبانی میکند.
اگر setup شبکه در این مرحله fail شود، پاد معمولاً پیش از اجرای واقعی اپلیکیشن گیر میکند و ممکن است در ContainerCreating بماند. در eventهای پاد نیز معمولاً خطاهایی مربوط به sandbox network، اجرای CNI یا IP allocation دیده میشود.
تخصیص آدرس با IPAM؛ IP پاد از کجا میآید؟
هر پاد عادی باید از یک منبع آدرس معتبر ایپی بگیرد. مسئولیت مدیریت این آدرسها را معمولاً IPAM مینامیم، اما یک مدل واحد برای همه کلاسترها وجود ندارد.
مدل اول؛ CIDR مخصوص هر نود
در بعضی کلاسترها، controller manager از یک کلاستر CIDR برای هر نود یک پاد CIDR جدا میکند. پلاگینهایی مانند host-local میتوانند IP پادهای همان نود را از آن CIDR تخصیص دهند.
برای دیدن CIDRهای یک نود، مخصوصاً در dual-stack، بهتر است فیلد جمع را بررسی کنیم:
root@pedram:~# kubectl get node <node-name> \
-o go-template='{{range .spec.podCIDRs}}{{printf "%s\n" .}}{{end}}'
خروجی single-stack ممکن است شبیه این باشد:
10.244.1.0/24
در این مدل، پادهای همان نود معمولاً از این بازه آدرس میگیرند.
مدل دوم؛ pool و block متعلق به خود پیادهسازی
بعضی پیادهسازیها IPAM خودشان را دارند. برای نمونه Calico IPAM بهصورت پیشفرض از Node.spec.podCIDR برای تخصیص عادی استفاده نمیکند؛ IPPoolها را به blockهای کوچکتر تقسیم میکند و آن blockها را به نودها وابسته میکند.
پس خالیبودن spec.podCIDR در چنین کلاستری الزاماً نشانه خرابی نیست. ابتدا بهتر است بفهمیم کلاستر از چه IPAMی استفاده میکند و state معتبر آن کجا نگهداری میشود.
مدل سوم؛ آدرسدهی cloud-native
در بعضی محیطهای cloud، ایپی پاد مستقیماً از subnet یا interfaceهای شبکه cloud میآید. در این حالت ممکن است کنترل ظرفیت ENI، secondary IP، prefix delegation یا route tableهای cloud از spec.podCIDR مهمتر باشد.
برای دیدن IPهای واقعی پاد، مخصوصاً در dual-stack، میتوانیم این فیلد را بررسی کنیم:
root@pedram:~# kubectl get pod <pod-name> -n <namespace> \
-o go-template='{{range .status.podIPs}}{{printf "%s\n" .ip}}{{end}}'
علائم محتمل خرابی IPAM اینها هستند:
- پاد در
ContainerCreatingمیماند و event از exhaustion، allocation یا اجرای پلاگین حرف میزند. - پاد ایپی نمیگیرد یا ایپی آن با pool و مدل آدرسدهی واقعی کلاستر سازگار نیست.
- ظرفیت pool، block، subnet یا interfaceهای cloud تمام شده است.
- agent یا controller مربوط به شبکه روی یک یا چند نود خطای IPAM دارد.
- IP تکراری، reservation باقیمانده یا state قدیمی مانع allocation شده است.
- در مدلی که واقعاً به Node CIDR allocation متکی است،
spec.podCIDRیاspec.podCIDRsتخصیص نیافته یا ناسازگار است.
برای شروع بررسی:
root@pedram:~# kubectl get pods -A -o wide
root@pedram:~# kubectl describe pod <pod-name> -n <namespace>
root@pedram:~# kubectl describe node <node-name>
این commandها فقط سرنخ اولیه میدهند. بعد از آن باید state اختصاصی IPAM همان پیادهسازی را بررسی کنیم.
آناتومی شبکه یک پاد؛ network namespace و veth pair
هر پاد معمولی نتورک نیماسپیس خودش را دارد. نیماسپیس یک view جدا از interfaceها، routeها، neighbor table، socketها و بخشی از تنظیمات شبکه میسازد. کانتینرهای همان پاد این view را با هم به اشتراک میگذارند.
در مسیر رایج لینوکسی، plugin شبکه برای اتصال namespace پاد به host از یک veth pair استفاده میکند. یک سر داخل نیماسپیس پاد قرار میگیرد و معمولاً eth0 نام دارد. سر دیگر روی host میماند و به bridge، route-based datapath، برنامههای eBPF یا سازوکار دیگر پیادهسازی متصل میشود.
برای دیدن interface و route داخل یک پاد آزمایشی:
root@pedram:~# kubectl exec -n <namespace> <pod-name> -- ip addr
root@pedram:~# kubectl exec -n <namespace> <pod-name> -- ip route
روی نود، سمت host و routeهای مربوط به پاد را هم میشود دید:
root@pedram:~# ip link show
root@pedram:~# ip route
این دستورها فقط نقطه شروع بررسی هستند. خروجی ساده ip route معمولاً جدول main را نشان میدهد و در کلاسترهایی که از مسیریابی مبتنی بر پالیسی استفاده میکنند، ممکن است تصمیم واقعی از پالیسی یا جدول دیگری بیاید.
دو کلاستر ممکن است داخل پاد هر دو یک eth0 و یک default route نشان دهند، اما دیتاپث پشت آنها کاملاً متفاوت باشد. یکی میتواند VXLAN داشته باشد، یا native routing، دیگری routeهای منتشرشده با BGP و یا policy و service load balancing مبتنی بر eBPF.
مسیر same-node و cross-node
وقتی هر دو پاد روی یک نود باشند، پکت از شبکه فیزیکی بین نودها عبور نمیکند. بسته به پیادهسازی ممکن است از veth، bridge، route، netfilter، eBPF یا ترکیبی از اینها رد شود، اما مسیر لوکال است و معمولاً به تانل بین نودها نیاز ندارد.
وقتی دو پاد روی نودهای متفاوتاند، نود مبدا باید بداند آدرس مقصد از طریق کدام نود یا مسیر قابل دسترسی است. سپس پکت از underlay، یعنی شبکه زیرساختی متصلکننده نودها، عبور میکند و روی نود مقصد وارد بخش لوکال دیتاپث میشود.
اگر same-node سالم باشد ولی cross-node خراب شود، مظنونهای اصلی اینها هستند:
- tunnel و پورت encapsulation
- routeهای remote Pod CIDR یا آدرسهای پاد
- انتشار route با BGP یا integration شبکه cloud
- firewall، security group یا ACL بین نودها
- MTU و Path MTU Discovery
- encryption بین نودها
- خرابی agent یا state شبکه فقط روی بعضی نودها
دو مدل رایج cross-node؛ tunneling و native routing
عبارت underlay نام خود شبکه زیرساختی است و مترادف direct routing نیست. مقایسه دقیقتر برای datapath پاد، میان tunneling و native routing است.
مدل tunneling
در tunneling، پکت داخلی پاد داخل یک پکت بیرونی قرار میگیرد. شبکه زیرین فقط آدرس نودهای اندپوینت را route میکند و لازم نیست همه پاد CIDRها را بشناسد.
مزیت این مدل این است که نیاز کمتری به تغییر شبکه زیردستی دارد. هزینه آن نیز header اضافه، MTU مفید کمتر، پردازش encapsulation و نیاز به بازبودن پروتکل یا پورت تانل است.
مدل native routing
در native routing، پکت پاد بدون tunnel شدن به routing subsystem سپرده میشود. شبکه و نودها باید route معتبر برای آدرسها یا CIDRهای پاد داشته باشند.
این مدل معمولاً سربار تونل را حذف میکند، اما این به معنی بهتر بودن همیشگی کارایی نیست. نوع کارت شبکه، طراحی مسیر عبور پکت، بزرگی جدول مسیریابی، رمزنگاری و الگوی ترافیک برنامه هم روی نتیجه واقعی اثر میگذارند.
در این مدل، routeها میتوانند استاتیک باشند، توسط cloud fabric ساخته شوند یا از طریق یک routing protocol مانند BGP منتشر شوند.
یک مدل ترکیبی هم رایج است. برای نمونه Calico میتواند در حالت cross-subnet فقط میان subnetهای متفاوت encapsulation انجام دهد و داخل یک subnet از native routing استفاده کند.
تفاوت VXLAN، Geneve و IP-in-IP
رروش VXLAN یک فریم لایه دو را داخل بستهای از نوع UDP میگذارد. پورت استاندارد آن 4789 است، اما پورت واقعی را باید از تنظیمات همان نصب بررسی کرد. Flannel روی لینوکس و Cilium با تنظیمات پیشفرض معمولاً از پورت 8472 استفاده میکنند. Flannel روی ویندوز هم بهطور پیشفرض از پورت 4789 استفاده میکند.
روش Geneve هم روی UDP کار میکند، اما هدر منعطفتری دارد و میتواند اطلاعات بیشتری همراه پکت حمل کند. سیلیوم از هر دو روش VXLAN و Geneve پشتیبانی میکند. در حالت Geneve نیز معمولاً باید پورت 6081 بین نودها باز باشد.
روش IP-in-IP سادهتر است و یک پکت ایپی را داخل پکت ایپی دیگری میگذارد. سربار آن در IPv4 معمولاً از VXLAN کمتر است، اما امکانات لایه دو و متادیتای VXLAN یا Geneve را ندارد.کالیکو میتواند بر اساس تنظیم IPPool از IP-in-IP استفاده کند. این روش در کالیکو فقط برای ترافیک IPv4 پشتیبانی میشود و ممکن است همیشه، فقط بین زیرشبکههای متفاوت یا اصلاً فعال نباشد.
برای troubleshooting مهمترین نکته این است:
هر encapsulation ظرفیت مفید پکت داخلی را کم میکند و فایروال باید پروتکل یا پورت واقعی همان تنظیم را اجازه دهد.
عدد MTU؛ جایی که شبکه بیسروصدا خراب میشود
مقدار MTU بزرگترین پکت لایه سه است که یک اینترفیس میتواند بدون fragmentation از خودش عبور دهد. در Ethernet رایج این مقدار معمولاً ۱۵۰۰ بایت است و اندازه کل IP packet را نشان میدهد، نه فقط payload برنامه.
وقتی datapath از encapsulation استفاده میکند، هدر بیرونی به پکت اضافه میشود. بنابراین MTU اینترفیس داخل پاد باید بهاندازه کافی از MTU مسیر زیرین کوچکتر باشد.
اگر MTU مسیر بین نودها برابر ۱۵۰۰ باشد، چند مقدار رایج برای IPv4 اینها هستند:
- بدون کپسولهسازی، MTU پاد میتواند ۱۵۰۰ باشد.
- در
VXLANبا حدود ۵۰ بایت سربار، مقدار رایج ۱۴۵۰ است. - در
IP-in-IPبا حدود ۲۰ بایت سربار، مقدار رایج ۱۴۸۰ است. - در
Geneveسربار پایه حدود ۵۰ بایت است و MTU پاد حداکثر حدود ۱۴۵۰ میشود. با اضافهشدن گزینههایGeneveاین مقدار میتواند کمتر شود.
در IPv4، اگر پکتی با DF به hop دارای MTU کوچکتر برسد و router نتواند آن را forward کند، باید پکت را drop کند و ICMP Fragmentation Needed برگرداند. در IPv6، روتر میانی پکت را fragment نمیکند و باید ICMPv6 Packet Too Big برگرداند.
این پیامها برای Path MTU Discovery حیاتیاند. اگر firewall، security group یا ACL آنها را drop کند، فرستنده از MTU واقعی مسیر باخبر نمیشود و بسته بزرگ بدون پاسخ مفید حذف میشود. نتیجه یک MTU black hole است.
نشانههای معمول این خرابی عبارتاند از:
- درخواستهای کوچک سالماند.
- ping کوچک جواب میدهد.
- TCP handshake ممکن است موفق شود.
- DNS کوچک روی UDP جواب میدهد.
- پاسخهای بزرگ، replication، backup یا stream میان پادها hang میکنند.
- بعضی مسیرها بهدلیل MSS clamping سالم میمانند، اما UDP یا مسیرهای دیگر همچنان خراباند.
برای تست IPv4 از داخل پاد، اگر بخواهیم پکت ایپی با اندازه کل ۱۵۰۰ بایت بسازیم، payload مربوط به ICMP برابر ۱۴۷۲ است؛ چون ۲۰ بایت header معمول IPv4 و ۸ بایت هدر ICMP به آن اضافه میشود.
root@pedram:~# kubectl exec -n <namespace> <source-pod> -- \
ping -M do -s 1472 -c 3 <DESTINATION-POD-IP>
اگر MTU interface داخل پاد ۱۴۵۰ باشد، payload متناظر IPv4 معمولاً ۱۴۲۲ است:
root@pedram:~# kubectl exec -n <namespace> <source-pod> -- \
ping -M do -s 1422 -c 3 <DESTINATION-POD-IP>
پاد مبدا و مقصد باید روی نودهای متفاوت باشند تا مسیر cross-node واقعاً آزمایش شود. همچنین این عددها مخصوص IPv4 با هدر معمولاند و برای IPv6 یا headerهای دارای اپشن یکسان نیستند.
در نتیجه، fail شدن ping بزرگ و موفقشدن ping کوچک یک نشانه قوی است، اما حکم قطعی نیست. رفتار ICMP در NetworkPolicy استاندارد برای همه pluginها یکسان و تضمینشده نیست و firewall نود نیز میتواند آن را ببندد.
برای دیدن MTU داخل network namespace پاد:
root@pedram:~# kubectl exec -n <namespace> <pod-name> -- ip link show
برای دیدن MTU اینترفیسهای host و tunnel روی خود نود:
root@pedram:~# ip link show
برای TCP بهتر است علاوه بر ping از curl، iperf3 و packet capture استفاده کنیم و MSS و پیامهای ICMP را ببینیم. اگر TCP MSS clamping در مسیر فعال باشد، ممکن است TCP سالم به نظر برسد ولی UDP همچنان مشکل داشته باشد.
conntrack، مسیر برگشت و asymmetric routing
هر کانکشن به مسیر رفت و برگشت معتبر نیاز دارد، اما asymmetric routing بهخودیخود الزاماً خطا نیست. یک شبکه routed میتواند پکت رفت و برگشت را از مسیرهای متفاوت عبور دهد و همچنان درست کار کند.
مشکل زمانی جدی میشود که state اتصال یا انتظار امنیتی به یک مسیر خاص وابسته باشد. چند نمونه مهم اینها هستند:
- NAT روی یک نود انجام شده ولی پاسخ از نودی برمیگردد که state مربوط به conntrack را ندارد.
- یک firewall stateful فقط یک جهت connection را میبیند.
- ECMP یا تغییر route جریان را میان datapathهایی با state ناسازگار پخش میکند.
- مقدار
rp_filterدر حالت strict، سورس پکت برگشتی را با route معکوس مورد انتظار سازگار نمیبیند و آن را drop میکند. - tunnel، encryption یا policy فقط در یکی از دو جهت درست همگرا شده است.
در نتیجه، conntrack برای هر پکت routed ساده الزام عمومی نیست؛ اهمیت آن در مسیرهایی مانند NAT، stateful filtering و بعضی پیادهسازیهای Service دیتاپث بیشتر میشود.
علائم معمول این خانواده از خطاها عبارتاند از:
- پیام SYN میرود ولی SYN/ACK برنمیگردد.
- handshake کامل میشود ولی data عبور نمیکند.
- مشکل فقط روی نود یا مسیر خاص دیده میشود.
- با جابهجایی پاد به نود دیگر، مشکل نیز جابهجا میشود.
- packet capture روی دو سر مسیر، view متفاوتی از source، destination یا state نشان میدهد.
در این حالت باید دقیقاً مشخص کنیم NAT کجا انجام میشود، state اتصال کجا نگهداری میشود، هر جهت پکت از کدام نود یا firewall میگذرد و reverse-path filtering چه تنظیمی دارد.
تفاوت پیادهسازیهای رایج از نگاه عملیاتی
نام Flannel، Calico و Cilium فقط یک label نیست. هرکدام مجموعهای از CNI binary، agent، controller، IPAM و datapath دارند و mode نصب میتواند رفتار واقعی را تغییر دهد.
گزینه Flannel
فِلَنِل یک شبکه پاد نسبتاً ساده فراهم میکند. mode رایج آن VXLAN است و backendهایی مانند host-gw و WireGuard نیز دارد.
فِلَنِل بهتنهایی NetworkPolicy استاندارد را enforce نمیکند. وجود آبجکت NetworkPolicy در API به معنی اجراشدن آن نیست، مگر component دیگری enforcement را انجام دهد.
از طرف دیگر، backend مربوط به IPsec در مستندات upstream بهعنوان experimental معرفی شده است و ممکن است در توزیع مورد استفاده حذف شده یا پشتیبانی نشود. بنابراین نباید وجود آن را از روی نام Flannel فرض کنیم.
گزینه Calico
کالیکو میتواند از VXLAN، IP-in-IP، native routing و BGP برای انتشار route استفاده کند. BGP در اینجا control plane route است، نه tunnel.
پیشفرض واقعی Calico به روش نصب، operator، IPPool، محیط و نسخه بستگی دارد. کالیکو IPAM نیز بهصورت پیشفرض از Node.spec.podCIDR استفاده نمیکند، هرچند مدل host-local میتواند به این فیلد متکی باشد.
کالیکو NetworkPolicy استاندارد و policyهای گستردهتر خودش را پشتیبانی میکند و dataplaneهای iptables، nftables و eBPF در نسخهها و modeهای مختلف آن دیده میشوند. این قدرت، پیچیدگی IPPool، routing، BGP و upgrade را هم به همراه دارد.
گزینه Cilium
سیلیوم حول eBPF ساخته شده است. برای cross-node میتواند VXLAN یا Geneve داشته باشد یا از native routing استفاده کند. native routing و eBPF مترادف نیستند؛ در این مود، packet forwarding به routing subsystem لینوکس و routeهای شبکه متکی است و eBPF بخشهایی مانند policy، load balancing و host datapath را اجرا میکند.
سیلیوم میتواند جای kube-proxy را بگیرد و Service load balancing را با eBPF انجام دهد، اما این mode همیشه فعال نیست. در troubleshooting باید مقدار واقعی kube-proxy replacement بررسی شود.
هابِل برای دیدن مسیر ترافیک، نتیجه اعمال سیاستها و علت حذف پکتها مفید است. سیاستهای لایه هفت هم با منابع اختصاصی سیلیوم مثل CiliumNetworkPolicy تعریف میشوند و جزو NetworkPolicy استاندارد کوبرنتیز نیستند.
برای سیلیوم ۱٫۲۰٫۰ حداقل کرنل نسخه ۵٫۱۰ یا معادل توزیعی مستندشده لازم است؛ برای نمونه بعضی کرنلهای enterprise قدیمیتر با backportهای لازم میتوانند معادل محسوب شوند. قابلیتهای پیشرفته ممکن است کرنل جدیدتری بخواهند.(قبل از نصب بررسی کنید)
گزینه Canal
کانال برای حالتی ساخته شده که شبکه پادها از قبل با فلنل کار میکند، اما میخواهیم قابلیتهای پالیسی کالیکو را هم داشته باشیم. در این ترکیب، فلنل ارتباط پادها و مسیر بین نودها را فراهم میکند و کالیکو با کمک فلیکس قوانین شبکه را روی هر نود اعمال میکند.
پس کانال یک پیادهسازی شبکه مستقل نیست؛ ترکیبی از فلنل برای شبکه و کالیکو برای پالیسی است. مزیتش این بود که میشد بدون کنارگذاشتن فلنل، پشتیبانی از NetworkPolicy را به کلاستر اضافه کرد.
این روش هنوز در مستندات کالیکو ۳٫۳۲ پشتیبانی میشود و فایل نصب مخصوص خودش را دارد. بااینحال، برای کلاستر جدیدی که فقط به شبکه VXLAN نیاز دارد، خود کالیکو استفاده از VXLAN بومی کالیکو را پیشنهاد میکند؛ چون دیگر نیازی نیست فلنل را هم کنار آن نگه داریم. بنابراین کانال بیشتر برای کلاسترهای قدیمی یا محیطهایی مناسب است که از قبل فلنل دارند، نه اینکه انتخاب پیشفرض یک نصب جدید باشد
جمعبندی عملیاتی:
| نگاه عملیاتی | پیادهسازی | مسیر رایج cross-node | NetworkPolicy | نکته مهم |
|---|---|---|---|---|
| گزینه ساده | Flannel | VXLAN یا host-gw و backendهای دیگر | بهتنهایی enforce نمیکند | mode، پورت و backend واقعی باید بررسی شود |
| گزینه قوی برای policy و routing | Calico | VXLAN، IP-in-IP یا native routing؛ امکان انتشار route با BGP | دارد | IPAM، IPPool و dataplane به mode نصب وابستهاند |
| گزینه eBPFمحور | Cilium | VXLAN، Geneve یا native routing | دارد؛ L7 با CRD اختصاصی | kube-proxy replacement و نیاز kernel باید بررسی شوند |
| الگوی ترکیبی | Canal | Flannel برای شبکه و Calico برای policy | از سمت Calico | هنوز پشتیبانی میشود، اما انتخاب پیشنهادی پیشفرض برای نصب جدید نیست |
ارتباط این لایه با Service و EndpointSlice
در این بخش، Service و EndpointSlice اطلاعات لازم برای endpoint selection را فراهم میکنند، اما خودشان تضمین نمیکنند ایپی انتخابشده در شبکه قابل دسترسی است.
پس از انتخاب endpoint توسطدیتاپث سرویس، pod network implementation یا همان پیاده سازی سمت شبکه پاد باید مسیر معتبر تا آن ایپی را فراهم کرده باشد. اگر route، tunnel یا policy خراب باشد، Service و EndpointSlice میتوانند کاملاً درست باشند ولی درخواست fail شود.
ارتباط این لایه با NetworkPolicy
در این بخش، NetworkPolicy فقط پالیسی موردنظر را در API تعریف میکند. enforcement را پیادهسازی شبکهای انجام میدهد که این قابلیت را پشتیبانی کند.
رفتار استاندارد NetworkPolicy برای TCP، UDP و در صورت پشتیبانی SCTP تعریف شده است. رفتار پروتکلهایی مانند ICMP یا ARP میان پلاگینها میتواند متفاوت باشد. بنابراین ping بهتنهایی تست قطعی NetworkPolicy نیست.
ارتباط این لایه با kube-proxy
در بعضی کلاسترها، kube-proxy قوانین لازم برای رساندن ترافیک سرویسها به پادهای مقصد را روی نودها میسازد. در بعضی دیگر، پیادهسازیای مانند Cilium میتواند این نقش را بگیرد.
برای دیباگ سرویس باید بدانیم چه componentی واقعاً بکاند را انتخاب و در صورت نیاز destination را rewrite میکند. خود آبجکت Service این کار را انجام نمیدهد.
ارتباط این لایه با DNS
پادهای CoreDNS نیز داخل همین شبکه قرار دارند. اگر ورکلود نتواند به endpointهای CoreDNS برسد، نتیجه از بیرون شبیه خرابی DNS دیده میشود، درحالیکه ریشه میتواند route، tunnel، MTU، firewall یا NetworkPolicy باشد.
در troubleshooting باید هم ClusterIP سرویس DNS و هم دسترسی مستقیم به endpointهای CoreDNS را جدا بررسی کنیم تا Service از pod تفکیک شود.
نگاه production؛ از کجا شروع کنیم؟
وقتی ارتباط پاد به پاد خراب است، این ترتیب از حدسزدن بهتر عمل میکند.
مرحله اول؛ پیادهسازی و mode واقعی را مشخص کنیم
اول بهتر است نام، نسخه، IPAM، routing mode، tunnel، encryption و Service دیتاپث را پیدا کنیم. گفتن «کلاستر Cilium دارد» یا «Calico نصب است» بهتنهایی کافی نیست.
مرحله دوم؛ سلامت componentها را بررسی کنیم
بیشتر پیادهسازیها agent نود دارند، اما فقط Running بودن پاد agent سلامت datapath را ثابت نمیکند. log، readiness، health command اختصاصی، controllerها و state وابسته نیز باید بررسی شوند.
root@pedram:~# kubectl -n kube-system get pods -o wide
root@pedram:~# kubectl -n kube-system get daemonset
در Cilium ممکن است کامندهایی مانند cilium status و Hubble مفید باشند. در Calico باید IPPool، logهای calico-node و در صورت استفاده وضعیت BGP بررسی شود. در Flannel باید ConfigMap، backend، interface و routeهای ساختهشده دیده شوند.(این موارد صرفا چند نمونه هستند و بسته به مود پیاده سازی باید موارد دیگر هم بررسی شوند)
مرحله سوم؛ ایپی و attachment پاد را بررسی کنیم
root@pedram:~# kubectl get pods -A -o wide
root@pedram:~# kubectl describe pod <pod-name> -n <namespace>
اگر پاد ایپی ندارد یا در ContainerCreating گیر کرده است، eventهای sandbox و IPAM را بررسی میکنیم. اما وجود ایپی بهتنهایی سلامت datapath را ثابت نمیکند.
مرحله چهارم؛ same-node و cross-node را جدا کنیم
اگر same-node سالم و cross-node خراب است، تمرکز را روی tunnel، remote route، firewall، BGP، route، encryption و MTU میبریم.
اگر هر دو خراباند، اینترفیس داخل پاد، route local، policy و agent همان نود اهمیت بیشتری دارند.
مرحله پنجم؛ مسیر و تصمیم مسیریابی را روی پاد و نود بررسی کنیم
root@pedram:~# kubectl exec -n <namespace> <pod-name> -- ip addr
root@pedram:~# kubectl exec -n <namespace> <pod-name> -- ip route
root@pedram:~# ip addr
root@pedram:~# ip route
root@pedram:~# ip link show
root@pedram:~# ip rule show
root@pedram:~# ip route show table all
این خروجیها تصمیم مسیریابی کرنل را بهتر نشان میدهند، اما رسیدن پکت به مقصد را ثابت نمیکنند. بسته به مسیر، ممکن است لازم باشد جدولهای ARP و NDP، جدول FDB، تونل، فایروال، پالیسی و ابزارهای اختصاصی همان پیادهسازی در دیتاپثهای eBPF را هم بررسی کنیم.
در محیط پروداکشن ممکن است ایمیج پاد ابزار ip نداشته باشد. در این حالت باید از debug container یا ابزار تشخیصی مورد تأیید همان محیط استفاده کنیم.(یه ایمیج دیباگ بهتره از قبل داشته باشیم با ابزارهای موردنیاز خودمون)
مرحله ششم؛ پورت و پروتکل واقعی را بررسی کنیم
نباید صرفاً از روی نام VXLAN پورت 4789 را باز کنیم. برای Flannel یا Cilium ممکن است پورت واقعی 8472 باشد و Geneve معمولاً 6081 است. کانفیگ موقع نصب منبع حقیقت است.
اگر شبکه از BGP استفاده میکند، فقط بازبودن پورت کافی نیست؛ باید نشستهای BGP مورد انتظار را بررسی کنیم و مطمئن شویم مسیرهای لازم واقعاً ردوبدل میشوند. این نشستها بسته به طراحی شبکه ممکن است بین خود نودها، با روترفلکتورها یا با روترهای زیرساخت برقرار شده باشند.
مرحله هفتم؛ MTU و PMTUD را آزمایش کنیم
تست باید میان دو پاد روی نودهای متفاوت انجام شود. MTU داخل پاد، tunnel interface، interface نود و مسیر زیرین باید با هم دیده شوند. همچنین ICMP و ICMPv6 لازم برای PMTUD نباید بدون دلیل drop شوند.
مرحله هشتم؛ policy را بررسی کنیم
اول باید مطمئن شویم پیادهسازی NetworkPolicy را enforce میکند. سپس ingress و egress هر دو سمت، DNS، namespace selector، IPBlock و portها را بررسی میکنیم.
در ابزارهایی مانند سیلیوم یا کالیکو، verdict و log اختصاصی policy میتواند از حدسزدن با YAML مؤثرتر باشد.
مرحله نهم؛ Service دیتاپث را جدا بررسی کنیم
دسترسی مستقیم به ایپی پاد و دسترسی از طریق ClusterIP دو تست متفاوتاند. اگر ایپی پاد سالم و ClusterIP خراب است، Service مظنون مهمتری است. اگر هر دو خراباند، ابتدا دیتاپث پاد را بررسی میکنیم.
نتیجه
شبکه پادها پایهای است که سرویس، DNS، NetworkPolicy و بخش بزرگی از observability روی آن میایستند. کوبرنتیز مدل مورد انتظار را تعریف میکند، اما یک implementation شبکه پاد آن را اجرا میکند. runtime در لینوکس معمولاً از CNI برای ساخت attachment شبکه استفاده میکند؛ پکتهای بعدی از datapath ساختهشده عبور میکنند، نه از خود CNI specification.
در عمل، ایپی پاد نیز همیشه از زنجیره ساده cluster CIDR تا per-node Pod CIDR نمیآید. ممکن است Node CIDR allocation، IPAM داخلی مانند Calico یا آدرسدهی cloud-native در کار باشد. برای همین اولین قدم troubleshooting شناخت مدل واقعی همان کلاستر است.
در cross-node باید tunneling را از native routing جدا کنیم و underlay را نام شبکه زیرساختی بدانیم. BGP هم route منتشر میکند، اما خودش datapath tunnel نیست. MTU نیز باید بر اساس تمام لایههای واقعی مسیر محاسبه شود، نه فقط نام CNI.
در نهایت، سالمبودن Service، EndpointSlice یا پاد agent شبکه هیچکدام بهتنهایی سلامت ارتباط را ثابت نمیکنند. باید IPAM، interface، route، policy، tunnel، stateful datapath، مسیر برگشت و MTU را بهترتیب و بر اساس mode واقعی کلاستر بررسی کنیم.
یادداشت فنی
این متن با مستندات جاری کوبرنتیز، مشخصات CNI نسخه ۱٫۱، کالیکو نسخه ۳٫۳۲، سیلیوم نسخه ۱٫۲۰٫۰ و مستندات جاری فلنل تطبیق داده شده است.
رفتار واقعی همیشه به نسخه کوبرنتیز، نسخه پیادهسازی شبکه، kernel نودها، runtime، IPAM، routing mode، tunnel، encryption، firewall و محیط cloud یا on-prem وابسته است.
هیچوقت route، MTU، IPAM یا mode شبکه را در پروداکشن کورکورانه تغییر ندهید. ابتدا مسیر واقعی پکت و state فعلی را ثبت کنید، تغییر را در sandbox آزمایش کنید و فقط با rollback plan روشن سراغ پروداکشن بروید.
منابع و مطالعهٔ بیشتر
- Services, Load Balancing, and Networkingمستندات رسمی
مرجع رسمی مدل شبکه کوبرنتیز؛ شامل IP یکتای پاد، ارتباط مستقیم پادها بدون NAT، اشتراک network namespace داخل پاد و دامنه ارتباط agentهای هر نود.
- Cluster Networkingمستندات رسمی
مرجع رسمی پیادهسازی شبکه کلاستر، dual-stack و نقش container runtime و network plugin در اجرای مدل شبکه.
- Network Pluginsمستندات رسمی
مرجع رسمی اتصال container runtime به pluginهای شبکه و الزامات عملیاتی network plugin در کوبرنتیز.
- Container Network Interface Specificationمستندات رسمی
مشخصات رسمی CNI نسخه 1.1.0؛ شامل عملیات ADD، DEL، CHECK، STATUS، VERSION و GC، ساخت network namespace توسط runtime و امکان ساخت یا تنظیم interface توسط plugin.
- EndpointSlicesمستندات رسمی
مرجع رسمی EndpointSlice، label اتصال به Service و conditionهای ready، serving و terminating.
- Network Policiesمستندات رسمی
مرجع رسمی NetworkPolicy؛ شامل نیاز به پیادهسازی دارای enforcement و تعریفنشدن رفتار ICMP و پروتکلهای خارج از TCP، UDP و SCTP.
- Get started with IP address managementمستندات رسمی
مرجع رسمی Calico IPAM؛ شامل block allocation و این نکته که Calico IPAM بهصورت پیشفرض از Node.spec.podCIDR استفاده نمیکند.
- Configure MTU to maximize network performanceمستندات رسمی
مرجع رسمی MTU؛ منبع اعداد رایج IPv4 برای IP-in-IP، VXLAN و WireGuard و تأکید بر وابستگی مقدار واقعی به مسیر شبکه.
- Install Calico for policy and flannel networkingمستندات رسمی
مرجع رسمی الگوی Canal در Calico 3.32؛ ترکیب Flannel برای شبکه و Calico برای policy، همراه با توصیه استفاده از شبکه بومی Calico برای نصبهای جدید.
- Flannel backendsمستندات رسمی
مستند رسمی backendهای Flannel؛ شامل VXLAN، host-gw، WireGuard، پورتهای پیشفرض و experimental بودن backend مربوط به IPsec.
- Cilium 1.20.0مستندات رسمی
مرجع رسمی انتشار نسخه 1.20.0 سیلیوم و مبنای نسخهای مقایسه این مقاله.
- Routingمستندات رسمی
مرجع رسمی تفاوت encapsulation و native routing، پورتهای VXLAN و Geneve و نیاز شبکه زیرین به route کردن Pod CIDRها.
- System Requirementsمستندات رسمی
مرجع رسمی حداقل kernel نسخه 5.10 یا معادل توزیعی و نیازهای kernel و firewall برای اجرای سیلیوم.
مرجع Path MTU Discovery در IPv4 و پیام ICMP Fragmentation Needed.
مرجع Path MTU Discovery در IPv6 و پیام ICMPv6 Packet Too Big.
- IP sysctlمستندات رسمی
مرجع تنظیمات شبکه کرنل لینوکس، از جمله reverse path filtering و اثر آن روی بعضی مسیرهای نامتقارن.
- Pod Conditionsمستندات رسمی
مرجع رسمی PodReadyToStartContainers و مرز آمادهشدن sandbox و شبکه پاد پیش از دریافت image و ساخت containerها.
- Configure BGP peeringمستندات رسمی
مرجع رسمی BGP در Calico؛ شامل full mesh، route reflector و peering با روترهای زیرساخت.