Русский
Русский
English
Статистика
Реклама

Asp.net mvc

Миграция с .NET Core 2.2 на .NET Core 3.1 на примере реального проекта

21.07.2020 12:10:27 | Автор: admin

image
Эта статья является логическим продолжением обновления проекта nopCommerce бесплатной CMS с открытым исходным кодом для создания интернет-магазинов. В прошлый раз мы рассказали о нашем опыте миграции проекта с ASP.NET MVC на ASP.NET Core 2.2. Теперь мы рассмотрим процесс миграции на .NET Core 3.1. Учитывая, что официальная поддержка .Net Core 3.1 будет длиться до декабря 2022 года, сейчас тема миграции очень актуальна. Поэтому, если вы хотите получить все преимущества обновленного фреймворка, идти в ногу с технологическими новинками и соответствовать набирающим популярность общемировым трендам, то самое время заняться миграцией.


Какие задачи предстояло решить в процессе перехода на .NET Core 3.1


Для быстро развивающегося проекта в области электронной коммерции, крайне важно уделять большое внимание производительности системы и ее безопасности. Уже в первых review .NET Core 3.0 анонсировалось, что новая версия фреймворка будет в разы производительней, появится многоуровневая компиляция и как следствие уменьшение времени запуска, встроенная высокопроизводительная и не требовательная к памяти поддержка JSON. Маршрутизация конечной точки, которая появилась в версии .NET Core 2.2 была улучшена. Основное преимущество в том, что теперь маршрут определяется до запуска промежуточного программного обеспечения. На момент выхода релиза эти ожидания подтвердились на практике. Далее вы узнаете, что именно повлияло на производительность системы и как эволюционировал .NET Core с момента выхода версии 2.2.


Что нового дает переход на .NET Core 3.1


Давайте подробнее остановимся на тех нововведениях которые мы можем использовать с новой версией фреймворка. Подробную инструкцию по переходу от .NET Core 2.2 к .NET Core 3.1 вы можете найти на официальном сайте Microsoft. В этой статье мы рассмотрим преимущества, которые мы получаем благодаря использованию того или иного нововведения.


Generic Host


В .NET Core 2.1 Generic Host является неким дополнением к Web Host, это позволяет использовать такие инструменты, как внедрение зависимостей (DI) и протоколирование абстракций. В .NET Core 3.х был сделан акцент на большую совместимость с Generic Host, теперь вы можете использовать обновленный Generic Host Builder вместо Web Host Builder. Это дает возможность создавать любые приложения, начиная от консольных приложений и WPF и заканчивая веб-приложениями на одной базовой хостинговой парадигме с одинаковыми общими абстракциями.


public class Program{    public static void Main(string[] args)    {        CreateHostBuilder(args).Build().Run();    }    public static IHostBuilder CreateHostBuilder(string[] args)    {        return Host.CreateDefaultBuilder(args)            .UseServiceProviderFactory(new AutofacServiceProviderFactory())            .ConfigureWebHostDefaults(webBuilder =>            {                webBuilder                    .UseStartup<Startup>();            });    }}

Вы все еще можете продолжать использовать WebHostBuilder, но необходимо понимать что некоторые типы устарели в ASP.NET Core 3.1, и могут быть заменены в следующей версии.



global.json


Сама идея и возможность использования этой фичи уже существовала в версии .NET Core 2.0, но ее возможности были неполными. Можно было только указать версию SDK, которую использует ваше приложение. Более гибкое управление версией пакета SDK стало доступным только в .NET Core 3.0 с появлением таких политик как allowPrerelease и rollForward. Ниже представлен наш вариант комбинации политики наката версий .NET Core SDK.


{  "sdk": {    "version": "3.1.201",    "rollForward": "latestFeature",    "allowPrerelease": false  }}

Полную версию кода приложения вы можете посмотреть в нашем репозитории на GitHub.


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


ASP.NET Core Module V2


До .NET Core 2.2 IIS по умолчанию размещал приложение .NET Core, выполняя экземпляр Kestrel (встроенный веб-сервер .NET Core) и перенаправляя запросы из IIS в Kestrel. В основном IIS действовал как прокси. Это работает, но медленно, так как выполняется двойной переход от IIS к Kestrel для обработки запроса. Этот метод хостинга получил название OutOfProcess.



