Змінні середовища дозволяють налаштовувати додатки без зміни коду. Вони відокремлюють зовнішні дані від логіки додатку, що може залишатися досить загадковим для початківців розробників (та навіть деяких досвідчених).
Через цей практичний посібник ми розкриємо суть змінних середовища, щоб ви могли зрозуміти, що вони означають, чому вони важливі, та як впевнено використовувати змінні середовища.
Візьміть свій улюблений напій (і можливо деякі печива), адже ми збираємося глибоко зануритися в тему. Давайте розберемо концепції екологічних змінних з самого початку.
Що таке змінні оточення?
Змінні середовища – це динамічні іменовані значення, що можуть впливати на поведінку процесів під час їх виконання на комп’ютері. Деякі ключові властивості змінних середовища включають в себе:
- Названі: Мають описові назви змінних, як APP_MODE і DB_URL.
- Зовнішні: Значення встановлюються поза кодом програми через файли, командні рядки та системи.
- Динамічні: Можливе оновлення змінних без перезапуску програм.
- Налаштовані: Код спирається на змінні, але не визначає їх.
- Розділені: Немає необхідності змінювати конфігурації коду після встановлення змінних.
Ось аналогія. Уявіть, що ви слідуєте рецепту шоколадного печива. Рецепт може говорити:
- Додайте 1 склянку цукру
- Додайте 1 пачку м’якого масла
- Додайте 2 яйця
Замість цих вбудованих значень, ви могли б використовувати змінні середовища:
- Додайте $SUGAR чашку цукру
- Додайте $BUTTER стіки розм’якшеного вершкового масла
- Додайте $EGGS яйця
Перед тим, як робити печиво, ви повинні встановити ці назви змінних середовища на значення, які ви оберете:
SUGAR=1
BUTTER=1
EGGS=2
Отже, коли ви дотримуєтесь рецепту, ваші інгредієнти будуть виглядати так:
- Додайте 1 склянку цукру
- Додайте 1 пачку м’якого масла
- Додайте 2 яйця
Це дозволяє вам налаштувати рецепт cookie без зміни коду рецепту.
Та сама концепція застосовна до обчислень і розробки. Змінні середовища дозволяють вам змінювати середовище, в якому виконується процес, не змінюючи базовий код. Ось кілька поширених прикладів:
- Встановлення середовища на “development” або “production”
- Налаштування API ключів для зовнішніх сервісів
- Передача секретних ключів або даних авторизації
- Перемикання певних функцій увімкнено або вимкнено
Змінні середовища забезпечують велику гнучкість. Ви можете розгортати один і той же код у кількох середовищах, не змінюючи сам код. Але давайте краще зрозуміємо, чому вони є цінними.
Чому змінні середовища є цінними?
Розгляньте змінні середовища як ручки додатку, що використовуються для налаштування переваг. Незабаром ми розглянемо чудові приклади використання.
Давайте зміцнимо інтуїцію, чому змінні середовища мають значення!
Причина #1: Вони відокремлюють код додатку від конфігурацій
Жорстке кодування конфігурацій та облікових даних безпосередньо у ваш код може спричинити різного роду проблеми:
- Випадкові зміни у системі контролю версій
- Перебудова та повторне розгортання коду лише для зміни значення
- Проблеми з конфігурацією при переході між середовищами
Це також призводить до неохайного коду:
import os
# Закодована конфігурація
DB_USER = 'appuser'
DB_PASS = 'password123'
DB_HOST = 'localhost'
DB_NAME = 'myappdb'
def connect_to_db():
print(f"Підключення до {DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}")
connect_to_db()
Це заплутує бізнес-логіку з деталями конфігурації. Щільне зв’язування часом ускладнює обслуговування:
- Зміни вимагають модифікації вихідного коду
- Ризик витоку секретних даних у систему контролю версій
Використання змінних середовища зменшує ці проблеми. Наприклад, ви можете встановити змінні середовища DB_USER та DB_NAME.
# .env file
DB_USER=appuser
DB_PASS=password123
DB_HOST=localhost
DB_NAME=myappdb
Код програми може отримувати доступ до змінних середовища за потреби, зберігаючи код чистим та простим.
import os
# Завантаження конфігурації з середовища
DB_USER = os.environ['DB_USER']
DB_PASS = os.environ['DB_PASS']
DB_HOST = os.environ['DB_HOST']
DB_NAME = os.environ['DB_NAME']
def connect_to_db():
print(f"Підключення до {DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}")
connect_to_db()
Змінні середовища чітко відокремлюють конфігурацію від коду, зберігаючи чутливі значення у абстрактному середовищі.
Ви можете розгортати той самий код з розробки в продакшн без будь-яких змін. Змінні середовища можуть відрізнятися між середовищами без будь-якого впливу на код.
Причина №2: Вони спрощують налаштування додатків
Змінні середовища спрощують налаштування конфігурацій без зміни коду:
# .env file:
DEBUG=true
Ось як ми могли б використати це у файлі сценарію:
# Вміст скрипта:
import os
DEBUG = os.environ.get('DEBUG') == 'true'
if DEBUG:
print("У режимі DEBUG")
Перемикання режиму відлагодження потребує лише оновлення файлу .env—зміни коду, перебудова чи перерозгортання не потрібні. «Env vars» для короткості, також допомагає безперервно розгортати середовища:
import os
# Отримати змінну середовища для визначення поточного середовища (production або staging)
current_env = os.getenv('APP_ENV', 'staging') # За замовчуванням 'staging', якщо не встановлено
# API-ключ для production
PROD_API_KEY = os.environ['PROD_API_KEY']
# API-ключ для staging
STG_API_KEY = os.environ['STG_API_KEY']
# Логіка, яка встановлює api_key на основі поточного середовища
if current_env == 'production':
api_key = PROD_API_KEY
else:
api_key = STG_API_KEY
# Ініціалізація клієнта API з відповідним API-ключем
api = ApiClient(api_key)
Той самий код може використовувати окремі API ключі для продакшену та тимчасового сайту без будь-яких змін.
І нарешті, вони дозволяють перемикати функції без нових розгортань:
NEW_FEATURE = os.environ['NEW_FEATURE'] == 'true'
if NEW_FEATURE:
enableNewFeature()
Зміна змінної NEW_FEATURE активує функціонал миттєво у нашому коді. Інтерфейс для оновлення конфігурацій залежить від систем:
- Хмарні платформи, як-от Heroku, використовують панелі керування
- Сервери використовують інструменти командного рядка ОС
- Локальна розробка може використовувати файли .env
Змінні середовища є корисними при створенні додатків, дозволяючи користувачам налаштовувати елементи відповідно до їхніх вимог.
Причина #3: Вони допомагають керувати секретами та обліковими даними
Зберігання секретів, таких як API ключі, паролі та приватні ключі безпосередньо в коді, створює значні ризики безпеки:
# Уникайте розкриття секретів у коді!
STRIPE_KEY = 'sk_live_1234abc'
DB_PASSWORD = 'password123'
stripe.api_key = STRIPE_KEY
db.connect(DB_PASSWORD)
Ці дані тепер відкриті, якщо цей код буде викладено у публічний GitHub репозиторій!
Змінні середовища запобігають витоку, зовнішньо ізолюючи секрети:
import os
STRIPE_KEY = os.environ.get('STRIPE_KEY')
DB_PASS = os.environ.get('DB_PASS')
stripe.api_key = STRIPE_KEY
db.connect(DB_PASS)
Фактичні секретні значення встановлюються у локальному файлі .env.
# .env file
STRIPE_KEY=sk_live_1234abc
DB_PASS=password123
Не забудьте .gitignore
файл .env, щоб тримати секрети поза контролем джерел. Це передбачає визначення файлу .env у файлі .gitignore в корені будь-якого репозиторію, що каже git ігнорувати файл під час створення коміту.
Це відокремлює секретні визначення від коду програми, завантажуючи їх безпечно з захищених середовищ під час виконання. Ризик випадкового викриття облікових даних значно знижується.
Причина №4: Вони забезпечують послідовність
Уявіть, що у вас є різні конфігураційні файли для середовищ розробки, QA та виробництва:
# Розробка
DB_HOST = 'localhost'
DB_NAME = 'appdb_dev'
# Виробництво
DB_HOST = 'db.myapp.com'
DB_NAME = 'appdb_prod'
Ця невідповідність призводить до тонких помилок, які важко помітити. Код, який працює бездоганно у розробці, може раптово зламатися у продакшені через невідповідні конфігурації.
Змінні оточення вирішують це шляхом централізації конфігурації в одному місці:
DB_HOST=db.myapp.com
DB_NAME=appdb_prod
Тепер одні й ті ж змінні використовуються послідовно у всіх середовищах. Вам більше не потрібно турбуватися про випадкові або некоректні налаштування, які можуть спрацювати.
Код додатку просто посилається на змінні:
import os
db_host = os.environ['DB_HOST']
db_name = os.environ['DB_NAME']
db.connect(db_host, db_name)
Чи запущений додаток локально чи на продуктивному сервері, він завжди використовує правильний хост бази даних та її назву.
Ця уніформність зменшує кількість помилок, підвищує передбачуваність та загалом робить додаток більш стійким. Розробники можуть бути впевнені, що код буде працювати однаково у всіх середовищах.
Як ви можете визначити змінні середовища
Змінні середовища можуть бути визначені в кількох місцях, що дозволяє гнучкість у налаштуванні та доступі до них у різних процесах та системах.
1. Змінні оточення операційної системи
Більшість операційних систем забезпечують вбудовані механізми для визначення глобальних змінних. Це робить змінні доступними на рівні всієї системи для всіх користувачів, програм тощо.
На системах Linux/Unix змінні можуть бути визначені в стартових скриптах оболонки.
Наприклад, ~/.bashrc можна використовувати для встановлення змінних на рівні користувача, тоді як /etc/environment призначений для системних змінних, доступних усім користувачам.
Змінні також можуть бути встановлені в лінійному режимі перед виконанням команд за допомогою команди export або безпосередньо через команду env у bash:
# In ~/.bashrc
export DB_URL=localhost
export APP_PORT=3000
# In /etc/environment
DB_HOST=localhost
DB_NAME=mydatabase
Змінні також можуть бути встановлені в лінії перед виконанням команд:
export TOKEN=abcdef
python app.py
Визначення змінних на рівні ОС робить їх загальнодоступними, що дуже корисно, коли ви хочете запустити програму без залежності від внутрішніх значень.
Ви також можете посилатися на визначені змінні у скриптах або аргументах командного рядка.
python app.py --db-name $DB_NAME --db-host $DB_HOST --batch-size $BATCH_SIZE
2. Визначення змінних середовища в коді програми
Крім змінних рівня ОС, змінні середовища можуть бути визначені та доступні безпосередньо в коді програми під час її виконання.
Словник os.environ у Python містить усі наразі визначені змінні середовища. Ми можемо задати нові, просто додавши пари ключ-значення:
Змінні оточення також можуть бути визначені та доступні безпосередньо у коді додатку. У Python, словник os.environ містить всі визначені змінні оточення:
import os
os.environ["API_KEY"] = "123456"
api_key = os.environ.get("API_KEY")
Отже, словник os.environ дозволяє динамічно встановлювати і отримувати змінні середовища з коду Python.
Більшість мов програмування мають вбудовані бібліотеки, що надають доступ до змінних оточення під час виконання.
Ви також можете використовувати фреймворки, такі як Express, Django та Laravel, для більш глибокої інтеграції, наприклад автоматичного завантаження файлів .env, що містять змінні середовища.
3. Створення локальних файлів конфігурації для змінних середовища
Окрім системних змінних, змінні середовища можуть завантажуватися з локальних файлів конфігурації додатків. Це дозволяє відокремити деталі конфігурації від коду, навіть для локальної розробки та тестування.
Деякі популярні підходи:
.env Files
Конвенція формату файлу .env, яку популяризував Node.js, забезпечує зручний спосіб вказування змінних середовища у форматі ключ-значення:
# .env
DB_URL=localhost
API_KEY=123456
Веб-фреймворки, такі як Django та Laravel, автоматично завантажують змінні, визначені в файлах .env, до середовища додатку. Для інших мов, таких як Python, бібліотеки, наприклад python-dotenv, обробляють імпорт файлів .env:
from dotenv import load_dotenv
load_dotenv() # Завантажує змінні з .env
print(os.environ['DB_URL']) # localhost
Перевага використання файлів .env полягає в тому, що вони забезпечують чистоту та окремість конфігурації, не вносячи змін до коду.
Файли конфігурації JSON
Для більш складних потреб конфігурації, що включають кілька змінних середовища, використання файлів JSON або YAML допомагає організувати змінні разом:
// config.json
{
"api_url": "https://api.example.com",
"api_key": "123456",
"port": 3000
}
Код додатку може швидко завантажити ці дані JSON як словник для доступу до налаштованих змінних:
import json
config = json.load('config.json')
api_url = config['api_url']
api_key = config['api_key']
port = config['port'] # 3000
Це запобігає плутанині у файлах dotenv при роботі з кількома конфігураціями додатків.
Як отримати доступ до змінних середовища у різних мовах програмування?
Безвідносно до того, як ми визначаємо змінні середовища, нашим програмам потрібен послідовний спосіб пошуку значень під час виконання.
Хоча існує кілька способів визначення змінних середовища, програмний код потребує стандартного способу доступу до них у режимі реального часу, незалежно від мови. Ось огляд технік доступу до змінних середовища в популярних мовах:
Python
Python надає словник os.environ для доступу до визначених змінних середовища:
import os
db = os.environ.get('DB_NAME')
print(db)
Ми можемо отримати змінну за допомогою os.environ.get(), яка повертає None, якщо вона не визначена. Або доступитися безпосередньо через os.environ(), що викличе KeyError, якщо вона відсутня.
Додаткові методи, такі як os.getenv() і os.environ.get(), дозволяють вказувати значення за замовчуванням, якщо вони не встановлені.
JavaScript (Node.js)
У коді JavaScript Node.js, змінні оточення доступні в глобальному об’єкті process.env:
// Get env var
const db = process.env.DB_NAME;
console.log(db);
Якщо не визначено, process.env буде містити undefined. Ми також можемо задати значення за замовчуванням, як:
const db = process.env.DB_NAME || 'defaultdb';
Ruby
Ruby додатки отримують доступ до змінних середовища через хеш ENV:
# Access variable
db = ENV['DB_NAME']
puts db
Ми також можемо передати значення за замовчуванням, якщо потрібний ключ не існує:
db = ENV.fetch('DB_NAME', 'defaultdb')
PHP
PHP надає глобальні методи getenv(), $_ENV і $_SERVER для доступу до змінних середовища:
// Отримати змінну середовища
$db_name = getenv('DB_NAME');
// Або доступ до масивів $_ENV чи $_SERVER
$db_name = $_ENV['DB_NAME'];
Залежно від джерела змінної, вона може бути доступна в різних глобальних змінних.
Java
У Java, метод System.getenv() повертає змінні оточення, до яких можна отримати доступ:
String dbName = System.getenv("DB_NAME");
Це дозволяє отримати доступ до змінних, визначених на глобальному системному рівні в Java.
Наразі деякі найкращі практики стосовно гігієни змінних середовища.
Керівництво з безпеки змінних середовища
Щодо безпечного управління змінними середовища, ми повинні пам’ятати кілька найкращих практик.
Ніколи не зберігайте конфіденційну інформацію у коді
Перш за все, ніколи не зберігайте конфіденційну інформацію, таку як паролі, API ключі, або токени безпосередньо у вашому коді.
Може бути спокусливо просто жорстко закодувати пароль до бази даних або ключ шифрування у вашому вихідному коді для швидкого доступу, але стримайте це бажання!
Якщо ви випадково внесли цей код у публічний репозиторій на GitHub, ви фактично розголошуєте свої секрети всьому світу. Уявіть, якщо хакер отримає доступ до ваших виробничих баз даних, тільки тому, що вони були відкрито вказані у вашій кодовій базі. Лякаюча думка, чи не так?
Замість цього завжди використовуйте змінні оточення для зберігання будь-яких важливих конфігурацій. Зберігайте свої секрети у безпечному місці, наприклад у файлі .env або у інструменті управління секретами, та посилайтеся на них у своєму коді через змінні оточення. Наприклад, замість таких дій у вашому Python коді:
db_password = "supers3cr3tpassw0rd"
Ви зберігатимете цей пароль у змінній середовища ось так:
# .env файл
DB_PASSWORD=supers3cr3tpassw0rd
А потім використовуйте його у своєму коді так:
import os
db_password = os.environ.get('DB_PASSWORD')
Таким чином, ваші секрети залишаються в безпеці, навіть якщо ваш вихідний код був скомпрометований. Змінні середовища виступають як безпечний рівень абстракції.
Використання специфічних для середовища змінних
Ще одна практика полягає у використанні різних змінних середовища для кожного середовища застосунку, таких як розробка, тимчасовий сайт і виробництво.
Ви не хочете випадково підключитися до своєї продукційної бази даних під час локальної розробки, просто тому що забули оновити змінну конфігурації! Використовуйте простір імен для змінних оточення для кожного середовища:
# Dev
DEV_API_KEY=abc123
DEV_DB_URL=localhost
# Production
PROD_API_KEY=xyz789
PROD_DB_URL=proddb.amazonaws.com
Після цього посилайтеся на відповідні змінні у вашому коді, залежно від поточного середовища. Багато фреймворків, таких як Rails, надають конфігураційні файли специфічні для середовища для цих цілей.
Не зберігайте секрети у системі контролю версій
Також важливо тримати ваші файли .env та конфігурації, які містять секрети, поза системою контролю версій. Додайте .env до вашого .gitignore
, щоб випадково не здійснити коміт у ваш репозиторій.
Ви можете використовувати git-secrets
для сканування чутливої інформації перед кожним комітом. Для додаткової безпеки зашифруйте файл з секретами перед його зберіганням. Інструменти на кшталт Ansible Vault та BlackBox можуть допомогти з цим.
Захист таємниць на виробничих серверах
Під час керування змінними середовища на вашому продакшн сервері, уникайте їх встановлення за допомогою аргументів командного рядка, які можна перевірити через таблицю процесів.
Замість цього використовуйте інструменти управління середовищем вашої операційної системи або платформи оркестрації контейнерів. Наприклад, ви можете використовувати Kubernetes Secrets для безпечного зберігання та викриття секретів у ваші додаткові модулі додатків.
Використовуйте сильні алгоритми шифрування
Використовуйте надійні та сучасні алгоритми шифрування для захисту ваших секретів, чи це під час передачі, чи зберігання. Уникайте застарілих алгоритмів, таких як DES або MD5, які мають відомі уразливості. Натомість вибирайте алгоритми, що є стандартом галузі, такі як AES-256 для симетричного шифрування та RSA-2048 або ECDSA для асиметричного шифрування.
Регулярно змінюйте секретні дані
Регулярно оновлюйте свої секрети, особливо якщо ви підозрюєте, що вони могли бути скомпрометовані. Ставтеся до секретів так само, як до пароля — оновлюйте їх кожні кілька місяців. Інструмент управління секретами, такий як Hashicorp Vault або AWS Secrets Manager, може допомогти автоматизувати цей процес.
Будьте уважні з логуванням та звітами про помилки
Будьте обережні з логуванням та звітуванням про помилки. Переконайтеся, що ви не реєструєте жодних змінних середовища, які містять конфіденційні значення. Якщо ви використовуєте сторонній інструмент відстеження помилок, налаштуйте його так, щоб він очищав конфіденційні дані. Останнє, чого ви б хотіли, це щоб ваші секрети з’явилися в трасуванні стеку на панелі керування звітами про винятки!
Коли уникати змінних оточення?
Існує кілька випадків, коли слід уникати використання змінних середовища:
Управління складними конфігураціями
Використання змінних середовища для керування конфігурацією складних програмних систем може стати заплутаним та схильним до помилок. Зі зростанням кількості параметрів конфігурації ви отримуєте довгі назви змінних середовища, які можуть несвідомо зіткнутися одна з одною. Також немає простого способу організувати споріднені значення конфігурації разом.
Замість змінних середовища розгляньте можливість використання файлів конфігурації у форматах, таких як JSON або YAML. Це дозволяє вам:
- Групуйте пов’язані параметри конфігурації разом у вкладеній структурі.
- Уникайте конфліктів назв, інкапсулюючи конфігурацію в області та простори імен.
- Визначайте користувацькі типи даних замість простих рядків.
- Швидко переглядайте та модифікуйте конфігурації за допомогою текстового редактора.
Зберігання конфіденційної інформації
Хоча змінні середовища здаються простим способом внесення зовнішніх конфігурацій, як-от ключі API, паролі баз даних тощо, це може спричинити проблеми з безпекою.
Проблема в тому, що змінні середовища доступні глобально в процесі. Таким чином, якщо у вашому додатку існує вразливість, це може призвести до компрометації секретів, збережених у змінних середовища.
Більш безпечний підхід полягає у використанні сервісу управління секретами, який забезпечує шифрування та контроль доступу. Ці сервіси дозволяють зберігати чутливі дані зовні та надають SDK для отримання значень додатків.
Отже, розгляньте можливість використання окремого рішення для управління секретами замість змінних оточення для ваших облікових даних та приватних ключів. Це зменшує ризик випадкового розголошення чутливих даних через експлойти або ненавмисне логування.
Робота з кількома середовищами
Управління змінними середовища може стати важким, оскільки додатки розвиваються і розгортаються в різних середовищах (dev, staging, staging, prod). У вас можуть бути фрагментовані дані конфігурації, розкидані по різних bash скриптах, інструментах розгортання тощо.
Рішення для управління конфігурацією допомагає консолідувати всі налаштування, специфічні для середовища, в одному централізованому місці. Це можуть бути файли в репозиторії, спеціалізований сервер конфігурації або інтеграція з вашими CI/CD пайплайнами.
Якщо метою є уникнення дублювання змінних середовища, одне джерело істини для конфігурацій має більше сенсу.
Конфігурація спільного доступу між командами
Оскільки змінні середовища завантажуються локально для кожного процесу, обмін і синхронізація конфігураційних даних між різними командами, які працюють над одним додатком або набором послуг, стає дуже складною.
Кожна команда може підтримувати свою копію значень конфігурації в різних bash сценаріях, маніфестах розгортання тощо. Ця децентралізована конфігурація призводить до наступного:
- Зсув конфігурації: Без єдиного джерела істини легко допустити, що конфігурація стає непослідовною в різних середовищах, оскільки різні команди вносять незалежні зміни.
- Відсутність видимості: Немає централізованого способу перегляду, пошуку та аналізу всього стану конфігурації по всіх сервісах. Це робить надзвичайно важким розуміння того, як налаштовано сервіс.
- Проблеми з аудитом: Зміни в змінних середовища не відстежуються стандартним способом, що ускладнює аудит того, хто і коли змінив конфігурацію.
- Труднощі з тестуванням: Без можливості легко зробити моментальний знімок і поділитися конфігурацією, забезпечення послідовних середовищ для розробки і тестування стає надзвичайно обтяжливим.
Замість цього фрагментованого підходу, наявність централізованого рішення для конфігурації дозволяє командам керувати конфігурацією з єдиної платформи або репозиторію.
Створюйте свої додатки зі змінними середовища для довгострокового використання
Коли ваш додаток розширюється, подумайте про те, як вам може знадобитися більш передові способи управління його конфігураційними налаштуваннями.
Те, що зараз здається простим, згодом може стати складнішим. Ймовірно, вам знадобляться кращі способи контролю доступу, спільного використання налаштувань команди, чіткої організації всього та плавного оновлення конфігурацій.
Не потрапляйте в пастку, використовуючи лише змінні середовища з самого початку. Ви повинні спланувати, як обробляти конфігурації, коли ваші потреби зростатимуть.
Хоча змінні середовища чудово підходять для обробки даних, орієнтованих на середовище, таких як дані для входу, назви баз даних, локальні IP-адреси тощо, ви хочете створити систему, яка дотримується здорових принципів, таких як безпека, можливість спільного використання, організація та здатність швидко адаптуватися до змін.
Альтернативи, про які ми говорили, такі як використання окремого конфігураційного файлу або сервісу, мають корисні особливості, які відповідають цим принципам. Це допоможе вам продовжувати швидко рухатися без зайвих затримок.