Projektkonfiguration inom och utanför Kubernetes

Jag skrev nyligen svar om projektliv i Docker och felsökning av kod utanför den, där han kort nämnde att du kan göra ditt eget konfigurationssystem så att tjänsten fungerar bra i Kuber, hämtar hemligheter och körs bekvämt lokalt, även utanför Docker helt och hållet. Inget komplicerat, men det beskrivna "receptet" kan vara användbart för någon :) Koden är i Python, men logiken är inte knuten till språket.

Projektkonfiguration inom och utanför Kubernetes

Bakgrunden till frågan är denna: det var en gång i tiden ett projekt, först var det en liten monolit med verktyg och skript, men med tiden växte den, delade upp i tjänster, som i sin tur började delas upp i mikrotjänster, och sedan skalas upp. Till en början gjordes allt detta på blott VPS, processerna för att ställa in och distribuera kod på vilken automatiserades med Ansible, och varje tjänst kompilerades med en YAML-konfiguration med nödvändiga inställningar och nycklar, och en liknande konfigurationsfil användes för lokala lanseringar, vilket var mycket bekvämt, eftersom .k den här konfigurationen laddas in i ett globalt objekt, tillgängligt från var som helst i projektet.

Men ökningen av antalet mikrotjänster, deras anslutningar och behov av centraliserad loggning och övervakning, förebådade en flytt till Kuber, som fortfarande pågår. Tillsammans med hjälp med att lösa de nämnda problemen erbjuder Kubernetes sina tillvägagångssätt för infrastrukturhantering, inklusive så kallade hemligheter и sätt att arbeta med dem. Mekanismen är standard och pålitlig, så det är bokstavligen synd att inte använda den! Men samtidigt skulle jag vilja behålla mitt nuvarande format för att arbeta med konfigurationen: för det första att använda det enhetligt i olika mikrotjänster i projektet, och för det andra att kunna köra koden på den lokala maskinen med en enkel konfigurationsfil.

I detta avseende modifierades mekanismen för att konstruera ett konfigurationsobjekt för att kunna fungera både med vår klassiska konfigurationsfil och med hemligheter från Kuber. En mer stel konfigurationsstruktur specificerades också, på språket för den tredje Python, enligt följande:

Dict[str, Dict[str, Union[str, int, flyta]]]

Det vill säga, den sista kuggbilden är en ordbok med namngivna sektioner, som var och en är en ordbok med värden från enkla typer. Och avsnitt beskriver konfigurationen och åtkomsten 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: [email protected]
  pw: "SuperHardPassword"

Samtidigt fältet engine databaser kan installeras på SQLite, och redis satt till mock, och anger även namnet på filen som ska sparas - 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 viktigt för oss eftersom det finns många andra behov - en del av vår kod är avsedd för olika analytiska beräkningar, den körs inte bara på servrar med orkestrering, utan också med olika skript, och på datorer för analytiker som behöver arbeta igenom och felsöka komplexa pipelines för databearbetning utan att oroa problem med backend. Förresten, det skulle inte skada att dela att våra huvudverktyg, inklusive konfigurationslayoutkoden, installeras via setup.py – tillsammans förenar detta vår kod till ett enda ekosystem, oberoende av plattform och användningsmetod.

Beskrivningen av en Kubernetes-pod 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 vill säga att varje hemlighet beskriver ett avsnitt. 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 resulterar detta i skapandet av YAML-filer längs vägen /etc/secrets/db-main/section_name.yaml

Och för lokala lanseringar används konfigurationen, som finns i projektets rotkatalog eller längs den sökväg som anges i miljövariabeln. Koden som är ansvarig 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 kombinerar stora konfigurationer från projektkatalogen och sökvägar efter miljövariabel, och små konfigurationssektioner från Kuber-hemligheter, och förbehandlar dem sedan lite. Plus några variabler. Jag noterar att när man söker efter filer från hemligheter används en djupbegränsning, eftersom K8s skapar en dold mapp i varje hemlighet där själva hemligheterna lagras, och bara en länk finns på en högre nivå.

Jag hoppas att det som beskrivs kommer att vara användbart för någon :) Alla kommentarer och rekommendationer angående säkerhet eller andra områden för förbättring accepteras. Communityns åsikt är också intressant, kanske det är värt att lägga till stöd för ConfigMaps (vårt projekt använder dem inte än) och publicera koden på GitHub / PyPI? Personligen tycker jag att sådana saker är för individuella för att projekt ska vara universella, och lite kikar på andras implementeringar, som den som ges här, och en diskussion om nyanser, tips och bästa praxis, som jag hoppas få se i kommentarerna , räcker 😉

Endast registrerade användare kan delta i undersökningen. Logga in, Snälla du.

Ska jag publicera som projekt/bibliotek?

  • 0,0%Ja, jag skulle använda /contribution0

  • 33,3%Ja, det låter bra4

  • 41,7%Nej, vem behöver göra det själv i sitt eget format och för att passa sina behov5

  • 25,0%Jag avstår från att svara3

12 användare röstade. 3 användare avstod från att rösta.

Källa: will.com

Lägg en kommentar