Το λείπον αρχείο swap που πάγωνε τους διακομιστές μας
Στις 24 Σεπτεμβρίου, ένας από τους διακομιστές παραγωγής σταμάτησε να απαντά. Ο load balancer τον σημάδεψε ως μη υγιή και το εργαλείο απομακρυσμένων εντολών δεν μπορούσε να τον φτάσει — αλλά τίποτα δεν είχε καταρρεύσει και τίποτα δεν είχε σκοτωθεί επειδή χρησιμοποιούσε υπερβολική μνήμη. Ο διακομιστής έκανε thrashing: χωρίς χώρο swap και με container που επιτρεπόταν να χρησιμοποιούν περισσότερη μνήμη από όση είχε το μηχάνημα, το Linux περνούσε τον χρόνο του εκκενώνοντας και ξαναδιαβάζοντας αρχεία που χρειαζόταν για να τρέξει, και σχεδόν δεν προχωρούσε. Η προσθήκη ενός αρχείου swap 2 GB σε κάθε διακομιστή το διόρθωσε. Το πιο ενδιαφέρον μάθημα αφορούσε τους ελέγχους υγείας.
Πώς έμοιαζε το «κολλημένο»
- Οι έλεγχοι υγείας του load balancer έληξαν σε εκείνον τον διακομιστή.
- Οι εντολές που στέλνονταν μέσω AWS Systems Manager έμεναν σε «Pending» και δεν εκτελούνταν ποτέ.
- Δεν υπήρχε θανάτωση λόγω μνήμης στα logs. Καμία διεργασία δεν πέθανε.
Μια εβδομάδα νωρίτερα είχαμε μεταφέρει την παραγωγή σε μικρότερους διακομιστές ARM με 2 GB RAM ο καθένας. Ο διακομιστής staging μας — ακόμη μικρότερος — είχε παγώσει με τον ίδιο τρόπο ενώ αποσυμπιέζε μια εικόνα container κατά μια ανάπτυξη.
Γιατί δεν υπήρχε σφάλμα μνήμης;
Το Linux σκοτώνει μια διεργασία μόνο όταν πραγματικά τελειώνει η μνήμη που μπορεί να ανακτηθεί. Πριν φτάσει εκεί, ανακτά μνήμη από την page cache — τα αντίγραφα αρχείων που κρατά στην RAM, συμπεριλαμβανομένου του κώδικα κάθε προγράμματος που τρέχει.
Στους διακομιστές μας, τα συνολικά όρια μνήμης των container ανέρχονταν σε περίπου 3,3 GB σε μηχάνημα με περίπου 1,8 GB διαθέσιμα. Κανονικά δεν χρησιμοποιούν όλα το όριό τους ταυτόχρονα. Όταν το έκαναν, ο πυρήνας συνέχιζε να εκκενώνει τον κώδικα των προγραμμάτων από την cache για να κάνει χώρο, και μετά έπρεπε να τον διαβάσει ξανά από τον δίσκο τη στιγμή που ο κώδικας έτρεχε ξανά. Αυτός ο κύκλος — το thrashing — μπορεί να κάνει ένα μηχάνημα σχεδόν εντελώς μη ανταποκρινόμενο ενώ τεχνικά ποτέ δεν τελειώνει η μνήμη. Άρα δεν σκοτώνεται τίποτα, και τίποτα δεν ανακάμπτει.
Χωρίς swap, ο πυρήνας δεν έχει πού να βάλει μνήμη που έχει δεσμευτεί αλλά χρησιμοποιείται σπάνια, οπότε την παίρνει από την cache.
Η διόρθωση
Το σενάριο αρχικοποίησης του διακομιστή δημιουργεί πλέον ένα αρχείο swap 2 GB με χαμηλή τιμή swappiness (10) κάθε φορά που ένας διακομιστής ξεκινά ή επισκευάζεται. Η χαμηλή swappiness σημαίνει ότι ο πυρήνας εξακολουθεί να προτιμά να κρατά τη μνήμη εργασίας στην RAM, αλλά οι σελίδες που σπάνια αγγίζονται μπορούν να μετακινηθούν στον δίσκο αντί να αναγκάζουν τον κώδικα των προγραμμάτων έξω από την cache. Το εφαρμόσαμε ζωντανά και στους δύο διακομιστές παραγωγής και στο staging, και η αγωγή ανάπτυξης ξαναδημοσιεύει το σενάριο αρχικοποίησης σε κάθε ανάπτυξη παραγωγής, ώστε ένας αντικαταστημένος διακομιστής να το πάρει κι αυτός.
Αυτό που μας ανησύχησε περισσότερο: ο μη υγιής διακομιστής έμεινε
Η ομάδα διακομιστών μας χρησιμοποιούσε έλεγχους υγείας EC2 — «τρέχει το μηχάνημα;» — και όχι έλεγχους υγείας του load balancer — «απαντά σε αιτήματα;». Άρα ένας διακομιστής που ο load balancer είχε εγκαταλείψει εξακολουθούσε να είναι υγιής για την ομάδα. Δεν θα αντικαθίστατο ποτέ. Η παραγωγή θα έτρεχε αθόρυβα σε έναν διακομιστή μέχρι να το προσέξει κάποιος και να επανεκκινήσει τον άλλο.
Η αλλαγή της ομάδας σε έλεγχους υγείας του load balancer ακούγεται σαν προφανής διόρθωση, αλλά δεν είναι απλή για εμάς: κατά τη διάρκεια μιας ανάπτυξης, οι διακομιστές απαντούν σκόπιμα με σφάλμα στο endpoint υγείας τους ώστε ο balancer να τους αποσύρει (δείτε πώς φτάσαμε σε μηδέν 502 στις αναπτύξεις). Μια ομάδα που αντικαθιστά διακομιστές με βάση αυτό το σήμα θα τους αντικαθιστούσε στη μέση κάθε ανάπτυξης.
Γι' αυτό προσθέσαμε προς το παρόν ένα βήμα στη λίστα ελέγχου ανάπτυξης: μετά από κάθε ανάπτυξη, ελέγχουμε την υγεία κάθε στόχου στον load balancer, όχι μόνο αν πέρασε η εργασία ανάπτυξης. Αν οι εντολές προς έναν διακομιστή μένουν σε «Pending», κάνει thrashing — κάντε επανεκκίνηση· είναι ήδη εκτός εξυπηρέτησης.
Τι κρατήσαμε από όλο αυτό
- Το «δεν καταρρέει» δεν σημαίνει «υγιές». Το thrashing παράγει καμία σφάλμα, μόνο μια επιβράδυνση τόσο ακραία που το μηχάνημα σταματά να απαντά.
- Οι μικροί διακομιστές χρειάζονται swap. Ακόμη και λίγο, με χαμηλή swappiness, δίνει στον πυρήνα πού να βάλει την κρύα μνήμη.
- Όρια μνήμης που αθροίζονται σε περισσότερο από το μηχάνημα είναι στοίχημα. Μερικές φορές το χάνετε. Μάθετε πότε το έχετε κάνει.
- Μάθετε ποιον έλεγχο υγείας εμπιστεύεται η αυτόματη κλιμάκωσή σας. Ένας έλεγχος που ρωτά μόνο «είναι ανοιχτή η VM;» δεν θα αντικαταστήσει έναν διακομιστή που έχει σταματήσει να δουλεύει.