Traefik v2 Εξισορρόπηση φορτίου με στάθμιση για διακομιστές

Home » Blog » Traefik v2 Weighted Load Balancing for Servers

Πότε ψάχναμε για ένα αναστρέφον πρόγραμμα που θα αποτελούσε το σημείο εισόδου για τη διασύνδεση των υποδομών του εξυπηρετητή μας, βρήκαμε το άριστο Traefik. Φυσικά, θέλαμε ισοκατανομή φορτίου και φυσικά, θέλαμε να εκχωρούμε βάρη· έτσι, σκόπασαν την τεκμηρίωση και γρήγορα βρήκαμε ό,τι ψάχναμε εδώ. Το Traefik υποστηρίζει ισοκατανομή φορτίου υπό τη μορφή της Κυκλικής Ισοκατανόμης με Βάρη (ΚΙΒ), η οποία κατευθύνει το κυκλοφοριακό φορτίο διαμέσου των διαθέσιμων υπηρεσιών. Αυτό ακούγεται ακριβώς όπως ό,τι θέλαμε. Ωστόσο, καθώς ανακαλύψαμε, δεν ήταν απόλυτα έτσι. Η πορεία μας ξεκινά εδώ.

Ενημέρωση: Μία μέρα μετά που πρότεινα τη διόρθωση στον αποθετήριο τους στο GitHub, η ομάδα του Traefik άρχισε να δουλεύει πάνω στη λειτουργία. Τελικά θα προσγειωθεί στο Traefik v3.0.

Σύντομη Περίληψη: Μπορείτε να παραλείψετε přímo στο λύση.

Υπηρεσίες έναντι Διακομιστών

Καταλήξαμε ότι είχε περάσει απαρατήρητο ένα μικρό σημείο στις τεκμηρίωση: η στρατηγική WRR είναι διαθέσιμη μόνο για τις υπηρεσίες, όχι για τους διακομιστές.

Αυτή η στρατηγική είναι διαθέσιμη μόνο για το φόρτωση ισοβαρή μεταξύ υπηρεσιών και όχι μεταξύ διακομιστών.

Αρχικά, έπρεπε να μάθουμε πώς να ρυθμίσουμε σωστά το Traefik και δεν είχαμε ιδέα τι σημαίνουν οι υπηρεσίες και οι διακομιστές στο πλαίσιο του Traefik. Τώρα που το γνωρίζουμε, θέλαμε να παρέχουμε μία seule υπηρεσία σε πολλές διακομιστές όπως παρακάτω:

services:
  my_service:
    loadBalancer:
      servers:
        - url: "https://server1:1234/"
        - url: "https://server2:1234/"
        - url: ...

Επειδή κάθε διακομιστής έχει διαφορετικό επίπεδο ισχύος, θέλαμε επίσης να δώσουμε βαρύτητα σε κάθε διακομιστή. Ωστόσο, δεν είναι δυνατόν να αναθέσουμε βαρύτητες στους διακομιστές σε αυτή τη ρύθμιση, και υπάρχει επίσης ένα ανοιχτό ζήτημα στο GitHub από το 2019.

Το μόνο Traefik v1 είχε μια ιδιότητα βαρύτητας, αλλά δεν θέλαμε να βασιστούμε σε λογισμικό παρακαταθήκη.

Ιδέες

Έχουμε διαφορετικές ιδέες για το πώς θα ξεπεράσουμε αυτό το λείπει χαρακτηριστικό.

Ιδέα 1: Κακή χρήση Ελέγχων Υγείας για τη模ίμηση των κακών διακομιστών

Η ιδέα ήταν να μιμούμε ένα άρρωστο διακομιστή όταν έφτανε στο μέγιστο της δυνατότητας. Έτσι, το Traefik θα σταματούσε να κατευθύνει την κυκλοφορία προς το διακομιστή όταν δεν είχε πια ελεύθερες θέσεις.

# Example health check configuration

http:
  services:
    my-service:
      loadBalancer:
        healthCheck:
          path: /health
          interval: "5s"
          timeout: "3s"
<pre class="wp-block-syntaxhighlighter-code"># Example FastAPI endpoint

@app.get('/health')
def health():
    if COMPUTE_CAPABILITY / job_q.qsize() < 1:
        return Response('Too Many Requests', status_code=429)

    return Response('OK', status_code=200)</pre>

Όταν η Ουρά Εργασιών υπερβαίνει τη δυνατότητα υπολογισμού του διακομιστή, θα αναφερόταν ως άρρωστο στο Traefik μέσα σε όχι περισσότερο από οκτώ δευτερόλεπτα. Το Traefik θα το έβγαινε από το Round Robin και θα άφηνε άλλους διακομιστές να εξυπηρετήσουν την αίτηση. Δοκιμάσαμε αυτή τη λύση και όπως αποδείχτηκε, προκάλεσε προβλήματα.

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

Δεύτερο και πιο εκπληκτικό, αντιμετωπίσαμε σφάλματα NS_ERROR_NET_PARTIAL_TRANSFER για ορισμένες αιτήσεις. Δεν διερευνήσαμε περαιτέρω, αλλά πιστεύουμε ότι συνέβη λόγω των διακομιστών που έμειναν κρεμαστοί μεταξύ της εξυπηρέτησης μακρόχρονων αιτήσεων και αναφέρθηκαν ως μη υγιείς, προκαλώντας την διακοπή της σύνδεσης του πελάτη. Η συντήρηση τέτοιου υποδομή είναι άθυρση.

Τρίτο, η υπηρεσία μας είναι καταστατική. Αυτό σημαίνει ότι χρησιμοποιούμε kleisiστικές συνεδρίες, εξασφαλίζοντας ότι κάθε πελάτης επικοινωνεί μόνο με ένα διακομιστή που έχει ανατεθεί στοό της συνεδρίας. Το να γίνει ένας από αυτούς τους διακομιστές μη διαθέσιμος κατά τη διάρκεια μιας συνεδρίας θέτει ένα πρόβλημα που πίστευαμε ότι μπορούσαμε να λύσουμε. Δεν μπορούσαμε. Έτσι, εγκατέλειψαμε την Ιδέα 1.

Ιδέα 2: Χειρισμός της Συνεδρίας

Όταν ιδρύουμε μια συνεδρία, χρησιμοποιούμε το JavaScript fetch() με πιστωτικά περιλαμβανόμενα:

await fetch('https://edge_server/', {credentials: 'include'})

Με αυτόν τον τρόπο, ο Traefik αναθέτει ένα νέο cookie συνεδρίας στο πελάτη με την κεφαλίδα Set-Cookie στο αρχικό αίτημα fetch.

# Example Traefik configuration with sticky sessions enabled

services:
  my_service:
    loadBalancer:
      sticky:
        cookie:
          name: session_name
          httpOnly: true

Αν παραλείψουμε τα διαπιστευτήρια στην αίτηση, το Traefik θα αναθέσει μια νέα συνεδρία σε κάθε nuova αίτηση χρησιμοποιώντας τη διαδικασία Round Robin. Η ιδέα 2 ήταν να επιτρέψει στον πελάτη να συγκεντρώσει νέες συνεδρίες μέχρι να βρει ένα διακομιστή με ελεύθερους θύλακες.

Μόνο χρειαζόμασταν να ορίσουμε ένα νέο τελικό σημείο FastAPI που να πληροφορεί τον πελάτη αν ο διακομιστής πίσω από τη τρέχουσα συνεδρία είχε ελεύθερους θύλακες, καθώς και μια μέθοδο επανάληψης λήψης στο JavaScript για να βρει το σωστό διακομιστή. Για το τελικό σημείο, μπορούσαμε να επαναχρησιμοποιήσουμε τη μέθοδο υγείας από πάνω. Για τη μέθοδο επανάληψης λήψης, θέλαμε φυσικά να χειριστούμε και την περίπτωση όταν όλοι οι διακομιστές ήταν繁忙. Δημιουργήσαμε αυτό:

