Projektkonfiguration inden for og uden for Kubernetes

For nylig skrev jeg et svar om livscyklussen for projektet i Docker og fejlfinding af koden udenfor det, hvor jeg kort nævnte, at det er muligt at lave sit eget konfigurationssystem, så tjenesten fungerer godt i Kubernetes, henter hemmeligheder og kan køres lokalt, endda helt uden Docker. Ikke noget kompliceret, men den beskrevne "opskrift" kan måske være nyttig for nogen 🙂 Koden er i Python, men logikken er ikke bundet til sproget.

Projektkonfiguration inden for og uden for Kubernetes

Baggrunden for spørgsmålet er som følger: der var engang et projekt, som først var en lille monolit med værktøjer og scripts, men som med tiden voksede, blev opdelt i tjenester, som igen blev opdelt i mikrotjenester, og derefter blev skaleret. I starten blev alt dette kørt på rå VPS'er, hvor processerne til opsætning og implementering af koden blev automatiseret ved hjælp af Ansible, og der blev lavet en YAML-konfiguration for hver tjeneste med de nødvendige indstillinger og nøgler, og en tilsvarende konfigurationsfil blev brugt til lokale kørsel, hvilket var meget praktisk, da denne konfiguration indlæses i et globalt objekt, der er tilgængeligt fra ethvert sted i projektet.

Men væksten af antallet af mikrotjenester, deres relationer, samt behovet for centraliseret logføring og overvågning, indikerede et skift til Kubernetes, som stadig er undervejs. Sammen med hjælp til at løse de nævnte opgaver tilbyder Kubernetes sine egne metoder til at håndtere infrastrukturen, herunder de såkaldte hemmeligheder og metoder til at arbejde med dem. Mekanismen er standard og pålidelig, så det ville i bogstavelig forstand være en skam ikke at bruge den! Men samtidig ville jeg gerne bevare mit nuværende format for arbejde med konfigurationen: for det første at bruge det ensartet i forskellige mikrotjenester i projektet, og for det andet at have mulighed for at køre koden på den lokale maskine ved hjælp af én simpel konfigurationsfil.

I den forbindelse blev mekanismen til at opbygge objekter-konfigurationer forbedret, så den kan arbejde både med vores klassiske konfigurationsfil og med hemmeligheder fra Kubernetes. Derudover blev der fastsat en strammere struktur for konfigurationen, sagt på det tredje Python-sprog, sådan her:

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

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

adminka:
  django_secret: "EkstraLangOgSværKode"

db_main:
  engine: mysql
  host: 256.128.64.32
  user: cool_user
  password: "SuperSværAdgangskode"

redis:
  host: 256.128.64.32
  pw: "SuperSværAdgangskode"
  port: 26379

smtp:
  server: smtp.gmail.com
  port: 465
  email: info@test.com
  pw: "SuperSværAdgangskode"

Samtidig er feltet engine databasen kan installeres på SQLite, og redis konfigureres til mock, ved at angive yderligere filnavn til opbevaring – disse parametre genkendes og behandles korrekt, hvilket gør det nemt at køre koden lokalt til debugging, enhedstestning og andre behov. Dette er især relevant for os, da der er mange andre behov – en del af vores kode er beregnet til forskellige analytiske beregninger, og kører ikke kun på servere med orkestrering, men også med forskellige scripts og på analytikeres computere, der har brug for at udvikle og debugge komplekse databehandlingspipelines uden at skulle tænke på backend-spørgsmål. Forresten kan det være værd at nævne, at vores primære værktøjer, herunder konfigurationsopbygningskode, installeres gennem setup.py – sammen samler dette vores kode i et samlet økosystem, uafhængigt af platform og anvendelsesmetode.

Beskrivelsen af pod'en i Kubernetes ser således 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 hver hemmelighed beskriver én sektion. Selve hemmelighederne oprettes således:

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

Sammen fører det til oprettelsen af YAML-filer på stien /etc/secrets/db-main/section_name.yaml

Og til lokale opstarter bruges en konfiguration, der ligger i rodmappen af projektet eller på den sti, der er 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 sections
    '''
    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 projektmappen og stien via miljøvariablen samt små konfigurationer-sektioner fra Kubernetes hemmeligheder, hvorefter vi forbereder dem lidt. Derudover nogle variabler. Jeg bemærker, at der anvendes en dybdebegrænsning, når der søges efter filer fra hemmelighederne, da K8s i hver hemmelighed opretter en skjult mappe, hvor selve hemmelighederne gemmes, og et niveau over findes der blot et link.

Jeg håber, det beskrevne vil være nyttigt for nogen 🙂 Alle kommentarer og anbefalinger om sikkerhed eller andre forbedringer er velkomne. Jeg er også interesseret i samfundets mening; måske bør vi tilføje understøttelse af ConfigMaps (de bruges ikke endnu i vores projekt) og offentliggøre koden på GitHub / PyPI? Personligt mener jeg, at sådanne ting er for individuelle til projekter til at være universelle, og det er ofte tilstrækkeligt at tage et kig på andres implementeringer, som den der er vist her, og diskutere nuancer, tips og bedste praksis, som jeg håber at se i kommentarerne 😉

Kun registrerede brugere kan deltage i undersøgelsen. Log ind, please.

Bør det offentliggøres som et projekt/bibliotek?

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

  • 33,3%Ja, det lyder godt

  • 41,7%Nej, de der har brug for det vil lave det i deres eget format og til deres behov

  • 25,0%Jeg afholder mig fra at svare

12 brugere har stemt. 3 brugere afholdt sig.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster