Configurația proiectului în interiorul și în afara Kubernetes

Recent am scris un răspuns despre viața proiectului în containere și depanarea codului în afara acestuia, unde am menționat în trecere că se poate crea propriul sistem de configurare, astfel încât serviciul să funcționeze bine și în Kubernetes, să tragă secrete și să pornească local convenabil, inclusiv complet în afara Docker-ului. Nu este nimic complicat, dar "rețeta" descrisă ar putea fi utilă cuiva 🙂 Codul este în Python, dar logica nu este legată de limbaj.

Configurația proiectului în interiorul și în afara Kubernetes

Preistoria întrebării este astfel: a existat un proiect, la început a fost un monolit mic cu utilitare și scripturi, dar, în timp, a crescut, s-a împărțit în servicii, care la rândul lor au început să se împartă în microservicii și apoi să scaleze. La început, totul se desfășura pe VPS-uri bare, procesul de configurare și desfășurare a codului fiind automatizat cu ajutorul Ansible, iar fiecărui serviciu i s-a realizat un fișier de configurare YAML cu setările și cheile necesare, iar un fișier de configurare similar a fost utilizat pentru pornirile locale, ceea ce a fost foarte convenabil, deoarece acest fișier de configurare se încarcă în un obiect global, accesibil din orice loc în proiect.

Cu toate acestea, creșterea numărului de microservicii, a conexiunilor acestora și nevoia de logare și monitorizare centralizată, preziceau mutarea în Kubernetes, care este încă în desfășurare. Împreună cu ajutorul în soluționarea acestor sarcini menționate, Kubernetes oferă propriile abordări pentru gestionarea infrastructurii, inclusiv așa-numitele Secrete și metodele de lucru cu acestea. Mecanismul este standard și fiabil, așa că, în sens literal, este păcat să nu-l folosești! Dar în același timp, ne-ar plăcea să păstrăm formatul nostru curent de lucru cu configurația: pe de o parte, să-l folosim uniform în diferite microservicii ale proiectului, iar pe de altă parte, să avem posibilitatea de a rula codul pe mașina locală folosind un singur fișier de configurare simplu.

În acest sens, mecanismul de construcție a obiectului de configurare a fost îmbunătățit astfel încât să poată lucra atât cu fișierul nostru clasic de configurație, cât și cu secretele din Kubernetes. De asemenea, a fost definită o structură mai riguroasă a fișierului de configurare, vorbind în limbajul Python 3, aceasta fiind:

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

Adică, configurația finală este un dicționar cu secțiuni denumite, fiecare dintre acestea fiind un dicționar cu valori din tipuri simple. iar secțiunile descriu configurația și accesul la resursele de un anumit tip. Exemplu de 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: info@test.com
  pw: "SuperHardPassword"

În acest context, câmpul engine baza de date poate fi setată pe SQLite, iar redis configurat pe mock, specificând, de asemenea, numele fișierului pentru salvare – aceste setări sunt corect recunoscute și procesate, permițând astfel rularea facilă a codului local pentru depanare, teste unitare și alte necesități. Acest lucru este deosebit de relevant pentru noi, deoarece avem multe astfel de necesități – o parte din codul nostru este destinat unor calcule analitice diverse, care sunt rulate nu doar pe servere cu orchestration, ci și pe diverse scripturi și pe calculatoarele analiștilor, care trebuie să dezvolte și să debuggeze procese complexe de prelucrare a datelor fără a fi nevoiți să se preocupe de problemele de back-end. Apropo, nu ar fi rău să subliniem că principalele noastre instrumente, inclusiv codul pentru configurarea fișierului, sunt instalate prin setup.py – toate acestea îmbină codul nostru într-un ecosistem unitar, independent de platformă și de modul de utilizare.

Descrierea pod-ului în Kubernetes arată așa:

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ă, în fiecare secret este descrisă o secțiune. Secretele sunt create astfel:

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

Împreună, aceasta duce la crearea fișierelor YAML pe calea /etc/secrets/db-main/section_name.yaml

Și pentru rulările locale se folosește un config plasat în directorul rădăcină al proiectului sau pe calea specificată î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 este destul de simplă: combinăm configurațiile mari din directorul proiectului și din calea variabilei de mediu, și configurațiile mici din secretele Kubernetes, apoi le preprocesăm puțin. De asemenea, folosim câteva variabile. Menționez că, în timpul căutării fișierelor din secrete, se folosește o limită de adâncime, deoarece Kubernetes creează un folder ascuns în fiecare secret, unde sunt stocate secretele, iar la nivelul superior se află doar un link.

Sper că cele descrise vor fi utile cuiva 🙂 Orice comentarii și sugestii referitoare la securitate sau alte aspecte de îmbunătățire sunt binevenite. De asemenea, sunt interesat de opinia comunității, poate ar trebui să adăugăm suport pentru ConfigMaps (în proiectul nostru nu se folosește deocamdată) și să publicăm codul pe GitHub / PyPI? Personal, cred că astfel de lucruri sunt prea individuale pentru proiecte, pentru a fi universale, și este suficient să te uiți puțin la implementările celorlalți, precum cea prezentată aici, și să discutăm despre nuanțe, sfaturi și cele mai bune practici, pe care sper să le văd în comentarii 😉

Numai utilizatorii înregistrați pot participa la sondaj. Conectați-vă, vă rugăm.

Ar trebui să publicăm ca proiect / bibliotecă?

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

  • 33,3%Da, sună bine

  • 41,7%Nu, cine dorește va face singur în formatul și pentru nevoile sale

  • 25,0%Mă abțin de la răspuns

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

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster