Русский
Русский
English
Статистика
Реклама

Конфигурация проекта внутри и вне 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: 26379smtp:  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-mainvolumes:  - name: db-main-secret-volume    secret:      secretName: db-main-secret

То есть, в каждом секрете описана одна секция. Сами секреты создаются так:


apiVersion: v1kind: Secretmetadata:  name: db-main-secrettype: OpaquestringData:  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 osimport 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 Falsedef 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 Truedef 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 configdef 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 configconfig = build_config()

Логика здесь довольно простая: объединяем крупные конфиги из директории проекта и пути по переменной окружения, и небольшие конфиги-секции из секретов Кубера, а затем немного их предобрабатываем. Плюс некоторые переменные. Замечу, что при поиске файлов из секретов используется ограничение глубины, т.к K8s в каждом секрете создаёт ещё скрытую папку, где сами секреты и хранится, а уровнем выше находится просто ссылка.


Надеюсь, описанное окажется кому-нибудь полезным :) Принимаются любые комментарии и рекомендации касательно безопасности или других моментов на улучшение. Также интересно мнение сообщества, возможно стоит добавить поддержку ConfigMaps (в нашем проекте они пока не используется) и оформить код на ГитХабе / PyPI? Лично я думаю, что такие вещи слишком индивидуальные для проектов, чтобы быть универсальными, и достаточно небольшого подглядывания на чужие реализации, вроде приведённой здесь, да обсуждения нюансов, советов и best practices, которое я надеюсь увидеть в комментариях ;)

Источник: habr.com
К списку статей
Опубликовано: 17.10.2020 20:10:41
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Devops

Kubernetes

Python

Docker

Configuration management

Yaml

Категории

Последние комментарии

© 2006-2020, personeltest.ru