Объяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация

  1. Главная
  2. Блог
  3. DevOps

DevOps

Объяснение переменных среды: .env файлы, секреты и правильная настройка

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

8 мин чтения
Объяснение переменных среды: файлы .env, секреты и конфигурация выполнены правильно

Главное

  • Переменные среды — это параметры «ключ-значение», которые процесс получает из своей среды.
  • Они не допускают использования конфигурации, например портов и URL-адресов баз данных, в вашем коде.
  • Значения всегда являются строками, поэтому «false» является правдивым, если вы его не анализируете.
  • Никогда не фиксируйте .env файлы с настоящими секретами и проверяйте необходимые настройки при запуске приложения.

Вашему приложению нужен URL-адрес базы данных. На вашем ноуте это локальная база данных, в CI одноразовая, а в продакшене управляемый кластер. Жесткое кодирование любого из них означает изменение кода для каждой среды. Переменные среды решают эту проблему. Код читается как DATABASE_URL, и каждая среда предоставляет свое собственное значение.

Эта идея является центральной в методе двенадцатифакторного приложения, который утверждает, что конфигурация, которая варьируется в зависимости от развертывания, принадлежит среде. Мы протестировали приведенное ниже поведение с помощью Python 3.13 в Windows. Все показанные выходные данные взяты из этого прогона.

Что такое переменные среды?

Каждый запущенный процесс имеет набор именованных текстовых значений, называемых его средой. Знакомые примеры: PATH, который сообщает оболочке, где найти программы, и HOME или USERPROFILE. Ваше собственное приложение может использовать тот же механизм для таких настроек, как APP_PORT, LOG_LEVEL или DATABASE_URL.

Установка переменных среды

Ракушка Для этой сессии За одну команду
Баш/зш экспортировать APP_PORT=8080 APP_PORT=8080 Python app.py
PowerShell $env:APP_PORT = "8080" Установите его, запустите, затем удалите.
Windows cmd установите APP_PORT=8080 Не встроен

Они действуют только в течение текущего сеанса терминала. Чтобы сделать их постоянными, добавьте их в профиль оболочки, например ~/.bashrc, или, в Windows, используйте настройки системной среды или setx. Что касается приложений, обычно лучше предоставлять их во время развертывания, а не устанавливать для всей системы.

Чтение их в коде

import os

port = os.environ.get("APP_PORT", "8000")   # default if missing
print(port)                                  # 8000

db = os.environ["DATABASE_URL_MISSING_DEMO"]
# KeyError: 'DATABASE_URL_MISSING_DEMO'

Используйте .get() со значением по умолчанию для дополнительных настроек. Используйте квадратные скобки, поднимающие KeyError, для настроек, без которых приложение не может работать. Другие языки аналогичны: process.env.APP_PORT в Node.js, System.getenv("APP_PORT") в Java и os.Getenv("APP_PORT") в Go.

Все это строка

Это ловит людей на каждом языке. Мы установили порт, а затем добавили к нему «1»:

os.environ["APP_PORT"] = "8080"
port = os.environ["APP_PORT"]
print(type(port).__name__, repr(port), port + "1")
# str '8080' 80801

os.environ["APP_PORT"] = 8080
# TypeError: str expected, not int

Значение вернулось в виде текста, и при добавлении получилось 80801, а не 8081. Преобразуйте явно, как в int(os.environ["APP_PORT"]). Булевы значения хуже:

os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"]))   # True

Любая непустая строка в Python является истинной, поэтому "false" стало True. Режим отладки был бы включен в производстве. Разбирайте логические значения намеренно:

def env_bool(name, default=False):
    value = os.environ.get(name)
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}

env_bool("DEBUG")                   # False
env_bool("NOT_SET", default=True)   # True

Как процессы их наследуют

Дочерний процесс получает копию среды своего родителя. Чтобы увидеть это, мы запустили дочерние процессы Python:

child sees: 8080 inherited     # default: full copy of the parent's environment
child sees: 9000 None          # given a custom env: only what we passed
parent sees child var: None    # a child's changes never flow back

Далее следуют три практических урока. Экспортируемые вами переменные видны каждой запускаемой вами программе, включая сценарии, которые вы не писали, что важно для секретов. Передача пользовательской среды заменяет все, поэтому включите все, что нужно ребенку. А сценарий не может изменять переменные своей родительской оболочки, поэтому инструменты просят вас source открыть файл вместо его запуска.

