Niedawno pisałem
Tło pytania jest takie: kiedyś był jeden projekt, początkowo był to mały monolit z narzędziami i skryptami, ale z czasem się rozrósł, podzielił się na usługi, które z kolei zaczęto dzielić na mikroserwisy i następnie skalowane. Na początku wszystko to odbywało się na gołym VPS-ie, procesy konfiguracji i wdrażania kodu, na którym zostały zautomatyzowane przy użyciu Ansible, a każda usługa została skompilowana z konfiguracją YAML z niezbędnymi ustawieniami i kluczami, a podobny plik konfiguracyjny został użyty do uruchamiane lokalnie, co było bardzo wygodne, ponieważ .k ta konfiguracja jest ładowana do obiektu globalnego, dostępnego z dowolnego miejsca w projekcie.
Jednak wzrost liczby mikroserwisów, ich połączeń i
W tym zakresie zmodyfikowano mechanizm konstruowania obiektu konfiguracyjnego, aby móc współpracować zarówno z naszym klasycznym plikiem konfiguracyjnym, jak i z sekretami z Kubera. Określono również bardziej sztywną strukturę konfiguracji w języku trzeciego Pythona w następujący sposób:
Dict[str, Dict[str, Union[str, int, float]]]
Oznacza to, że ostatecznym cogfig jest słownik z nazwanymi sekcjami, z których każda jest słownikiem z wartościami z prostych typów. Sekcje opisują konfigurację i dostęp do zasobów określonego typu. Przykładowy fragment naszej konfiguracji:
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"
Jednocześnie pole engine
bazy danych można zainstalować na SQLite i redis
Ustawić mock
, podając także nazwę pliku do zapisania - parametry te są poprawnie rozpoznawane i przetwarzane, co ułatwia lokalne uruchomienie kodu w celu debugowania, testów jednostkowych i wszelkich innych potrzeb. Jest to dla nas szczególnie ważne, ponieważ potrzeb jest wiele innych - część naszego kodu przeznaczona jest do różnych obliczeń analitycznych, działa nie tylko na serwerach z orkiestracją, ale także z różnymi skryptami, oraz na komputerach analityków, którzy muszą przepracować i debuguj złożone potoki przetwarzania danych bez martwienia się problemami z backendem. Nawiasem mówiąc, nie zaszkodzi podzielić się informacją, że nasze główne narzędzia, w tym kod układu konfiguracji, są instalowane za pośrednictwem setup.py
– razem łączy to nasz kod w jeden ekosystem, niezależny od platformy i sposobu użycia.
Opis poda Kubernetes wygląda następująco:
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
Oznacza to, że każdy sekret opisuje jedną sekcję. Same sekrety są tworzone w następujący sposób:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Razem powoduje to utworzenie plików YAML wzdłuż ścieżki /etc/secrets/db-main/section_name.yaml
W przypadku uruchomień lokalnych używana jest konfiguracja znajdująca się w katalogu głównym projektu lub wzdłuż ścieżki określonej w zmiennej środowiskowej. Kod odpowiedzialny za te udogodnienia widać w spoilerze.
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()
Logika tutaj jest dość prosta: łączymy duże konfiguracje z katalogu projektu i ścieżek według zmiennej środowiskowej oraz małe sekcje konfiguracyjne z tajemnic Kubera, a następnie trochę je wstępnie przetwarzamy. Plus trochę zmiennych. Zauważam, że przy wyszukiwaniu plików z sekretów stosowane jest ograniczenie głębokości, ponieważ K8s tworzy w każdym sekrecie ukryty folder, w którym przechowywane są same sekrety, a po prostu link znajduje się na wyższym poziomie.
Mam nadzieję, że to, co jest opisane, komuś się przyda :) Wszelkie uwagi i zalecenia dotyczące bezpieczeństwa lub innych obszarów wymagających poprawy są akceptowane. Ciekawa jest także opinia społeczności, może warto dodać obsługę ConfigMaps (nasz projekt jeszcze z nich nie korzysta) i opublikować kod na GitHub/PyPI? Osobiście uważam, że takie rzeczy są zbyt indywidualne, aby projekty były uniwersalne, i trochę podglądania wdrożeń innych osób, jak ta podana tutaj, i omówienie niuansów, wskazówek i dobrych praktyk, które mam nadzieję zobaczyć w komentarzach , wystarczy 😉
W ankiecie mogą brać udział tylko zarejestrowani użytkownicy.
Czy powinienem publikować jako projekt/bibliotekę?
-
0,0%Tak, użyłbym /contribution0
-
33,3%Tak, to brzmi świetnie4
-
41,7%Nie, kto musi to zrobić sam, w swoim własnym formacie i na miarę swoich potrzeb5
-
25,0%Powstrzymam się od odpowiedzi 3
Głosowało 12 użytkowników. 3 użytkowników wstrzymało się od głosu.
Źródło: www.habr.com