ექვსი საათი NotReady-ში, ნოდა კი მთელი ამ დროის განმავლობაში ცოცხალი იყო
ყველა სიგნალი ამბობდა, რომ ნოდა მკვდარია. სინამდვილეში მეხსიერება აკლდა, ხოლო აშკარა გამოსავალი — ნოდის წაშლა და ტომის იძულებითი მოხსნა — ერთი ბრძანებით იყო დაშორებული ბაზის დაზიანებისგან.
Gambchamp-ში, AWS EKS-ზე მომუშავე პროდუქტის პლატფორმაზე, მონაცემთა ბაზის ნოდა ექვსი საათი იყო NotReady მდგომარეობაში. kubectl describe node ყველა პირობაზე წერდა Kubelet stopped posting node status. ClickHouse-ის StatefulSet-ის წევრი ამ ნოდაზე Terminating-ში გაიჭედა, ტომი ისევ მიერთებული ჰქონდა, მისმა წყვილმა კი 96-ჯერ გადაიტვირთა.
ყველაფერი ამბობდა: „მანქანა აღარ არსებობს“. სახელმძღვანელოს გამოსავალიც ისეთივე ნათელია: kubectl delete node, რათა გაჭედილი პოდი გათავისუფლდეს, ტომი იძულებით მოიხსნას და StatefulSet ხელახლა დაიგეგმოს. ეს ბრძანება ერთი ღილაკით იყო დაშორებული მონაცემების განადგურებისგან.
ჯერ fencing
ნებისმიერ force-detach-მდე უნდა დაამტკიცო, რომ ძველი ჩამწერი ნამდვილად მკვდარია. ამიტომ მოქმედების ნაცვლად fencing გავაკეთე — მხოლოდ წაკითხვით:
- EC2 ინსტანსი
runningმდგომარეობაში იყო; - ორივე სტატუსის შემოწმება, სისტემური და ინსტანსის, —
ok; - SSM აგენტმა პასუხი kubelet-იდან დაახლოებით სამ წუთში შეწყვიტა.
მომუშავე ინსტანსი მწვანე შემოწმებებით მკვდარი მანქანა არ არის. ნამდვილი მინიშნება ის იყო, რომ ორი დამოუკიდებელი დემონი თითქმის ერთდროულად გაჩუმდა: დემონები სინქრონულად არ ვარდებიან, მაგრამ სინქრონულად შიმშილობენ.
შიმშილობს და არა მკვდარია
CloudWatch მთელ ფანჯარაში აჩვენებდა CPU-ს თანაბრად 55 %-ზე, 99,9 %-მდე ნახტომებით. 2 vCPU-იან ნოდაზე 55 % ნიშნავს ერთ ბირთვს, სრულად დატვირთულს. ასე გამოიყურება მეხსიერების უწყვეტი reclaim.
მიზეზი არითმეტიკის ერთი სტრიქონია:
container limits.memory (4Gi) > node allocatable (3.07Gi)
cgroup-ის ლიმიტი, რომელიც აღემატება იმას, რისი მიცემაც ნოდას რეალურად შეუძლია, cgroup-ის დონეზე OOM-kill-ს არასოდეს გამოიწვევს. კონტეინერი იზრდება, სანამ მთელი მანქანის მეხსიერება არ ამოიწურება, და რადგან swap არ არის, ბირთვი დროს პირდაპირ reclaim-ზე ხარჯავს. kubelet და SSM აგენტი CPU-ს ვერ იღებენ და heartbeat-ს ვერ აგზავნიან. NotReady kubelet-ზე იყო ნათქვამი და არა მანქანაზე — ბაზა კი, დიდი ალბათობით, ისევ წერდა თავის ტომში.
ცოცხალი ჩამწერისგან ტომის მოხსნა დაზიანებული დისკის მიღების საიმედო გზაა. reclaim=Retain დისკს ინახავს, მის მთლიანობას — არა.
რა მოხდა შემდეგ
არაფერი — განზრახ. Fencing-მა კითხვაზე „უსაფრთხოა თუ არა force-detach?“ არა უპასუხა და არც ერთი ცვლილება არ გამიკეთებია. გარკვეული დროის შემდეგ ბირთვის სისტემურმა OOM killer-მა საბოლოოდ ClickHouse-ის პროცესი აირჩია, მეხსიერება დაბრუნდა, kubelet-მა სტატუსის გაგზავნა განაახლა და ნოდა Ready-ში დაბრუნდა.
ასეთი აღდგენა იღბალია და არა გამოსწორება. ნამდვილი გამოსწორება ის არის, რომ ლიმიტი ეტეოდეს: limits.memory ნოდის allocatable-ზე ნაკლები, მაშინ cgroup ერთ კონტეინერს მოკლავს და ბირთვი მთელ ნოდას არ დაახრჩობს. და პულის სწორად შერჩევა, რომელიც უბრალოდ პატარა იყო მასზე გაშვებული დატვირთვისთვის. ლიმიტის კიდევ აწევა მდგომარეობას გააუარესებდა.
დასკვნები
NotReadykubelet-ს აღწერს და არა მანქანას. ნებისმიერ დესტრუქციულ ნაბიჯამდე — fencing: ინსტანსის მდგომარეობა, სტატუსის შემოწმებები, აგენტის პინგი, CPU ინციდენტის ფანჯარაში.runningდაstatus checks okმხოლოდ იმას ნიშნავს, რომ ჰიპერვიზორი ბირთვს წვდება. შეამოწმეთ CPU-სა და აგენტის heartbeat-თან ერთად.- ორი ერთად გაჩუმებული დემონი რესურსების შიმშილზე მიუთითებს და არა ქსელურ იზოლაციაზე.
- მეხსიერების ლიმიტები ნოდის allocatable-ს შეადარეთ და არა capacity-ს. ლიმიტი, რომელიც არ ეტევა, ნოდას ვერ დაიცავს.
delete node-ს force-detach-თან ერთად უკან დაბრუნება არ აქვს. თუ ვერ ამტკიცებთ, რომ ჩამწერი მკვდარია — ნუ გააკეთებთ.