.env-файлы

Вводить множество переменных вручную утомительно, поэтому большинство проектов сохраняют локальные значения разработки в файле .env:

# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true

Такие библиотеки, как python-dotenv и dotenv для Node.js, загружают этот файл при запуске. Node.js 20.6 и более поздние версии также могут загружать его с помощью --env-file. Чтобы увидеть эту идею, мы написали крошечный парсер, который превратил приведенный выше файл в:

{'APP_PORT': '8080',
 'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
 'FEATURE_SEARCH': 'true'}

Используйте настоящую библиотеку на практике. Он обрабатывает кавычки, escape-символы, комментарии и многострочные значения, которые игнорирует наша 10-строчная версия. По умолчанию большинство загрузчиков dotenv не переопределяют уже установленные переменные, поэтому реальные значения среды имеют преимущество над файлом. Обычно это то, чего вы хотите.

Совет: Добавьте .env в .gitignore перед первым коммитом и вместо этого зафиксируйте .env.example с каждым именем переменной и безопасными значениями-заполнителями. Новые разработчики копируют его и подставляют свои значения.

Проверка конфигурации при запуске

Отсутствующая переменная должна немедленно остановить приложение с четким сообщением, а не вызывать запутанную ошибку через час. При загрузке мы проверили необходимые настройки:

REQUIRED = ["DATABASE_URL", "SECRET_KEY"]

def check_required(env):
    missing = [k for k in REQUIRED if not env.get(k)]
    if missing:
        raise SystemExit(f"Missing required settings: {', '.join(missing)}")

# Missing required settings: SECRET_KEY

Библиотеки типизированной конфигурации идут дальше. Настройки Pydantic в Python, zod или envalid в Node.js и свойства конфигурации Spring в Java — все они преобразуют типы и сообщают обо всех проблемах одновременно.

Переменные среды и секреты

Переменные среды — это распространенный способ передачи секретов, таких как ключи API, в приложение, и они намного лучше, чем их жесткое кодирование. У них есть слабые стороны, о которых следует знать:

  • Дочерние процессы наследуют их, как показал наш тест.
  • Они могут появляться в отчетах о сбоях, страницах отладки и журналах, которые выгружают среду.
  • В некоторых системах их могут читать другие процессы, запускаемые тем же пользователем.

В производстве многие команды хранят секреты в специальном менеджере, таком как HashiCorp Vault, AWS Secrets Manager, Azure Key Vault или Google Secret Manager, и внедряют их во время выполнения. Некоторые предпочитают монтировать секреты в виде файлов, а не переменных. В любом случае, никогда не печатайте конфигурацию в журналах и не меняйте любой секрет, который когда-либо был передан в Git, даже после того, как вы удалили его из истории.

В Докере и CI

# Docker (not run for this article)
docker run -e APP_PORT=8080 --env-file .env.production myapp

# GitHub Actions (not run for this article)
env:
  APP_PORT: 8080
  API_KEY: ${{ secrets.API_KEY }}

Платформы CI хранят секреты в зашифрованном виде и маскируют их в журналах. Избегайте включения секретов в образы Docker с помощью ENV в файле Dockerfile, поскольку они остаются читаемыми в слоях изображения. Подробные настройки см. в нашем руководстве по Docker Compose и руководстве по GitHub Actions CI/CD.

Лучшие практики

  • Используйте понятные имена в верхнем регистре с префиксом, например SHOP_DB_URL.
  • Прочтите всю конфигурацию в одном модуле, преобразуйте там типы и проверьте при запуске.
  • Предоставляйте безопасные значения по умолчанию только для неконфиденциальных настроек.
  • Держите .env вне Git и сохраняйте .env.example.
  • Используйте диспетчер секретов в производстве для конфиденциальных значений.
  • Задокументируйте каждую переменную, ее тип и пример значения.

Отладка проблем с переменными среды