В .NET Core 2.2 была представлена новая модель хостинга под названием InProcess. Вместо того чтобы IIS пересылал запросы в Kestrel, он обслуживает запросы внутри IIS. Это намного быстрее при обработке запросов, потому что не нужно пересылать запрос в Kestrel. Однако это была необязательная функция, и не использовалась по умолчанию.



В .NET Core 3.1 внутрипроцессная модель размещения уже настроена по умолчанию, что значительно повышает пропускную способность запросов ASP.NET Core в IIS. При тестировании мы фиксировали двукратное увеличение производительности системы при большом количестве запросов.


<PropertyGroup>    <TargetFramework>netcoreapp3.1</TargetFramework>    <Copyright>Copyright (c) Nop Solutions, Ltd</Copyright>    <Company>Nop Solutions, Ltd</Company>    <Authors>Nop Solutions, Ltd</Authors>    <Version>4.4.0.0</Version>    <Description>Nop.Web is also an MVC web application project, a presentation layer for public store and admin area.</Description>    <PackageLicenseUrl>https://www.nopcommerce.com/license</PackageLicenseUrl>    <PackageProjectUrl>https://www.nopcommerce.com/</PackageProjectUrl>    <RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>    <RepositoryType>Git</RepositoryType>    <!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project-->    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>    <!--When true, compiles and emits the Razor assembly as part of publishing the project-->    <RazorCompileOnPublish>false</RazorCompileOnPublish>  </PropertyGroup>

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


Маршрутизация Endpoint Routing


В .NET Core 2.1 маршрутизация выполнялась в Middleware (промежуточном программном обеспечении ASP.NET Core MVC) в конце конвейера обработки HTTP-запросов. Это означает, что информация о маршруте, например, какое действие контроллера будет выполнено, была недоступна промежуточному ПО, которое обработало запрос до промежуточного ПО MVC в конвейере запросов. Поэтому начиная с .NET Core 2.2 была введена новая система маршрутизации, основанная на конечных точках (Endpoints), призванная решить вышеупомянутые проблемы.
Теперь в .NET Core 3.1 система маршрутизации Endpoint Routing построена иначе, этап маршрутизации отделен от вызова конечной точки. По сути мы имеем два промежуточных middleware:


  • EndpointRoutingMiddleware тут определяется, какая конечная точка будет вызвана для каждого пути URL запроса, по сути выполняя роль маршрутизации
  • EndpointMiddleware вызывает конечную точку


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


/// <summary>/// Configure Endpoints routing/// </summary>/// <param name="application">Builder for configuring an application's request pipeline</param>public static void UseNopEndpoints(this IApplicationBuilder application){    //Add the EndpointRoutingMiddleware    application.UseRouting();    //Execute the endpoint selected by the routing middleware    application.UseEndpoints(endpoints =>    {        //register all routes        EngineContext.Current.Resolve<IRoutePublisher>().RegisterRoutes(endpoints);    });}

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


С# 8.0 синтаксический сахар


Кроме обновления самого .NET Core так же была выпущена и новая версия C# 8.0. Обновлений очень много. Одни из них достаточно глобальны, другие затрагивают косметические улучшения, давая разработчикам так называемый синтаксический сахар.



Подробное описание вы можете найти в документации.


Замеры производительности в приложении


После того как мы обновили наше приложение nopCommerce, нам необходимо было проверить на сколько именно в нашем случае увеличилась производительность, т.к. для пользователей это один из самых важных критериев выбора eCommerce платформы.
Тесты мы проводили под управлением Windows 10 (10.0.19041.388), где IIS 10 (10.0.19041.1) выступал в качестве прокси-сервера для веб-сервера Kestrel на той же машине. Для моделирования нагрузки мы использовали Apache JMeter, который позволяет имитировать очень серьезную нагрузку со множеством параллельных запросов. Для теста мы специально подготовили среднестатистическую базу данных интернет магазина среднего бизнеса, где количество продуктов составляло около 50 тысяч наименований, количество категорий продуктов 516 с произвольной вложенностью, количество зарегистрированных пользователей порядка 50 тысяч, и количество заказов около 80 тысяч с рандомным включением от 1 до 5 продуктов. Все это под управлением MS SQL Server 2017 (14.0.2014.14).
JMeter выполнял несколько нагрузочных тестов, генерируя каждый раз сводный отчет об измерении каждого из запросов. Усредненные результаты после нескольких прогонов представлены ниже.


