Недавно мне захотелось обновить один из моих сайд-проектов по-быстрому. Этот проект представляет собой стандартное веб-приложение, предназначенное для использования в качестве отправной точки для приложений Go. Ранее я написал собственный пакет конфигурации в рамках проекта, чтобы получить конфигурацию из переменных среды в соответствии с манифестом 12 Factor Apps. Но я хотел расширить возможности настройки моего проекта пользователями.

Введите Консул HashiCorp. Consul имеет открытый исходный код и решает многие проблемы платформы, связанные с запуском современных приложений. Одним из них является динамическая конфигурация через распределенное хранилище ключей и значений. С Consul приложения могут загружаться, используя Consul в качестве удаленного источника своей конфигурации. Эти приложения также могут периодически получать и обновлять свои параметры конфигурации из Consul.

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

Почему Viper

Цель моего стандартного проекта — предоставить простое в использовании начальное приложение Go. Хотя многие были бы рады иметь встроенную конфигурацию с поддержкой Consul, не все используют Consul. Многие пользователи, особенно те, кто только начинает работать с Go, скорее всего, выберут более простой способ настройки. Как переменные среды.

Поскольку мне нужно поддерживать настройку приложения несколькими способами (переменные среды, Consul, может быть, даже файл JSON), мне нужна была гораздо более надежная библиотека конфигурации, такая как spf13/viper.

Viper — это пакет Go, цель которого — полная библиотека конфигурации. Это позволяет пользователям использовать несколько источников для настройки и даже использовать их вместе. Эта возможность — то, что мне нужно; поэтому я решил заменить свой пользовательский пакет конфигурации на Viper. Но при этом я обнаружил, что использование Viper с Consul не так просто, как я думал.

Эта статья покажет, как мой проект использует Viper и что нужно сделать, чтобы Viper работал с Consul.

Начиная

Прежде чем мы перейдем к настройке Viper, полезно рассмотреть, как устроен мой проект. Как и в моей статье Структура приложения командной строки, основной код приложения находится в пакете app. Этот пакет app имеет функции Run() и Stop() для запуска и остановки приложения.

Однако, в отличие от моего примера с командной строкой, я предпочитаю создавать каталог cmd/<application-name> для пакета main службы приложения. Я делаю эту структуру, потому что, в отличие от приложения командной строки. Я не ожидаю, что кто-то попытается использовать go get для установки моей службы приложений, и я считаю, что это немного чище для служб приложений.

Приведенная ниже команда tree показывает структуру моего проекта перед началом работы с Viper.

Теперь, когда мы изучили структуру приложения, первое, что нам нужно сделать, это заменить пакет go-quick/config на Viper. Поскольку текущий пакет извлекает конфигурации из переменных среды, мы изначально настроим Viper для выполнения той же задачи. Как только это сработает, мы перейдем к более сложным аспектам.

Для начала в пакете app мы изменим cfg с типа config.Config на *viper.Viper.

С этим изменением также необходимо будет изменить ссылки в коде приложения на переменную cfg. Такие ссылки, как cfg.ListenerAddr(), необходимо изменить на cfg.GetString("listener_addr"). Но об этом мы поговорим позже. Теперь мы настроим способ инициализации Viper и передачи его в функцию Run().

Загрузка конфигурации из Env

Во-первых, нам нужно инициализировать Viper, создав объект *viper.Viper в пакете main. После того, как мы это сделали, мы можем добавить код, необходимый для того, чтобы сказать Viper загрузить конфигурацию из среды, и использовать функцию ReadInConfig(), чтобы Viper нашел и загрузил конфигурацию из файлов и среды.

В приведенном выше примере мы создали объект *viper.Viper с помощью функции New(). Мы также сказали Viper разрешать пустые переменные среды, вызывая AllowEmptyEnv() с параметром true. Но важно помнить об использовании SetEnvPrefix() со значением app.

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

Используя функцию SetEnvPrefix(), мы настроили Viper для загрузки только переменных окружения с префиксом APP_. При такой настройке такой элемент, как APP_DEBUG, будет преобразован в debug.

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

Загрузка конфигурации из файла

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

К счастью, сделать это с Viper довольно просто; все, что нужно, это вызвать функцию AddConfigPath().

В приведенном выше примере я использовал функцию AddConfigPath(), чтобы указать Viper по умолчанию просматривать каталог ./conf для любых файлов конфигурации. Viper примечателен тем, что пользователи могут создать этот каталог и разместить любой файл конфигурации, который они хотят, JSON, YAML, TOML или даже файлы свойств Java. Вайпер это понимает.

Однако при добавлении поддержки файлов в Viper я обнаружил, что при отсутствии файлов конфигурации функция ReadInConfig() будет возвращать ошибку с типом viper.ConfigFileNotFoundError. Эта ошибка может сбросить проверку ошибок, особенно если вы применяете философию закрытия приложения, когда оно не может загрузить конфигурацию.

Чтобы справиться с этим лучше, я добавил простой оператор switch, который проверяет тип ошибки, позволяя мне принять ошибку и записать предупреждение, если файл не найден, но выйти, если есть фундаментальная ошибка конфигурации.

Загрузка конфига из Consul

Использование Viper для загрузки конфигурации из файлов и переменных среды достаточно просто. Но с добавлением Консула все немного запутаннее, особенно если вы использовали Консул в прошлом.

Когда я впервые попытался использовать Viper с Consul, мне потребовалось довольно много времени, чтобы понять, что я делаю неправильно. Проблема оказалась в том, что я не загружал конфигурацию в Consul так, как этого ожидает Viper.

Традиционно в Consul, когда вы добавляете элементы конфигурации, каждый из них считается уникальным ключом. Consul поддерживает ключевой путь с множеством подразделов, что позволяет пользователям независимо управлять каждым конфигурационным ключом и значением. Например, параметр конфигурации «отладка» будет иметь ключ go-quick/config/debug, а его значение будет true. «След» будет go-quick/config/trace, а его значение будет false.

Viper ожидает один ключ, go-quick/config, и значением этого ключа является JSON, YAML или другой поддерживаемый формат. Вместо того, чтобы каждый параметр был уникальным ключом, вы должны загрузить строку, которую Viper может разобрать и понять.

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

Consulator — это удобная утилита, которая читает файл YAML (в данном случае) и загружает содержимое этого файла YAML в качестве ключей в Consul. В этом примере мы видим, что ключ go-quick/config заполнен строкой, которая является JSON.

Viper прочитает этот JSON, проанализирует его, а затем применит содержащиеся в нем значения. Это означает, что мы можем получить доступ к значению from_consul как cfg.GetBool("from_consul").

Как только мы загрузим конфигурацию в Consul, как и ожидает Viper, добавить поддержку Consul будет довольно просто.

Чтобы настроить Viper для использования вытягивания из Consul, нам нужно использовать функцию AddRemoteProvider(), предоставив ей адрес Consul и ключевой путь, оба из которых я извлекаю из переменных среды в моем примере. Однако, прежде чем эта функция заработает, мы сначала используем пустой импорт, чтобы добавить функциональность удаленного поставщика в Viper.

После загрузки адреса Consul и ключевого пути нам также нужно использовать функцию SetConfigType(), чтобы сообщить Viper, какой тип формата ожидать от Consul. Поскольку в нашем примере использовалась строка текста JSON, мы установим для этого параметра значение json.

Как только все будет готово, мы можем вызвать функцию ReadRemoteConfig(), чтобы указать Viper прочитать и загрузить конфигурацию из Consul. Если Viper обнаружит какие-либо проблемы с извлечением структуры из Consul, это будет возвращено здесь как ошибка.

Следим за обновлениями Consul

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

В приведенном выше примере я использовал пакет madflojo/tasks для создания запланированной задачи, которая при выполнении будет вызывать функцию WatchRemoteConfig(). Эта функция извлечет последнюю конфигурацию из Consul и обновит значения внутренней конфигурации Viper.

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

Другие параметры Консула

В приведенном выше примере Viper запрашивает адрес Консула и путь к ключу. Однако пользователи, хорошо знакомые с Consul, могут заметить, что для токена Consul нет спецификации. Хорошая новость заключается в том, что сам Viper не запрашивает эти параметры; под прикрытием Viper использует пакет Consul API. Это означает, что мы можем использовать стандартные переменные среды, такие как CONSUL_HTTP_TOKEN, для аутентификации в Consul.

Чтобы увидеть список доступных переменных среды для Consul API, мы можем обратиться к его Go Documentation.

Резюме

В этой статье рассказывается, как добавить Viper в существующее приложение и использовать Viper для загрузки конфигурации из нескольких источников, включая Consul. Хотя эта статья посвящена Consul, читатели могут легко использовать примеры для подключения к другим удаленным провайдерам, таким как etcd.

Первоначально опубликовано на https://bencane.com.