Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 6 из 25 ~18 мин +20 XP

Переменные окружения: AutomaticEnv

Viper AutomaticEnv включает проверку переменных окружения при каждом чтении ключа: деплой передаёт настройки через env, и файл конфига остаётся лишь базой. Разберём EnvKeyReplacer и превращение server.port в SERVER_PORT, строки вместо чисел, приоритет окружения над файлом и слепую зону AllKeys, из-за которой дамп показывает пустой конфиг при живом сервисе.

Содержание урока · 9 разделов

Пятница, выкатка. DevOps говорит: «в нашем Kubernetes конфиг-файлы не монтируем — настройки передаём переменными окружения». А ваш сервис читает config.yaml с портом стенда, и пересобирать бинарник под каждый контур — тот путь, от которого ушли ещё в первом уроке. Дефолты прошлого урока решают только половину проблемы: программа запускается, но настройки деплоя до неё всё ещё не доходят. Вторую половину закрывает viper AutomaticEnv: одна строка кода учит реестр заглядывать в переменные процесса при каждом чтении. Деплой задаёт порт, хост базы и уровень логов, не трогая ни код, ни файлы, — а viper сам решает, что перекрылось. Разберём механику, преобразование имён, приоритет env над файлом и одну слепую зону, которая регулярно заводит инженеров в тупик.

viper AutomaticEnv: как это работает#

AutomaticEnv не копирует переменные в реестр заранее — она меняет поведение каждого чтения. На Get, GetString, GetInt и IsSet viper спускается по лестнице приоритетов и на уровне окружения делает следующее: берёт ваш ключ, приводит его к верхнему регистру и ищет переменную с таким именем через os.LookupEnv. Запрос server.port превращается в поиск SERVER.PORT — и вот первая засада: переменную с точкой в имени shell не выставит, export SERVER.PORT=9090 bash отклонит как невалидный идентификатор. Поэтому без настройки имён AutomaticEnv в одиночку находит только ключи без точек: retries, debug, loglevel. Регистр работает в обе стороны: ключ в коде пишется как угодно — Server.Port, SERVER.PORT — реестр приведёт его к нижнему регистру, а поиск в окружении всегда идёт в верхнем. Запомните порядок: верхний регистр, потом замена разделителей, потом поиск. Проверку окружения можно и точечно: для ручного управления существует BindEnv, но в большинстве сервисов одной AutomaticEnv хватает, а тонкости явной привязки — тема ближайшего урока.

AutomaticEnv без настройки имён

GO
viper.AutomaticEnv()
viper.SetDefault("retries", 3)
viper.SetDefault("server.port", 8080)

// запуск: RETRIES=5 ./app
fmt.Println(viper.GetInt("retries"))     // 5 — env найден
fmt.Println(viper.GetInt("server.port")) // 8080 — SERVER.PORT не выставить

retries прочитался из окружения: ключ без точек после приведения к регистру становится именем переменной RETRIES. Вложенный server.port ищется как SERVER.PORT — а такое имя shell не даёт создать, и viper тихо спускается к дефолту. Чинится заменой разделителей — следующий блок.

SetEnvKeyReplacer: точки становятся подчёркиваниями#

Связка SetEnvKeyReplacer(strings.NewReplacer(".", "_")) описывает, как превращать ключ в имя переменной: viper берёт ключ в верхнем регистре и прогоняет через замену — server.port становится SERVER_PORT, database.host — DATABASE_HOST. Replacer — обычный strings.NewReplacer, тот же тип, что и для массовых замен строк: можно заменить не только точки, но и дефисы. Правило одно: SetEnvKeyReplacer должен быть вызван до первых чтений, и обычно его ставят рядом с AutomaticEnv в начале конфигурационной секции. После этого связка полностью предсказуема: ключ → верхний регистр → замена разделителей → поиск в os.LookupEnv. Нашли — значение вернётся с этого слоя; не нашли — viper пойдёт дальше по файлу и дефолтам, как будто окружения вообще нет.

Рабочая связка: replacer, AutomaticEnv, дефолты

GO
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("server.port", 8080)

// запуск: SERVER_PORT=9090 ./app
port := viper.GetInt("server.port")
fmt.Println(port) // 9090

Три строки подготовки и одно чтение. Ключ server.port приводится к SERVER.PORT, replacer заменяет точку — viper проверяет SERVER_PORT через os.LookupEnv, находит строку "9090" и cast приводит её к int. Уберите переменную — вернётся дефолт 8080: окружение перекрывает, но не ломает. Проверяется локальным Go: go build, затем запуск с переменной перед бинарником.