Время прохождения теста стало быстрее примерно на 20% (меньше лучше)



Среднее время отклика (Average) быстрее примерно на 13.7% (меньше лучше)



Количество запросов на единицу времени, т.е. пропускная способность (throughput) увеличилось на 12.7% (больше лучше)



Также нас интересовало использование памяти, затраченной приложением после запуска, и после краткосрочного нагрузочного теста, который проводился несколькими сериями подряд. Нашей целью было узнать, насколько эффективно освобождается память в процессе работы приложения. И как повлияло обновление модуля ASP.NET Core с новой моделью размещения на стартовое потребление памяти.



Результаты представлены на диаграмме модель внутрипроцессного размещения показывает более чем в 1,5 раза лучшую эффективность после прохождения первого цикла нагрузки. Далее наблюдается более эффективное использование ресурсов, и к концу третьего цикла нагрузочных тестов общее потребление памяти оказывается более чем в 2 раза эффективнее по сравнению с внепроцессным размещением. Это очень хороший результат.


Для сравнения мы приведем таблицу с результатами испытаний, в том числе и для AspNetCoreModule, который мы использовали с .NET Core 2.2. Новый модуль несомненно выигрывает у своего предшественника.



Стартовое потребление памяти практически не изменилось, показания изменяются в пределах погрешности измерений.



Итоги


Процесс миграции платформы .NET Core по-прежнему остается трудоемким и требующим времени мероприятием. И если делать обновления вовремя и последовательно, это позволит избежать огромного количества ошибок, а главное экономить время. Многие аспекты .NET Core были улучшены, бонусом вы получаете улучшенную производительность. В нашем конкретном случае мы фиксировали увеличение производительности в среднем на 13%. Это привело к тому, что запросы к приложению от наших клиентов выполняются быстрее, что позволяет отправлять больше данных за меньшее время, делая работу с приложением более комфортной. Поэтому это довольно существенная прибавка, учитывая, что вам фактически не требуется проводить performance refactoring вашего приложения: можно просто обновить фреймворк и получить общее повышение эффективности платформы.
Также немаловажным является тот факт, что, как и всегда, в .NET Core много внимания уделяется вопросам безопасности, к слову, с момента релиза .NET Core 3.1.0 уже успел выйти ряд обновлений (последняя версия на текущий момент 3.5.1) в том числе и обновлений безопасности security patch.
Очень важным моментом является и то, что .NET Core 3.1 является очередной версией LTS после 2.1, что гарантирует нам и нашим клиентам поддержку и получение самых последних исправлений, в том числе и исправлений безопасности. Поэтому для нас важно двигаться вперед вместе с выходом LTS версий .NET Core. Следующим глобальным выпуском версии LTS с долгосрочной поддержкой станет .NET 5, и перенос приложения на .NET Core 3.1 это лучший способ подготовиться к этому.
В дальнейшем мы планируем и дальше обновлять наше приложение nopCommerce, добавляя все новые и новые возможности которые предоставляет платформа .NET Core. Одна из них это переход на использование System.Text.Json вместо Newtonsoft.Json. Это более производительный, безопасный и стандартизованный подход к обработке JSON объектов. Также в планах внедрить и использовать как можно больше фичей, которые предоставляет C# 8.0.


Узнать больше о нашем проекте вы можете на сайте nopcommerce.com или посетив наш репозиторий на GitHub.

Подробнее..

Переход с Azure на GCP, с ASP.NET MVC на ASP.NET Core 3.1

26.01.2021 20:09:19 | Автор: admin

Автор: Андрей Жуков, .NET Team Leader, DataArt

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

Задача, поставленная заказчиком: Azure -> GCP

Заказчик решил перейти из одного облака (Azure) в другое (Google Cloud Platform). В некотором отдаленном будущем вообще планировалось перевести серверную часть на Node.js и развивать систему силами команды full-stack typescript-разработчиков. На момент моего входа в проект там существовала пара ASP.NET MVC приложений, которым решили продлить жизнь. Их мне и предстояло перенести в GCP.

Начальная состояние, факторы, мешающие сразу перейти на GCP

Первоначально имелось два ASP.NET MVC-приложения, которые взаимодействовали с одной общей MS SQL базой данных. Они были развернуты на Azure App Services.

Первое приложение назовем его Web Portal имело пользовательский интерфейс, построенный на базе Razor, TypeScript, JavaScript, Knockout и Bootstrap. С этими клиентскими технологиями никаких проблем не предвиделось. Зато серверная часть приложения использовала несколько сервисов, специфичных для Azure: Azure Service Bus, Azure Blobs, Azure Tables storage, Azure Queue storage. С ними предстояло что-то делать, т. к. в GCP ни один из них не поддерживается. Кроме того, приложение использовало Azure Cache for Redis. Для обработки длительных запросов была задействована служба Azure WebJob, задачи которой передавались через Azure Service Bus. По словам программиста, занимавшегося поддержкой, фоновые задачи могли выполняться до получаса.

Изначально архитектура Web Portal в нашем проекте выглядела такИзначально архитектура Web Portal в нашем проекте выглядела так

Azure WebJobs тоже предстояло чем-то заменить. Архитектура с очередью заданий для длительных вычислений не единственное среди возможных решений можно использовать специализированные библиотеки для фоновых задач, например, Hangfire, или обратиться к IHostedService от Microsoft.

Второе приложение назовем его Web API представляло собой ASP.NET WEB API. Оно использовало только MS SQL базы данных. Вернее, в конфигурационном файле были ссылки на несколько баз данных, в реальности же приложение обращалось только к одной их них. Но об этом нюансе мне только предстояло узнать.

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

Итак, нужно было перевести ASP.NET MVC приложения на ASP.NET Core 3.1, перевести WebJob c .NET Framework на .NET Core, чтобы можно было разворачивать их под Linux. Использовать Windows на GCP возможно, но не целесообразно. Надо было избавиться от сервисов, специфичных для Azure, заменить чем-то Azure WebJob, решить, как будем развертывать приложения в GCP, т. е. выбрать альтернативу Azure App Services. Требовалось добавить поддержку Docker. При этом неплохо было бы внести хоть какую-то архитектуру и поправить качество кода.

Общие принципы и соображения

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

В конце каждого этапа приложение должно находиться в стабильном состоянии, т. е. пройти хотя бы Smoke tests.

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

Шаги и этапы должны быть максимально короткими: работу нужно раздробить настолько, насколько это возможно. Порой нам все равно приходилось делать шаги, когда приложение не компилировалось в течение одного-двух дней. Случалось, что в конце шага компилировался только тот проект решения, который был недавно изменен. Если этап или шаг можно разбить по проектам, начинать работу надо с того проекта, который не зависит от других, и далее переходить к тем, которые зависят только от него, и т. д. План, который составили мы, представлен ниже.

При замене сервисов Azure можно либо подобрать альтернативный GCP-сервис, либо выбрать cloud-agnostic-решение. Выбор сервисов в этом проекте и его обоснование в каждом случае мы рассмотрим отдельно.

План работ

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

  1. Web Portal c ASP.NET MVC на ASP.NET Core

    1.1. Анализ кода и зависимостей Web Portal от сервисов Azure и сторонних библиотек, оценка необходимого времени.

    1.2. Перевод Web Portal на .NET Core.

    1.3. Рефакторинг с целью устранения основных проблем.

    1.4. Merge изменений Web Portal из основной ветки репозитория, сделанных параллельно другими разработчиками.

    1.5. Докеризация Web Portal.

    1.6. Тестирование Web Portal, устранение ошибок и развертывание новой версии на Azure.

  2. Web API c ASP.NET MVC на ASP.NET Core

    2.1. Написание E2E автоматических тестов для Web API.

    2.2. Анализ кода и зависимостей Web API от сервисов Azure и сторонних библиотек, оценка необходимого времени.

    2.3. Удаление неиспользуемого исходного кода из Web API.

    2.4. Перевод Web API на .NET Core.

    2.5. Рефакторинг Web API с целью устранения основных проблем.

    2.6. Merge изменений Web API из основной ветки репозитория, сделанных параллельно другими разработчиками.

    2.7. Докеризация Web API.

    2.8. Тестирование Web API, устранение ошибок и развертывание новой версии на Azure.

  3. Устранение зависимостей от Azure

    3.1. Устранение зависимостей Web Portal от Azure.

  4. Развертывание в GCP

    4.1. Развертывание Web Portal в тестовой среде в GCP.

    4.2. Тестирование Web Portal и устранение возможных ошибок.

    4.3. Миграция базы данных для тестовой среды.

    4.4. Развертывание Web API в тестовой среде в GCP.

    4.5. Тестирование Web API и устранение возможных ошибок.

    4.6. Миграция базы данных для prod-среды.

    4.7. Развертывание Web Portal и Web API в prod GCP.

Целиком план представлен лишь в ознакомительных целях, далее в статье я постараюсь подробно раскрыть лишь наиболее интересные, с моей точки зрения, вопросы.

.NET Framework -> .NET Core

Перед началом переноса кода я нашел статью о миграции .Net Framework на .Net Core от Microsoft и далее ссылку на миграцию ASP.NET на ASP.NET Core.

С миграцией не-Web-проектов все обстояло относительно просто:

  • преобразование формата хранения NuGet-пакетов с помощью Visual Studio 2019;

  • адаптирование списка этих пакетов и их версий;

  • переход с App.config в XML на settings.json и замена всех имеющихся обращений к конфигурационным значениям на новый синтаксис.

Некоторые версии NuGet-пакетов Azure SDK претерпели изменения, повлекшие несовместимость. В большинстве случаев удалось найти не всегда самую новую, зато поддерживаемую кодом .NET Core версию, которая не требовала бы изменений в логике старого программного кода. Исключением стали пакеты для работы с Azure Service Bus и WebJobs SDK. Пришлось с Azure Service Bus перейти на бинарную сериализацию, а WebJob перевести на новую, обратно несовместимую версию SDK.

C миграцией ASP.NET MVC на ASP.NET Core дело обстояло намного сложнее. Все перечисленные выше действия нужно было проделать и для Web-проектов. Но начинать пришлось с нового ASP.NET Core проекта, куда мы перенесли код старого проекта. Структура ASP.NET Core проекта сильно отличается от предшественника, многие стандартные классы ASP.NET MVC претерпели изменения. Ниже я привожу список того, что изменили мы, и большая его часть будет актуальна для любого перехода с ASP.NET MVC на ASP.NET Core.

  1. Создание нового проекта ASP.NET Core и перенос в него основного кода из старого ASP.NET MVC проекта.

  2. Корректировка зависимостей проекта от внешних библиотек (в нашем случае это были только NuGet-пакеты, соображения по поводу версий библиотек см. выше).

  3. Замена Web.config на appsettings.json и все связанные с этим изменения в коде.

  4. Внедрение стандартного механизма Dependency injection от .NET Core вместо любой его альтернативы, использовавшейся в Asp.NET MVC проекте.

  5. Использование StaticFiles middleware для всех корневых папок статических файлов: изображений, шрифтов, JavaScript-скриптов, CSS-стилей и т. д.

app.UseStaticFiles(); // wwwrootapp.UseStaticFiles(new StaticFileOptions   {     FileProvider = new PhysicalFileProvider(         Path.Combine(Directory.GetCurrentDirectory(), "Scripts")),     RequestPath = "/Scripts"});

Можно перенести все статические файлы в wwwroot.

6. Переход к использованию bundleconfig.json для всех JavaScript и CSS-бандлов вместо старых механизмов. Изменение синтаксиса подключения JavaScript и CSS:

