Prosjektkonfigurasjon i og utenfor Kubernetes

jeg skrev nylig svar om prosjektliv i Docker og feilsøking av kode utenfor den, hvor han kort nevnte at du kan lage ditt eget konfigurasjonssystem slik at tjenesten fungerer bra i Kuber, trekker opp hemmeligheter og kjører praktisk lokalt, selv utenfor Docker helt. Ikke noe komplisert, men den beskrevne "oppskriften" kan være nyttig for noen :) Koden er i Python, men logikken er ikke knyttet til språket.

Prosjektkonfigurasjon i og utenfor Kubernetes

Bakgrunnen for spørsmålet er denne: Det var en gang i tiden ett prosjekt, først var det en liten monolitt med verktøy og skript, men over tid vokste det, delt inn i tjenester, som igjen begynte å bli delt inn i mikrotjenester, og deretter oppskalert. Til å begynne med ble alt dette gjort på bare VPS, prosessene med å sette opp og distribuere kode som ble automatisert med Ansible, og hver tjeneste ble kompilert med en YAML-konfigurasjon med nødvendige innstillinger og nøkler, og en lignende konfigurasjonsfil ble brukt for lokale lanseringer, noe som var veldig praktisk, fordi .k denne konfigurasjonen er lastet inn i et globalt objekt, tilgjengelig fra hvor som helst i prosjektet.

Imidlertid er veksten i antall mikrotjenester, deres tilkoblinger og behov for sentralisert logging og overvåking, varslet en flytting til Kuber, som fortsatt pågår. Sammen med bistand til å løse de nevnte problemene tilbyr Kubernetes sine tilnærminger til infrastrukturstyring, bl.a såkalte hemmeligheter и måter å jobbe med dem på. Mekanismen er standard og pålitelig, så det er bokstavelig talt synd å ikke bruke den! Men samtidig vil jeg gjerne opprettholde mitt nåværende format for å jobbe med konfigurasjonen: for det første å bruke den enhetlig i ulike mikrotjenester i prosjektet, og for det andre å kunne kjøre koden på den lokale maskinen ved å bruke en enkel konfigurasjonsfil.

I denne forbindelse ble mekanismen for å konstruere et konfigurasjonsobjekt modifisert for å kunne fungere både med vår klassiske konfigurasjonsfil og med hemmeligheter fra Kuber. En mer rigid konfigurasjonsstruktur ble også spesifisert, på språket til den tredje Python, som følger:

Dict[str, Dict[str, Union[str, int, flyte]]]

Det vil si at det endelige tannhjulet er en ordbok med navngitte seksjoner, som hver er en ordbok med verdier fra enkle typer. Og seksjoner beskriver konfigurasjonen og tilgangen til ressurser av en bestemt type. Et eksempel på en del av konfigurasjonen vår:

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"

Samtidig feltet engine databaser kan installeres på SQLite, og redis satt til mock, spesifiserer også navnet på filen som skal lagres - disse parameterne gjenkjennes og behandles riktig, noe som gjør det enkelt å kjøre koden lokalt for feilsøking, enhetstesting og andre behov. Dette er spesielt viktig for oss fordi det er mange andre behov - en del av koden vår er ment for ulike analytiske beregninger, den kjører ikke bare på servere med orkestrering, men også med ulike skript, og på datamaskinene til analytikere som trenger å jobbe seg gjennom. og feilsøk komplekse databehandlingsrørledninger uten å bekymre deg for backend-problemer. Forresten, det ville ikke skade å dele at hovedverktøyene våre, inkludert konfigurasjonslayoutkoden, er installert via setup.py – sammen forener dette koden vår til ett enkelt økosystem, uavhengig av plattform og bruksmåte.

Beskrivelsen av en Kubernetes-pod ser slik ut:

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 si at hver hemmelighet beskriver én seksjon. Selve hemmelighetene er laget slik:

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

Sammen resulterer dette i opprettelsen av YAML-filer langs banen /etc/secrets/db-main/section_name.yaml

Og for lokale lanseringer brukes konfigurasjonen, plassert i rotkatalogen til prosjektet eller langs banen spesifisert i miljøvariabelen. Koden som er ansvarlig for disse bekvemmelighetene, kan sees 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 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()

Logikken her er ganske enkel: vi kombinerer store konfigurasjoner fra prosjektkatalogen og stier etter miljøvariabel, og små konfigurasjonsseksjoner fra Kuber-hemmeligheter, og forbehandler dem deretter litt. Pluss noen variabler. Jeg legger merke til at når du søker etter filer fra hemmeligheter, brukes en dybdebegrensning, fordi K8s lager en skjult mappe i hver hemmelighet hvor selve hemmelighetene er lagret, og bare en lenke er plassert på et høyere nivå.

Jeg håper det som er beskrevet vil være nyttig for noen :) Eventuelle kommentarer og anbefalinger angående sikkerhet eller andre forbedringsområder aksepteres. Fellesskapets mening er også interessant, kanskje det er verdt å legge til støtte for ConfigMaps (prosjektet vårt bruker dem ikke ennå) og publisere koden på GitHub / PyPI? Personlig synes jeg at slike ting er for individuelle til at prosjekter kan være universelle, og en liten titt på andres implementeringer, som den som er gitt her, og en diskusjon av nyanser, tips og beste praksis, som jeg håper å se i kommentarfeltet. , er nok 😉

Kun registrerte brukere kan delta i undersøkelsen. Logg inn, vær så snill.

Bør jeg publisere som et prosjekt/bibliotek?

  • 0,0%Ja, jeg ville brukt /contribution0

  • 33,3%Ja, det høres bra ut 4

  • 41,7%Nei, hvem trenger å gjøre det selv i sitt eget format og for å passe deres behov5

  • 25,0%Jeg vil avstå fra å svare3

12 brukere stemte. 3 brukere avsto.

Kilde: www.habr.com

Legg til en kommentar