В окружении процесса не существует чисел и булевых значений — всё строки. SERVER_PORT=9090 в памяти процесса лежит как "9090", а не как int. Для viper это не проблема: cast приводит строку к нужному типу на чтении, и правила те же, что для файла — GetInt справится с "9090", GetBool поймёт "true", "1" и "t", GetDuration распарсит "30s". Из строковости следуют две практики. Первая: не кладите в env сырые структуры — список придётся передавать строкой с разделителями, и GetStringSlice разобьёт её по пробелам. Вторая: валидация не отменяется — GetInt("8080порт") вернёт ноль так же молча, как и для конфиг-файла. Отдельный нюанс: пустая переменная SERVER_PORT="" по умолчанию считается несуществующей — viper пропустит её и возьмёт значение ниже по лестнице. Поведение переключается AllowEmptyEnv, но включать пустоту как легальное значение стоит осознанно. Кстати, у файла поведение другое: yaml со значением port: "" вернёт пустую строку как полноценное значение, а не провал к дефолту — окружение и файл согласованы не во всём.

Слепая зона: AllKeys и AllSettings не видят окружение#

Самая коварная особенность AutomaticEnv: значения читаются, а ключей в реестре нет. AllKeys собирает ключи из файлов, дефолтов, Set и явно привязанных env — но перечислить все переменные процесса он не может и не пытается: viper не знает, какие из тысяч системных переменных относятся к вашей конфигурации. Поэтому AllSettings покажет только файл и дефолты, и дамп на старте напечатает «пустой конфиг» для сервиса, который прекрасно работает на env-настройках. Отсюда правило: не используйте AllSettings для логирования конфигурации целиком — печатайте конкретные ключи через Get, они честно сходят в окружение. Явная привязка BindEnv регистрирует ключ в реестре — и тогда он появляется в AllKeys вместе со значением; вместе с префиксами это тема следующего урока.

Дамп, который вводит в заблуждение

GO
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("server.port", 8080)
viper.SetDefault("log.level", "info")

// запуск: SERVER_PORT=9090 ./app
fmt.Println(viper.AllSettings())      // map[log.level:info server.port:8080]
fmt.Println(viper.Get("server.port")) // 9090 — из окружения

AllSettings отчитался дефолтами, а Get в соседней строке вернул 9090 из переменной — противоречие налицо, и это не баг, а следствие дизайна. Для дампа «как поедет сервис» печатайте конкретные ключи через Get, а не AllSettings целиком. Воспроизведите у себя локально — две минуты опыта против вечера отладки в проде.

Что видит API viper по каждому источнику#

ИсточникGet и IsSetAllKeysAllSettings
Конфиг-файлдадада
SetDefaultдадада
viper.Setдадада
env: AutomaticEnvданетнет
env: BindEnvдадада

На лестнице приоритетов окружение стоит между флагом и конфиг-файлом: переменная перекрывает и файл, и дефолт, но уступает флагу и явному Set. Практический смысл ровно такой, каким его задумали в двенадцати факторах: файл в репозитории описывает разумную базу, деплой уточняет её переменными, и один бинарник едет во все контуры без пересборки. Дефолты из прошлого урока при этом не теряют ценности: они гарантируют запуск без окружения — локально, в тестах, в одноразовом контейнере. Одного нюанса хватает на целый следующий урок: два сервиса на одной машине хотят переменную PORT каждый по-своему, и решается это префиксом SetEnvPrefix — PORT превращается в APP_PORT. Здесь достаточно знать, что механизм существует и что до него у окружения есть слепая зона из предыдущего раздела.

Гигиена env-конфигурации#

  • SetEnvKeyReplacer(strings.NewReplacer(".", "_")) — в начале конфигурационной секции: без него вложенные ключи не найдут переменные.
  • Дефолты SetDefault остаются обязательными: без окружения и файла запуск должен давать разумные значения, а не нули.
  • Окружение — всегда строки: GetInt и GetBool приведут сами, но границы значений проверяет ваша валидация.
  • Не логируйте конфиг через AllSettings: env-ключи в дамп не попадут — печатайте критичные ключи через Get.
  • Пустая переменная по умолчанию считается несуществующей: SERVER_PORT="" уступит дефолту, пока не включён AllowEmptyEnv.
  • Секреты через env передавать можно, печатать их в лог на старте — нельзя: маскируйте значения в дампах.

Проверка: имя переменной

AutomaticEnv включена, задан SetEnvKeyReplacer(strings.NewReplacer(".", "_")). Какую переменную окружения проверит viper.Get("server.port")?

