Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Config — конфигурация в среде
config

Config — конфигурация в среде

Хранение конфигурации в переменных окружения, разделение кода и конфига

Config: Конфигурация в среде

Храните конфигурацию в переменных окружения

#Суть принципа

Config — третий фактор 12-Factor App. Принцип гласит:

Конфигурация (пароли, домены, настройки) должна храниться в переменных окружения, а не в коде приложения.

# ❌ Неправильно: конфигурация в коде DATABASE_URL = 'postgres://admin:password123@prod-db.example.com:5432/myapp' SECRET_KEY = 'super-secret-key-12345' DEBUG = False # ✅ Правильно: конфигурация из переменных окружения import os DATABASE_URL = os.environ['DATABASE_URL'] SECRET_KEY = os.environ['SECRET_KEY'] DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'

#Что такое конфигурация?

Конфигурация — это всё, что может измениться между развёртываниями без изменения кода:

ТипПримеры
Учётные данныеПароли БД, API-ключи, секретные токены
Хосты и портыDATABASE_URL, REDIS_HOST, SMTP_PORT
Флаги функцийENABLE_NEW_UI, DEBUG_MODE, MAINTENANCE_MODE
Внешние сервисыSENTRY_DSN, STRIPE_KEY, AWS_BUCKET
Настройки приложенияMAX_UPLOAD_SIZE, CACHE_TTL, LOG_LEVEL

#Почему не в коде?

#Проблема: секреты в коде

# config.py # ❌ Пароль БД в коде DB_PASSWORD = 'SuperSecret123!' # ❌ API-ключ в коде STRIPE_API_KEY = 'sk_live_abc123xyz' # ❌ Внутренний домен в коде INTERNAL_API = 'http://192.168.1.100:8080'

Проблемы:

  1. Утечка секретов — код попадает в Git, секреты скомпрометированы
  2. Разные среды — нужно менять код для dev/staging/prod
  3. Безопасность — любой разработчик видит все пароли
  4. Аудит — невозможно отследить, кто и когда изменил настройку

#Решение: переменные окружения

# config.py import os # ✅ Пароль из переменной окружения DB_PASSWORD = os.environ['DB_PASSWORD'] # ✅ API-ключ из переменной окружения STRIPE_API_KEY = os.environ['STRIPE_API_KEY'] # ✅ Домен из переменной окружения INTERNAL_API = os.environ['INTERNAL_API']
# Установка переменных окружения export DB_PASSWORD='SuperSecret123!' export STRIPE_API_KEY='sk_live_abc123xyz' export INTERNAL_API='http://192.168.1.100:8080' python app.py

Преимущества:

  • ✅ Секреты не в коде — можно безопасно коммитить в Git
  • ✅ Одна кодовая база для всех сред — меняется только конфигурация
  • ✅ Контроль доступа — только ops-инженеры знают production-пароли
  • ✅ Аудит — можно логировать изменение переменных окружения

#Переменные окружения на практике

#Базовый синтаксис

# Установка переменной export DATABASE_URL=postgres://user:pass@localhost:5432/db # Чтение переменной echo $DATABASE_URL # Запуск приложения с переменной DATABASE_URL=postgres://... python app.py

#Чтение в разных языках

#Python

import os # Обязательная переменная (выбросит ошибку, если не установлена) database_url = os.environ['DATABASE_URL'] # Необязательная переменная со значением по умолчанию debug_mode = os.environ.get('DEBUG', 'False') log_level = os.environ.get('LOG_LEVEL', 'INFO')

#Node.js

// Обязательная переменная const databaseUrl = process.env.DATABASE_URL; // Необязательная со значением по умолчанию const debugMode = process.env.DEBUG || 'false'; const logLevel = process.env.LOG_LEVEL || 'info';

#Ruby

# Обязательная переменная database_url = ENV['DATABASE_URL'] # Необязательная со значением по умолчанию debug_mode = ENV.fetch('DEBUG', 'false') log_level = ENV.fetch('LOG_LEVEL', 'info')

#Go

import "os" // Обязательная переменная databaseURL := os.Getenv("DATABASE_URL") // Необязательная со значением по умолчанию debugMode := os.Getenv("DEBUG") if debugMode == "" { debugMode = "false" }

#Типы конфигурации

#Строковые значения

export DATABASE_URL=postgres://user:pass@host:5432/db export REDIS_URL=redis://localhost:6379/0 export SMTP_HOST=smtp.example.com

#Числовые значения

export PORT=5000 export MAX_CONNECTIONS=100 export CACHE_TTL=3600 export RATE_LIMIT=1000
# Преобразование в число port = int(os.environ.get('PORT', 5000)) max_connections = int(os.environ.get('MAX_CONNECTIONS', 100))

#Булевы значения

export DEBUG=true export ENABLE_FEATURE_X=false export MAINTENANCE_MODE=1
# Преобразование в boolean def parse_bool(value, default=False): if value is None: return default return value.lower() in ('true', '1', 'yes', 'on') debug = parse_bool(os.environ.get('DEBUG'), default=False)

#Списки значений

export ALLOWED_HOSTS=example.com,www.example.com,api.example.com export CORS_ORIGINS=https://app.com,https://admin.com
# Преобразование в список allowed_hosts = os.environ.get('ALLOWED_HOSTS', '').split(',') cors_origins = os.environ.get('CORS_ORIGINS', '').split(',')

#Локальная разработка и .env файлы

#Проблема: неудобно каждый раз экспортировать переменные

# ❌ Утомительно вводить каждый раз export DATABASE_URL=postgres://localhost/myapp export REDIS_URL=redis://localhost:6379 export DEBUG=true python app.py

#Решение: .env файлы

# .env (локальный файл, не коммитить в Git!) DATABASE_URL=postgres://localhost/myapp REDIS_URL=redis://localhost:6379 DEBUG=true SECRET_KEY=dev-secret-key
# Автоматическая загрузка .env from dotenv import load_dotenv load_dotenv() # Загружает переменные из .env import os database_url = os.environ['DATABASE_URL']

#.env.example для команды

# .env.example (можно коммитить в Git) # Шаблон для разработчиков DATABASE_URL=postgres://localhost/myapp REDIS_URL=redis://localhost:6379 DEBUG=true SECRET_KEY=change-me-in-production API_KEY=your-api-key-here

Разработчики копируют:

cp .env.example .env # Редактируют .env своими значениями

#.gitignore для .env

# .gitignore .env .env.local .env.*.local

#Конфигурация для разных сред

#Development

# .env.dev DATABASE_URL=postgres://localhost:5432/dev_db DEBUG=true LOG_LEVEL=DEBUG SECRET_KEY=dev-secret-key EMAIL_BACKEND=console

#Staging

# Переменные окружения на staging-сервере DATABASE_URL=postgres://staging-db:5432/app DEBUG=false LOG_LEVEL=INFO SECRET_KEY=<staging-secret> EMAIL_BACKEND=smtp SMTP_HOST=smtp.staging.example.com

#Production

# Переменные окружения в production DATABASE_URL=postgres://prod-db-cluster:5432/app DEBUG=false LOG_LEVEL=WARNING SECRET_KEY=<production-secret> EMAIL_BACKEND=smtp SMTP_HOST=smtp.example.com SENTRY_DSN=https://...@sentry.io/...

#Менеджеры секретов

#Проблема: переменные окружения небезопасны

  • Видны в ps aux и /proc/[pid]/environ
  • Попадают в логи при ошибках
  • Сложно ротировать секреты

#Решение: специализированные инструменты

#HashiCorp Vault

import hvac client = hvac.Client(url='http://vault:8200', token='my-token') secret = client.secrets.kv.v2.read_secret_version('myapp/database') db_password = secret['data']['data']['password']

#AWS Secrets Manager

import boto3 client = boto3.client('secretsmanager') secret = client.get_secret_value(SecretId='myapp/database') db_password = secret['SecretString']

#Azure Key Vault

from azure.keyvault.secrets import SecretClient client = SecretClient(vault_url="https://myvault.vault.azure.net/", credential) db_password = client.get_secret("database-password").value

#Doppler

# Установка Doppler CLI doppler setup # Запуск приложения с секретами из Doppler doppler run -- python app.py

#Конфигурация в Docker

#Передача переменных в контейнер

# Dockerfile FROM python:3.12 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . # Переменные не указываются в Dockerfile! # Они передаются при запуске CMD ["gunicorn", "app:app"]
# Запуск с переменными окружения docker run -d \ -e DATABASE_URL=postgres://user:pass@db:5432/app \ -e SECRET_KEY=my-secret-key \ -e DEBUG=false \ -p 5000:5000 \ myapp

#Docker Compose

# docker-compose.yml version: '3.8' services: web: build: . ports: - "5000:5000" environment: - DATABASE_URL=postgres://db:5432/app - REDIS_URL=redis://redis:6379 - DEBUG=true env_file: - .env.local # Дополнительные переменные из файла depends_on: - db - redis db: image: postgres:15 environment: - POSTGRES_PASSWORD=postgres - POSTGRES_DB=app redis: image: redis:7

#Kubernetes ConfigMaps и Secrets

# configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: myapp-config data: DEBUG: "false" LOG_LEVEL: "INFO" MAX_CONNECTIONS: "100"
# secret.yaml apiVersion: v1 kind: Secret metadata: name: myapp-secret type: Opaque stringData: DATABASE_URL: postgres://user:pass@db:5432/app SECRET_KEY: super-secret-key
# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: template: spec: containers: - name: web image: myapp:latest envFrom: - configMapRef: name: myapp-config - secretRef: name: myapp-secret

#Антипаттерны

#❌ Конфигурация в коде

# ❌ Неправильно class Config: DATABASE_URL = 'postgres://admin:password@prod-db/app' SECRET_KEY = 'hardcoded-secret' DEBUG = False

Проблема: секреты в коде, невозможно менять без изменения кода.

#❌ Конфигурация в базе данных

# ❌ Неправильно: чтение конфига из БД config = db.query("SELECT * FROM config WHERE key = 'database_url'")

Проблема: курица и яйцо — для подключения к БД нужна конфигурация БД.

#❌ JSON/YAML файлы с секретами

# ❌ Неправильно: секреты в файле import json with open('config.json') as f: config = json.load(f) db_password = config['database']['password']

Проблема: файл с секретами может попасть в Git, сложно управлять для разных сред.

#❌ Передача секретов в аргументах командной строки

# ❌ Неправильно: пароль виден в ps aux python app.py --db-password SuperSecret123

Проблема: аргументы командной строки видны всем пользователям системы через ps.

#Валидация конфигурации

#Проверка обязательных переменных

import os from typing import Optional class Config: def __init__(self): # Обязательные переменные self.database_url = self._require_env('DATABASE_URL') self.secret_key = self._require_env('SECRET_KEY') # Необязательные с дефолтами self.debug = self._get_bool('DEBUG', default=False) self.log_level = os.environ.get('LOG_LEVEL', 'INFO') self.port = int(os.environ.get('PORT', 5000)) def _require_env(self, name: str) -> str: value = os.environ.get(name) if not value: raise ValueError(f"Required environment variable {name} is not set") return value def _get_bool(self, name: str, default: bool = False) -> bool: value = os.environ.get(name, str(default)) return value.lower() in ('true', '1', 'yes', 'on') # Использование config = Config() # Выбросит ошибку, если нет обязательных переменных

#Pydantic для валидации

from pydantic import BaseSettings, Field, validator class Settings(BaseSettings): database_url: str = Field(..., env='DATABASE_URL') secret_key: str = Field(..., env='SECRET_KEY') debug: bool = Field(default=False, env='DEBUG') log_level: str = Field(default='INFO', env='LOG_LEVEL') port: int = Field(default=5000, env='PORT') @validator('log_level') def validate_log_level(cls, v): allowed = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] if v.upper() not in allowed: raise ValueError(f'log_level must be one of {allowed}') return v.upper() class Config: env_file = '.env' env_file_encoding = 'utf-8' # Использование settings = Settings() # Автоматическая валидация

#Проверка соответствия принципу

Задайте себе вопросы:

  • Все ли секреты вынесены в переменные окружения?
  • Можно ли развернуть приложение без изменения кода?
  • Используется ли .env.example для документации переменных?
  • Заблокирован ли .env в .gitignore?
  • Есть ли валидация обязательных переменных?
  • Используются ли менеджеры секретов для production?

#Связь с другими факторами

  • Build, Release, Run — конфигурация добавляется на стадии Release
  • Backing Services — URL внешних сервисов хранятся в конфигурации
  • Dev/Prod Parity — одинаковые переменные окружения во всех средах

#Пример из практики

#До: секреты в коде

# config.py DATABASE = { 'host': 'prod-db.example.com', 'user': 'admin', 'password': 'SuperSecret123!', # ❌ 'database': 'myapp' } STRIPE_KEY = 'sk_live_abc123' # ❌

Проблемы:

  • Пароль попал в Git при коммите
  • Нужно менять код для staging
  • Все разработчики видят production-пароль

#После: конфигурация в среде

# config.py import os DATABASE = { 'host': os.environ['DB_HOST'], 'user': os.environ['DB_USER'], 'password': os.environ['DB_PASSWORD'], 'database': os.environ['DB_NAME'] } STRIPE_KEY = os.environ['STRIPE_KEY']
# Production export DB_HOST=prod-db.example.com export DB_USER=admin export DB_PASSWORD=<secret> export STRIPE_KEY=<secret> # Development export DB_HOST=localhost export DB_USER=dev export DB_PASSWORD=dev export STRIPE_KEY=sk_test_...

Результат:

  • Секреты не в коде
  • Одна кодовая база для всех сред
  • Безопасное управление доступом

Ключевой вывод: Конфигурация должна храниться в переменных окружения, а не в коде. Это обеспечивает безопасность, переносимость и возможность развёртывания одного артефакта в разных средах без изменения кода.

Далее: Backing Services — внешние сервисы