Ho scritto di recente
Lo sfondo della domanda è questo: c'era una volta un progetto, all'inizio era un piccolo monolite con utilità e script, ma col tempo è cresciuto, diviso in servizi, che a loro volta hanno cominciato a dividersi in microservizi, e poi ingrandito. Inizialmente, tutto ciò è stato fatto su VPS nudo, i processi di impostazione e distribuzione del codice su cui sono stati automatizzati utilizzando Ansible, e ogni servizio è stato compilato con una configurazione YAML con le impostazioni e le chiavi necessarie e un file di configurazione simile è stato utilizzato per lanci locali, il che è stato molto conveniente, perché .k questa configurazione viene caricata in un oggetto globale, accessibile da qualsiasi punto del progetto.
Tuttavia, la crescita del numero di microservizi, delle loro connessioni e
A questo proposito, il meccanismo per costruire un oggetto di configurazione è stato modificato per poter funzionare sia con il nostro file di configurazione classico che con i segreti di Kuber. È stata inoltre specificata una struttura di configurazione più rigida, nel linguaggio del terzo Python, come segue:
Dict[str, Dict[str, Unione[str, int, float]]]
Cioè, il cogfig finale è un dizionario con sezioni denominate, ognuna delle quali è un dizionario con valori di tipi semplici. E le sezioni descrivono la configurazione e l'accesso alle risorse di un certo tipo. Un esempio di un pezzo della nostra 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: [email protected]
pw: "SuperHardPassword"
Allo stesso tempo, il campo engine
i database possono essere installati su SQLite e redis
impostato mock
, specificando anche il nome del file da salvare: questi parametri vengono riconosciuti ed elaborati correttamente, il che facilita l'esecuzione del codice in locale per debugging, unit testing e qualsiasi altra esigenza. Questo è particolarmente importante per noi perché ci sono molte altre esigenze: parte del nostro codice è destinata a vari calcoli analitici, funziona non solo su server con orchestrazione, ma anche con vari script e sui computer degli analisti che devono elaborare ed eseguire il debug di pipeline di elaborazione dati complesse senza preoccuparsi dei problemi di backend. A proposito, non sarebbe male condividere che i nostri strumenti principali, incluso il codice del layout di configurazione, vengono installati tramite setup.py
– insieme questo unisce il nostro codice in un unico ecosistema, indipendente dalla piattaforma e dal metodo di utilizzo.
La descrizione di un pod Kubernetes è simile alla 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-secret
Cioè, ogni segreto descrive una sezione. I segreti stessi sono creati in questo modo:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Insieme, ciò si traduce nella creazione di file YAML lungo il percorso /etc/secrets/db-main/section_name.yaml
E per i lanci locali, viene utilizzata la configurazione, situata nella directory principale del progetto o lungo il percorso specificato nella variabile di ambiente. Il codice responsabile di queste comodità può essere visto 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):
'''
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 logica qui è abbastanza semplice: combiniamo configurazioni di grandi dimensioni dalla directory del progetto e percorsi per variabile di ambiente e piccole sezioni di configurazione dai segreti di Kuber, quindi le preelaboriamo un po'. Oltre ad alcune variabili. Noto che quando si cercano file dai segreti, viene utilizzata una limitazione di profondità, poiché K8 crea una cartella nascosta in ogni segreto in cui sono archiviati i segreti stessi e solo un collegamento si trova a un livello superiore.
Spero che quanto descritto possa essere utile a qualcuno :) Si accettano commenti e suggerimenti riguardanti la sicurezza o altre aree di miglioramento. Interessante anche il parere della community, forse vale la pena aggiungere il supporto per ConfigMaps (il nostro progetto non li utilizza ancora) e pubblicare il codice su GitHub / PyPI? Personalmente, penso che queste cose siano troppo individuali perché i progetti siano universali, e una piccola sbirciatina alle implementazioni di altre persone, come quella fornita qui, e una discussione su sfumature, suggerimenti e migliori pratiche, che spero di vedere nei commenti , basta 😉
Solo gli utenti registrati possono partecipare al sondaggio.
Dovrei pubblicare come progetto/biblioteca?
-
0,0%Sì, utilizzerei /contribution0
-
33,3%Sì, sembra fantastico4
-
41,7%No, chi deve farlo da solo nel proprio formato e in base alle proprie esigenze5
-
25,0%Mi asterrò dal rispondere3
12 utenti hanno votato. 3 utenti si sono astenuti.
Fonte: habr.com