Μια αποτυχημένη επανάπτυξη δεν διαγράφει ποτέ ένα backend που δουλεύει
Στις 25 Σεπτεμβρίου, μια αποτυχημένη επανάπτυξη διέγραψε το backend μιας εφαρμογής που ήταν ζωντανό και λειτουργούσε για περίπου δυόμισι ώρες. Ο κώδικας που αναιρεί μια αποτυχημένη δημοσίευση διέγραψε κάθε εφαρμογή διακομιστή που είχε αγγίξει η δημοσίευση, συμπεριλαμβανομένων εκείνων που ήδη εξυπηρετούσαν χρήστες πριν ξεκινήσει. Πλέον διαγράφονται μόνο οι εφαρμογές που δημιούργησε μια δημοσίευση. Μια εφαρμογή που υπήρχε ήδη επιστρέφεται στην ακριβή έκδοση που έτρεχε πριν. Δύο επιπλέον διορθώσεις εκείνη την ημέρα εξασφάλισαν ότι η αναίρεση τρέχει και όταν αποτυγχάνει το ίδιο το εργαλείο deploy της πλατφόρμας φιλοξενίας, και ότι μια υγιής εφαρμογή δεν θεωρείται χαλασμένη επειδή ήταν σε αδράνεια.
Πώς δημοσιεύεται ένα backend
Όταν η εφαρμογή σας στο Mythex έχει backend, δηλαδή API ή διακομιστή και όχι μόνο στατικά αρχεία, η δημοσίευση το χτίζει από ένα Dockerfile (μια συνταγή για το πώς πακετάρεται η εφαρμογή σε image) και το ανεβάζει στα μηχανήματα της πλατφόρμας φιλοξενίας. Στη συνέχεια, ένας έλεγχος υγείας περιμένει να ξεκινήσει και να μείνει σε λειτουργία η νέα έκδοση, πριν σημανθεί η δημοσίευση ως ενεργή.
Αν κάτι αποτύχει στην πορεία, τρέχει μια επαναφορά (rollback) για να το αναιρέσει. Το να γίνεται σωστά αυτή η επαναφορά έχει μεγαλύτερη σημασία από σχεδόν οτιδήποτε άλλο στη δημοσίευση, επειδή τρέχει ακριβώς όταν κάτι ήδη πάει στραβά.
Πρόβλημα 1: η επαναφορά διέγραψε αυτό που έπρεπε να επαναφέρει
Η επαναφορά κρατούσε λίστα με τις εφαρμογές διακομιστή στις οποίες είχε αναπτυχθεί η δημοσίευση, και σε περίπτωση αποτυχίας τις κατέστρεφε όλες. Αυτό είναι σωστό για την πρώτη δημοσίευση: η εφαρμογή δημιουργήθηκε μόλις πριν, τίποτα δεν εξαρτάται από αυτήν, και η διαγραφή αφήνει τα πράγματα όπως ήταν.
Είναι ακριβώς λάθος για μια επαναδημοσίευση. Η εφαρμογή υπήρχε ήδη και εξυπηρετούσε πραγματικούς επισκέπτες. Στις 25 Σεπτεμβρίου, το API μιας εφαρμογής που ήταν ζωντανό για περίπου δυόμισι ώρες διαγράφηκε από μια αποτυχημένη επαναδημοσίευση.
Υπήρχε και δεύτερη αστοχία κρυμμένη στον ίδιο κώδικα. Όταν η διαγραφή δεν ολοκληρωνόταν, η χαλασμένη νέα image έμενε στα μηχανήματα, ενώ εμείς είχαμε ενημερώσει τον ιδιοκτήτη ότι ο τρέχων ιστότοπός του εξακολουθούσε να είναι ζωντανός. Μέχρι να μπορέσει να αποτύχει ο έλεγχος υγείας, η νέα image έχει ήδη ανέβει σε κάθε μηχάνημα. Το να μην κάνεις τίποτα δεν είναι το ίδιο με το να αναιρέσεις.
Η διόρθωση: διάγραψε μόνο όσα δημιούργησες, επανάφερε τα υπόλοιπα
Κάθε δημοσίευση καταγράφει πλέον, για κάθε εφαρμογή διακομιστή που αγγίζει:
- αν αυτή η δημοσίευση τη δημιούργησε, και
- αν όχι, ποια image έτρεχε λίγο πριν.
Σε περίπτωση αποτυχίας, κάθε εφαρμογή παίρνει μία από τρεις μεταχειρίσεις:
- Δημιουργήθηκε από αυτή τη δημοσίευση: διαγράφεται, όπως και πριν.
- Υπήρχε ήδη, και η προηγούμενη image είναι γνωστή: επαναναπτύσσεται η προηγούμενη image με την ίδια ρύθμιση. Δεν γίνεται build, μόνο επιστρέφει η image.
- Υπήρχε ήδη, αλλά η προηγούμενη image είναι άγνωστη: αφήνεται ως έχει. Μια υπάρχουσα εφαρμογή δεν διαγράφεται ποτέ, ακόμη και όταν δεν μπορούμε να την επαναφέρουμε.
Οι κανόνες αυτοί είναι μια μικρή καθαρή συνάρτηση με δικά της τεστ, ώστε η απόφαση να μην μπορεί να ξαναγίνει «διέγραψε τα πάντα» χωρίς να αποτύχει κάποιο τεστ.
Πρόβλημα 2: το εργαλείο deploy μπορούσε να αποτύχει πριν γράψουμε πώς γίνεται η αναίρεση
Λίγο μετά τη διόρθωση εκείνη, βρήκαμε ένα κενό. Το εργαλείο deploy της πλατφόρμας τρέχει τους δικούς του ελέγχους καπνού (smoke checks): αν τα νέα μηχανήματα κρασάρουν, βγάζει σφάλμα. Το κάνει όμως αφού η νέα image έχει ήδη ανέβει στα μηχανήματα.
Ο κώδικάς μας καταχωρούσε την εφαρμογή για επαναφορά μόνο όταν το εργαλείο επέστρεφε επιτυχία. Όταν το ίδιο το εργαλείο αποτύγχανε το deploy, η εφαρμογή δεν ήταν ακόμη στη λίστα επαναφοράς, οπότε δεν επαναφερόταν τίποτα. Στο staging μας, μια χαλασμένη επαναδημοσίευση άφησε τη χαλασμένη image να εξυπηρετεί.
Η διόρθωση: καταχώρισε την αναίρεση πριν ξεκινήσεις
Η εφαρμογή καταχωρείται πλέον για επαναφορά, με το αν δημιουργήθηκε και την image που έτρεχε, μόλις ετοιμαστεί και πριν αναπτυχθεί πάνω της οτιδήποτε. Ό,τι κι αν αποτύχει μετά, η επαναφορά ξέρει τι πρέπει να επαναφέρει. Το επαληθεύσαμε τοπικά: μια χαλασμένη επαναδημοσίευση απέτυχε στους δικούς τους ελέγχους καπνού του εργαλείου deploy, και η προηγούμενη έκδοση συνέχισε να απαντά.
Πρόβλημα 3: μια εφαρμογή σε αδράνεια χαρακτηρίστηκε ως «δεν ξεκίνησε ποτέ»
Οι δημοσιευμένες εφαρμογές στο Mythex μπαίνουν σε αδράνεια όταν δεν χρησιμοποιούνται, για να εξοικονομούν τα credits σας. Μια επαναδημοσίευση αφήνει ένα μηχάνημα σε αδράνεια σταματημένο, οπότε ο έλεγχος υγείας πρέπει να το ξυπνήσει. Ζητούσε από την πλατφόρμα να ξεκινήσει το μηχάνημα μία φορά και αγνοούσε την απάντηση.
Στις 25 Σεπτεμβρίου, εκείνο το μοναδικό αίτημα εκκίνησης δεν έφτασε πουθενά. Ένα υγιές API έμεινε σταματημένο για ολόκληρα τα πέντε λεπτά του ελέγχου υγείας και χαρακτηρίστηκε ως αποτυχημένο με το μήνυμα «1 μηχάνημα δεν ξεκίνησε ποτέ (τελευταία κατάσταση: stopped)».
Η πλατφόρμα μερικές φορές αρνείται μια εκκίνηση. Κατά τη δοκιμή του προβλήματος, επέστρεψε σύγκρουση «lease currently held» χωρίς να την έχουμε προκαλέσει, και αρνείται εκκινήσεις όσο ένα μηχάνημα ενημερώνεται.
Η διόρθωση: ξαναζήτα, και χτύπα την πόρτα
Ο έλεγχος υγείας τώρα:
- ξαναζητά την εκκίνηση ενός σταματημένου μηχανήματος κάθε 20 δευτερόλεπτα όσο περιμένει,
- καταγράφει τι απάντησε η πλατφόρμα όταν αρνείται, και
- στέλνει ένα κανονικό αίτημα στην εφαρμογή, όπως ένας επισκέπτης. Ο proxy της πλατφόρμας ξυπνά ένα μηχάνημα σε αδράνεια όταν φτάσει αίτημα.
Το αναπαράγαμε στην πραγματική πλατφόρμα με μια προσωρινή εφαρμογή της οποίας η πρώτη εκκίνηση αρνήθηκε. Πριν από τη διόρθωση, η δημοσίευση απέτυχε μετά από 91 δευτερόλεπτα. Μετά, δημοσιεύτηκε σε 20 δευτερόλεπτα. Μια εφαρμογή που πραγματικά κρασάρει κατά την εκκίνηση εξακολουθεί να αποτυγχάνει, όπως πρέπει.
Τι κρατήσαμε από όλο αυτό
- Η επαναφορά πρέπει να ξέρει τη διαφορά ανάμεσα σε νέο και υπάρχον. Το «αναίρεση» για κάτι που δημιούργησες είναι διαγραφή. Το «αναίρεση» για κάτι που υπήρχε ήδη είναι επαναφορά. Όταν τα αντιμετωπίζεις το ίδιο, ένας μηχανισμός ασφαλείας διαγράφει μια εφαρμογή που δούλευε.
- Γράψε πώς γίνεται η αναίρεση πριν ενεργήσεις. Αν η αναίρεση καταγράφεται μόνο αφού ένα βήμα πετύχει, ένα βήμα που αποτυγχάνει στη μέση αφήνει τίποτα για να αναιρέσεις.
- Μην αγνοείτε ποτέ την απάντηση σε ένα αίτημα από το οποίο εξαρτάστε. Ένα «όχι» που αγνοήθηκε μετέτρεψε μια υγιή εφαρμογή σε αποτυχημένη δημοσίευση.
Μια σχετική διόρθωση από την ίδια ημέρα: η σύλληψη εφαρμογών που κρασάρουν αμέσως μετά τη δημοσίευση. Για το τι συμβαίνει όταν μια δημοσίευση αποτυγχάνει από την πλευρά σας, δείτε την αντιμετώπιση προβλημάτων δημοσίευσης στα docs.