Если приложение ведет себя так, как будто какой-то параметр отсутствует, выполните следующие проверки по порядку:

  1. Правильно ли она установлена? Переменная, экспортированная в одну вкладку терминала, не существует в другой или в службе, запущенной системой.
  2. Приложение было перезапущено? Запущенный процесс сохраняет среду, из которой он был запущен. Последующее изменение переменной не имеет никакого эффекта до перезапуска.
  3. Точно ли имя? В Linux и macOS имена чувствительны к регистру, поэтому App_Port и APP_PORT — это разные переменные. Windows рассматривает их одинаково.
  4. Файл .env переопределяет его или игнорируется? Проверьте, переопределяет ли ваш загрузчик существующие значения и какой файл он на самом деле читает.
  5. Есть ли случайные кавычки или пробелы? Значение, скопированное с конечным пробелом или дополнительными кавычками, может не поддаваться анализу или не сопоставляться, что трудно обнаружить.

Чтобы увидеть, что на самом деле получил процесс, выведите имена переменных, а не значения секретов. В Linux и macOS printenv перечисляет текущую среду оболочки, а в PowerShell Get-ChildItem Env: делает то же самое. Внутри контейнера выполнение команд с env по docker exec показывает вид контейнера, который часто отличается от представления вашей собственной оболочки.

Последний совет: при запуске запишите одну строку, в которой указано, какие дополнительные настройки используют значения по умолчанию. Это ничего не стоит и отвечает большинству вопросов: «Почему он это делает?» вопросы сразу.

Итоги

Переменные среды просты, но они скрывают несколько ловушек: все является строкой, дети наследуют их, и через них может просачиваться секрет. Рассматривайте их как входные данные для вашего приложения. Читайте их в одном месте, преобразуйте и проверяйте на ранних стадиях и храните настоящие секреты в файлах, которые будут зафиксированы.

Часто задаваемые вопросы

Что такое переменная среды?

Это именованное текстовое значение, которое процесс получает из своей среды, например APP_PORT=8080. Приложения используют их для настроек, которые меняются в зависимости от среды.

Что такое файл .env?

Текстовый файл со строками KEY=value, загружаемый при запуске такой библиотекой, как python-dotenv. Он используется в основном для локальной разработки и не должен содержать настоящих секретов.

Почему моя переменная среды является строкой?

Операционные системы хранят значения среды в виде текста. Преобразуйте числа с помощью int() и явно анализируйте логические значения, поскольку строка «false» считается истинной.

Защищены ли переменные среды для секретов?

Они лучше, чем жесткое программирование, но дочерние процессы наследуют их, и журналы могут их раскрыть. В производственных системах часто используется специальный менеджер секретов.

Может ли сценарий изменить переменные родительской оболочки?

Нет. Дочерний процесс получает копию среды, поэтому его изменения не возвращаются. Чтобы установить переменные в текущей оболочке, вместо этого создайте исходный файл.

Продолжайте учиться

Работайте с Stack Quora

Нужна помощь с тестированием, WordPress или SEO?

Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.

Продолжайте читать

Больше руководств по тестирование, искусственный интеллект и современные разработки

Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.

Искусственный интеллект

Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить

Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…

Читать статью →
DevOps

Объяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация

Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…

Читать статью →
Разработка ПО

Объяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429

Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…

Читать статью →
Автоматизация тестирования

Издевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут

Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …

Читать статью →
Тестирование ПО

Объяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%

Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…

Читать статью →
Базы данных и SQL

DELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них

Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…

Читать статью →
Базы данных и SQL

Объяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами

Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…

Читать статью →
Разработка ПО

Интерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора

Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…

Читать статью →
Разработка ПО

Объяснение Python *args и **kwargs: гибкие функции с понятными примерами

Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…

Читать статью →
Разработка ПО

Лямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать

Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …

Читать статью →
Искусственный интеллект

Что такое ИИ-агенты? Как они работают: можно запустить простой цикл агента

Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…

Читать статью →
DevOps

Мониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками

Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…

Читать статью →
DevOps

Docker Volumes против Bind Mounts: правильное сохранение данных контейнера

Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…

Читать статью →
Автоматизация тестирования

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

TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…

Читать статью →
Разработка ПО

Git merge vs rebase: отличия, примеры и когда что использовать

Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.

Читать статью →
Разработка ПО

Генераторы списков в Python: синтаксис, примеры и когда их избегать

Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.

Читать статью →
Базы данных и SQL

SQL JOIN: объяснение с примерами (Inner, Left, Right, Full)

Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…

Читать статью →