Проверка: дамп на старте

Сервис с AutomaticEnv работает и берёт значения из переменных окружения. Что покажет viper.AllSettings()?

Что выведет программа?

Прочитайте код и выберите, что он выведет:

viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("retries", 3)

// запуск: RETRIES=5 ./app
fmt.Println(viper.GetInt("retries"))

Соберите чтение из окружения

Допишите включение AutomaticEnv, создание replacer и чтение числа.

viper.() viper.SetEnvKeyReplacer(strings.(".", "_")) // запуск: SERVER_PORT=9090 ./app port := viper.("server.port") fmt.Println(port) // 9090

Соберите env-секцию конфигурации

Расставьте строки: replacer, включение окружения, дефолт и чтение значения.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: окружение в viper

Термин

AutomaticEnv

нажмите, чтобы перевернуть
Определение

viper.AutomaticEnv() — на каждом чтении ключ проверяется в переменных окружения. Ничего не копирует заранее: проверка происходит в момент Get и IsSet.

1 / 6

Что дальше#

Окружение подключено, и один бинарник принимает настройки от деплоя. Нерешённым остался конфликт имён: два сервиса на общей машине дерутся за PORT, а переменные соседних приложений просачиваются в ваш реестр. Следующий урок про SetEnvPrefix и BindEnv чинит обе беды: префикс превращает PORT в APP_PORT, а явная привязка ключей возвращает их в AllKeys и делает валидацию полной. Тема переменных окружения в CLI уже встречалась в курсе Cobra — там os.Getenv и флаги; здесь всё сходится в один реестр с приоритетами. Потренируйтесь локальным Go: соберите секцию из урока, запустите с парой переменных и попробуйте предсказать дамп AllSettings до печати. Общий маршрут по темам — на странице курса viper.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • AutomaticEnv включает проверку окружения на каждом чтении: ключ приводится к верхнему регистру, replacer меняет разделители, viper ищет переменную через os.LookupEnv.
  • Без EnvKeyReplacer вложенный ключ ищется как SERVER.PORT — имя, которое shell выставить не может; replacer ставится до первых чтений.
  • Окружение всегда строки: GetInt, GetBool и GetDuration приводят значения сами, молча давая нули на мусоре.
  • env перекрывает конфиг-файл и дефолт, но уступает флагам и Set — порядок тот же, что и на общей лестнице приоритетов.
  • AllKeys и AllSettings слепы к AutomaticEnv: дамп показывает файл и дефолты даже при работающих env-настройках.
  • Пустая переменная по умолчанию считается несуществующей; AllowEmptyEnv меняет это поведение.
  • Префикс и явная привязка BindEnv — следующий шаг: они убирают коллизии имён и возвращают env-ключи в дампы.

Домашнее задание

Добавьте в проект из прошлого урока SetEnvKeyReplacer и AutomaticEnv, задайте дефолт порта и запустите бинарник с SERVER_PORT=9090. Напечатайте GetInt и AllSettings рядом — убедитесь, что значение из окружения живёт только в Get. Затем добавьте IsSet-проверку обязательного API_KEY и попробуйте запустить без него.

Личные заметкиПусто
Хранятся только в вашем браузере (Viper · Переменные окружения: AutomaticEnv)

Вопросы и ответы по уроку

AutomaticEnv видит переменные без префикса — не опасно ли это?

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

Почему AllSettings не показывает переменные окружения?

AllSettings строится от AllKeys, а тот собирает только зарегистрированные ключи: файл, дефолты, Set, BindEnv. Список переменных процесса огромен и к конфигурации относится лишь частично — viper не пытается угадать, какие из них ваши. Печатайте нужные ключи через Get или регистрируйте их BindEnv.

Что вернёт Get, если переменная задана пустой строкой?

По умолчанию viper пропустит пустую переменную и возьмёт значение ниже по лестнице — из файла или дефолта. Логика такая: пустая строка чаще означает, что переменную забыли задать. Если пустота — легальное значение, включите AllowEmptyEnv(true) и обрабатывайте её валидацией на старте.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Chi25 уроков~9 чпрактика25 уроков уже доступны

Chi: роутер для Go HTTP-сервисов

Интерактивный курс Chi — роутера Go на net/http: маршруты, middleware, группы, контекст, JSON API, таймауты, graceful shutdown. 25 уроков с примерами и практикой.

  • Строить маршруты chi: методы, параметры пути, wildcards
  • Писать middleware и понимать порядок их выполнения
  • Группировать маршруты: Route, Group и подроутеры
  • Прокидывать данные в хендлеры через context.Context