<link rel="stylesheet" href="~/bundles/Content.css" asp-append-version="true" /><script src="~/bundles/modernizr.js" asp-append-version="true"></script>

Чтобы директива asp-append-version="true" работала корректно, бандлы (bundles) должны находиться в корне, т. е. в папке wwwroot (смотри здесь).

Для отладки бандлов я использовал адаптированную версию хелпера отсюда.

7. Изменение механизма обработки UnhadledExceptions: в ASP.NET Core реализована его поддержка, остается с ней разобраться и использовать вместо того, что применялось в проекте раньше.

8. Логирование: я адаптировал старые механизмы логирования для использования стандартных в ASP.NET Core и внедрил Serilog. Последнее опционально, но, по-моему, сделать это стоит для получения гибкого structured logging c огромным количеством вариантов хранения логов.

9. Session если в старом проекте использовалась сессия, то код обращения к ней надо будем немного адаптировать и написать хелпер для сохранения любого объекта, поскольку изначально поддерживается только строка.

10. Routing: в старом проекте использовался механизм, основанный на templates, его надо было чуть-чуть подправить.

11. JSON-сериализация: В ASP.NET Core по умолчанию используется библиотека System.Text.Json вместо Newtonsoft.Json. Microsoft утверждает, что она работает быстрее предшественницы, однако, в отличие от последней, она не поддерживает многое из того, что Newtonsoft.Json умела делать из коробки безо всякого участия программиста. Хорошо, что есть возможность переключиться обратно на Newtonsoft.Json. Именно это я и сделал, когда выяснил, что большая часть сериализации в Web API была сломана, и вернуть ее в рабочее состояние с помощью новой библиотеки, если и возможно, очень непросто. Подробнее об использовании Newtonsoft.Json можно прочитать здесь.

12. В старом проекте использовался Typescript 2.3. С его подключением пришлось повозиться, потребовалось установить Node.js, подобрать правильную версию пакета Microsoft.TypeScript.MSBuild, добавить и настроить tsconfig.json, поправить файл определений (Definitions) для библиотеки Knockout, кое-где добавить директивы //@ts-ignore.

13. Код для принудительной поддержки HTTPS включается автоматически при включении этой опции в визарде проекта. Старый код, использующий пользовательский атрибут HttpsOnly, был при этом убран.

14. Все низкоуровневые действия, такие как получение параметров из body запроса, URL запроса, HTTP Headers и HttpContext потребовали изменений, т. к. API для доступа к ним претерпел изменения по сравнению с ASP.NET MVC. Работы было бы заметно меньше, если бы в старом проекте чаще использовались стандартные binding механизмы через параметры экшенов (Actions) и контроллеров (Controllers).

15. Был добавлен Swagger c помощью библиотеки Swashbuckle.AspNetCore.Swagger.

16. Нестандартный механизм Authentication потребовал рефакторинга для приведения его к стандартному виду.

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

Что делать со специфичными сервисами Azure?

После перехода на ASP.NET Core предстояло избавиться от Azure-сервисов. Можно было либо подобрать решения, которые не зависят от облачной платформы, либо найти что-то подходящее из списка GCP. Благо у многих сервисов есть прямые альтернативы у других облачных провайдеров.

Azure Service Bus мы по настоятельной рекомендации заказчика решили заменить на Redis Pub/Sub. Это достаточно простой инструмент, не настолько мощный и гибкий как, например, RabbitMQ. Но для нашего простого сценария его хватало, а в пользу такого выбора говорило то, что Redis в проекте уже использовался. Время подтвердило решение было правильным. Логика работы с очередью была абстрагирована и выделена в два класса, один из которых реализует отправку произвольного объекта, другой получает сообщения и передает их на обработку. На выделение этих объектов ушло всего несколько часов, а если сам Redis Pub/Sub вдруг потребуется заменить, то и это будет очень просто.

Azure Blobs были заменены на GCP Blobs. Решение очевидное, но все-таки различие в функциональности сервисов нашлось: GCP Blobs не поддерживает добавление данных в конец существующего блоба. В нашем проекте такой блоб использовался для создания подобия логов в формате CSV. На платформе Google мы решили записывать эту информацию в Google Cloud operations suite, ранее известный как Stackdriver.

Хранилище Azure Table Storage использовалось для записи логов приложения и доступа к ним из Web Portal. Для этого существовал логгер, написанный самостоятельно. Мы решили привести этот процесс в соответствие с практиками от Microsoft, т. е. использовать их интерфейс ILogger. Кроме того, была внедрена библиотека для структурного логирования Serilog. В GCP логирование настроили в Stackdriver.

Какое-то время проект должен был параллельно работать и на GCP, и на Azure. Поэтому вся функциональность, зависящая от платформы, была выделена в отдельные классы, реализующие общие интерфейсы: IBlobService, IRequestLogger, ILogReader. Абстрагирование логирования было достигнуто автоматически за счет использования библиотеки Serilog. Но для того, чтобы показывать логи в Web Portal, как это делалось в старом приложении, понадобилось адаптировать порядок записей в Azure Table Storage, реализуя свой Serilog.Sinks.AzureTableStorage.KeyGenerator.IKeyGenerator. В GCP для чтения логов изGoogle Cloud operations были созданы Log Router Sinks, передающие данные в BigQuery, откуда приложение и получало их.

Что делать с Azure WebJobs?

Сервис Azure WebJobs доступен только для Azure App Services on Windows. По сути он представляет собой консольное приложение, использующее специальный Azure WebJobs SDK. Зависимость от этого SDK я убрал. Приложение осталось постоянно работающим консольным и следует похожей логике:

static async Task Main(string[] args){.   var builder = new HostBuilder();  ...              var host = builder.Build();  using (host)  {     await host.RunAsync();  }...}

За всю работу отвечает зарегистрированный с помощью Dependency Injection класс

public class RedisPubSubMessageProcessor : Microsoft.Extensions.Hosting.IHostedService{...public async Task StartAsync(CancellationToken cancellationToken)...public async Task StopAsync(CancellationToken cancellationToken)...}

Это стандартный для .NET Core механизм. Несмотря на отсутствие зависимости от Azure WebJob SDK, это консольное приложение успешно работает как Azure WebJob. Оно также без проблем работает в Linux Docker-контейнере под управлением Kubernetes, о чем речь в статье пойдет позже.

Рефакторинг по дороге

Архитектура и код приложения были далеки от идеала. В ходе многих шагов постепенно производились небольшие изменения кода, который они затрагивали. Были и специально запланированные этапы рефакторинга, согласованные и оцененные вместе с заказчиком. На этих этапах мы устраняли проблемы с аутентификацией и авторизацией, переводили их на практики от Microsoft. Был отдельный этап по внесению некой архитектуры, выделению слоев, устранению ненужных зависимостей. Работа с Web API началась с этапа удаления неиспользуемого кода. При замене многих Azure-сервисов на первом этапе производилось определение интерфейсов, выделение данных зависимостей в отдельные классы.

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

Docker

С поддержкой Docker все сложилось довольно гладко. Dockerfile можно легко добавить с помощью Visual Studio. Я добавил их для всех проектов, соответствующих приложениям, для Web Portal, Web API, WebJob (который в дальнейшем превратился просто в консольное приложение). Эти стандартные Dockerfile от Microsoft не претерпели особенных изменений и заработали из коробки за единственным исключением пришлось в Dockerfile для Web Portal добавить команды для установки Node.js. Этого требует build контейнер для работы с TypeScript.

RUN apt-get update && \apt-get -y install curl gnupg && \curl -sL https://deb.nodesource.com/setup_12.x  | bash - && \apt-get -y install nodejs

Azure App Services -> GKE

Нет единственно правильного решения для развертывания .NET Core-приложений в GCP, вы всегда можете выбрать из нескольких опций:

  • App Engine Flex.

  • Kubernetes Engine.

  • Compute Engine.

В нашем случае я остановился на Google Kubernetes Engine (GKE). Причем к этому моменту у нас уже были контейнеризованные приложения (Linux). GKE, оказалось, пожалуй, наиболее гибким из трех представленных выше решений. Оно позволяет разделять ресурсы кластера между несколькими приложениями, как в нашем случае. В принципе для выбора одного из трех вариантов можно воспользоваться блок-схемой по этой сслыке.

Выше описаны все решения по используемым сервисам GCP, кроме MS SQL Server, который мы заменили на Cloud SQL от Google.

Архитектура нашей системы после миграции в GCPАрхитектура нашей системы после миграции в GCP

Тестирование

Web Portal тестировался вручную, после каждого этапа я сам проводил простенький Smoke-тест. Это было обусловлено наличием пользовательского интерфейса. Если по завершении очередного этапа, новый кусок кода выпускался в Prod, к его тестированию подключались другие пользователи, в частности, Product Owner. Но выделенных QA-специалистов, в проекте, к сожалению, не было. Разумеется, все выявленные ошибки исправлялись до начала очередного этапа. Позднее был добавлен простой Puppeteer-тест, который исполнял сценарий загрузки одного из двух типов отчетов с какими-то параметрами и сравнивал полученный отчет с эталонным. Тест был интегрирован в CICD. Добавить какие-то юнит-тесты было проблематично по причине отсутствия какой-либо архитектуры.

Первым этапом миграции Web API, наоборот, было написание тестов. Для это использовался Postman, затем эти тесты вызывались в CICD с помощью Newman. Еще раньше к старому коду была добавлена интеграция со Swagger, который помог сформировать начальный список адресов методов и попробовать многие из них. Одним из следующих шагов было определение актуального перечня операций. Для этого использовались логи IIS (Internet Information Services), которые были доступны за полтора месяца. Для многих актуальных методов перечня было создано несколько тестов с разными параметрами. Тесты, приводящие к изменению данных в базе, были выделены в отдельную Postman-коллекцию и не запускались на общих средах выполнения. Разумеется, все это было параметризовано, чтобы можно было запускать и на Staging, и на Prod, и на Dev.

Тестирование позволило нам убедиться, что продукт после миграции сохранил стабильность. Конечно, идеальным вариантов было бы покрыть всю функциональность автоматизированными тестами. Поэтому в случае с Web API несмотря на гораздо большие усилия, затраченные в самом начале, миграция, поиск и исправление ошибок затем проходили гораздо легче.

Azure MS SQL -> GCP Managed MS SQL

Миграция MS SQL из Managed Azure в GCP Cloud SQL оказалась не такой простой задачей, как представлялось вначале. Основных причин тому оказался несколько:

  • Очень большой размер базы данных (Azure портал показал: Database data storage /

    Used space 181GB).

  • Наличие зависимостей от внешних таблиц.

  • Отсутствие общего формата для экспорта из Azure и импорта в GCP Cloud SQL.

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

Перед началом миграции нужно удалить все ссылки на внешние таблицы и базы данных, иначе миграция будет неудачной. Azure SQL поддерживает экспорт только в формат bacpac, более компактный по сравнению со стандартным backup форматом. В нашем случае вышло 6 Гб в bacpac против 154 Гб в backup. Но GCP Cloud позволят импортировать только backup, поэтому нам потребовалась конвертация, сделать которую удалось лишь посредством восстановления в локальную MS SQL из bacpac и создания backup уже из нее. Для этих операций потребовалось установить последнюю версию Microsoft SQL Server Management Studio, причем локальный сервер MS SQL Server был версией ниже. Немало операций заняли по многу часов, некоторые и вовсе длились по несколько дней. Рекомендую увеличить квоту Azure SQL перед импортом и сделать копию prod базы, чтобы импортировать из нее. Где-то нам потребовалось передавать файл между облаками, чтобы ускорить загрузку на локальную машину. Мы также добавили SSD-диск на 1 Тб специально под файлы базы данных.

Задачи на будущее

При переходе с Azure App Services на GCP Kubernetes мы потеряли CICD, Feature Branch deployments, Blue/Green deployment. На Kubernetes все это несколько сложнее и требует иной реализации, но наверняка делается посредством все тех же Github Actions. В новом облаке следуем концепции Iac (Infrastructure-as-Code) вместе с Pulumi.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru