Nedavno sam napisao , gdje sam u prolazu spomenuo da se može napraviti vlastiti sustav konfiguracije kako bi usluga također dobro radila u Kubernetesu, povukla tajne i lokalno se lako pokretala, uključujući i van Dockera. Nema ništa komplicirano, ali opisani "recept" može nekome biti koristan 🙂 Kod je u Pythonu, ali logika nije vezana uz jezik.

Predhistoria pitanja je takva: živio jedan projekt, prvo je bio mali monolit s alatima i skriptama, ali s vremenom je rastao, dijelio se na usluge, koje su se pak dijelile na mikroservise, a zatim su se još i skalirali. U početku se sve to obavljalo na golim VPS-ima, a procesi postavljanja i implementacije koda automatizirani su uz pomoć Ansibla, i za svaku uslugu je izrađen YAML konfiguracijski datoteka s potrebnim postavkama i ključevima, a sličan konfiguracijski datoteka korištena je za lokalna pokretanja, što je bilo vrlo praktično, jer se ova konfiguracija učitava u globalni objekt, dostupan s bilo kojeg mjesta u projektu.
Međutim, rast broja mikroservisa, njihovi međusobni odnosi, kao i , predviđali su prelazak u Kubernetes, koji je još uvijek u fazi. Uz pomoć u rješavanju spomenutih zadataka, Kubernetes nudi svoje pristupe upravljanju infrastrukturom, uključujući i . Mehanizam je standardan i pouzdan, pa je u doslovnom smislu grijeh ne iskoristiti ga! No pritom bih volio zadržati svoj trenutni format rada s konfiguracijom: prvo, dosljedno ga koristiti u različitim mikroservisima projekta, a drugo, imati mogućnost pokretanja koda na lokalnom računalu koristeći jednu jednostavnu konfiguracijsku datoteku.
S tim u vezi, mehanizam izgradnje objekta konfiguracije je doradjen tako da može raditi i s našom klasičnom konfiguracijskom datotekom, kao i s tajnama iz Kubea. Također je postavljena stroža struktura konfiguracije, govoreći jezikom trećeg Pythona, ovako:
Dict[str, Dict[str, Union[str, int, float]]]
Drugim riječima, konačna konfiguracija je rječnik s imenovanim sekcijama, od kojih svaka predstavlja rječnik s vrijednostima iz osnovnih tipova. A sekcije opisuju konfiguraciju i pristup resursima određenog tipa. Primjer dijela naše konfiguracije:
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"U tom slučaju, polje engine baza podataka može biti postavljena na SQLite, dok redis može se konfigurirati na mock, navodeći još i ime datoteke za spremanje, – ovi parametri se ispravno prepoznaju i obrađuju, što omogućava lako pokretanje koda lokalno radi debagiranja, jediničnih testiranja i drugih potreba. To nam je posebno važno jer imamo mnogo tih drugih potreba – dio našeg koda je namijenjen raznim analitičkim izračunima, pokreće se ne samo na serverima s orkestrom, nego i raznim skriptama, kao i na računalima analitičara koji moraju obrađivati i debagirati složene tokove obrade podataka bez brige o backend pitanjima. Usput, ne bi bilo loše podijeliti da se naši glavni alati, uključujući kod za konfiguraciju, instaliraju putem setup.py – zajedno to povezuje naš kod u jedinstveni ekosustav, neovisno o platformi i načinu korištenja.
Opis poda u Kubernetesu izgleda ovako:
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-secretTo jest, u svakom sekretu opisana je jedna sekcija. Sami se sekreti kreiraju ovako:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3To zajedno dovodi do stvaranja YAML datoteka na putu /etc/secrets/db-main/section_name.yaml
A za lokalna pokretanja koristi se konfiguracija smještena u glavnoj direktoriji projekta ili na putu navedenom u varijabli okruženja. Kod koji je odgovoran za ove pogodnosti može se vidjeti u spoileru.
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()Logika je ovdje prilično jednostavna: spajamo velike konfiguracije iz direktorija projekta i putem varijable okruženja, te male sekcije konfiguracije iz Kubernetovih tajni, a zatim ih malo predobrađujemo. Plus, neke varijable. Napominjem da se prilikom pretraživanja datoteka iz tajni koristi ograničenje dubine, budući da K8s u svakoj tajni stvara još jednu skrivenu mapu, gdje se tajne zapravo pohranjuju, dok je jedan nivo iznad samo poveznica.
Nadam se da će ovo što sam opisao nekome biti od koristi 🙂 Prihvaćaju se svi komentari i preporuke vezani uz sigurnost ili druge aspekte za poboljšanje. Također me zanima mišljenje zajednice, možda bi bilo korisno dodati podršku za ConfigMaps (u našem projektu trenutno se ne koristi) i objaviti kod na GitHubu / PyPI? Osobno mislim da su takve stvari previše individualne za projekte da bi bile univerzalne, i dovoljno je malo zaviriti u tuđe implementacije, poput one koja je ovdje prikazana, te raspraviti nijanse, savjete i najbolje prakse, što se nadam vidjeti u komentarima 😉
Samo registrirani korisnici mogu sudjelovati u anketi. , molimo.
Trebalo bi li objaviti kao projekt / biblioteku?
0,0%Da, ja bih koristio / doprinio
33,3%Da, zvuči sjajno
41,7%Ne, tko treba, sami će to napraviti u svom formatu i prema svojim potrebama
25,0%Suzdržat ću se od odgovora
Glasovalo je 12 korisnika. Suzdržale su se 3 korisnika.
Izvor: habr.com
