Нещодавно я написав
Передісторія питання така: жив-був один проект, спочатку він був маленьким монолітом з утилітами та скриптами, але згодом ріс, ділився на сервіси, які у свою чергу стали ділитися на мікросервіси, а потім ще й скейлі. Спочатку це все виконувалося на голих VPS, процеси налаштування та розгортання коду на яких були автоматизовані за допомогою Ansible, і кожному сервісу складався YAML-конфіг з потрібними налаштуваннями та ключами, і аналогічний конфіг-файл використовувався для локальних запусків, що було дуже зручно. .до цього конфіг вантажиться у глобальний об'єкт, доступний з будь-якого місця у проекті.
Однак зростання кількості мікросервісів, їх зв'язків, а також
У зв'язку з цим механізм побудови об'єкта-конфігурації був доопрацьований так, щоб уміти працювати як з нашим класичним конфіг-файлом, так і з секретами з Кубера. Також була задана жорсткіша структура конфіга, говорячи мовою третього Пітона, така:
Dict[str, Dict[str, Union[str, int, float]]]
Тобто, підсумковий когфіг - це словник з іменованими секціями, кожна з яких є словником зі значеннями з простих типів. А секції описують конфігурацію та доступи до ресурсів певного виду. Приклад шматка нашого конфігу:
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"
При цьому поле engine
баз даних можна встановити на SQLite, а redis
налаштувати на mock
, вказавши ще ім'я файлу для збереження, – ці параметри коректно розпізнаються та обробляються, що дозволяє легко запускати код локально для налагодження, юніт-тестування та інших потреб. Нам це особливо актуально тому, що цих інших потреб багато – частина нашого коду призначена для різноманітних аналітичних розрахунків, запускається не тільки на серверах з оркестрацією, але й різними скриптами, та на комп'ютерах аналітиків, яким потрібно опрацьовувати та налагоджувати складні конвеєри обробки даних, не парячись бекендерськими питаннями. До речі, не зайвим буде поділитися тим, що наші основні інструменти, включаючи код компонування конфігу, встановлюються через setup.py
- разом це об'єднує наш код в єдину екосистему, яка не залежить від платформи та способу використання.
Опис пода в Kubernetes виглядає так:
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
Тобто, у кожному секреті описано одну секцію. Самі секрети створюються так:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Разом це призводить до створення YAML-файлів шляхом /etc/secrets/db-main/section_name.yaml
А для локальних запусків використовується конфіг, розташований у кореневій директорії проекту або шляхом, зазначеним у змінній оточенні. Код, відповідальний за ці зручності, можна побачити в спойлері.
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()
Логіка тут досить проста: об'єднуємо великі конфіги з директорії проекту та шляхи змінної оточення, і невеликі конфіги-секції із секретів Кубера, а потім трохи їх передробляємо. Плюс деякі змінні. Зауважу, що при пошуку файлів із секретів використовується обмеження глибини, тому що K8s у кожному секреті створює ще приховану папку, де самі секрети і зберігається, а рівнем вище просто посилання.
Сподіваюся, описане виявиться комусь корисним 🙂 Приймаються будь-які коментарі та рекомендації щодо безпеки або інших моментів на покращення. Також цікава думка спільноти, чи можливо варто додати підтримку ConfigMaps (у нашому проекті вони поки не використовується) і оформити код на ГітХабі / PyPI? Особисто я думаю, що такі речі є надто індивідуальними для проектів, щоб бути універсальними, і досить невеликого підглядання на чужі реалізації, на кшталт наведеної тут, та обговорення нюансів, порад та best practices, яке я сподіваюся побачити в коментарях 😉
Тільки зареєстровані користувачі можуть брати участь в опитуванні.
Чи варто публікувати проект/бібліотеку?
-
0,0%Так, я б використав / контрибутил0
-
33,3%Так, звучить здорово4
-
41,7%Ні, кому треба зроблять самі у своєму форматі та під свої потреби5
-
25,0%Утримаюся від відповіді3
Проголосували 12 користувачів. Утрималися 3 користувача.
Джерело: habr.com