Konfiguracija projekta znotraj in zunaj Kubernetesa

Pred kratkim sem napisal odgovor o življenju projekta v Dockerju in kodi za odpravljanje napak zunaj njega, kjer je na kratko omenil, da lahko naredite svoj konfiguracijski sistem, tako da storitev dobro deluje v Kuberju, potegne skrivnosti in deluje priročno lokalno, tudi zunaj Dockerja. Nič zapletenega, vendar bo opisani "recept" morda komu koristil :) Koda je v Pythonu, vendar logika ni vezana na jezik.

Konfiguracija projekta znotraj in zunaj Kubernetesa

Ozadje vprašanja je naslednje: nekoč je bil en projekt, sprva je bil majhen monolit s pripomočki in skriptami, sčasoma pa se je povečal, razdelil na storitve, te pa na mikrostoritve in nato povečano. Sprva se je vse to izvajalo na golem VPS, procesi nastavitve in uvajanja kode na katerem so bili avtomatizirani z uporabo Ansible, vsaka storitev pa je bila prevedena s konfiguracijo YAML s potrebnimi nastavitvami in ključi, podobna konfiguracijska datoteka pa je bila uporabljena za lokalne zagone, kar je bilo zelo priročno, ker je .k ta konfiguracija naložena v globalni objekt, dostopen od koder koli v projektu.

Vendar pa je rast števila mikrostoritev, njihovih povezav in potreba po centraliziranem beleženju in spremljanju, je napovedal selitev v Kuber, ki je še v teku. Kubernetes skupaj s pomočjo pri reševanju omenjenih težav ponuja svoje pristope k upravljanju infrastrukture, vključno z tako imenovane Skrivnosti и načine dela z njimi. Mehanizem je standarden in zanesljiv, zato ga je dobesedno greh ne uporabiti! Toda hkrati bi rad ohranil svoj trenutni format za delo s konfiguracijo: prvič, da bi ga enotno uporabljal v različnih mikrostoritvah projekta, in drugič, da bi lahko izvajal kodo na lokalnem računalniku z eno preprosto konfiguracijska datoteka.

V zvezi s tem je bil mehanizem za izdelavo konfiguracijskega objekta spremenjen, da lahko deluje tako z našo klasično konfiguracijsko datoteko kot s skrivnostmi Kuberja. Določena je bila tudi bolj toga konfiguracijska struktura v jeziku tretjega Pythona, kot sledi:

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

To pomeni, da je končni cogfig slovar z imenovanimi odseki, od katerih je vsak slovar z vrednostmi iz preprostih tipov. In razdelki opisujejo konfiguracijo in dostop do virov določene vrste. Primer dela 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: [email protected]
  pw: "SuperHardPassword"

Hkrati pa polj engine baze podatkov je mogoče namestiti na SQLite in redis nastavljena mock, ki določa tudi ime datoteke za shranjevanje - ti parametri so pravilno prepoznani in obdelani, kar olajša lokalno izvajanje kode za odpravljanje napak, testiranje enote in vse druge potrebe. To je za nas še posebej pomembno, ker je veliko drugih potreb – del naše kode je namenjen različnim analitičnim izračunom, teče ne le na strežnikih z orkestracijo, ampak tudi z različnimi skripti in na računalnikih analitikov, ki morajo delati skozi in odpravite napake v zapletenih cevovodih za obdelavo podatkov, ne da bi imeli težave z zaledjem. Mimogrede, ne bi škodilo, če bi povedali, da so naša glavna orodja, vključno s kodo postavitve konfiguracije, nameščena prek setup.py – skupaj to združuje našo kodo v enoten ekosistem, neodvisen od platforme in načina uporabe.

Opis pod Kubernetes izgleda takole:

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 pomeni, da vsaka skrivnost opisuje en razdelek. Same skrivnosti so ustvarjene takole:

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

Skupaj to povzroči ustvarjanje datotek YAML na poti /etc/secrets/db-main/section_name.yaml

Za lokalne zagone se uporablja konfiguracija, ki se nahaja v korenskem imeniku projekta ali vzdolž poti, določene v spremenljivki okolja. Kodo, odgovorno za te ugodnosti, lahko vidite v spojlerju.

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 tukaj je precej preprosta: združimo velike konfiguracije iz projektnega imenika in poti po spremenljivki okolja ter majhne konfiguracijske odseke iz skrivnosti Kuber in jih nato malo predhodno obdelamo. Plus nekaj spremenljivk. Opažam, da se pri iskanju datotek iz skrivnosti uporablja omejitev globine, ker K8s v vsaki skrivnosti ustvari skrito mapo, kjer so shranjene same skrivnosti, in samo povezava se nahaja na višji ravni.

Upam, da bo opisano komu koristilo :) Sprejemamo morebitne komentarje in priporočila glede varnosti ali drugih področij za izboljšave. Zanimivo je tudi mnenje skupnosti, morda je vredno dodati podporo za ConfigMaps (naš projekt jih še ne uporablja) in objaviti kodo na GitHub / PyPI? Osebno menim, da so takšne stvari preveč individualne, da bi bili projekti univerzalni, in malo pokukanja v izvedbe drugih ljudi, kot je ta, ki je navedena tukaj, in razprava o odtenkih, nasvetih in najboljših praksah, ki jih upam videti v komentarjih , je dovolj 😉

V anketi lahko sodelujejo samo registrirani uporabniki. Prijaviti se, prosim.

Ali naj objavim kot projekt/knjižnica?

  • 0,0%Da, uporabil bi /contribution0

  • 33,3%Ja, to se sliši super4

  • 41,7%Ne, kdo mora to narediti sam v svojem formatu in po svojih potrebah5

  • 25,0%Vzdržal se bom odgovora3

Glasovalo je 12 uporabnikov. 3 uporabnika sta se vzdržala.

Vir: www.habr.com

Dodaj komentar