Наскоро писах
Предисторията на въпроса е следната: едно време имаше един проект, първоначално беше малък монолит с помощни програми и скриптове, но с времето се разрасна, раздели се на услуги, които от своя страна започнаха да се разделят на микроуслуги и след това увеличен. Отначало всичко това беше направено на гол VPS, процесите на настройка и внедряване на код, върху който бяха автоматизирани с помощта на Ansible, и всяка услуга беше компилирана с YAML конфигурация с необходимите настройки и ключове и подобен конфигурационен файл беше използван за локални стартирания, което беше много удобно, защото .k тази конфигурация се зарежда в глобален обект, достъпен отвсякъде в проекта.
Въпреки това нарастването на броя на микроуслугите, техните връзки и
В тази връзка механизмът за конструиране на конфигурационен обект беше модифициран, за да може да работи както с нашия класически конфигурационен файл, така и с тайни от Kuber. Беше определена и по-строга конфигурационна структура на езика на третия Python, както следва:
Dict[str, Dict[str, Union[str, int, float]]]
Тоест последният cogfig е речник с именувани раздели, всеки от които е речник със стойности от прости типове. А разделите описват конфигурацията и достъпа до ресурси от определен тип. Пример за част от нашата конфигурация:
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 pod изглежда така:
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()
Логиката тук е доста проста: комбинираме големи конфигурации от директорията на проекта и пътеки по променлива на средата и малки конфигурационни секции от тайните на Kuber и след това малко ги обработваме предварително. Плюс някои променливи. Отбелязвам, че при търсене на файлове от тайни се използва ограничение на дълбочината, тъй като K8s създава скрита папка във всяка тайна, където се съхраняват самите тайни, и просто връзката се намира на по-високо ниво.
Надявам се описаното да е полезно за някого :) Приемат се всякакви коментари и препоръки относно сигурността или други области за подобрение. Мнението на общността също е интересно, може би си струва да добавим поддръжка за ConfigMaps (нашият проект все още не ги използва) и да публикуваме кода в GitHub / PyPI? Лично аз смятам, че такива неща са твърде индивидуални, за да бъдат проектите универсални, и малко надникване в чужди реализации, като даденото тук, и обсъждане на нюанси, съвети и добри практики, които се надявам да видя в коментарите , достатъчно е 😉
В анкетата могат да участват само регистрирани потребители.
Трябва ли да публикувам като проект/библиотека?
-
0,0%Да, бих използвал /contribution0
-
33,3%Да, това звучи страхотно4
-
41,7%Не, който трябва да го направи сам в свой собствен формат и според нуждите си5
-
25,0%Ще се въздържа от отговор3
12 потребители гласуваха. 3 потребители се въздържаха.
Източник: www.habr.com