Διαμόρφωση έργου εντός και εκτός Kubernetes

Έγραψα πρόσφατα απάντηση σχετικά με τη διάρκεια ζωής του έργου στο Docker και τον εντοπισμό σφαλμάτων κώδικα εκτός αυτού, όπου ανέφερε εν συντομία ότι μπορείτε να φτιάξετε το δικό σας σύστημα διαμόρφωσης έτσι ώστε η υπηρεσία να λειτουργεί καλά στο Kuber, να ανακαλύπτει μυστικά και να εκτελείται βολικά τοπικά, ακόμη και εκτός του Docker συνολικά. Τίποτα περίπλοκο, αλλά η περιγραφόμενη "συνταγή" μπορεί να είναι χρήσιμη σε κάποιον :) Ο κώδικας είναι σε Python, αλλά η λογική δεν συνδέεται με τη γλώσσα.

Διαμόρφωση έργου εντός και εκτός Kubernetes

Το υπόβαθρο της ερώτησης είναι το εξής: κάποτε υπήρχε ένα έργο, στην αρχή ήταν ένας μικρός μονόλιθος με βοηθητικά προγράμματα και σενάρια, αλλά με την πάροδο του χρόνου μεγάλωσε, χωρίστηκε σε υπηρεσίες, οι οποίες με τη σειρά τους άρχισαν να χωρίζονται σε μικροϋπηρεσίες και στη συνέχεια κλιμακώθηκε. Αρχικά, όλα αυτά γίνονταν σε γυμνά VPS, οι διαδικασίες ρύθμισης και ανάπτυξης κώδικα στους οποίους αυτοματοποιήθηκαν χρησιμοποιώντας το Ansible και κάθε υπηρεσία μεταγλωττίστηκε με μια διαμόρφωση YAML με τις απαραίτητες ρυθμίσεις και κλειδιά και ένα παρόμοιο αρχείο διαμόρφωσης χρησιμοποιήθηκε για τοπικές εκκινήσεις, κάτι που ήταν πολύ βολικό, επειδή .k αυτή η διαμόρφωση φορτώνεται σε ένα καθολικό αντικείμενο, προσβάσιμο από οπουδήποτε στο έργο.

Ωστόσο, η αύξηση του αριθμού των μικροϋπηρεσιών, των συνδέσεών τους και ανάγκη για κεντρική καταγραφή και παρακολούθηση, προμήνυε μια μετακόμιση στην Kuber, η οποία είναι ακόμη σε εξέλιξη. Μαζί με τη βοήθεια για την επίλυση των αναφερθέντων προβλημάτων, η Kubernetes προσφέρει τις προσεγγίσεις της στη διαχείριση υποδομής, συμπεριλαμβανομένων τα λεγόμενα Μυστικά и τρόπους συνεργασίας μαζί τους. Ο μηχανισμός είναι τυπικός και αξιόπιστος, επομένως είναι κυριολεκτικά αμαρτία να μην τον χρησιμοποιήσετε! Αλλά ταυτόχρονα, θα ήθελα να διατηρήσω την τρέχουσα μορφή μου για την εργασία με το config: πρώτον, να το χρησιμοποιήσω ομοιόμορφα σε διαφορετικές μικροϋπηρεσίες του έργου και δεύτερον, να μπορώ να εκτελέσω τον κώδικα στο τοπικό μηχάνημα χρησιμοποιώντας ένα απλό αρχείο ρυθμίσεων.

Από αυτή την άποψη, ο μηχανισμός για την κατασκευή ενός αντικειμένου διαμόρφωσης τροποποιήθηκε ώστε να μπορεί να λειτουργεί τόσο με το κλασικό αρχείο ρυθμίσεων όσο και με μυστικά από τον Kuber. Μια πιο άκαμπτη δομή διαμόρφωσης καθορίστηκε επίσης, στη γλώσσα της τρίτης Python, ως εξής:

Dict[str, Dict[str, Union[str, int, float]]]

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

adminka:
  django_secret: "ExtraLongAndHardCode"

db_main:
  engine: mysql
  host: 256.128.64.32
  user: cool_user
  password: "SuperHardPassword"

redis:
  host: 256.128.64.32
  pw: "SuperHardPassword"
  port: 26379

smtp:
  server: smtp.gmail.com
  port: 465
  email: [email protected]
  pw: "SuperHardPassword"

Παράλληλα το χωράφι engine Οι βάσεις δεδομένων μπορούν να εγκατασταθούν στο SQLite και redis οριστεί σε mock, καθορίζοντας επίσης το όνομα του αρχείου προς αποθήκευση - αυτές οι παράμετροι αναγνωρίζονται και επεξεργάζονται σωστά, γεγονός που καθιστά εύκολη την εκτέλεση του κώδικα τοπικά για εντοπισμό σφαλμάτων, δοκιμή μονάδας και οποιεσδήποτε άλλες ανάγκες. Αυτό είναι ιδιαίτερα σημαντικό για εμάς γιατί υπάρχουν πολλές άλλες ανάγκες - μέρος του κώδικά μας προορίζεται για διάφορους αναλυτικούς υπολογισμούς, εκτελείται όχι μόνο σε διακομιστές με ενορχήστρωση, αλλά και με διάφορα σενάρια και σε υπολογιστές αναλυτών που πρέπει να εργαστούν και εντοπισμός σφαλμάτων σύνθετων αγωγών επεξεργασίας δεδομένων χωρίς ανησυχητικά προβλήματα υποστήριξης. Παρεμπιπτόντως, δεν θα ήταν κακό να μοιραστούμε ότι τα κύρια εργαλεία μας, συμπεριλαμβανομένου του κώδικα διάταξης διαμόρφωσης, είναι εγκατεστημένα μέσω setup.py – μαζί αυτό ενώνει τον κώδικά μας σε ένα ενιαίο οικοσύστημα, ανεξάρτητο από πλατφόρμα και μέθοδο χρήσης.

