Μετάβαση στο περιεχόμενο
EuroVPSEuroVPS
Περιοχή πελατών

Πώς μια επανεκκίνηση backend γίνεται αόρατη στους επισκέπτες σας

Δύο web servers δείχνουν ακόμα σφάλματα όταν ο ένας κάνει επανεκκίνηση. Οι ρυθμίσεις HAProxy, Varnish και keepalived που κάνουν μια επανεκκίνηση αόρατη, και πώς το δοκιμάζετε.

Από EuroVPS Engineering

5 λεπτά ανάγνωσης
Πώς μια επανεκκίνηση backend γίνεται αόρατη στους επισκέπτες σας

Το να έχετε δύο web servers πίσω από έναν load balancer δεν σημαίνει ότι μια επανεκκίνηση είναι αόρατη. Σημαίνει ότι μια επανεκκίνηση παράγει μια σύντομη ριπή σφαλμάτων αντί για διακοπή. Η διαφορά ανάμεσα στο «σύντομη» και στο «καμία» ζει σε λίγες ρυθμίσεις στο edge, και στη δοκιμή τους με πραγματική κίνηση πριν βασιστείτε σε αυτές. Έτσι κλείνουμε εμείς αυτό το κενό.

Γιατί ο πλεονασμός από μόνος του δείχνει ακόμα σφάλματα

Όταν το PHP-FPM ή το nginx κάνει επανεκκίνηση σε έναν web κόμβο, τρία πράγματα συμβαίνουν στο ένα ή δύο δευτερόλεπτα που παίρνει. Τα αιτήματα που βρίσκονται ήδη σε εξέλιξη σε αυτόν τον κόμβο κόβονται. Νέα αιτήματα συνεχίζουν να φτάνουν, επειδή ο load balancer δεν έχει ακόμα αντιληφθεί ότι ο κόμβος λείπει. Και όταν ο κόμβος επιστρέψει, μπορεί να του σταλεί πλήρες μερίδιο κίνησης πριν ζεσταθούν οι caches και η opcode cache του.

Ένας load balancer με προεπιλεγμένες ρυθμίσεις δεν χειρίζεται κανένα από αυτά με χάρη. Ο έλεγχος υγείας του τρέχει κάθε λίγα δευτερόλεπτα και χρειάζεται αρκετές αποτυχίες πριν σημειώσει έναν κόμβο ως down, οπότε για αυτό το παράθυρο στέλνει συνειδητά επισκέπτες σε έναν server που δεν απαντά. Καθένα από αυτά τα αιτήματα γίνεται ένα 502 ή 503 στην οθόνη κάποιου.

Retry και redispatch στο edge

Η πρώτη διόρθωση είναι να αφήσετε το HAProxy να ξαναπροσπαθήσει, κάπου αλλού, όταν μια σύνδεση αποτύχει:

defaults
    mode http
    retries 3
    option redispatch 1
    retry-on conn-failure empty-response response-timeout
    timeout connect 3s

Το retries επιτρέπει στο HAProxy να ξαναπροσπαθήσει ένα αίτημα του οποίου η σύνδεση απέτυχε. Το option redispatch 1 κάνει την επανάληψη να πάει σε διαφορετικό server αντί να χτυπά αυτόν που μόλις αρνήθηκε. Το retry-on επεκτείνει τις επαναλήψεις πέρα από τις συνδέσεις που απορρίφθηκαν, σε κενές απαντήσεις και timeouts, που είναι ακριβώς ό,τι παράγει συχνά μια διεργασία στη μέση επανεκκίνησης.

Οι επαναλήψεις είναι ασφαλείς για αιτήματα που δεν έχουν επεξεργαστεί. Για αιτήματα POST που μπορεί να έχουν φτάσει στην εφαρμογή, το HAProxy είναι συντηρητικό από προεπιλογή, και αυτό θέλετε: ένα checkout δεν πρέπει ποτέ να υποβληθεί δύο φορές.

Έλεγχοι υγείας που βλέπουν την εφαρμογή, όχι την πόρτα

Ένας έλεγχος TCP περνά όσο το nginx ακούει, ακόμα και όταν κάθε PHP worker έχει κολλήσει. Ελέγξτε κάτι που ασκεί το stack:

backend be_web
    option httpchk GET /healthz
    http-check expect status 200
    default-server inter 2s fall 3 rise 2 slowstart 20s
    server web1 <web1>:80 check
    server web2 <web2>:80 check

Το endpoint /healthz πρέπει να είναι ένα μικροσκοπικό PHP script που αγγίζει ό,τι μετράει (ανοίγει μια σύνδεση βάσης δεδομένων, διαβάζει ένα κλειδί cache) και επιστρέφει γρήγορα. Το fall 3 σημαίνει ότι ένας αργός έλεγχος κατά τη διάρκεια ενός log rotation ή ενός reload δεν βγάζει έναν υγιή κόμβο. Το rise 2 κρατά έναν κόμβο έξω μέχρι να απαντήσει δύο φορές. Το slowstart ανεβάζει σταδιακά έναν server που επιστρέφει, ώστε ένας κόμβος με κρύες caches να μη δέχεται πλήρες μερίδιο κίνησης μονομιάς.

Αδειάστε πριν επανεκκινήσετε

Η πιο καθαρή επανεκκίνηση είναι αυτή που ο load balancer ξέρει εκ των προτέρων. Το runtime API του HAProxy σας επιτρέπει να βγάλετε έναν κόμβο από την περιστροφή χωρίς να πέσουν τα εγκατεστημένα αιτήματα:

echo "set server be_web/web1 state drain" | socat stdio /run/haproxy/admin.sock
# wait for current sessions to finish
echo "show servers state be_web" | socat stdio /run/haproxy/admin.sock
systemctl restart php-fpm        # on web1
echo "set server be_web/web1 state ready" | socat stdio /run/haproxy/admin.sock

Τυλίξτε το σε ένα script και κάθε προγραμματισμένη επανεκκίνηση (μια ενημέρωση ασφαλείας PHP, μια αλλαγή ρυθμίσεων, μια επανεκκίνηση kernel) γίνεται drain, αναμονή, επανεκκίνηση, επαλήθευση, επιστροφή, ένας κόμβος τη φορά.

Αφήστε την cache να καλύψει το κενό

Μια full page cache κάνει περισσότερα από το να επιταχύνει την ανώνυμη κίνηση. Με ρυθμισμένο grace, το Varnish συνεχίζει να σερβίρει το τελευταίο καλό αντίγραφο μιας σελίδας όταν δεν μπορεί να φέρει φρέσκο:

import std;

sub vcl_recv {
    if (std.healthy(req.backend_hint)) {
        set req.grace = 10s;
    }
}

sub vcl_backend_response {
    set beresp.ttl = 5m;
    set beresp.grace = 24h;
}

Όσο τα backends είναι υγιή, τα αντικείμενα ανανεώνονται μέσα σε δευτερόλεπτα από τη λήξη τους. Όταν κάθε backend είναι μη υγιές, το Varnish μπορεί να σερβίρει αντικείμενα μέχρι μια μέρα μετά το TTL τους. Οι αναγνώστες συνεχίζουν να παίρνουν σελίδες κατά τη διάρκεια μιας συντήρησης βάσης δεδομένων ή ενός κολλημένου PHP pool, και οι διαδρομές των συνδεδεμένων χρηστών και του checkout, που παρακάμπτουν την cache εκ σχεδιασμού, είναι οι μόνες που εξαρτώνται από το να είναι επάνω τα backends.

Το ίδιο το edge χρειάζεται συνεργάτη

Δύο edge κόμβοι που τρέχουν keepalived μοιράζονται floating διευθύνσεις, και ένα tracking script υποβιβάζει ένα edge του οποίου το HAProxy έχει σταματήσει:

vrrp_script chk_haproxy {
    script "/usr/sbin/pidof haproxy"
    interval 2
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    track_script { chk_haproxy }
    virtual_ipaddress { <floating address>/<prefix> }
}

Χτίζουμε τα edges χιαστί: κάθε κόμβος είναι primary για μία δημόσια διεύθυνση και standby για την άλλη, με την εγγραφή DNS να φέρει και τις δύο. Και τα δύο edges μεταφέρουν κίνηση όλη την ώρα, και μια αστοχία μετακινεί μία διεύθυνση στον επιζώντα μέσα σε δευτερόλεπτα.

Αποδείξτε το με κίνηση, όχι με αίσθηση

Μια επανεκκίνηση είναι αόρατη μόνο αν την έχετε δει να είναι αόρατη. Η δοκιμή που τρέχουμε πριν από την παράδοση είναι απλή:

  1. Βάλτε σταθερό φορτίο στο site από έξω, με μείγμα cached και uncached URLs. Ένας μικρός load generator ή ένας βρόχος curl αρκεί.
  2. Μετρήστε τις απαντήσεις ανά κωδικό κατάστασης στον client, με χρονοσήμανση.
  3. Επανεκκινήστε το PHP-FPM σε έναν web κόμβο. Μετά στον άλλο. Μετά ρίξτε ένα edge.
  4. Συγκρίνετε τη μέτρηση από την πλευρά του client με τα logs του origin.
while true; do
  printf '%s ' "$(date +%T.%N)"
  curl -s -o /dev/null -w '%{http_code} %{time_total}\n' https://www.example.com/
  sleep 0.2
done | tee restart-test.log
grep -vc ' 200 ' restart-test.log

Ο origin συνήθως θα καταγράψει μια χούφτα σφάλματα σε κάθε επανεκκίνηση: αυτό είναι το κενό που το edge είναι φτιαγμένο να απορροφά. Αυτό που μετράει είναι ο αριθμός από την πλευρά του client. Όταν retry, redispatch και grace δουλεύουν, η μέτρηση του επισκέπτη μένει στο τίποτα ενώ ο origin δείχνει το σύντομο κενό της επανεκκίνησής του. Αν κάποια σφάλματα φτάσουν στον client, οι χρονοσημάνσεις σας λένε ποιο επίπεδο τα άφησε να περάσουν.

Η checklist σας

  • Το HAProxy ξαναπροσπαθεί με redispatch, και το retry-on καλύπτει κενές απαντήσεις και timeouts.
  • Ένας έλεγχος υγείας HTTP που αγγίζει PHP και βάση δεδομένων, με fall και rise ρυθμισμένα κατά του flapping.
  • Προγραμματισμένες επανεκκινήσεις που κάνουν πρώτα drain, μέσω του runtime API.
  • Varnish grace αρκετά μεγάλο για να καλύψει τη μεγαλύτερη συντήρηση backend που κάνετε.
  • Δύο edges με floating διευθύνσεις και tracking script, και τα δύο με κίνηση.
  • Μια δοκιμή επανεκκίνησης υπό φορτίο, με τον αριθμό σφαλμάτων από την πλευρά του client γραμμένο.

Αν θέλετε βοήθεια για να ρυθμίσετε ένα stack μέχρι οι επανεκκινήσεις του να εξαφανιστούν, το κάνουμε κάθε εβδομάδα.

CTA

Μιλήστε με έναν μηχανικό

Related

Σχετικά: Web tiers υψηλής διαθεσιμότητας, N+1 σε κάθε επίπεδο · Object caching με αυτόματο failover · Έλεγχος κίνησης στο edge για crawlers και scrapers

Δημοσιεύθηκε 27 Αυγούστου 2026.

Όλες οι σημειώσεις