Конфигурация на проекта вътре и извън Kubernetes

Наскоро написах отговор за живота на проекта в Докерите и отстраняването на кода извън него, където накратко споменах, че може да се направи собствена система за конфигуриране, така че услугата да работи добре и в Кубер, да извлича секрети и да се стартира удобно локално, включително и извън Докера. Нищо сложно, но описаната "рецепта" може да бъде полезна на някого 🙂 Кодът е на Питон, но логиката не е свързана с езика.

Конфигурация на проекта вътре и извън Kubernetes

Предисторията на въпроса е следната: имаше един проект, в началото той беше малък монолит с утилити и скриптове, но с времето нарасна, разделя се на услуги, които от своя страна започнаха да се делят на микросервизи, а после и да се разширяват. Първоначално всичко това се извършваше на голи VPS, процесите по настройка и разгръщане на кода на които бяха автоматизирани с помощта на Ansible, и за всяка услуга се съставяше YAML-конфигурация с необходимите настройки и ключове, а подобен конфигурационен файл се използваше и за локални стартирания, което беше много удобно, тъй като тази конфигурация се зарежда в глобален обект, достъпен от всяко място в проекта.

Обаче растежът на броя на микросервисите, тяхната взаимовръзка, а също така потребността от централизирано логиране и мониторинг, предвещаваха преминаване в Кубер, което все още е в процес. Заедно с помощта при решаването на споменатите задачи, Kubernetes предлага свои подходи за управление на инфраструктурата, включително т.н. Секрети и начини за работа с тях. Механизмът е стандартен и надежден, затова в буквалния смисъл е грях да не се възползваш от него! Но същевременно искахме да запазим текущия си формат на работа с конфигурацията: на първо място, да го използваме последователно в различни микросервизи на проекта, а на второ място, да имаме възможност да стартираме кода на локалната машина, използвайки един прост конфигурационен файл.

В тази връзка механизмът за изграждане на конфигурационен обект беше доразвит, за да може да работи както с нашия класически конфигурационен файл, така и с секретите от Кубер. Също така беше зададена по-строга структура на конфигурацията, говорейки на езика на третия Питон, такава:

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: info@test.com
  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? Лично смятам, че тези неща са твърде индивидуални за проектите, за да бъдат универсални, и е достатъчно да хвърлиш поглед на чужди реализации, подобни на споменатата тук, а също така да обсъдим нюансите, съветите и добрите практики, които се надявам да видя в коментарите 😉

Само регистрирани потребители могат да участват в анкетата. Влезте, моля.

Струва ли си да публикуваме като проект/библиотека?

  • 0,0%Да, бих използвал/допринасял.

  • 33,3%Да, звучи страхотно.

  • 41,7%Не, който има нужда, сам ще го направи в своя формат и за своите нужди.

  • 25,0%Ще се въздържа от отговор.

Гласували са 12 потребители. Въздържали се 3 потребителя.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster