[DevOps, Kubernetes, Python] Конфигурация проекта внутри и вне Kubernetes
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно я написал ответ о жизни проекта в Докерах и отладке кода вне него, где мельком упомянул о том, что можно сделать свою систему конфигурирования, чтобы сервис и в Кубере хорошо работал, подтягивал секреты, и локально удобно запускался, в том числе вообще вне Докера. Ничего сложного, но описанный "рецепт" может кому-то пригодится :) Код на Питоне, но логика к языку не привязана.
Предыстория вопроса такова: жил-был один проект, сначала он был маленьким монолитом с утилитами и скриптами, но со временем рос, делился на сервисы, которые в свою очередь стали делиться на микросервисы, а потом ещё и скейлиться. Поначалу это всё выполнялось на голых 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
SPL
__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, которое я надеюсь увидеть в комментариях ;)
===========
Источник:
habr.com
===========
Похожие новости:
- [PostgreSQL, Django, Apache] Поднимаем Django стек на MS Windows
- [Системное администрирование, Серверное администрирование, DevOps, Kubernetes] 12 инструментов, делающих Kubernetes легче (перевод)
- [Хостинг, Децентрализованные сети, DevOps, Интернет вещей, Kubernetes] Turing Pi — кластерная плата для self-hosted приложений и сервисов
- [DevOps, Kubernetes, Серверное администрирование, Системное администрирование] Доступен NGINX Service Mesh (перевод)
- [Python, Программирование] Парсинг и аудит
- [Занимательные задачки, Python, Учебный процесс в IT, Логические игры] DataArt запустил бесплатную платформу Kiddo — онлайн-задачник для школьников, изучающих Питон
- [Разработка веб-сайтов, Python, Открытые данные] Разработка онлайн-сервиса для инвесторов на pythonanywhere.com с использованием данных Yahoo Finance
- [] Retry и Circuit Breaker в Kubernetes с помощью Istio и Spring Boot (перевод)
- [DevOps, Kubernetes, Серверное администрирование, Системное администрирование] 11 инструментов, делающих Kubernetes лучше (перевод)
- [GitHub, Open source, Python, Машинное обучение, Программирование] Делаем нейронную сеть, которая сможет отличить борщ от пельмешек
Теги для поиска: #_devops, #_kubernetes, #_python, #_python, #_devops, #_kubernetes, #_docker, #_configuration_management, #_yaml, #_devops, #_kubernetes, #_python
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:17
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно я написал ответ о жизни проекта в Докерах и отладке кода вне него, где мельком упомянул о том, что можно сделать свою систему конфигурирования, чтобы сервис и в Кубере хорошо работал, подтягивал секреты, и локально удобно запускался, в том числе вообще вне Докера. Ничего сложного, но описанный "рецепт" может кому-то пригодится :) Код на Питоне, но логика к языку не привязана. Предыстория вопроса такова: жил-был один проект, сначала он был маленьким монолитом с утилитами и скриптами, но со временем рос, делился на сервисы, которые в свою очередь стали делиться на микросервисы, а потом ещё и скейлиться. Поначалу это всё выполнялось на голых 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.pySPL__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, которое я надеюсь увидеть в комментариях ;) =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:17
Часовой пояс: UTC + 5