Projektkonfiguration inuti och utanför Kubernetes

Nyligen skrev jag ett svar om projektets liv i Docker och felsökning av kod utanför det, dĂ€r jag kort nĂ€mnde att man kan skapa sitt eget konfigurationssystem för att tjĂ€nsten Ă€ven ska fungera bra i Kubernetes, hĂ€mta hemligheter och köras bekvĂ€mt lokalt, Ă€ven helt utanför Docker. Inget komplicerat, men det beskrivna "receptet" kan vara till nytta för nĂ„gon 🙂 Koden Ă€r skriven i Python, men logiken Ă€r inte knuten till sprĂ„ket.

Projektkonfiguration inuti och utanför Kubernetes

Bakgrunden till frÄgan Àr följande: det fanns ett projekt som först var en liten monolit med verktyg och skript, men med tiden vÀxte det, delades upp i tjÀnster, som i sin tur delades upp i mikrotjÀnster och sedan Àven skalades. Initialt kördes allt detta pÄ rena VPS:er, dÀr processen att konfigurera och distribuera koden automatiserades med hjÀlp av Ansible och varje tjÀnst fick en YAML-konfiguration med nödvÀndiga instÀllningar och nycklar, och en liknande konfigurationsfil anvÀndes för lokala körningar, vilket var mycket praktiskt, eftersom denna konfig laddas in i ett globalt objekt som Àr tillgÀngligt frÄn vilken plats som helst inom projektet.

Men antalet mikrotjÀnster, deras kopplingar och behovet av centraliserad loggning och övervakning, förutsade en flytt till Kubernetes, som fortfarande Àr under process. Tillsammans med hjÀlp med att lösa ovan nÀmnda uppgifter, erbjuder Kubernetes sina metoder för infrastrukturhantering, inklusive sÄ kallade hemligheter och metoder för att arbeta med dem. Mekanismen Àr standardiserad och pÄlitlig, sÄ det Àr bokstavligen synd att inte dra nytta av den! Men samtidigt ville jag behÄlla vÄrt nuvarande arbetssÀtt med konfigurationen: för det första, enhetligt anvÀnda den i olika mikrotjÀnster i projektet, och för det andra, ha möjlighet att köra koden pÄ den lokala maskinen med hjÀlp av en enkel konfigurationsfil.

I samband med detta har mekanismen för att bygga konfigurationsobjekt utvecklats sÄ att den kan arbeta bÄde med vÄr klassiska konfigurationsfil och med hemligheterna frÄn Kubernetes. En striktare struktur för konfigurationen har ocksÄ definierats, pÄ det tredje Python-sprÄket, sÄ hÀr:

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

Det vill sÀga, den slutgiltiga konfigurationen Àr en ordbok med namngivna sektioner, dÀr varje del Àr en ordbok med vÀrden av enkla typer. Sektionerna beskriver konfigurationen och tillgÄngarna till resurser av en viss typ. Ett exempel pÄ en del av vÄr konfiguration:

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"

Samtidigt, fĂ€ltet engine databasen kan sĂ€ttas till SQLite, medan redis konfigureras till mock, med ett filnamn för lagring angivet – dessa parametrar kĂ€nns igen och bearbetas korrekt, vilket gör det enkelt att köra koden lokalt för felsökning, enhetstestning och andra behov. Detta Ă€r sĂ€rskilt aktuellt för oss, eftersom vi har mĂ„nga sĂ„dana behov – en del av vĂ„r kod Ă€r avsedd för olika analytiska berĂ€kningar, och körs inte bara pĂ„ servrar med orkestrering, utan Ă€ven av olika skript och pĂ„ analytikers datorer som behöver arbeta igenom och felsöka komplexa databehandlingskedjor utan att bekymra sig om backend-frĂ„gor. Förresten, det vore bra att nĂ€mna att vĂ„ra huvudverktyg, inklusive koden för att sĂ€tta ihop konfigurationen, installeras via setup.py – tillsammans sammanfogar detta vĂ„r kod till ett sammanhĂ€ngande ekosystem, oberoende av plattform och anvĂ€ndningsmetod.

Beskrivningen av podden i Kubernetes ser ut sÄ hÀr:

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 betyder att varje hemlighet beskriver en sektion. SjÀlva hemligheterna skapas sÄ hÀr:

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

Tillsammans leder detta till skapandet av YAML-filer pÄ vÀgen /etc/secrets/db-main/section_name.yaml

Och för lokala körningar anvÀnds en konfiguration som finns i projektets rotmapp eller pÄ sökvÀgen angiven i miljövariabeln. Koden som ansvarar för dessa bekvÀmligheter kan ses i spoilern.

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

Logiken hÀr Àr ganska enkel: vi slÄr ihop stora konfigurationer frÄn projektmappen och sökvÀgen genom miljövariabeln, och smÄ konfigurationer-sektioner frÄn K8s-hemligheter, och bearbetar dem sedan lite. Plus nÄgra variabler. Jag noterar att det finns en djupbegrÀnsning vid sökning av filer frÄn hemligheter, eftersom K8s skapar ytterligare en dold mapp i varje hemlighet dÀr sjÀlva hemligheterna lagras, och en nivÄ ovanför finns bara en lÀnk.

Jag hoppas att det jag beskriver kommer att vara till nytta för nĂ„gon 🙂 Alla kommentarer och rekommendationer kring sĂ€kerhet eller andra förbĂ€ttringar tas emot. Det skulle ocksĂ„ vara intressant att höra vad samhĂ€lle tycker, kanske borde vi lĂ€gga till stöd för ConfigMaps (de anvĂ€nds inte i vĂ„rt projekt Ă€n) och publicera koden pĂ„ GitHub / PyPI? Personligen tror jag att sĂ„dana saker Ă€r för individuella för projekt för att vara universella, och det rĂ€cker med lite insyn i andras implementeringar, som den som presenteras hĂ€r, och att diskutera detaljer, rĂ„d och bĂ€sta metoder som jag hoppas fĂ„ se i kommentarerna 😉

Endast registrerade anvÀndare kan delta i omröstningen. Logga in, tack.

Bör vi publicera det som ett projekt/bibliotek?

  • 0,0%Ja, jag skulle anvĂ€nda / bidra

  • 33,3%Ja, det lĂ„ter bra

  • 41,7%Nej, de som behöver det gör det sjĂ€lva i sitt format och för sina behov

  • 25,0%Jag avstĂ„r frĂ„n att svara

12 anvÀndare röstade. 3 anvÀndare avstod.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster