Ich habe kürzlich geschrieben
Der Hintergrund der Frage ist folgender: Es war einmal ein Projekt, zunächst war es ein kleiner Monolith mit Dienstprogrammen und Skripten, aber im Laufe der Zeit wuchs es, unterteilt in Dienste, die wiederum in Mikrodienste unterteilt wurden, und dann vergrößert. Zunächst wurde dies alles auf einem reinen VPS durchgeführt, wobei die Prozesse zum Einrichten und Bereitstellen von Code mithilfe von Ansible automatisiert wurden. Jeder Dienst wurde mit einer YAML-Konfiguration mit den erforderlichen Einstellungen und Schlüsseln kompiliert und eine ähnliche Konfigurationsdatei wurde dafür verwendet Lokale Starts, was sehr praktisch war, da diese Konfiguration in ein globales Objekt geladen wird, auf das von überall im Projekt aus zugegriffen werden kann.
Das Wachstum der Anzahl der Microservices, ihrer Verbindungen usw
In diesem Zusammenhang wurde der Mechanismus zum Erstellen eines Konfigurationsobjekts geändert, um sowohl mit unserer klassischen Konfigurationsdatei als auch mit Geheimnissen von Kuber arbeiten zu können. Außerdem wurde eine strengere Konfigurationsstruktur in der Sprache des dritten Pythons wie folgt angegeben:
Dict[str, Dict[str, Union[str, int, float]]]
Das heißt, das letzte Zahnrad ist ein Wörterbuch mit benannten Abschnitten, von denen jeder ein Wörterbuch mit Werten aus einfachen Typen ist. Und Abschnitte beschreiben die Konfiguration und den Zugriff auf Ressourcen eines bestimmten Typs. Ein Beispiel für einen Teil unserer 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: [email protected]
pw: "SuperHardPassword"
Gleichzeitig ist das Feld engine
Datenbanken können auf SQLite installiert werden, und redis
einstellen mock
, wobei auch der Name der zu speichernden Datei angegeben wird – diese Parameter werden korrekt erkannt und verarbeitet, was es einfach macht, den Code lokal für Debugging, Unit-Tests und andere Zwecke auszuführen. Dies ist für uns besonders wichtig, da es viele andere Anforderungen gibt – ein Teil unseres Codes ist für verschiedene analytische Berechnungen gedacht, er läuft nicht nur auf Servern mit Orchestrierung, sondern auch mit verschiedenen Skripten und auf den Computern von Analysten, die durcharbeiten müssen und debuggen Sie komplexe Datenverarbeitungspipelines, ohne sich um Backend-Probleme kümmern zu müssen. Übrigens würde es nicht schaden, mitzuteilen, dass unsere Haupttools, einschließlich des Konfigurationslayoutcodes, über installiert werden setup.py
– Zusammen vereint dies unseren Code in einem einzigen Ökosystem, unabhängig von Plattform und Nutzungsmethode.
Die Beschreibung eines Kubernetes-Pods sieht folgendermaßen aus:
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
Das heißt, jedes Geheimnis beschreibt einen Abschnitt. Die Geheimnisse selbst werden wie folgt erstellt:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Zusammen führt dies zur Erstellung von YAML-Dateien entlang des Pfades /etc/secrets/db-main/section_name.yaml
Und für lokale Starts wird die Konfiguration verwendet, die sich im Stammverzeichnis des Projekts oder entlang des in der Umgebungsvariablen angegebenen Pfads befindet. Der für diese Annehmlichkeiten verantwortliche Code ist im Spoiler zu sehen.
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()
Die Logik hier ist ganz einfach: Wir kombinieren große Konfigurationen aus dem Projektverzeichnis und Pfaden nach Umgebungsvariablen und kleine Konfigurationsabschnitte aus Kuber-Geheimnissen und verarbeiten sie dann ein wenig vor. Plus einige Variablen. Ich stelle fest, dass bei der Suche nach Dateien aus Geheimnissen eine Tiefenbeschränkung verwendet wird, da K8s in jedem Geheimnis einen versteckten Ordner erstellt, in dem die Geheimnisse selbst gespeichert sind, und sich lediglich ein Link auf einer höheren Ebene befindet.
Ich hoffe, dass das, was beschrieben wird, für jemanden nützlich sein wird :) Alle Kommentare und Empfehlungen zur Sicherheit oder anderen Bereichen mit Verbesserungsbedarf werden akzeptiert. Interessant ist auch die Meinung der Community. Vielleicht lohnt es sich, Unterstützung für ConfigMaps hinzuzufügen (unser Projekt verwendet sie noch nicht) und den Code auf GitHub / PyPI zu veröffentlichen? Persönlich denke ich, dass solche Dinge zu individuell sind, als dass Projekte universell sein könnten, und ein kleiner Blick auf die Implementierungen anderer Leute, wie die hier gegebene, und eine Diskussion von Nuancen, Tipps und Best Practices, die ich hoffentlich in den Kommentaren sehen kann , reicht 😉
An der Umfrage können nur registrierte Benutzer teilnehmen.
Soll ich als Projekt/Bibliothek veröffentlichen?
-
0,0%Ja, ich würde /contribution0 verwenden
-
33,3%Ja, das klingt großartig4
-
41,7%Nein, wer muss es schon selbst tun, in seinem eigenen Format und passend zu seinen Bedürfnissen5
-
25,0%Ich verzichte auf eine Antwort3
12 Benutzer haben abgestimmt. 3 Benutzer enthielten sich der Stimme.
Source: habr.com