Η περιγραφή ενός pod Kubernetes μοιάζει με αυτό:

containers:
  - name : enter-api
    image: enter-api:latest
    ports:
      - containerPort: 80
    volumeMounts:
      - name: db-main-secret-volume
        mountPath: /etc/secrets/db-main

volumes:
  - name: db-main-secret-volume
    secret:
      secretName: db-main-secret

Δηλαδή, κάθε μυστικό περιγράφει ένα τμήμα. Τα μυστικά δημιουργούνται ως εξής:

apiVersion: v1
kind: Secret
metadata:
  name: db-main-secret
type: Opaque
stringData:
  db_main.yaml: |
    engine: sqlite
    filename: main.sqlite3

Μαζί αυτό έχει ως αποτέλεσμα τη δημιουργία αρχείων YAML κατά μήκος της διαδρομής /etc/secrets/db-main/section_name.yaml

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

config.py

__author__ = 'AivanF'
__copyright__ = 'Copyright 2020, AivanF'

import os
import yaml

__all__ = ['config']
PROJECT_DIR = os.path.abspath(__file__ + 3 * '/..')
SECRETS_DIR = '/etc/secrets'
KEY_LOG = '_config_log'
KEY_DBG = 'debug'

def is_yes(value):
    if isinstance(value, str):
        value = value.lower()
        if value in ('1', 'on', 'yes', 'true'):
            return True
    else:
        if value in (1, True):
            return True
    return False

def update_config_part(config, key, data):
    if key not in config:
        config[key] = data
    else:
        config[key].update(data)

def parse_big_config(config, filename):
    '''
    Parse YAML config with multiple section
    '''
    if not os.path.isfile(filename):
        return False
    with open(filename) as f:
        config_new = yaml.safe_load(f.read())
        for key, data in config_new.items():
            update_config_part(config, key, data)
        config[KEY_LOG].append(filename)
        return True

def parse_tiny_config(config, key, filename):
    '''
    Parse YAML config with a single section
    '''
    with open(filename) as f:
        config_tiny = yaml.safe_load(f.read())
        update_config_part(config, key, config_tiny)
        config[KEY_LOG].append(filename)

def combine_config():
    config = {
        # To debug config load code
        KEY_LOG: [],
        # To debug other code
        KEY_DBG: is_yes(os.environ.get('DEBUG')),
    }
    # For simple local runs
    CONFIG_SIMPLE = os.path.join(PROJECT_DIR, 'config.yaml')
    parse_big_config(config, CONFIG_SIMPLE)
    # For container's tests
    CONFIG_ENVVAR = os.environ.get('CONFIG')
    if CONFIG_ENVVAR is not None:
        if not parse_big_config(config, CONFIG_ENVVAR):
            raise ValueError(
                f'No config file from EnvVar:n'
                f'{CONFIG_ENVVAR}'
            )
    # For K8s secrets
    for path, dirs, files in os.walk(SECRETS_DIR):
        depth = path[len(SECRETS_DIR):].count(os.sep)
        if depth > 1:
            continue
        for file in files:
            if file.endswith('.yaml'):
                filename = os.path.join(path, file)
                key = file.rsplit('.', 1)[0]
                parse_tiny_config(config, key, filename)
    return config

def build_config():
    config = combine_config()
    # Preprocess
    for key, data in config.items():
        if key.startswith('db_'):
            if data['engine'] == 'sqlite':
                data['filename'] = os.path.join(PROJECT_DIR, data['filename'])
    # To verify correctness
    if config[KEY_DBG]:
        print(f'** Loaded config:n{yaml.dump(config)}')
    else:
        print(f'** Loaded config from: {config[KEY_LOG]}')
    return config

config = build_config()

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

Ελπίζω ότι αυτό που περιγράφεται θα είναι χρήσιμο σε κάποιον :) Οποιεσδήποτε παρατηρήσεις και συστάσεις σχετικά με την ασφάλεια ή άλλους τομείς προς βελτίωση γίνονται δεκτές. Η γνώμη της κοινότητας είναι επίσης ενδιαφέρουσα, ίσως αξίζει να προσθέσουμε υποστήριξη για ConfigMaps (το έργο μας δεν τους χρησιμοποιεί ακόμα) και να δημοσιεύσουμε τον κώδικα στο GitHub / PyPI; Προσωπικά, πιστεύω ότι τέτοια πράγματα είναι πολύ μεμονωμένα για να είναι καθολικά τα έργα, και λίγη ματιά στις υλοποιήσεις άλλων ανθρώπων, όπως αυτή που δίνεται εδώ, και μια συζήτηση για αποχρώσεις, συμβουλές και βέλτιστες πρακτικές, που ελπίζω να δω στα σχόλια , είναι αρκετό 😉

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

Πρέπει να δημοσιεύσω ως έργο/βιβλιοθήκη;

  • 0,0%Ναι, θα χρησιμοποιούσα το /contribution0

  • 33,3%Ναι, αυτό ακούγεται υπέροχο4

  • 41,7%Όχι, ποιος πρέπει να το κάνει μόνος του στη δική του μορφή και να ταιριάζει στις ανάγκες του5

  • 25,0%Θα αποφύγω να απαντήσω3

Ψήφισαν 12 χρήστες. 3 χρήστες απείχαν.

Πηγή: www.habr.com

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