<pre class="wp-block-syntaxhighlighter-code">async function fetch_retry(...args) {
    
    for (let i = 0; i < 300; i++) {
        for (let s = 0; s < N_SERVERS; s++) {
            const response = await fetch(...args);
    
            if (response.status === 503) {
                // Waiting for a free slot...
                if (s === (N_SERVERS - 1)) {
                  await new Promise(r => setTimeout(r, 20000));
                }
                continue;
            }
            
            return response;
        }
    }
    
    return response;
}
</pre>

Τώρα, πριν από οποιαδήποτε άλλη κλήση του fetch(), καλούσαμε το τελικό σημείο μας χωρίς διαπιστευτήρια:

await fetch_retry('https://edge_server/dispatch');

Και πράγματι, έλαβα ένα νέο στοιχείο Set-Cookie για κάθε επανάληψη της βρόχης.

Δυστυχώς, το υπάρχον session cookie δεν ενημερώνεται με την κλήση fetch_retry(), καθώς αυτό συμβαίνει μόνο όταν υπάρχουν διαπιστευτήρια, ít LEAST με το Firefox. Τότε προσπαθήσαμε να συμπεριλάβουμε αυτά και να καθαρίσουμε τη sessionstorage όταν έχουμε συνεδρία για ένα “κακό” διακομιστή. Δυστυχώς, η μέθοδος clear() της sessionstorage δεν καθαρίζει το cookie διαπιστευτηρίων, ακόμα και αν είναι επίσης ένα session cookie. Αυτό ισχύει ακόμα και όταν ορίζουμε το httpOnly ως false (αν και αυτό δεν συνιστάται για λόγους ασφαλείας πάντως).

Έπειτα χρειαστήκαμε να κοιμηθούμε أخرى νύχτα πριν βρούμε άλλη μία ιδέα.

Η Τελική Ιδέα: Ψευδοδιακομιστές

Η τελική ιδέα βασίστηκε στο ερώτημα: Πώς χειρίζεται το Traefik τις διευθύνσεις διακομιστών;

Αν μπορούσαμε να δείξουμε πολλαπλές διευθύνσεις URL που θεωρούνται διαφορετικές στο Traefik προς τον ίδιο сервер, θα μπορούσαμε να μιμηθούμε το βάρος του διακομιστή δημιουργώντας ψευδο-διαφορετικές διευθύνσεις URL που όλες οδηγούν στον ίδιο διακομιστή. Και πράγματι, αυτό λειτουργούσε όπως περιμέναμε. Χρειάζονταν μόνο να προσθέσουμε ένα μη υπάρχον μονοπάτι, όπως το «/1/» ή το «/2/», στις διευθύνσεις URL, και το Traefik θα τις χειριζόταν ξεχωριστά αλλά θα τις οδηγούσε χωρίς λάθηση και χωρίς να προσθέτει το μονοπάτι στο σωστό διακομιστή.

services:
  my_service:
    loadBalancer:
      servers:
        - url: "https://server_A:1234/1/"
        - url: "https://server_A:1234/2/"
        - url: "https://server_B:1234/"

Μόνο έπρεπε να βεβαιωθούμε ότι δημιουργούσαμε τον σωστό αριθμό από διπλούς ψευδο-διακομιστές URL που αντιστοιχούν στις αντίστοιχες ικανότητες του διακομιστή. Για παράδειγμα, αν είχαμε έναν διακομιστή Α, ο οποίος ήταν δύο φορές πιο ισχυρός από τον διακομιστή Β, τότε έπρεπε να προσθέσουμε δύο διευθύνσεις URL για τον διακομιστή Α. Αυτό σημαίνει ότι κατά τη διάρκεια της Περιστροφής Ροής, ο διακομιστής Α θαenerima δύο φορές περισσότερες αιτήσεις από τον διακομιστή Β.

Share it