schreef ik onlangs
De achtergrond van de vraag is deze: er was eens één project, aanvankelijk was het een kleine monoliet met hulpprogramma's en scripts, maar na verloop van tijd groeide het, verdeeld in services, die op hun beurt weer werden onderverdeeld in microservices, en vervolgens opgeschaald. In eerste instantie werd dit allemaal gedaan op kale VPS, waarbij de processen voor het opzetten en implementeren van code werden geautomatiseerd met behulp van Ansible, en elke service werd gecompileerd met een YAML-configuratie met de nodige instellingen en sleutels, en een soortgelijk configuratiebestand werd gebruikt voor lokale lanceringen, wat erg handig was, omdat .k deze configuratie in een globaal object wordt geladen, dat overal in het project toegankelijk is.
De groei van het aantal microservices, hun verbindingen en
In dit opzicht is het mechanisme voor het construeren van een configuratieobject aangepast om zowel met ons klassieke configuratiebestand als met geheimen van Kuber te kunnen werken. Er werd ook een stijvere configuratiestructuur gespecificeerd, in de taal van de derde Python, als volgt:
Dict[str, Dict[str, Union[str, int, float]]]
Dat wil zeggen, de laatste cogfig is een woordenboek met benoemde secties, elk een woordenboek met waarden van eenvoudige typen. En secties beschrijven de configuratie en toegang tot bronnen van een bepaald type. Een voorbeeld van een stukje van onze configuratie:
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"
Tegelijkertijd het veld engine
databases kunnen op SQLite worden geïnstalleerd, en redis
ingesteld op mock
, waarbij ook de naam wordt opgegeven van het bestand dat moet worden opgeslagen. Deze parameters worden correct herkend en verwerkt, waardoor het gemakkelijk wordt om de code lokaal uit te voeren voor foutopsporing, unit-tests en andere behoeften. Dit is vooral belangrijk voor ons omdat er nog veel meer behoeften zijn: een deel van onze code is bedoeld voor verschillende analytische berekeningen, het draait niet alleen op servers met orkestratie, maar ook met verschillende scripts, en op de computers van analisten die er doorheen moeten werken en debuggen van complexe gegevensverwerkingspijplijnen zonder zorgen over backend-problemen. Het zou trouwens geen kwaad kunnen om te delen dat onze belangrijkste tools, inclusief de configuratie-indelingscode, zijn geïnstalleerd via setup.py
– samen verenigt dit onze code in één ecosysteem, onafhankelijk van platform en gebruiksmethode.
De beschrijving van een Kubernetes-pod ziet er als volgt uit:
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
Dat wil zeggen, elk geheim beschrijft één sectie. De geheimen zelf worden als volgt gemaakt:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Samen resulteert dit in het maken van YAML-bestanden langs het pad /etc/secrets/db-main/section_name.yaml
En voor lokale lanceringen wordt de configuratie gebruikt, gelegen in de hoofdmap van het project of langs het pad dat is opgegeven in de omgevingsvariabele. De code die verantwoordelijk is voor deze gemakken is te zien in de 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):
'''
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()
De logica hier is vrij eenvoudig: we combineren grote configuraties uit de projectmap en paden per omgevingsvariabele, en kleine configuratiesecties uit Kuber-geheimen, en verwerken ze vervolgens een beetje voor. Plus enkele variabelen. Ik merk op dat bij het zoeken naar bestanden uit geheimen een dieptebeperking wordt gebruikt, omdat K8s in elk geheim een verborgen map aanmaakt waar de geheimen zelf worden opgeslagen, en alleen een link zich op een hoger niveau bevindt.
Ik hoop dat wat wordt beschreven nuttig zal zijn voor iemand :) Alle opmerkingen en aanbevelingen met betrekking tot beveiliging of andere gebieden voor verbetering worden geaccepteerd. De mening van de community is ook interessant, misschien is het de moeite waard om ondersteuning voor ConfigMaps toe te voegen (ons project gebruikt ze nog niet) en de code op GitHub / PyPI te publiceren? Persoonlijk denk ik dat zulke dingen te individueel zijn om projecten universeel te laten zijn, en een beetje kijken naar de implementaties van anderen, zoals die hier gegeven, en een bespreking van nuances, tips en best practices, die ik hoop terug te zien in de commentaren , is genoeg 😉
Alleen geregistreerde gebruikers kunnen deelnemen aan het onderzoek.
Moet ik publiceren als een project/bibliotheek?
-
0,0%Ja, ik zou /contribution0 gebruiken
-
33,3%Ja, dat klinkt geweldig4
-
41,7%Nee, die moeten het zelf doen, in hun eigen format en volgens hun behoeften5
-
25,0%Ik zal mij onthouden van antwoord 3
12 gebruikers hebben gestemd. 3 gebruikers onthielden zich van stemming.
Bron: www.habr.com