For nylig skrev jeg , hvor jeg kort nævnte, at det er muligt at lave sit eget konfigurationssystem, så tjenesten fungerer godt i Kubernetes, henter hemmeligheder og kan køres lokalt, endda helt uden Docker. Ikke noget kompliceret, men den beskrevne "opskrift" kan måske være nyttig for nogen 🙂 Koden er i Python, men logikken er ikke bundet til sproget.

Baggrunden for spørgsmålet er som følger: der var engang et projekt, som først var en lille monolit med værktøjer og scripts, men som med tiden voksede, blev opdelt i tjenester, som igen blev opdelt i mikrotjenester, og derefter blev skaleret. I starten blev alt dette kørt på rå VPS'er, hvor processerne til opsætning og implementering af koden blev automatiseret ved hjælp af Ansible, og der blev lavet en YAML-konfiguration for hver tjeneste med de nødvendige indstillinger og nøgler, og en tilsvarende konfigurationsfil blev brugt til lokale kørsel, hvilket var meget praktisk, da denne konfiguration indlæses i et globalt objekt, der er tilgængeligt fra ethvert sted i projektet.
Men væksten af antallet af mikrotjenester, deres relationer, samt , indikerede et skift til Kubernetes, som stadig er undervejs. Sammen med hjælp til at løse de nævnte opgaver tilbyder Kubernetes sine egne metoder til at håndtere infrastrukturen, herunder og . Mekanismen er standard og pålidelig, så det ville i bogstavelig forstand være en skam ikke at bruge den! Men samtidig ville jeg gerne bevare mit nuværende format for arbejde med konfigurationen: for det første at bruge det ensartet i forskellige mikrotjenester i projektet, og for det andet at have mulighed for at køre koden på den lokale maskine ved hjælp af én simpel konfigurationsfil.
I den forbindelse blev mekanismen til at opbygge objekter-konfigurationer forbedret, så den kan arbejde både med vores klassiske konfigurationsfil og med hemmeligheder fra Kubernetes. Derudover blev der fastsat en strammere struktur for konfigurationen, sagt på det tredje Python-sprog, sådan her:
Dict[str, Dict[str, Union[str, int, float]]]
Det vil sige, den endelige konfiguration er et ordbog med navngivne sektioner, hvor hver sektion er et ordbog med værdier fra simple typer. Og sektionerne beskriver konfigurationen og adgangen til ressourcer af en bestemt type. Et eksempel på en del af vores konfiguration:
adminka:
django_secret: "EkstraLangOgSværKode"
db_main:
engine: mysql
host: 256.128.64.32
user: cool_user
password: "SuperSværAdgangskode"
redis:
host: 256.128.64.32
pw: "SuperSværAdgangskode"
port: 26379
smtp:
server: smtp.gmail.com
port: 465
email: info@test.com
pw: "SuperSværAdgangskode"Samtidig er feltet engine databasen kan installeres på SQLite, og redis konfigureres til mock, ved at angive yderligere filnavn til opbevaring – disse parametre genkendes og behandles korrekt, hvilket gør det nemt at køre koden lokalt til debugging, enhedstestning og andre behov. Dette er især relevant for os, da der er mange andre behov – en del af vores kode er beregnet til forskellige analytiske beregninger, og kører ikke kun på servere med orkestrering, men også med forskellige scripts og på analytikeres computere, der har brug for at udvikle og debugge komplekse databehandlingspipelines uden at skulle tænke på backend-spørgsmål. Forresten kan det være værd at nævne, at vores primære værktøjer, herunder konfigurationsopbygningskode, installeres gennem setup.py – sammen samler dette vores kode i et samlet økosystem, uafhængigt af platform og anvendelsesmetode.
Beskrivelsen af pod'en i Kubernetes ser således ud:
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 vil sige, at hver hemmelighed beskriver én sektion. Selve hemmelighederne oprettes således:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3Sammen fører det til oprettelsen af YAML-filer på stien /etc/secrets/db-main/section_name.yaml
Og til lokale opstarter bruges en konfiguration, der ligger i rodmappen af projektet eller på den sti, der er angivet i miljøvariablen. Koden, der er ansvarlig for disse bekvemmeligheder, kan ses i spoileren.
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 sections
'''
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()Logikken her er ret simpel: Vi kombinerer store konfigurationer fra projektmappen og stien via miljøvariablen samt små konfigurationer-sektioner fra Kubernetes hemmeligheder, hvorefter vi forbereder dem lidt. Derudover nogle variabler. Jeg bemærker, at der anvendes en dybdebegrænsning, når der søges efter filer fra hemmelighederne, da K8s i hver hemmelighed opretter en skjult mappe, hvor selve hemmelighederne gemmes, og et niveau over findes der blot et link.
Jeg håber, det beskrevne vil være nyttigt for nogen 🙂 Alle kommentarer og anbefalinger om sikkerhed eller andre forbedringer er velkomne. Jeg er også interesseret i samfundets mening; måske bør vi tilføje understøttelse af ConfigMaps (de bruges ikke endnu i vores projekt) og offentliggøre koden på GitHub / PyPI? Personligt mener jeg, at sådanne ting er for individuelle til projekter til at være universelle, og det er ofte tilstrækkeligt at tage et kig på andres implementeringer, som den der er vist her, og diskutere nuancer, tips og bedste praksis, som jeg håber at se i kommentarerne 😉
Kun registrerede brugere kan deltage i undersøgelsen. , please.
Bør det offentliggøres som et projekt/bibliotek?
0,0%Ja, jeg ville bruge/kontributere
33,3%Ja, det lyder godt
41,7%Nej, de der har brug for det vil lave det i deres eget format og til deres behov
25,0%Jeg afholder mig fra at svare
12 brugere har stemt. 3 brugere afholdt sig.
Kilde: habr.com
