Nyligen skrev jag , dĂ€r jag kort nĂ€mnde att man kan skapa sitt eget konfigurationssystem för att tjĂ€nsten Ă€ven ska fungera bra i Kubernetes, hĂ€mta hemligheter och köras bekvĂ€mt lokalt, Ă€ven helt utanför Docker. Inget komplicerat, men det beskrivna "receptet" kan vara till nytta för nĂ„gon đ Koden Ă€r skriven i Python, men logiken Ă€r inte knuten till sprĂ„ket.

Bakgrunden till frÄgan Àr följande: det fanns ett projekt som först var en liten monolit med verktyg och skript, men med tiden vÀxte det, delades upp i tjÀnster, som i sin tur delades upp i mikrotjÀnster och sedan Àven skalades. Initialt kördes allt detta pÄ rena VPS:er, dÀr processen att konfigurera och distribuera koden automatiserades med hjÀlp av Ansible och varje tjÀnst fick en YAML-konfiguration med nödvÀndiga instÀllningar och nycklar, och en liknande konfigurationsfil anvÀndes för lokala körningar, vilket var mycket praktiskt, eftersom denna konfig laddas in i ett globalt objekt som Àr tillgÀngligt frÄn vilken plats som helst inom projektet.
Men antalet mikrotjÀnster, deras kopplingar och , förutsade en flytt till Kubernetes, som fortfarande Àr under process. Tillsammans med hjÀlp med att lösa ovan nÀmnda uppgifter, erbjuder Kubernetes sina metoder för infrastrukturhantering, inklusive och . Mekanismen Àr standardiserad och pÄlitlig, sÄ det Àr bokstavligen synd att inte dra nytta av den! Men samtidigt ville jag behÄlla vÄrt nuvarande arbetssÀtt med konfigurationen: för det första, enhetligt anvÀnda den i olika mikrotjÀnster i projektet, och för det andra, ha möjlighet att köra koden pÄ den lokala maskinen med hjÀlp av en enkel konfigurationsfil.
I samband med detta har mekanismen för att bygga konfigurationsobjekt utvecklats sÄ att den kan arbeta bÄde med vÄr klassiska konfigurationsfil och med hemligheterna frÄn Kubernetes. En striktare struktur för konfigurationen har ocksÄ definierats, pÄ det tredje Python-sprÄket, sÄ hÀr:
Dict[str, Dict[str, Union[str, int, float]]]
Det vill sÀga, den slutgiltiga konfigurationen Àr en ordbok med namngivna sektioner, dÀr varje del Àr en ordbok med vÀrden av enkla typer. Sektionerna beskriver konfigurationen och tillgÄngarna till resurser av en viss typ. Ett exempel pÄ en del av vÄr konfiguration:
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"Samtidigt, fĂ€ltet engine databasen kan sĂ€ttas till SQLite, medan redis konfigureras till mock, med ett filnamn för lagring angivet â dessa parametrar kĂ€nns igen och bearbetas korrekt, vilket gör det enkelt att köra koden lokalt för felsökning, enhetstestning och andra behov. Detta Ă€r sĂ€rskilt aktuellt för oss, eftersom vi har mĂ„nga sĂ„dana behov â en del av vĂ„r kod Ă€r avsedd för olika analytiska berĂ€kningar, och körs inte bara pĂ„ servrar med orkestrering, utan Ă€ven av olika skript och pĂ„ analytikers datorer som behöver arbeta igenom och felsöka komplexa databehandlingskedjor utan att bekymra sig om backend-frĂ„gor. Förresten, det vore bra att nĂ€mna att vĂ„ra huvudverktyg, inklusive koden för att sĂ€tta ihop konfigurationen, installeras via setup.py â tillsammans sammanfogar detta vĂ„r kod till ett sammanhĂ€ngande ekosystem, oberoende av plattform och anvĂ€ndningsmetod.
Beskrivningen av podden i Kubernetes ser ut sÄ hÀr:
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-secretDet betyder att varje hemlighet beskriver en sektion. SjÀlva hemligheterna skapas sÄ hÀr:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3Tillsammans leder detta till skapandet av YAML-filer pÄ vÀgen /etc/secrets/db-main/section_name.yaml
Och för lokala körningar anvÀnds en konfiguration som finns i projektets rotmapp eller pÄ sökvÀgen angiven i miljövariabeln. Koden som ansvarar för dessa bekvÀmligheter kan ses i spoilern.
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()Logiken hÀr Àr ganska enkel: vi slÄr ihop stora konfigurationer frÄn projektmappen och sökvÀgen genom miljövariabeln, och smÄ konfigurationer-sektioner frÄn K8s-hemligheter, och bearbetar dem sedan lite. Plus nÄgra variabler. Jag noterar att det finns en djupbegrÀnsning vid sökning av filer frÄn hemligheter, eftersom K8s skapar ytterligare en dold mapp i varje hemlighet dÀr sjÀlva hemligheterna lagras, och en nivÄ ovanför finns bara en lÀnk.
Jag hoppas att det jag beskriver kommer att vara till nytta för nĂ„gon đ Alla kommentarer och rekommendationer kring sĂ€kerhet eller andra förbĂ€ttringar tas emot. Det skulle ocksĂ„ vara intressant att höra vad samhĂ€lle tycker, kanske borde vi lĂ€gga till stöd för ConfigMaps (de anvĂ€nds inte i vĂ„rt projekt Ă€n) och publicera koden pĂ„ GitHub / PyPI? Personligen tror jag att sĂ„dana saker Ă€r för individuella för projekt för att vara universella, och det rĂ€cker med lite insyn i andras implementeringar, som den som presenteras hĂ€r, och att diskutera detaljer, rĂ„d och bĂ€sta metoder som jag hoppas fĂ„ se i kommentarerna đ
Endast registrerade anvÀndare kan delta i omröstningen. , tack.
Bör vi publicera det som ett projekt/bibliotek?
0,0%Ja, jag skulle anvÀnda / bidra
33,3%Ja, det lÄter bra
41,7%Nej, de som behöver det gör det sjÀlva i sitt format och för sina behov
25,0%Jag avstÄr frÄn att svara
12 anvÀndare röstade. 3 anvÀndare avstod.
KĂ€lla: habr.com
