Τοπικά αρχεία κατά τη μετεγκατάσταση μιας εφαρμογής στο Kubernetes

Τοπικά αρχεία κατά τη μετεγκατάσταση μιας εφαρμογής στο Kubernetes

Κατά την κατασκευή μιας διαδικασίας CI/CD χρησιμοποιώντας το Kubernetes, μερικές φορές προκύπτει το πρόβλημα της ασυμβατότητας μεταξύ των απαιτήσεων της νέας υποδομής και της εφαρμογής που μεταφέρεται σε αυτήν. Συγκεκριμένα, στο στάδιο κατασκευής της εφαρμογής είναι σημαντικό να αποκτήσετε ένας εικόνα που θα χρησιμοποιηθεί σε όλα περιβάλλοντα έργου και συμπλέγματα. Αυτή η αρχή βασίζεται στο σωστό σύμφωνα με την Google διαχείριση κοντέινερ (περισσότερες από μία φορές για αυτό ακτίνα και το τεχνικό μας τμήμα).

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

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

Στατική αποθήκευση

Για παράδειγμα, σκεφτείτε μια εφαρμογή Ιστού που χρησιμοποιεί κάποιο είδος στατικής γεννήτριας για να αποκτήσει ένα σύνολο εικόνων, στυλ και άλλων πραγμάτων. Για παράδειγμα, το πλαίσιο PHP Yii έχει έναν ενσωματωμένο διαχειριστή στοιχείων που δημιουργεί μοναδικά ονόματα καταλόγου. Κατά συνέπεια, η έξοδος είναι ένα σύνολο μονοπατιών για τη στατική τοποθεσία που προφανώς δεν τέμνονται μεταξύ τους (αυτό έγινε για διάφορους λόγους - για παράδειγμα, για την εξάλειψη των διπλότυπων όταν πολλά στοιχεία χρησιμοποιούν τον ίδιο πόρο). Έτσι, από το κουτί, την πρώτη φορά που αποκτάτε πρόσβαση σε μια λειτουργική μονάδα πόρου Ιστού, στατικά αρχεία (στην πραγματικότητα, συχνά συμβολικοί σύνδεσμοι, αλλά για αυτό αργότερα) σχηματίζονται και τοποθετούνται με έναν κοινό ριζικό κατάλογο μοναδικό για αυτήν την ανάπτυξη:

  • webroot/assets/2072c2df/css/…
  • webroot/assets/2072c2df/images/…
  • webroot/assets/2072c2df/js/…

Τι σημαίνει αυτό όσον αφορά ένα σύμπλεγμα;

Το πιο απλό παράδειγμα

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

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

Σε μια απλοποιημένη μορφή, η διαμόρφωση nginx συνοψίζεται στα εξής:

apiVersion: v1
kind: ConfigMap
metadata:
  name: "nginx-configmap"
data:
  nginx.conf: |
    server {
        listen 80;
        server_name _;
        charset utf-8;
        root  /var/www;

        access_log /dev/stdout;
        error_log /dev/stderr;

        location / {
            index index.php;
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi_params;
        }
    }

Κατά την πρώτη πρόσβαση στον ιστότοπο, τα στοιχεία εμφανίζονται στο κοντέινερ PHP. Αλλά στην περίπτωση δύο κοντέινερ σε ένα pod, το nginx δεν γνωρίζει τίποτα για αυτά τα στατικά αρχεία, τα οποία (σύμφωνα με τη διαμόρφωση) θα πρέπει να τους δοθούν. Ως αποτέλεσμα, ο πελάτης θα δει ένα σφάλμα 404 για όλα τα αιτήματα σε αρχεία CSS και JS. Η απλούστερη λύση εδώ θα ήταν η οργάνωση ενός κοινού καταλόγου για κοντέινερ. Πρωτόγονη επιλογή - γενική emptyDir:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: assets
          emptyDir: {}
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

Τώρα τα στατικά αρχεία που δημιουργούνται στο κοντέινερ εξυπηρετούνται από το nginx σωστά. Αλλά επιτρέψτε μου να σας υπενθυμίσω ότι αυτή είναι μια πρωτόγονη λύση, πράγμα που σημαίνει ότι απέχει πολύ από το ιδανικό και έχει τις δικές της αποχρώσεις και μειονεκτήματα, τα οποία συζητούνται παρακάτω.

Πιο προηγμένη αποθήκευση

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

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

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

Γενικά το αποτέλεσμα είναι και πάλι λάθη.

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

Ποιες είναι οι λύσεις;

  1. Εάν το υλικό και οι πόροι επιτρέπουν, μπορείτε να χρησιμοποιήσετε cephfs να οργανώσει έναν εξίσου προσβάσιμο κατάλογο για στατικές ανάγκες. Επίσημη τεκμηρίωση συνιστά μονάδες SSD, τουλάχιστον τριπλή αναπαραγωγή και σταθερή «παχιά» σύνδεση μεταξύ κόμβων συμπλέγματος.
  2. Μια λιγότερο απαιτητική επιλογή θα ήταν η οργάνωση ενός διακομιστή NFS. Ωστόσο, τότε πρέπει να λάβετε υπόψη την πιθανή αύξηση του χρόνου απόκρισης για την επεξεργασία αιτημάτων από τον διακομιστή web και η ανοχή σφαλμάτων θα αφήσει πολλά να είναι επιθυμητά. Οι συνέπειες της αποτυχίας είναι καταστροφικές: η απώλεια της βάσης καταδικάζει το σύμπλεγμα σε θάνατο υπό την πίεση του φορτίου LA που ορμάει στον ουρανό.

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

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

Σύσταση

Εάν η εφαρμογή των προτεινόμενων επιλογών αποθήκευσης σας φαίνεται επίσης αδικαιολόγητη (περίπλοκη, ακριβή...), τότε αξίζει να δείτε την κατάσταση από την άλλη πλευρά. Δηλαδή, να εμβαθύνουμε στην αρχιτεκτονική του έργου και διορθώστε το πρόβλημα στον κώδικα, που συνδέεται με κάποια στατική δομή δεδομένων στην εικόνα, έναν ξεκάθαρο ορισμό των περιεχομένων ή της διαδικασίας "προθέρμανσης" ή/και προμεταγλώττισης στοιχείων στο στάδιο της συναρμολόγησης εικόνας. Με αυτόν τον τρόπο έχουμε απολύτως προβλέψιμη συμπεριφορά και το ίδιο σύνολο αρχείων για όλα τα περιβάλλοντα και τα αντίγραφα της εφαρμογής που εκτελείται.

Αν επιστρέψουμε στο συγκεκριμένο παράδειγμα με το πλαίσιο Yii και δεν εμβαθύνουμε στη δομή του (που δεν είναι ο σκοπός του άρθρου), αρκεί να επισημάνουμε δύο δημοφιλείς προσεγγίσεις:

  1. Αλλάξτε τη διαδικασία δημιουργίας εικόνας για να τοποθετήσετε τα στοιχεία σε μια προβλέψιμη τοποθεσία. Αυτό προτείνεται/εφαρμόζεται σε επεκτάσεις όπως yii2-static-assets.
  2. Ορίστε συγκεκριμένους κατακερματισμούς για καταλόγους στοιχείων, όπως συζητείται π.χ. αυτή την παρουσίαση (ξεκινώντας από τη διαφάνεια Νο. 35). Παρεμπιπτόντως, ο συντάκτης της αναφοράς τελικά (και όχι χωρίς λόγο!) συμβουλεύει μετά τη συναρμολόγηση στοιχείων στον διακομιστή κατασκευής, να τα ανεβάσετε σε έναν κεντρικό αποθηκευτικό χώρο (όπως το S3), μπροστά από τον οποίο τοποθετείτε ένα CDN.

Λήψεις

Μια άλλη περίπτωση που σίγουρα θα εμφανιστεί κατά τη μετεγκατάσταση μιας εφαρμογής σε ένα σύμπλεγμα Kubernetes είναι η αποθήκευση αρχείων χρήστη στο σύστημα αρχείων. Για παράδειγμα, έχουμε πάλι μια εφαρμογή PHP που δέχεται αρχεία μέσω μιας φόρμας αποστολής, κάνει κάτι μαζί τους κατά τη λειτουργία και τα στέλνει πίσω.

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

Σύσταση

Μια λύση είναι χρησιμοποιώντας αποθηκευτικό χώρο συμβατό με S3 (ακόμα κι αν πρόκειται για κάποιο είδος αυτο-φιλοξενούμενης κατηγορίας όπως το minio). Η μετάβαση στο S3 θα απαιτήσει αλλαγές σε επίπεδο κώδικα, και πώς θα παραδοθεί το περιεχόμενο στο μπροστινό μέρος, έχουμε ήδη писали.

Συνεδρίες χρήστη

Ξεχωριστά, αξίζει να σημειωθεί η οργάνωση της αποθήκευσης των περιόδων σύνδεσης χρήστη. Συχνά πρόκειται επίσης για αρχεία στο δίσκο, τα οποία στο πλαίσιο του Kubernetes θα οδηγήσουν σε συνεχή αιτήματα εξουσιοδότησης από τον χρήστη, εάν το αίτημά του καταλήξει σε άλλο κοντέινερ.

Το πρόβλημα λύνεται εν μέρει με την ενεργοποίηση stickySessions κατά την είσοδο (η δυνατότητα υποστηρίζεται σε όλους τους δημοφιλείς ελεγκτές εισόδου - για περισσότερες λεπτομέρειες, βλ την κριτική μας)για να συνδέσετε τον χρήστη σε ένα συγκεκριμένο pod με την εφαρμογή:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-test
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "route"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

spec:
  rules:
  - host: stickyingress.example.com
    http:
      paths:
      - backend:
          serviceName: http-svc
          servicePort: 80
        path: /

Αλλά αυτό δεν θα εξαλείψει τα προβλήματα με τις επαναλαμβανόμενες αναπτύξεις.

Σύσταση

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

Συμπέρασμα

Οι λύσεις υποδομής που συζητούνται στο κείμενο είναι άξιες χρήσης μόνο με τη μορφή προσωρινών «πατερίτσες» (που ακούγεται πιο όμορφο στα αγγλικά ως λύση). Μπορεί να είναι συναφείς στα πρώτα στάδια της μετεγκατάστασης μιας εφαρμογής στο Kubernetes, αλλά δεν πρέπει να ριζώνουν.

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

PS

Διαβάστε επίσης στο blog μας:

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο