Konfiguracija projekta unutar i izvan Kubernetes-a

Nedavno sam napisao odgovor o životu projekta u Dockeru i debagiranju koda izvan njega, gdje sam u prolazu spomenuo da se može napraviti vlastiti sustav konfiguracije kako bi usluga također dobro radila u Kubernetesu, povukla tajne i lokalno se lako pokretala, uključujući i van Dockera. Nema ništa komplicirano, ali opisani "recept" može nekome biti koristan 🙂 Kod je u Pythonu, ali logika nije vezana uz jezik.

Konfiguracija projekta unutar i izvan Kubernetes-a

Predhistoria pitanja je takva: živio jedan projekt, prvo je bio mali monolit s alatima i skriptama, ali s vremenom je rastao, dijelio se na usluge, koje su se pak dijelile na mikroservise, a zatim su se još i skalirali. U početku se sve to obavljalo na golim VPS-ima, a procesi postavljanja i implementacije koda automatizirani su uz pomoć Ansibla, i za svaku uslugu je izrađen YAML konfiguracijski datoteka s potrebnim postavkama i ključevima, a sličan konfiguracijski datoteka korištena je za lokalna pokretanja, što je bilo vrlo praktično, jer se ova konfiguracija učitava u globalni objekt, dostupan s bilo kojeg mjesta u projektu.

Međutim, rast broja mikroservisa, njihovi međusobni odnosi, kao i potreba za centraliziranim evidentiranjem i praćenjem, predviđali su prelazak u Kubernetes, koji je još uvijek u fazi. Uz pomoć u rješavanju spomenutih zadataka, Kubernetes nudi svoje pristupe upravljanju infrastrukturom, uključujući tzv. Tajne i metode rada s njima. Mehanizam je standardan i pouzdan, pa je u doslovnom smislu grijeh ne iskoristiti ga! No pritom bih volio zadržati svoj trenutni format rada s konfiguracijom: prvo, dosljedno ga koristiti u različitim mikroservisima projekta, a drugo, imati mogućnost pokretanja koda na lokalnom računalu koristeći jednu jednostavnu konfiguracijsku datoteku.

S tim u vezi, mehanizam izgradnje objekta konfiguracije je doradjen tako da može raditi i s našom klasičnom konfiguracijskom datotekom, kao i s tajnama iz Kubea. Također je postavljena stroža struktura konfiguracije, govoreći jezikom trećeg Pythona, ovako:

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

Drugim riječima, konačna konfiguracija je rječnik s imenovanim sekcijama, od kojih svaka predstavlja rječnik s vrijednostima iz osnovnih tipova. A sekcije opisuju konfiguraciju i pristup resursima određenog tipa. Primjer dijela naše konfiguracije:

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: info@test.com
  pw: "SuperHardPassword"

U tom slučaju, polje engine baza podataka može biti postavljena na SQLite, dok redis može se konfigurirati na mock, navodeći još i ime datoteke za spremanje, – ovi parametri se ispravno prepoznaju i obrađuju, što omogućava lako pokretanje koda lokalno radi debagiranja, jediničnih testiranja i drugih potreba. To nam je posebno važno jer imamo mnogo tih drugih potreba – dio našeg koda je namijenjen raznim analitičkim izračunima, pokreće se ne samo na serverima s orkestrom, nego i raznim skriptama, kao i na računalima analitičara koji moraju obrađivati i debagirati složene tokove obrade podataka bez brige o backend pitanjima. Usput, ne bi bilo loše podijeliti da se naši glavni alati, uključujući kod za konfiguraciju, instaliraju putem setup.py – zajedno to povezuje naš kod u jedinstveni ekosustav, neovisno o platformi i načinu korištenja.

Opis poda u Kubernetesu izgleda ovako:

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

To jest, u svakom sekretu opisana je jedna sekcija. Sami se sekreti kreiraju ovako:

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

To zajedno dovodi do stvaranja YAML datoteka na putu /etc/secrets/db-main/section_name.yaml

A za lokalna pokretanja koristi se konfiguracija smještena u glavnoj direktoriji projekta ili na putu navedenom u varijabli okruženja. Kod koji je odgovoran za ove pogodnosti može se vidjeti u spoileru.

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

Logika je ovdje prilično jednostavna: spajamo velike konfiguracije iz direktorija projekta i putem varijable okruženja, te male sekcije konfiguracije iz Kubernetovih tajni, a zatim ih malo predobrađujemo. Plus, neke varijable. Napominjem da se prilikom pretraživanja datoteka iz tajni koristi ograničenje dubine, budući da K8s u svakoj tajni stvara još jednu skrivenu mapu, gdje se tajne zapravo pohranjuju, dok je jedan nivo iznad samo poveznica.

Nadam se da će ovo što sam opisao nekome biti od koristi 🙂 Prihvaćaju se svi komentari i preporuke vezani uz sigurnost ili druge aspekte za poboljšanje. Također me zanima mišljenje zajednice, možda bi bilo korisno dodati podršku za ConfigMaps (u našem projektu trenutno se ne koristi) i objaviti kod na GitHubu / PyPI? Osobno mislim da su takve stvari previše individualne za projekte da bi bile univerzalne, i dovoljno je malo zaviriti u tuđe implementacije, poput one koja je ovdje prikazana, te raspraviti nijanse, savjete i najbolje prakse, što se nadam vidjeti u komentarima 😉

Samo registrirani korisnici mogu sudjelovati u anketi. Prijavite se, molimo.

Trebalo bi li objaviti kao projekt / biblioteku?

  • 0,0%Da, ja bih koristio / doprinio

  • 33,3%Da, zvuči sjajno

  • 41,7%Ne, tko treba, sami će to napraviti u svom formatu i prema svojim potrebama

  • 25,0%Suzdržat ću se od odgovora

Glasovalo je 12 korisnika. Suzdržale su se 3 korisnika.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster