MetalLB-ის შეცვლა Cilium LB IPAM-ით ისე, რომ არც ერთი მოთხოვნა არ დაკარგულა
კლასტერში მხოლოდ ერთი LoadBalancer IP იყო და ყველა მარშრუტი მასზე იყო დამოკიდებული. როგორ გადავეცი ის MetalLB-დან Cilium-ს შეცდომების გარეშე — ორივე კონტროლერს ერთდროულად ვამუშავებდი.
სახლის ლაბორატორიის Cilium-ზე გადაყვანის შემდეგ (kube-proxy-ის ჩანაცვლებით) MetalLB დარჩა პლატფორმის ბოლო კომპონენტად GitOps-ის გარეთ. ის ძველი იყო (2023 წლის რელიზი) და ჯერ კიდევ ემსახურებოდა მოძველებულ AddressPool API-ს, რომელიც apiserver_requested_deprecated_apis-ში ჩანდა. Cilium-ში LB IPAM უკვე ჩართული იყო, ხოლო LoadBalancer IP-ების L2-ით გამოცხადება Cilium-ს თავადაც შეუძლია, ამიტომ მეორე კონტროლერი უბრალოდ დუბლირება იყო.
მთელი რისკი ერთ რიცხვზე მოდიოდა. კლასტერში მხოლოდ ერთი LoadBalancer Service იყო — Gateway-ის — და reverse-proxy-ის ყველა upstream მის მისამართზე მიუთითებდა. უარესიც: მისამართი არასოდეს ყოფილა დაფიქსირებული. MetalLB-მა ის პულიდან დინამიკურად გასცა, ხოლო მიმდინარე მნიშვნელობა ისტორიული დამთხვევა იყო. გულუბრყვილო მიგრაცია „წარმატებით“ გასცემდა ახალი პულის პირველ თავისუფალ მისამართს და ყველა მარშრუტს ერთბაშად დაარღვევდა.
ხაფანგი, რომელიც ჯერ უნდა მომეშორებინა
სანამ რამეს შევეხებოდი, შევამოწმე, რომელი მანქანა პასუხობს ARP-ს ამ მისამართზე. უპასუხა შვიდივემ.
kube-proxy რამდენიმე კვირით ადრე წავშალე, მაგრამ kubeadm upgrade node განახლების ყოველ ნაბიჯზე ჩუმად აბრუნებდა kube-proxy-ის addon-ს. მისი IPVS ინტერფეისი kube-ipvs0 კლასტერის ორ სრულ გადატვირთვას გადაურჩა და კვლავ ინახავდა LoadBalancer IP-ს და დაახლოებით 44 მოძველებულ ClusterIP-ს — IPVS-ის ნული წესით. ნაგულისხმევი arp_ignore=0-ით ბირთვი ARP-ს პასუხობს ნებისმიერ ლოკალურ მისამართზე ნებისმიერ ინტერფეისზე, ამიტომ IP-ს ყველა ნოდი აცხადებდა.
MetalLB-ის დროს ეს ასატანი იყო: externalTrafficPolicy: Cluster-ით და eBPF-ში სერვისების დამუშავებით ნებისმიერ ნოდს შეუძლია მისამართის მომსახურება. მაგრამ ამან გაანადგურა L2 გადაცემის შემოწმების ერთადერთი იაფი გზა — ვისი MAC ფლობს IP-ს. ლიდერის არჩევა დეკორაცია გახდებოდა. ამიტომ პირველი ნაბიჯი იყო თითოეულ ნოდზე kube-ipvs0-დან მხოლოდ ამ ერთი /32-ის მოშორება: ჯერ საცდელად არამფლობელ ნოდზე, მიმდინარე მფლობელი — ბოლოს.
კონტრინტუიციური თანმიმდევრობა
- მოძველებული მისამართის მოშორება, რომ ARP-ის მფლობელობა ცალსახა და დაკვირვებადი გახდეს.
- Cilium-ის Helm values-ში
l2announcements-ის ჩართვა GitOps-ით და აგენტისა და ოპერატორის გადატვირთვა: ჩარტი პოდებს არ ტვირთავს, თუ მხოლოდ ConfigMap შეიცვალა. - Cilium-ის IP პულისა და L2 პოლიტიკის გამოყენება, სანამ MetalLB ჯერ კიდევ მუშაობს.
- მხოლოდ ამის შემდეგ MetalLB-ის ნულამდე შემცირება, failover-ის შემოწმება და წაშლა.
მესამე ნაბიჯია მიზეზი, რის გამოც დაუნთაიმი არ ყოფილა. ორი ARP-მპასუხე split brain-ს ჰგავს, მაგრამ externalTrafficPolicy: Cluster-ის დროს არ აქვს მნიშვნელობა, ვინ გაიმარჯვებს: ტრაფიკს ნებისმიერი ნოდი eBPF-ში მოემსახურება. რადგან ახალი პული ერთი /32 იყო, ორივე IPAM-ს მხოლოდ ერთი და იგივე მისამართის გამოთვლა შეეძლო — Service-ის სტატუსი არც ერთხელ არ შეცვლილა. Cilium-მა აიღო lease და ARP მის ნოდზე გადავიდა, სანამ MetalLB ჯერ კიდევ ცოცხალი იყო.
მისამართი ერთმისამართიანი პულით დავაფიქსირე და არა ანოტაციით. Service Gateway-ს ეკუთვნის და მისი კონტროლერი ანოტაციებს შლის. /32 პული სწორ შედეგს ერთადერთ არითმეტიკულად შესაძლო ვარიანტად აქცევს — ეს მინიშნებაზე დაყრდნობას სჯობს.
გაზომილი და არა ნავარაუდევი
| ნაბიჯი | შედეგი |
|---|---|
| პული და პოლიტიკა ცოცხალი MetalLB-ის დროს | ARP lease-ის მფლობელზე გადავიდა, 0 შეცდომა |
| MetalLB ნულამდე შემცირდა | 10 შემოწმება ზედიზედ, მისამართი უცვლელია |
| lease-ის მფლობელი მოკლეს | მისამართი დაახლოებით 2,3 წამში გადავიდა, 40-დან 0 შეცდომა |
| MetalLB სრულად წაიშალა | ingress ყოველი წაშლის შემდეგ 200-ს აბრუნებდა |
წაშლის რიგსაც აქვს მნიშვნელობა: validating webhook პირველი, რადგან webhook ცოცხალი ბექენდის გარეშე საკუთარი custom resource-ების წაშლას ბლოკავს.
შიში, რომელიც ცუდი ტესტი აღმოჩნდა
ნოდიდან curl LoadBalancer IP-ზე 000-ს აბრუნებდა, ARP ცხრილი კი ცარიელი რჩებოდა. ეს ისე იკითხებოდა, თითქოს ნოდები საკუთარ LoadBalancer IP-ს ვერ აღწევენ, და კინაღამ მიგრაციის ფანჯარა შეჭამა. არც გაუმართაობა იყო და არც ძველი დაზიანება. გამოსვლის კოდი იყო 35 — TLS შეცდომა, ანუ TCP კავშირი დამყარდა. Cilium eBPF-ში მისამართს ნოდის ლოკალურ Envoy-ზე გადაამისამართებს, ამიტომ პაკეტი ქსელში საერთოდ არ გადის და ARP არ სჭირდება. TLS ეცემოდა passthrough listener-ის გამო, რომელსაც შიშველ IP-ზე SNI-სთან შესადარებელი არაფერი აქვს. სწორი Host სათაურით ან --resolve-ით პასუხი 200 იყო.
რა წავიღოთ
- L2 გადაცემამდე იპოვეთ მისამართის ყველა ARP-მპასუხე. დავიწყებული dummy ინტერფეისები „მფლობელს“ უაზროს ხდის.
- ორი კონტროლერი, რომელიც ერთსა და იმავე მისამართს აცხადებს, უსაფრთხოა, როცა მას ნებისმიერი ნოდი ემსახურება. გამოიყენეთ ეს გადაფარვისთვის მკვეთრი გადართვის ნაცვლად.
- კრიტიკული მისამართი დააფიქსირეთ პულით, რომელიც სხვას ვერაფერს გასცემს.
%{http_code}ტრანსპორტის ნებისმიერ შეცდომას000-ად აქცევს. ყოველთვის შეხედეთ გამოსვლის კოდს.- სწრაფი failover რბილი სცენარის რიცხვია. ნოდის მკვეთრი დაკარგვისას lease-ის სრული ვადის ლოდინი მოგიწევთ.