Configurarea proiectului în interiorul și în afara Kubernetes

Am scris recent răspuns despre durata proiectului în Docker și codul de depanare în afara acestuia, unde a menționat pe scurt că vă puteți crea propriul sistem de configurare, astfel încât serviciul să funcționeze bine în Kuber, să afle secrete și să ruleze convenabil la nivel local, chiar și în afara Docker. Nimic complicat, dar „rețeta” descrisă poate fi utilă cuiva :) Codul este în Python, dar logica nu este legată de limbaj.

Configurarea proiectului în interiorul și în afara Kubernetes

Contextul întrebării este următorul: a existat odată un singur proiect, la început a fost un mic monolit cu utilități și scripturi, dar cu timpul a crescut, împărțit în servicii, care la rândul lor au început să fie împărțite în microservicii și apoi a crescut. La început, toate acestea s-au făcut pe VPS complet, procesele de configurare și implementare a codului pe care au fost automatizate folosind Ansible, iar fiecare serviciu a fost compilat cu o configurație YAML cu setările și cheile necesare, iar un fișier de configurare similar a fost folosit pentru lansări locale, ceea ce a fost foarte convenabil, deoarece .k această configurație este încărcată într-un obiect global, accesibil de oriunde în proiect.

Cu toate acestea, creșterea numărului de microservicii, a conexiunilor acestora și necesitatea de înregistrare și monitorizare centralizată, a prefigurat o mutare la Kuber, care este încă în curs. Împreună cu asistența în rezolvarea problemelor menționate, Kubernetes oferă abordările sale de management al infrastructurii, inclusiv așa-numitele Secrete и modalități de a lucra cu ei. Mecanismul este standard și de încredere, așa că este literalmente un păcat să nu-l folosești! Dar, în același timp, aș dori să-mi mențin formatul actual pentru lucrul cu configurația: în primul rând, să-l folosesc uniform în diferite microservicii ale proiectului și, în al doilea rând, să pot rula codul pe mașina locală folosind un singur simplu fișier de configurare.

În acest sens, mecanismul de construire a unui obiect de configurare a fost modificat pentru a putea funcționa atât cu fișierul nostru clasic de configurare, cât și cu secretele de la Kuber. A fost specificată și o structură de configurare mai rigidă, în limbajul celui de-al treilea Python, după cum urmează:

Dict[str, Dict[str, Uniune[str, int, float]]]

Adică, cogfig final este un dicționar cu secțiuni denumite, fiecare dintre acestea fiind un dicționar cu valori din tipuri simple. Și secțiunile descriu configurația și accesul la resurse de un anumit tip. Un exemplu de o parte din configurația noastră:

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"

În același timp, câmpul engine bazele de date pot fi instalate pe SQLite și redis setat la mock, specificând și numele fișierului de salvat - acești parametri sunt recunoscuți și procesați corect, ceea ce facilitează rularea locală a codului pentru depanare, testare unitară și orice alte necesități. Acest lucru este deosebit de important pentru noi, deoarece există multe alte nevoi - o parte din codul nostru este destinată diferitelor calcule analitice, rulează nu numai pe servere cu orchestrare, ci și cu diferite scripturi și pe computerele analiștilor care trebuie să lucreze. și depanați conductele complexe de procesare a datelor fără a vă face griji problemele de backend. Apropo, nu ar strica să spunem că instrumentele noastre principale, inclusiv codul de configurație, sunt instalate prin setup.py – împreună, acest lucru unește codul nostru într-un singur ecosistem, independent de platformă și metoda de utilizare.

Descrierea unui pod Kubernetes arată astfel:

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

Adică, fiecare secret descrie o secțiune. Secretele în sine sunt create astfel:

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

Împreună, acest lucru duce la crearea de fișiere YAML de-a lungul căii /etc/secrets/db-main/section_name.yaml

Iar pentru lansările locale se folosește configurația, aflată în directorul rădăcină al proiectului sau de-a lungul căii specificate în variabila de mediu. Codul responsabil pentru aceste facilități poate fi văzut în spoiler.

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

Logica aici este destul de simplă: combinăm configurații mari din directorul proiectului și căile după variabila de mediu și secțiuni mici de configurare din secretele Kuber, apoi le preprocesăm puțin. Plus câteva variabile. Observ că atunci când căutați fișiere din secrete, se folosește o limitare de adâncime, deoarece K8s creează un folder ascuns în fiecare secret în care sunt stocate secretele în sine și doar un link este situat la un nivel superior.

Sper că ceea ce este descris va fi de folos cuiva :) Orice comentarii și recomandări privind securitatea sau alte domenii de îmbunătățire sunt acceptate. Interesantă este și părerea comunității, poate că merită să adăugați suport pentru ConfigMaps (proiectul nostru încă nu le folosește) și să publicați codul pe GitHub / PyPI? Personal, cred că astfel de lucruri sunt prea individuale pentru ca proiectele să fie universale și să aruncăm o privire asupra implementărilor altora, precum cea prezentată aici, și o discuție despre nuanțe, sfaturi și bune practici, pe care sper să le văd în comentarii. , este suficient 😉

Numai utilizatorii înregistrați pot participa la sondaj. Loghează-te, Vă rog.

Ar trebui să public ca proiect/bibliotecă?

  • 0,0%Da, aș folosi /contribution0

  • 33,3%Da, sună grozav4

  • 41,7%Nu, cine trebuie să o facă singur în format propriu și pentru a se potrivi nevoilor lor5

  • 25,0%Mă abțin să răspund3

Au votat 12 utilizatori. 3 utilizatori s-au abținut.

Sursa: www.habr.com

Adauga un comentariu