Как установить файл конфигурации в .NET Core Console app для нескольких сред разработки при запуске Docker-контейнера

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

На ранних стадиях разработки проектов возникают проблемы при развёртывании сервисов в нескольких средах разработки. А именно сложности касаются установки различных версий файла конфигурации appsettings.json для нескольких сред разработки. Правка файла конфигурации вручную приводит к ошибкам и потере времени из-за человеческого фактора, поэтому необходимо автоматизировать процесс развёртывания сервисов. Как решить эту проблему без команды DevOps и в очень короткие сроки я опишу в пошаговой инструкции.

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

Стоит учесть следующие начальные условия:

  1. Сервис представляет собой Console Application и в отличии от ASP.NET Core Web Application для консольного приложения не существует решения из коробки.
  2. Запуск приложения происходит из docker-контейнера.

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

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

<ItemGroup>
		<Content Include="appsettings.json">
			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
		</Content>
		<Content Include="appsettings.Dev.json;appsettings.Testing.json;">
			<DependentUpon>appsettings.json</DependentUpon>
			<CopyToOutputDirectory>Always</CopyToOutputDirectory>
		</Content>
	</ItemGroup>

В файле appsettings.json появились вложенные файлы с названием среды разработки:

В файлы appsettings.{environment}.json надо добавить те части конфига, которые меняются относительно среды. Например, для изменения названия топиков для Kafka в контуре нагрузочного тестирования надо добавить в файл appsettings.Testing.json следующие изменения:

{
  "Kafka": {
    "EventMainTopicTitle": "Test_EventMain",
    "EventDelayTopicTitle": "Test_EventDelay",
    "EventRejectTopicTitle": "Test_EventReject"
  }
}

Осталось только выбрать нужный файл appsettings.json во время старта сервиса. Для этого необходимо внести изменения в класс Program:

        /// <summary>
        /// Конфигурация сервиса
        /// </summary>
        private static IServiceProvider ConfigureServices()
        {
            const string environmentVariableName = "ASPNETCORE_ENVIRONMENT";
            var environmentName = 
                Environment.GetEnvironmentVariable(environmentVariableName);
            var services = new ServiceCollection();
            _configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName)
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{environmentName}.json")
                .AddEnvironmentVariables()
                .Build();
            services.AddSingleton(_configuration);
            services.AddSingleton<KafkaHandler>();
            return services.BuildServiceProvider();
        }

Теперь всё готово к запуску сервиса в docker-контейнере.

Для начала надо указать переменные окружения для контейнера. Есть несколько способов сделать это:

  • командная строка
  • текстовый файл
  • docker compose

Я остановилась на указании переменных в командной строке. Вот пример скрипта для создания образа и запуска контейнера:

# Build image
# docker build . -t consoleapp
# Run container on Dev
# docker run -d <i>--env ASPNETCORE_ENVIRONMENT=Dev</i> --name app consoleapp

Я рассмотрела именно этот способ, как наиболее подходящий на ранних стадиях разработки, потому что его можно реализовать за короткое время, а время является критическим фактором.

GitHub с проектом — по ссылке.

Спасибо за внимание и приятного кодинга!

👍НравитсяПонравилось3
В избранноеВ избранном2
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

Как рекомендуется с точки зрения DevOps:

— приложение конфигурируется на работу с appsettings.json из кастомной директории, к примеру /app
— используется дополнительный volume, точка монтирования которого в контейнере как раз /app
— файл appsettings.json подготавливается перед стартом контейнера
— запускается контейнер, монтируется volume в /app, стартует приложение, читается файл /app/appsettings.json

Этот метод является несколько сложнее описанного в статье, но он есть product-ready approach и если ваш проект собирается дойти до прод-системы, лучше внедрить и начать использовать Configuration Management сразу же, а не заниматься пересборкой имиджей для прод-системы.

Плюсы:
— формируем конфигурацию как отдельный layer в системе, а не как часть приложения
— способ работает как для одиночных докер-контейнеров на локалхосте, так и для K8s
— при правильном подходе приложение может периодично выполнять poll метаданных конфиг-файла(ов) и перечитывать конфиг, позволяя динамически реконфигурировать приложение без редеплоя или перезапуска контейнера
— файл /app/appsettings.json может формироваться с помощью sidecar-контейнер из Hashicorp Vault/Consul, что есть следующим шагом Configuration Management-а

Минусы:
— для запуска контейнера нужен или батник или пайплайн
— нужно импрувить приложение

Для «продукт реди апроач» по хорошему нужно маунтить сикрет стор с кредами в keyvault а уже в приложении сразу тянуть конфигурцию из него. Или как минимум просто хранить конфигурацию в сикрет сторе если не хочется подключать key vault.
Как более advanced решение для веб приложений:
Пишем условный сикрет провайдер, деплоим на супер секьюрную тачку под 7 замками.
Он Post запросом, зная ip, передает конфигурацию на задеплоенное приложение. Получается как KeyVault только наоборот push модель. В итоге все секреты будут только в памяти приложения. Но это дорого писать, хотя может что-то есть готовое.
Ну а описанный в статье способ это конечно смех:)

Да, я забыл еще про интеграцию приложения с Vault/Consul напрямую, чтобы без sidecar-костылей ...

А почему бы не использовать стандартный шаблон (GenericHost) и RunConsoleAsync() ?
будет всё тоже самое, только всё из коробки.

Подписаться на комментарии