Am scris recent
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
Î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.
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