Projektkonfiguration i og udenfor Kubernetes

jeg skrev for nylig et svar om projektets levetid i Docker og fejlfinding af kode uden for det, hvor han kort nævnte, at du kan lave dit eget konfigurationssystem, så tjenesten fungerer godt i Kuber, trækker hemmeligheder frem og kører lokalt bekvemt, også uden for Docker helt. Ikke noget kompliceret, men den beskrevne "opskrift" kan være nyttig for nogen 🙂 Koden er i Python, men logikken er ikke bundet til sproget.

Projektkonfiguration i og udenfor Kubernetes

Baggrunden for spørgsmålet er som følger: der var engang et projekt, først var det en lille monolit med hjælpeprogrammer og scripts, men med tiden voksede det, opdelt i tjenester, som igen begyndte at blive opdelt i mikrotjenester, og så også skaleret. Til at begynde med blev det hele gjort på bare VPS, processerne for opsætning og implementering af koden, som blev automatiseret ved hjælp af Ansible, og en YAML-konfiguration blev kompileret for hver tjeneste med de nødvendige indstillinger og nøgler, og en lignende konfigurationsfil blev brugt til lokale lanceringer, hvilket var meget bekvemt, t .til denne konfiguration indlæses i et globalt objekt, tilgængeligt fra hvor som helst i projektet.

Men væksten i antallet af mikrotjenester, deres forbindelser, samt behovet for centraliseret logning og overvågning, varslede flytningen til Kuber, som stadig er i gang. Sammen med hjælp til at løse ovenstående problemer tilbyder Kubernetes sine egne tilgange til infrastrukturstyring, herunder såkaldte hemmeligheder и måder at arbejde med dem på. Mekanismen er standard og pålidelig, så det er bogstaveligt talt synd ikke at bruge den! Men samtidig vil jeg gerne beholde mit nuværende format til at arbejde med konfigurationen: for det første at bruge det ensartet i forskellige mikrotjenester i projektet, og for det andet at kunne køre koden på den lokale maskine ved hjælp af en simpel konfigurationsfil.

I denne henseende er mekanismen til opbygning af konfigurationsobjektet blevet forbedret på en sådan måde, at den kan arbejde både med vores klassiske konfigurationsfil og med hemmeligheder fra Kuber. En mere stiv konfigurationsstruktur blev også indstillet, på sproget i den tredje Python, som dette:

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

Det vil sige, at den endelige konfiguration er en ordbog med navngivne sektioner, som hver er en ordbog med værdier fra simple typer. Og afsnit beskriver konfigurationen og adgangen til ressourcer af en bestemt type. Et eksempel på et stykke af vores konfiguration:

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"

Samtidig feltet engine databaser kan installeres på SQLite, og redis tune ind mock, der også angiver navnet på filen, der skal gemmes - disse parametre genkendes og behandles korrekt, hvilket gør det nemt at køre koden lokalt til fejlfinding, enhedstest og andre behov. Dette gælder især for os, fordi der er mange andre behov - en del af vores kode er designet til forskellige analytiske beregninger, den kører ikke kun på servere med orkestrering, men også på forskellige scripts og på computere til analytikere, der skal træne og debug komplekse databehandlingspipelines uden at bekymre sig om backend-spørgsmål. Det ville i øvrigt ikke være overflødigt at dele, at vores hovedværktøjer, inklusive konfigurationslinkkoden, er installeret via setup.py – tilsammen forener dette vores kode i et enkelt økosystem, der er uafhængigt af platformen og den måde, den bruges på.

Beskrivelsen af ​​en pod i Kubernetes ser sådan ud:

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

Det vil sige, at der er beskrevet et afsnit i hver hemmelighed. Selve hemmelighederne er skabt sådan:

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

Tilsammen resulterer dette i oprettelsen af ​​YAML-filer langs stien /etc/secrets/db-main/section_name.yaml

Og til lokale lanceringer bruges konfigurationen, placeret i projektets rodbibliotek eller langs stien angivet i miljøvariablen. Koden, der er ansvarlig for disse bekvemmeligheder, kan ses i spoileren.

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()

Logikken her er ret simpel: vi kombinerer store konfigurationer fra projektbiblioteket og sti efter miljøvariabel, og små konfigurationssektioner fra Kubers hemmeligheder, og så forbehandler vi dem lidt. Plus nogle variabler. Jeg bemærker, at når man søger efter filer fra hemmeligheder, bruges en dybdegrænse, fordi K8s opretter endnu en skjult mappe i hver hemmelighed, hvor selve hemmelighederne er gemt, og blot et link er placeret et niveau højere.

Jeg håber, at det beskrevne vil være nyttigt for nogen 🙂 Eventuelle kommentarer og anbefalinger vedrørende sikkerhed eller andre punkter til forbedringer accepteres. Fællesskabets mening er også interessant, måske er det værd at tilføje support til ConfigMaps (de er ikke brugt i vores projekt endnu) og indsende koden på GitHub / PyPI? Personligt synes jeg, at sådanne ting er for individuelle til, at projekter kan være universelle, og kigger lidt på andres implementeringer, som den her, og diskussionen om nuancer, tips og bedste praksis, som jeg håber at se i kommentarerne , er nok 😉

Kun registrerede brugere kan deltage i undersøgelsen. Log ind, Vær venlig.

Skal jeg udgive som et projekt/bibliotek?

  • 0,0 %Ja, jeg ville bruge /contributyl0

  • 33,3 %Ja, det lyder godt

  • 41,7 %Nej, hvem skal selv gøre det i deres eget format og for at passe til deres behov5

  • 25,0 %Undlad at svare 3

12 brugere stemte. 3 brugere undlod at stemme.

Kilde: www.habr.com

Tilføj en kommentar