Configuració del projecte dins i fora de Kubernetes

Fa poc vaig escriure resposta sobre la vida del projecte a Docker i el codi de depuració fora d'ell, on va esmentar breument que podeu crear el vostre propi sistema de configuració perquè el servei funcioni bé a Kuber, reculli secrets i s'executi de manera local, fins i tot fora de Docker. Res complicat, però la "recepta" descrita pot ser útil per a algú :) El codi està en Python, però la lògica no està lligada a l'idioma.

Configuració del projecte dins i fora de Kubernetes

El rerefons de la pregunta és el següent: hi havia una vegada un projecte, al principi era un petit monòlit amb utilitats i scripts, però amb el temps va anar creixent, dividit en serveis, que al seu torn es van començar a dividir en microserveis i després s'ha ampliat. Al principi, tot això es va fer en VPS nu, els processos de configuració i desplegament de codi en els quals es van automatitzar mitjançant Ansible, i cada servei es va compilar amb una configuració YAML amb la configuració i les claus necessàries, i es va utilitzar un fitxer de configuració similar per a llançaments locals, que era molt convenient, perquè .k aquesta configuració es carrega en un objecte global, accessible des de qualsevol lloc del projecte.

Tanmateix, el creixement del nombre de microserveis, les seves connexions i necessitat d'un registre i un seguiment centralitzats, va presagiar un moviment a Kuber, que encara està en curs. Juntament amb l'assistència per resoldre els problemes esmentats, Kubernetes ofereix els seus enfocaments de gestió d'infraestructures, incloent els anomenats Secrets и maneres de treballar amb ells. El mecanisme és estàndard i fiable, per la qual cosa és literalment un pecat no utilitzar-lo! Però al mateix temps, m'agradaria mantenir el meu format actual per treballar amb la configuració: en primer lloc, utilitzar-lo de manera uniforme en diferents microserveis del projecte i, en segon lloc, poder executar el codi a la màquina local utilitzant un senzill fitxer de configuració.

En aquest sentit, es va modificar el mecanisme per construir un objecte de configuració per poder funcionar tant amb el nostre fitxer de configuració clàssic com amb secrets de Kuber. També es va especificar una estructura de configuració més rígida, en el llenguatge del tercer Python, de la següent manera:

Dict[str, Dict[str, Unió[str, int, float]]]

És a dir, el cogfig final és un diccionari amb seccions anomenades, cadascuna de les quals és un diccionari amb valors de tipus simples. I les seccions descriuen la configuració i l'accés a recursos d'un determinat tipus. Un exemple d'una peça de la nostra configuració:

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"

Al mateix temps, el camp engine les bases de dades es poden instal·lar a SQLite i redis ajustat a mock, especificant també el nom del fitxer a desar: aquests paràmetres es reconeixen i es processen correctament, cosa que facilita l'execució del codi localment per a la depuració, les proves d'unitat i qualsevol altra necessitat. Això és especialment important per a nosaltres perquè hi ha moltes altres necessitats: part del nostre codi està pensat per a diversos càlculs analítics, no només s'executa en servidors amb orquestració, sinó també amb diversos scripts i en els ordinadors dels analistes que necessiten treballar-hi. i depureu canalitzacions complexes de processament de dades sense preocupar-vos de problemes de backend. Per cert, no estaria malament compartir que les nostres eines principals, inclòs el codi de disseny de configuració, s'instal·len mitjançant setup.py – junts, això uneix el nostre codi en un únic ecosistema, independent de la plataforma i el mètode d'ús.

La descripció d'un pod de Kubernetes és així:

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

És a dir, cada secret descriu una secció. Els propis secrets es creen així:

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

En conjunt, això resulta en la creació de fitxers YAML al llarg del camí /etc/secrets/db-main/section_name.yaml

I per als llançaments locals, s'utilitza la configuració, situada al directori arrel del projecte o al llarg del camí especificat a la variable d'entorn. El codi responsable d'aquestes comoditats es pot veure al 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()

La lògica aquí és bastant senzilla: combinem configuracions grans del directori del projecte i camins per variable d'entorn, i petites seccions de configuració dels secrets de Kuber, i després les preprocessem una mica. A més d'algunes variables. Tinc en compte que quan es busquen fitxers a partir de secrets, s'utilitza una limitació de profunditat, perquè K8s crea una carpeta oculta a cada secret on s'emmagatzemen els propis secrets i només es troba un enllaç a un nivell superior.

Espero que el que es descriu sigui útil a algú :) S'accepten tots els comentaris i recomanacions sobre seguretat o altres àrees de millora. L'opinió de la comunitat també és interessant, potser val la pena afegir suport per a ConfigMaps (el nostre projecte encara no els utilitza) i publicar el codi a GitHub / PyPI? Personalment, crec que aquestes coses són massa individuals perquè els projectes siguin universals, i una mica d'ull a les implementacions d'altres persones, com la que es mostra aquí, i una discussió de matisos, consells i bones pràctiques, que espero veure en els comentaris. , n'hi ha prou 😉

Només els usuaris registrats poden participar en l'enquesta. Inicia sessiósi us plau.

He de publicar com a projecte/biblioteca?

  • 0,0%Sí, faria servir /contribution0

  • 33,3%Sí, sona genial4

  • 41,7%No, qui ha de fer-ho ells mateixos en el seu propi format i segons les seves necessitats5

  • 25,0%M'abstindré de respondre3

Han votat 12 usuaris. 3 usuaris es van abstenir.

Font: www.habr.com

Afegeix comentari