Recentemente ho scritto , dove ho accennato brevemente al fatto che è possibile creare un proprio sistema di configurazione, in modo che il servizio funzioni bene anche in Kubernetes, estragga segreti e si avvii comodamente in locale, anche al di fuori di Docker. Niente di complicato, ma la "ricetta" descritta potrebbe tornare utile a qualcuno 🙂 Il codice è in Python, ma la logica non è legata a un linguaggio specifico.

La storia dietro la domanda è la seguente: c'era un progetto, inizialmente un piccolo monolite con utilità e script, ma nel tempo è cresciuto, suddividendosi in servizi, che a loro volta si sono suddivisi in microservizi, e poi hanno iniziato anche a scalare. All'inizio, tutto questo veniva eseguito su VPS nudi, i processi di configurazione e distribuzione del codice erano automatizzati con Ansible, e a ciascun servizio veniva fornito un file di configurazione YAML con le impostazioni necessarie e le chiavi; e un file di configurazione simile veniva utilizzato per le esecuzioni locali, il che era molto comodo, poiché questo file di configurazione viene caricato in un oggetto globale accessibile da qualsiasi punto del progetto.
Tuttavia, la crescita del numero di microservizi, delle loro interazioni e la , preannunciavano un trasferimento a Kubernetes, che è ancora in corso. Oltre ad aiutare nella risoluzione di queste problematiche, Kubernetes offre i propri approcci alla gestione dell'infrastruttura, incluso e . Il meccanismo è standard e affidabile, quindi, in senso letterale, è un peccato non approfittarne! Tuttavia, sarebbe bello mantenere il proprio formato attuale di lavoro con la configurazione: da un lato, usarlo in modo uniforme in diversi microservizi del progetto, e dall'altro, avere la possibilità di eseguire il codice sulla macchina locale utilizzando un semplice file di configurazione.
A questo proposito, il meccanismo di costruzione dell'oggetto di configurazione è stato migliorato per poter lavorare sia con il nostro classico file di configurazione, sia con i segreti da Kubernetes. È stata anche definita una struttura più rigorosa per il file di configurazione, parlando in termini di Python 3, così che:
Dict[str, Dict[str, Union[str, int, float]]]
Cioè, la configurazione finale è un dizionario con sezioni nominate, ognuna delle quali è un dizionario con valori di tipi semplici. Le sezioni descrivono la configurazione e l'accesso alle risorse di un certo tipo. Ecco un esempio del nostro file di configurazione:
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"In questo caso, il campo engine del database può essere impostato su SQLite, mentre redis è possibile configurare su mock, specificando anche il nome del file per il salvataggio, – questi parametri vengono riconosciuti e gestiti correttamente, il che consente di eseguire facilmente il codice localmente per il debug, i test unitari e altre esigenze. Questo è particolarmente rilevante per noi perché ci sono molte altre esigenze – parte del nostro codice è destinata a vari calcoli analitici, ed è eseguita non solo su server con orchestrazione, ma anche tramite vari script e sui computer degli analisti, che devono lavorare e ottimizzare complessi pipeline di elaborazione dei dati senza preoccuparsi di questioni di backend. A proposito, non è superfluo condividere che i nostri strumenti principali, incluso il codice di assemblaggio della configurazione, vengono installati tramite setup.py – tutto ciò unisce il nostro codice in un'unica ecosistema, indipendente dalla piattaforma e dal modo di utilizzo.
La descrizione del pod in Kubernetes è la seguente:
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-secretCioè, in ogni segreto è descritta una sezione. I segreti stessi vengono creati in questo modo:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3Questo porta alla creazione di file YAML al percorso /etc/secrets/db-main/section_name.yaml
E per le esecuzioni locali viene utilizzata una configurazione situata nella directory radice del progetto o nel percorso specificato nella variabile di ambiente. Il codice responsabile di queste comodità è visibile nello 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):
'''
Analizza la configurazione YAML con più sezioni
'''
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):
'''
Analizza la configurazione YAML con una singola sezione
'''
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 = {
# Per il debug del caricamento della configurazione
KEY_LOG: [],
# Per il debug di altro codice
KEY_DBG: is_yes(os.environ.get('DEBUG')),
}
# Per esecuzioni locali semplici
CONFIG_SIMPLE = os.path.join(PROJECT_DIR, 'config.yaml')
parse_big_config(config, CONFIG_SIMPLE)
# Per i test del container
CONFIG_ENVVAR = os.environ.get('CONFIG')
if CONFIG_ENVVAR is not None:
if not parse_big_config(config, CONFIG_ENVVAR):
raise ValueError(
f'Nessun file di configurazione da EnvVar:n'
f'{CONFIG_ENVVAR}'
)
# Per i segreti di K8s
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()
# Preprocesso
for key, data in config.items():
if key.startswith('db_'):
if data['engine'] == 'sqlite':
data['filename'] = os.path.join(PROJECT_DIR, data['filename'])
# Per verificare la correttezza
if config[KEY_DBG]:
print(f'** Configurazione caricata:n{yaml.dump(config)}')
else:
print(f'** Configurazione caricata da: {config[KEY_LOG]}')
return config
config = build_config()La logica qui è piuttosto semplice: uniamo le configurazioni grandi dalla directory del progetto e dal percorso della variabile d'ambiente, e le piccole configurazioni-sezioni dai segreti di K8s, e poi le preprocessiamo un po'. Inoltre alcune variabili. Noterò che nella ricerca di file dai segreti viene utilizzato un limite di profondità, poiché K8s crea una cartella nascosta in ciascun segreto in cui vengono effettivamente memorizzati i segreti, e un livello più sopra si trova semplicemente un collegamento.
Spero che quanto descritto sia utile a qualcuno 🙂 Accettiamo commenti e raccomandazioni riguardo alla sicurezza o ad altri aspetti per migliorare. È interessante anche il parere della comunità, magari varrebbe la pena aggiungere il supporto per i ConfigMaps (nel nostro progetto non sono ancora utilizzati) e presentare il codice su GitHub / PyPI? Personalmente penso che queste cose siano troppo individuali per ogni progetto per essere universali, e basti dare un'occhiata alle implementazioni altrui, come quella riportata qui, e discutere le sfumature, i consigli e le best practices, che spero di vedere nei commenti 😉
Solo gli utenti registrati possono partecipare al sondaggio. , per favore.
Vale la pena pubblicarlo come progetto / libreria?
0,0%Sì, lo utilizzerei / contribuirei
33,3%Sì, sembra fantastico
41,7%No, chi ne ha bisogno lo farà da solo nel proprio formato e secondo le proprie necessità
25,0%Mi astengo dal rispondere
Hanno votato 12 utenti. 3 utenti si sono astenuti.
Fonte: habr.com
