Konfigurace projektu uvnitř a vně Kubernetes

Nedávno jsem napsal odpověď o životě projektu v Dockerech a ladění kódu mimo něj, kde jsem okrajově zmínil, že lze vytvořit svůj vlastní systém konfigurace, aby služba dobře fungovala i v Kubernetu, načítala tajemství a byla snadno spouštěna lokálně, včetně úplně mimo Docker. Nic složitého, ale popsaný "recept" může někomu přijít vhod 🙂 Kód je v Pythonu, ale logika není jazykově závislá.

Konfigurace projektu uvnitř a vně Kubernetes

Předhistorie otázky je taková: žil byl jeden projekt, ze začátku byl malým monolitem s utilitami a skripty, ale časem rostl, dělil se na služby, které se také začaly dělít na mikroslužby a pak ještě škálovaly. Zpočátku se to vše provádělo na holých VPS, procesy nastavení a nasazení kódu na nich byly automatizovány pomocí Ansible a každé službě byl vytvořen YAML konfigurační soubor s potřebnými nastaveními a klíči, a obdobný konfigurační soubor se používal pro lokální spuštění, což bylo velmi pohodlné, protože se tento konfigurační soubor načítal do globálního objektu dostupného z jakéhokoli místa v projektu.

Nicméně růst počtu mikroslužeb, jejich vazeb, a také požadavek na centralizované logování a monitoring, předznamenával stěhování do Kubernetes, které je stále v procesu. Spolu s pomocí při řešení zmíněných úkolů, Kubernetes nabízí své přístupy k řízení infrastruktury, včetně tzv. Tajemství a způsobů práce s nimi. Mechanismus je standardní a spolehlivý, proto by bylo doslova hříchem ho nevyužít! Ale zároveň bych chtěl zachovat svůj současný formát práce s konfigurací: za prvé, jednotně ho používat v různých mikroslužbách projektu, a za druhé, mít možnost spouštět kód na lokálním stroji pomocí jednoho jednoduchého konfiguračního souboru.

V souvislosti s tím byl mechanismus pro vytváření konfiguračního objektu přepracován tak, aby uměl pracovat jak s naším klasickým konfiguračním souborem, tak s tajemstvími z Kubernetu. Byla také nastavena přísnější struktura konfigu, mluvíme-li jazykem třetího Pythonu, takto:

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

To znamená, že konečný konfig je slovník s pojmenovanými sekcemi, z nichž každá je slovníkem se hodnotami z jednoduchých typů. A sekce popisují konfiguraci a přístupy k prostředkům určitého typu. Příklad části našeho konfigu:

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"

Přitom pole engine databáze lze nastavit na SQLite, a redis nastavit na mock, uvedením i názvu souboru pro uložení – tyto parametry jsou správně rozpoznány a zpracovány, což umožňuje snadné spouštění kódu lokálně pro ladění, unit testování a jakékoli další potřeby. To je pro nás zvlášť relevantní, protože těchto dalších potřeb je mnoho – část našeho kódu je určena pro různé analytické výpočty, spouští se nejen na serverech s orchestrací, ale i různými skripty a na počítačích analytiků, kteří potřebují zpracovávat a ladit složité datové toky, aniž by se museli zabývat backendovými otázkami. Mimochodem, nebylo by od věci podělit se o to, že naše hlavní nástroje, včetně kódu pro sestavení konfiguračního souboru, se instalují přes setup.py – dohromady to spojuje náš kód do jedné ekosystémy, nezávislé na platformě a způsobu použití.

Popis podu v Kubernetes vypadá takto:

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 znamená, že v každém tajemství je popsána jedna sekce. Samotná tajemství se vytvářejí takto:

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

To vše vede k vytvoření YAML souborů na cestě /etc/secrets/db-main/section_name.yaml

A pro lokální spouštění se používá konfigurace umístěná v kořenovém adresáři projektu nebo na cestě uvedené v proměnné prostředí. Kód zodpovědný za toto pohodlí lze spatřit v spolku.

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 zde docela jednoduchá: spojujeme velké konfigurační soubory z adresáře projektu a cesty podle proměnné prostředí, a malé konfigurační sekce ze tajemství Kubernetesa, a potom je trochu předzpracováváme. Navíc některé proměnné. Upozorňuji, že při vyhledávání souborů z tajemství se používá omezení hloubky, protože K8s vytváří v každém tajemství ještě skrytou složku, kde jsou tajemství uložena, a o úroveň výš je jen odkaz.

Doufám, že výše uvedené bude někomu užitečné 🙂 Přijímám jakékoli komentáře a doporučení týkající se bezpečnosti nebo jiných oblastí zlepšení. Zajímalo by mě také názory komunity, zda bychom neměli přidat podporu pro ConfigMaps (v našem projektu je zatím nepoužíváme) a zpřístupnit kód na GitHubu / PyPI? Osobně si myslím, že takové věci jsou příliš individuální pro projekty, aby byly univerzální, a postačuje malé nahlédnutí na realizace ostatních, jako je ta zde uvedená, a diskuse o detailech, radách a osvědčených postupech, které doufám uvidím v komentářích 😉

Pouze registrovaní uživatelé se mohou účastnit ankety. Přihlaste se, prosím.

Měli bychom to publikovat jako projekt/knihovnu?

  • 0,0%Ano, určitě bych to použil/přispěl.

  • 33,3%Ano, zní to skvěle.

  • 41,7%Ne, kdo chce, udělá si to sám ve svém formátu a podle svých potřeb.

  • 25,0%Zdržím se odpovědi.

Hlasovalo 12 uživatelů. 3 uživatelé se zdrželi.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster