Kürzlich habe ich , in der ich kurz erwähnt habe, dass man sein eigenes Konfiguration-System erstellen kann, damit der Service auch in Kubernetes gut funktioniert, Geheimnisse zieht und lokal bequem gestartet wird, auch ganz außerhalb von Docker. Nichts Kompliziertes, aber das beschriebene "Rezept" könnte für jemanden nützlich sein 🙂 Der Code ist in Python, aber die Logik ist nicht an die Sprache gebunden.

Die Vorgeschichte der Frage ist wie folgt: Es gab einmal ein Projekt, das zunächst ein kleines Monolith mit Tools und Skripten war, aber mit der Zeit wuchs, sich in Services aufteilte, die ihrerseits in Mikrodienste aufgeteilt wurden, und dann weiter skaliert wurden. Zunächst wurde alles auf nackten VPS durchgeführt, wobei die Prozesse der Konfiguration und des Deployments des Codes mit Hilfe von Ansible automatisiert wurden, und für jeden Service wurde eine YAML-Konfiguration mit den erforderlichen Einstellungen und Schlüsseln erstellt, und eine ähnliche Konfigurationsdatei wurde für lokale Starts verwendet, was sehr praktisch war, da diese Konfiguration in ein globales Objekt geladen wird, das von überall im Projekt zugänglich ist.
Allerdings wuchs die Anzahl der Mikrodienste, deren Beziehungen, sowie , die auf einen Umzug nach Kubernetes hindeuteten, der noch immer im Gange ist. Neben der Unterstützung bei der Lösung der genannten Aufgaben bietet Kubernetes seine eigenen Ansätze zur Infrastrukturverwaltung, einschließlich und . Der Mechanismus ist standardmäßig und zuverlässig, sodass es im wahrsten Sinne des Wortes eine Schande wäre, ihn nicht zu nutzen! Gleichzeitig möchte ich jedoch mein aktuelles Format für die Arbeit mit der Konfiguration beibehalten: Erstens, um es einheitlich in verschiedenen Mikrodiensten des Projekts zu nutzen, und zweitens, um die Möglichkeit zu haben, Code auf der lokalen Maschine mit einer einfachen Konfigurationsdatei auszuführen.
In diesem Zusammenhang wurde der Mechanismus zur Erstellung von Konfigurationsobjekten so überarbeitet, dass er sowohl mit unserer klassischen Konfigurationsdatei als auch mit Geheimnissen aus Kubernetes arbeiten kann. Außerdem wurde eine striktere Struktur für die Konfiguration festgelegt, ausgedrückt in der Sprache von Python 3 so:
Dict[str, Dict[str, Union[str, int, float]]]
Das bedeutet, dass die endgültige Konfiguration ein Wörterbuch mit benannten Abschnitten ist, von denen jeder ein Wörterbuch mit Werten aus einfachen Typen ist. Die Abschnitte beschreiben die Konfiguration und den Zugriff auf Ressourcen bestimmter Arten. Ein Beispielausschnitt aus 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: info@test.com
pw: "SuperHardPassword"Dabei ist das Feld engine der Datenbanken kann auf SQLite gesetzt werden, während redis es auf mock, indem Sie einen weiteren Dateinamen zum Speichern angeben – diese Parameter werden korrekt erkannt und verarbeitet, was es ermöglicht, den Code einfach lokal für Debugging, Unit-Tests und andere Bedürfnisse auszuführen. Dies ist für uns besonders relevant, da es viele andere Bedürfnisse gibt – ein Teil unseres Codes ist für verschiedene analytische Berechnungen vorgesehen, läuft nicht nur auf Servern mit Orchestrierung, sondern auch mit verschiedenen Skripten und auf den Computern von Analysten, die komplexe Datenverarbeitungs-Pipelines bearbeiten und debuggen müssen, ohne sich mit Backend-Fragen belasten zu müssen. Übrigens, es wäre nicht schlecht zu erwähnen, dass unsere Hauptwerkzeuge, einschließlich des Codes zur Komposition der Konfiguration, über setup.py – dies vereint unseren Code in einem einheitlichen Ökosystem, das unabhängig von Plattform und Nutzungsmethode ist.
Die Beschreibung des Pods in Kubernetes sieht so 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-secretDas heißt, in jedem Geheimnis ist ein Abschnitt beschrieben. Die Geheimnisse selbst werden so erstellt:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3Zusammen führt dies zur Erstellung von YAML-Dateien unter dem angegebenen Pfad /etc/secrets/db-main/section_name.yaml
Für lokale Ausführungen wird die Konfiguration verwendet, die sich im Stammverzeichnis des Projekts oder unter dem in der Umgebungsvariablen angegebenen Pfad befindet. Der Code, der für diese Funktionen verantwortlich ist, befindet sich im 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):
'''
YAML-Konfiguration mit mehreren Abschnitten analysieren
'''
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):
'''
YAML-Konfiguration mit einem einzelnen Abschnitt analysieren
'''
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 = {
# Um den Konfigurationsladecode zu debuggen
KEY_LOG: [],
# Um anderen Code zu debuggen
KEY_DBG: is_yes(os.environ.get('DEBUG')),
}
# Für einfache lokale Ausführungen
CONFIG_SIMPLE = os.path.join(PROJECT_DIR, 'config.yaml')
parse_big_config(config, CONFIG_SIMPLE)
# Für Container-Tests
CONFIG_ENVVAR = os.environ.get('CONFIG')
if CONFIG_ENVVAR is not None:
if not parse_big_config(config, CONFIG_ENVVAR):
raise ValueError(
f'Keine Konfigurationsdatei von EnvVar:n'
f'{CONFIG_ENVVAR}'
)
# Für K8s-Geheimnisse
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()
# Vorverarbeitung
for key, data in config.items():
if key.startswith('db_'):
if data['engine'] == 'sqlite':
data['filename'] = os.path.join(PROJECT_DIR, data['filename'])
# Um die Richtigkeit zu überprüfen
if config[KEY_DBG]:
print(f'** Geladene Konfiguration:n{yaml.dump(config)}')
else:
print(f'** Geladene Konfiguration von: {config[KEY_LOG]}')
return config
config = build_config()Die Logik ist hier recht einfach: Wir kombinieren große Konfigurationen aus dem Projektverzeichnis und Pfade über Umgebungsvariablen sowie kleinere Konfigurationsabschnitte aus den Kubernetes-Geheimnissen und bearbeiten sie dann ein wenig vor. Außerdem einige Variablen. Ich möchte erwähnen, dass bei der Suche nach Dateien aus den Geheimnissen eine Tiefeneinschränkung verwendet wird, da K8s in jedem Geheimnis einen zusätzlichen versteckten Ordner erstellt, in dem die Geheimnisse gespeichert sind, und eine Ebene höher lediglich ein Link vorhanden ist.
Ich hoffe, das Beschriebene ist für jemanden nützlich 🙂 Kommentare und Empfehlungen bezüglich Sicherheit oder anderen Verbesserungsmöglichkeiten sind willkommen. Es wäre auch interessant, die Meinung der Community zu hören; vielleicht sollten wir die Unterstützung für ConfigMaps hinzufügen (in unserem Projekt werden sie derzeit nicht verwendet) und den Code auf GitHub/PyPI bereitstellen? Persönlich denke ich, dass solche Dinge zu individuell für Projekte sind, um universell zu sein, und es braucht nur einen kleinen Blick auf die Implementierungen anderer, wie die hier angeführte, sowie eine Diskussion der Nuancen, Ratschläge und Best Practices, die ich hoffe, in den Kommentaren zu sehen 😉
Nur registrierte Benutzer können an der Umfrage teilnehmen. Sind Sie an Contour interessiert?
Sollten wir es als Projekt/Bibliothek veröffentlichen?
0,0%Ja, ich würde es verwenden/mitwirken.
33,3%Ja, klingt großartig.
41,7%Nein, wer es braucht, wird es selbst in seinem Format und nach seinen Bedürfnissen tun.
25,0%Ich werde mich enthalten.
12 Benutzer haben abgestimmt. 3 Benutzer haben sich enthalten.
Quelle: habr.com
