Konfigurácia projektu vnútri a mimo Kubernetes

Nedávno som napísal odpoveď na život projektu v Dockeri a ladenie kódu mimo neho, kde stručne spomenul, že si môžete vytvoriť svoj vlastný konfiguračný systém, aby služba v Kuberi dobre fungovala, vyťahovala tajomstvá a bežala pohodlne lokálne, dokonca aj mimo Docker. Nič zložité, ale popísaný “recept” sa niekomu môže hodiť :) Kód je v Pythone, ale logika nie je viazaná na jazyk.

Konfigurácia projektu vnútri a mimo Kubernetes

Pozadie otázky je takéto: kedysi tu bol jeden projekt, najprv to bol malý monolit s pomôckami a skriptami, ale časom sa rozrástol, rozdelil sa na služby, ktoré sa zase začali rozdeľovať na mikroslužby a potom zväčšené. Najprv sa to všetko robilo na holých VPS, procesy nastavenia a nasadenia kódu boli automatizované pomocou Ansible a každá služba bola zostavená s konfiguráciou YAML s potrebnými nastaveniami a kľúčmi a podobný konfiguračný súbor bol použitý na lokálne spúšťanie, čo bolo veľmi pohodlné, pretože .k táto konfigurácia sa načíta do globálneho objektu, ktorý je prístupný odkiaľkoľvek v projekte.

Rast počtu mikroslužieb, ich pripojení a potreba centralizovaného zaznamenávania a monitorovania, predznamenal prestup ku Kuberovi, ktorý stále prebieha. Spolu s pomocou pri riešení spomínaných problémov ponúka Kubernetes svoje prístupy k riadeniu infraštruktúry vrátane takzvané Tajomstvá и spôsoby, ako s nimi pracovať. Mechanizmus je štandardný a spoľahlivý, takže je doslova hriech ho nepoužívať! Zároveň by som si však rád zachoval svoj súčasný formát pre prácu s konfiguráciou: po prvé, aby som ho používal jednotne v rôznych mikroslužbách projektu a po druhé, aby som mohol spustiť kód na lokálnom počítači pomocou jedného jednoduchého konfiguračný súbor.

V tomto smere bol upravený mechanizmus na zostavenie konfiguračného objektu tak, aby dokázal pracovať ako s naším klasickým konfiguračným súborom, tak aj s tajomstvami od Kuberu. V jazyku tretieho Pythonu bola špecifikovaná aj prísnejšia štruktúra konfigurácie, a to takto:

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

To znamená, že konečný cogfig je slovník s pomenovanými sekciami, z ktorých každá je slovníkom s hodnotami jednoduchých typov. A sekcie popisujú konfiguráciu a prístup k zdrojom určitého typu. Príklad časti našej konfigurácie:

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"

Zároveň pole engine databázy je možné nainštalovať na SQLite a redis nastavený na mocks uvedením názvu súboru, ktorý sa má uložiť - tieto parametre sú správne rozpoznané a spracované, čo uľahčuje lokálne spustenie kódu na ladenie, testovanie jednotiek a akékoľvek iné potreby. Je to pre nás obzvlášť dôležité, pretože je tu mnoho ďalších potrieb – časť nášho kódu je určená na rôzne analytické výpočty, beží nielen na serveroch s orchestráciou, ale aj s rôznymi skriptami a na počítačoch analytikov, ktorí sa potrebujú prepracovať a ladenie zložitých kanálov spracovania údajov bez obávaných problémov s koncovým zariadením. Mimochodom, nebolo by na škodu sa podeliť o to, že naše hlavné nástroje vrátane kódu rozloženia konfigurácie sa inštalujú cez setup.py – to spolu spája náš kód do jedného ekosystému, nezávislého od platformy a spôsobu použitia.

Popis modulu Kubernetes vyzerá 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 každé tajomstvo opisuje jednu časť. Samotné tajomstvá sú vytvorené takto:

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

Spoločne to vedie k vytvoreniu súborov YAML pozdĺž cesty /etc/secrets/db-main/section_name.yaml

A pre lokálne spúšťanie sa používa konfigurácia, ktorá sa nachádza v koreňovom adresári projektu alebo pozdĺž cesty špecifikovanej v premennej prostredia. Kód zodpovedný za tieto vymoženosti je vidieť v spojleri.

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 tu celkom jednoduchá: kombinujeme veľké konfigurácie z adresára projektu a ciest podľa premennej prostredia a malé konfiguračné sekcie z tajomstiev Kuber a potom ich trochu predspracujeme. Plus nejaké premenné. Podotýkam, že pri vyhľadávaní súborov z tajomstiev sa používa obmedzenie hĺbky, pretože K8s vytvára v každom tajomstve skrytý priečinok, kde sú uložené samotné tajomstvá a na vyššej úrovni sa nachádza len odkaz.

Dúfam, že to, čo je popísané, bude pre niekoho užitočné :) Akékoľvek pripomienky a odporúčania týkajúce sa bezpečnosti alebo iných oblastí na zlepšenie sú akceptované. Zaujímavý je aj názor komunity, možno stojí za to pridať podporu pre ConfigMaps (náš projekt ich zatiaľ nepoužíva) a zverejniť kód na GitHub / PyPI? Osobne si myslím, že takéto veci sú príliš individuálne na to, aby boli projekty univerzálne, a trochu nahliadnuť do implementácií iných ľudí, ako je tu uvedený, a diskusia o nuansách, tipoch a osvedčených postupoch, ktoré dúfam uvidím v komentároch , stačí 😉

Do prieskumu sa môžu zapojiť iba registrovaní užívatelia. Prihlásiť saProsím.

Mám publikovať ako projekt/knižnica?

  • 0,0%Áno, použil by som /contribution0

  • 33,3%Áno, to znie skvele 4

  • 41,7%Nie, kto to potrebuje urobiť sám vo svojom vlastnom formáte a podľa svojich potrieb5

  • 25,0%Zdržím sa odpovede 3

Hlasovalo 12 užívateľov. 3 používatelia sa zdržali hlasovania.

Zdroj: hab.com

Pridať komentár