» » Структура и модель выполнения .NET Core приложений

 

Структура и модель выполнения .NET Core приложений

Автор: admin от 1-12-2017, 22:45, посмотрело: 459

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



Текст объемный и рассчитан на:




  • начинающих разработчиков, которые только знакомятся с платформой .NET Core;

  • опытных разработчиков, выполняющих роль DevOps-инженеров в produсtion-окружении.



В статье не упоминается процесс создания приложений при помощи SDK (dotnet CLI), однако эта информация будет полезной для понимания, как работает SDK, а именно её основной компонент (ядро) — «драйвер» dotnet.dll, поскольку эта библиотека является управляемой сборкой и выполняется на .NET Core.



Примеры процессов выполнения описаны для ОС Windows, но работают по тому же принципу и на других ОС (с учетом различных расширений исполняемых файлов и нативных библиотек).

два типа развертывания .NET Core-приложений:




  • Portable (Framework-dependent deployment — FDD)

  • Standalone (Self-contained deployment — SCD)



Portable (FDD)-приложение похоже на традиционное .NET Framework-приложение. В данном случае определенная версия .NET Core-фреймворка (также используются термины shared framework, .NET Core Runtime, redist) должна находиться на целевом компьютере, и при запуске хост процесс загрузит Core CLR, Core FX из папки фреймворка.



В Standalone (SCD)-приложении все компоненты для выполнения (CoreCLR, CoreFX), а также сторонние библиотеки, то есть абсолютно все зависимости, поставляются вместе с самим приложением (чаще всего в одной папке).



Важно понимать, что Standalone-приложение привязано к определенной ОС и архитектуре (например, Windows 7 x64 или OSX 10.12 x64). Такой идентификатор называется Runtime identifier (RID). Для каждой ОС/архитектуры существует своя версия библиотеки Core CLR (и прочих нативных компонентов), поэтому для Standalone-приложений на этапе компиляции в свойстве RuntimeIdentifier нужно указывать параметры целевой системы (RID).



Такое приложение будет работать на любом компьютере с определенной ОС/архитектурой, независимо от того, установлен ли .NET Core или нет.



2. .NET Core Runtimes (shared frameworks)



Для выполнения Portable-приложений на целевой машине должен быть установлен хотя бы один .NET Core Runtime (shared framework).



.NET Core Runtime устанавливается в папку C:Program Filesdotnet:



Структура и модель выполнения .NET Core приложений


Файлы фреймворка(-ов) хранятся в папке C:Program Filesdotnetshared.



Основные составляющие .NET Core Runtime:




  • «Утилита» dotnet.exe для запуска .NET Core-приложения. Она называется мультплексор (muxer), и является основным драйвером инфраструктуры .NET Core. Эта программа служит «точкой входа» для запуска любых приложений и выполнения команд разработки. если установлена .NET Core SDK, то есть является хост-процессом любого приложения — corehost.

  • Runtime-компоненты (CoreCLR, CoreFX, и т.д.), устанавливаются в отдельную папку фреймворка C:Program Filesdotnetshared[Framework name][Framework version].

  • Host framework resolver — нативная библиотека, находится в папке

    C:Program Filesdotnethost[версия]hostfxr.dll. При запуске приложения, максимальная версия этой библиотеки выполняет разрешение версии фреймворка для последующего выполнения приложения.



Структура файлов при установке .NET Core Runtime.



Можно установить несколько версий фреймворка:



Структура и модель выполнения .NET Core приложений


Для выполнения Portable-приложения необходимо запустить хост-процесс dotnet.exe и передать ему в качестве аргумента путь к управляемой сборке.



«C:Program Filesdotnet» добавляется к значению переменной среды PATH, благодаря чему Portable-приложения теперь могут запускаться из командной строки:



> dotnet path/to/App.dll



В папке приложения (там, где находится [AppName].dll) должен лежать файл [AppName].runtimeconfig.json. В нём указаны имя и версия фреймворка, которые должны быть использованы для выполнения Portable-приложения. Например:



MyApp.runtimeconfig.json

{
  "runtimeOptions": {
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "2.0.0"
    }
  }
}


Этот файл является обязательным для Portable-приложений.



Имея вышеприведенную конфигурацию, компоненты среды выполнения будут загружены из папки C:Program FilesdotnetsharedMicrosoft.NETCore.App2.0.0.



3. Структура Portable (FDD) .NET Core-приложения



Любое Portable .NET Core-приложение состоит из таких обязательных файлов:




  • [AppName].dll — IL-код приложения, точка входа.

  • [App dependencies]*.dll — все зависимости приложения, не входящие в состав CoreFX (сборки проектов, сторонние библиотеки, FCL).

  • [AppName].runtimeconfig.json — конфигурация среды выполнения, здесь указаны имя и версия .NET Core-фреймворка (runtime-компонентов). Файл является чем-то вроде MyApp.exe.config в .NET Frameowork. Эту конфигурацию можно изменять, если необходимо явно указать конкретный фреймворк.

  • [AppName].deps.json — перечень всех зависимостей приложения. Не рекомендуется изменять этот файл, потому что он генерируется при компиляции. Файл не является обязательным, но если его удалить, хост-процесс при запуске не сможет проверить пути всех файлов зависимостей, и выполнение начнется на свой страх и риск.



Документация.



Артефакты одного и того же Portable-приложения для различных версий платформы .NET Core:



Структура и модель выполнения .NET Core приложений



Уменьшение количества файлов объясняется тем, что в Core FX 1.0 отсутствовали многие библиотеки, поэтому они шли в составе приложения, как обычные зависимости. В Core FX 2.0 эти сборки были добавлены, поэтому они больше не поставляются с приложением, а берутся из папки фреймворка.



4. Структура Standalone (SCD) .NET Core приложения



Такая же, как для Portable (FDD)-приложения, но дополнительно содержит все runtime-компоненты (CoreCLR, CoreFX) и собственный мультиплексор dotnet.exe, переименованный в [AppName].exe. Для .NET Core до версии 2.0 мультиплексор для запуска Standalone-приложения идентичен C:Program Filesdotnet.exe (тот же файл, только переименованный). Для .NET Core 2.0 используется мультиплексор из NuGet-пакета Microsoft.NETCore.DotNetAppHost. В пакете находится один файл apphost.exe, в который при компиляции «зашивается» в имя сборки (MyApp.dll), а сам файл переименовывается в MyApp.exe. При старте Standalone-приложения проверяется «привязка» исполняемого файла (MyApp.exe) к имени сборки, которую он может запустить (MyApp.dll).



Содержимое одного и того же Standalone-приложения для различных версий платформы .NET Core:



Структура и модель выполнения .NET Core приложений



Наблюдается картина, противоположная Portable-приложениям — чем больше становится Core FX, тем больше файлов поставляется с приложением.



Рекомендации по выбору типа развертывания




  • Всегда отдавайте предпочтение Portable-развертыванию, потому что этот тип гораздо меньше по объему и более стабилен при запуске крупных приложений с большим количеством зависимостей. Кроме того, Portable-приложения легче в настройке, ведь они не зависят от RID.

  • Выбирайте Standalone, если нет возможности установить .NET Core Runtime, или если критична продолжительность запуска приложения. В Standalone-версии можно выиграть 1-2 секунды при запуске за счет удаления файла конфигурации [AppName].deps.json (помните, что при этом на вас ложится ответственность за наличие всех файлов зависимостей).



5. Runtime Configuration Files



Файлы [AppName].runtimeconfig.json и [AppName].deps.json называют
Runtime Configuration Files (*.deps.json называют dependency manifest file). Они создаются в процессе компиляции и содержат всю информацию, необходимую для запуска dotnet.exe и выполнения приложения.



В [AppName].runtimeconfig.json задаются имя и версия .NET Core runtime (там же указывается, будет ли учтена patch-версия (SemVer) при поиске фреймворка), а также устанавливаются параметры работы Core CLR (режим работы сборщика мусора). Этот файл обязателен для Portable- и не обязателен для Standalone-приложения.



dotnet.exe ([AppName].exe) использует файл [AppName].deps.json для определения абсолютных путей всех зависимостей приложения при его запуске.



Структура [AppName].deps.json:




  • Секция targets



    Термином target называют целевую платформу (имя и версия), на котором должно выполняться данное приложение (напр., .NET Framework 4.6.2, .NET Core App 1.1, Xamarin.Mac 1.0, .NET Standard 1.6). Эта конфигурация аналогична NuGet target framework.



    Секция targets определяет платформу и дерево зависимостей для нее в формате

    [ID зависимости (пакета)]/[версия]: {

        dependencies: { список зависимостей (пакетов) данного пакета },

        относительные пути к управляемым и нативным файлам данного пакета

    }


    Для выполнения любого приложения, target должен обязательно содержать RID, например .NETCoreApp,Version=v1.1/win10-x64. Файл deps.json Standalone-приложения всегда один и содержит RID целевой платформы. Для Portable-приложения файлов deps.json два — один в папке фреймворка, второй в папке приложения. RID для Portable-приложений указан в файле [FrameworkName].deps.json в папке фреймворка. После того, как dotnet.exe определил фреймворк для выполнения приложения, он сперва загружает deps-файл этого фреймворка (например, C:Program FilesdotnetsharedMicrosoft.NETCore.App2.0.0Microsoft.NETCore.App.deps), а затем deps-файл приложения. Deps-файл приложения имеет более высокий приоритет.



    Рассмотрим подробнее содержимое файла deps.json Standalone-приложения:





    В свойстве dependencies перечислены зависимости (пакеты) конкретного пакета.

    Свойство runtimeTargets используется в deps-файле Portable-приложения и определяет пути файлов библиотек для конкретного RID. Такие RID-specific библиотеки поставляются вместе с Portable-приложением в папке runtimes.



    Свойства runtime и native содержат относительные пути управляемых (managed) и нативных библиотек соответственно. Свойство resources содержит относительные пути и локали локализованных сборок-ресурсов.



    Пути относительны к NuGet package cache, а не deps-файлу.



    Добавить сторонний deps-файл можно передав значение аргумента --additional-deps или переменную среды DOTNET_ADDITIONAL_DEPS.



    Такая возможность доступна только для Portable приложений.



    Значение аргумента может содержать полный путь к deps-файлу, а также путь к директории, где расположены общие deps-файлы. Внутри этой директории deps-файлы должны быть расположены в структуре shared[FX name][FX version]*.deps. Например, sharedMicrosoft.NETCore.App2.0.3MyAdditional.deps.json.



    Такой подход использует Visual Studio для неявного добавления в проект Application Insights через файл

    C:Program FilesdotnetadditionalDeps Microsoft.AspNetCore.ApplicationInsights.HostingStartup

    sharedMicrosoft.NETCore.App 2.0.3 Microsoft.AspNetCore.ApplicationInsights.HostingStartup.deps.json



    Когда dotnet.exe (MyApp.exe) определяет пути зависимостей приложения, для каждой отдельной библиотеки составляется список из runtime- и native-путей.



    Если в runtimeTargets для определенного RID есть библиотека, она добавляется к runtime- или native-списку исходя из указанного assetType.


  • Секция runtimeTarget

    содержит название и версию целевой платформы для выполнения. Секция targets в действительности содержит два элемента — для компиляции (без RID) и выполнения (обязательно с RID). Секция runtimeTarget используется для удобства и дублирует значение из секции targets, чтобы dotnet.exe не тратил время на обработку targets секции. Как уже говорилось, для Standalone-приложения RID целевой ОС содержится в deps-файле приложения, а для Portable — в deps-файле фреймворка.


  • Секция libraries

    определяет список всех зависимостей приложения (в формате ID пакета/версия:{metadata}) и содержит метаданные о каждой из них. В метаданных указываются:


    • тип зависимости (project, package, reference),

    • serviceable (только для типа package) — индикатор того, является ли пакет Serviceable (определяет, может ли сборка пакета быть пропатчена (заменена) внешними службами, Windows Update или .NET Core Servicing Index).

    • хэш пакета (для package зависимостей)

    • др. данные




6. Процесс запуска Portable .NET Core-приложения



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



6.1. Запуск приложения

выполняется при помощи мультплексора (muxer) из командной строки (одинаково на любой ОС).







dotnet.exe — переименованный corehost.exe, эта программа является хост-процессом любого .NET Core-приложения, с неё начинается процесс запуска.



6.2. [corehost] Поиск и загрузка Framework Resolver (hostfxr.dll)

На этом этапе dotnet.exe идет в папку [own directory]/host/fxr/. Для Portable-приложений эта библиотека расположена в общей папке C:Program Filesdotnethostfxr[FXR version]hostfxr.dll. Если версий будет несколько, dotnet.exe будет всегда использовать последнюю.



После загрузки hostfxr.dll (Framework Resolver) процесс запуска переходит в рамки этой библиотеки.



6.3. [hostfxr] Определение режима выполнения (standalone, muxer, split/FX)

Первая задача hostfxr — определить режим, в котором будет работать хост процесс и таким образом тип приложения — Portable (FDD) или Standalone (SCD). В Portable (FDD)-режиме он также определяет: это запускаемое приложение или команда SDK.



Определение типа выполнения (программа или команда SDK) происходит следующим образом:



— если среди аргументов есть такой, значение которого оканчивается на .dll или .exe — процесс запуска продолжится в режиме выполнение указанного файла. Если такого аргумента нет, управление будет передано SDK. Для этого из папки [own directory]sdk[version] (если такая существует) будет запущен dotnet.dll (как Portable приложение), и этой сборке будут переданы аргументы текущего хост процесса.


Также для Portable (FDD)-приложения hostfxr определяет фреймворк (.NET Core Runtime), откуда будут загружены компоненты для выполнения.



Алгоритм проверки очень простой — если в папке, откуда был запущен мультиплексор [AppName].exe (в нашем случае dotnet.exe), отсутствует coreclr.dll или [AppName].dll, то приложение Portable. Если один из этих двух файлов существует, то далее идет проверка — приложение Portable (split/FX) или Standalone. Если существует [AppName].dll, то приложение Standalone, иначе — Portable (split/FX).



Режим Split/FX используется для запуска xunit и означает, что приложение запускается происходит как Portable, с собственным hostfxr.dll. Этот режим не используется в версии .NET Core 2.0.


Запуск Portable-приложения может также осуществляться в так называемом Exec mode.

Для этого команда запуска первым аргументом должна содержать exec C:> dotnet exec ....



При запуске в таком режиме можно явно указать пути к файлам конфигурации:

--depsfile

--runtimeconfig


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



6.4. [hostfxr] Определение .NET Core Runtime

Первым делом hostfxr определяет и загружает файлы конфигурации deps и runtimeconfig. Если ничего не переопределено в аргументах, эти файлы берутся из папки приложения.



На текущем этапе hostfxr определяет (по данным файла конфигурации), является ли приложение Portable или Standalone.



После загрузки файлов конфигурации и определения режима hostfxr определяет папку фреймворка (.NET Core Runtime).



Для этого hostfxr сначала определит, какие версии установлены в папке shared, а затем выберет из этого списка релиз-версию, с учетом значений в [AppName].runtimeconfig.json.



При выборе версии учитывается параметр Roll Forward On No Candidate Fx, который указывает строгость соответствия заданной версии и имеющихся на машине.



6.5. [hostfxr] Поиск и загрузка hostpolicy.dll

На текущем этапе всё готово для определения путей runtime-компонентов. Этой задачей занимается библиотека hostpolicy.dll, которая называется Host library.



Процесс поиска hostpolicy.dll заключается в последовательных проверках различных локаций. Но сначала определяется версия hostpolicy из deps-файла фреймворка (напр. C:Program FilesdotnetsharedMicrosoft.NETCore.App2.0.0Microsoft.NETCore.App.deps). В этом файле будет найден пакет с именем Microsoft.NETCore.DotNetHostPolicy и взята его версия.



Затем ищется патч (замена) hostpolicy.dll (с учетом версии, если она была определена на предыдущем шаге, и RID) в папке .NET Core Servicing (для Windows — в папке C:Program Files[ (x86)]coreservicingpkgs). Если такой файл найден, он загружается для дальнейшего использования.



Если файл не был найден на предыдущем этапе, hostpolicy.dll будет найдено в папке фреймворка.



Как только опеределена hostpolicy.dll, hostfxr загружает эту библиотеку и передает ей управление.



6.6. [hostpolicy] Определение списка зависимостей

Библиотека hostpolicy.dll отвечает за определение абсолютных путей всех зависимостей приложения.



Прежде всего hostpolicy создаст компонент под названием Dependencies Resolver, который в свою очередь загрузит два deps-файла — файл фреймворка и файл приложения.



Сперва загружается список из deps-файл фреймворка, где будут определены такие зависимости, как CoreCLR и библиотеки CoreFX. Затем список из deps-файла приложения, в котором указаны сборки нашего приложения и их зависимости.



Для каждого deps-файла Dependency Resolver составляет список всех зависимостей для указанной runtimeTarget.



Для каждого пакета сначала составляется список файлов из всех секций runtimeTargets (RID specific зависимости), далее — список всех файлов из секций native и runtime. Такой объединенный список относительных путей всех зависимостей в условном формате

ID пакета — RID — тип asset'а (runtime, native) — пути к файлам называется Target assets.



После того, как были составлены эти два списка файлов зависимостей (RID и не RID), выполняется процесс под названием Reconciling libraries with targets (согласования). Он заключается в том, что для каждого пакета из секции libraries проверяется, существует ли RID specific-файлы, которые должны переопределить обычные.



6.7. [hostpolicy] Определение путей TPA, Core CLR и CLR Jit

Далее Dependency resolver составляет список абсолютных путей файлов управляемых сборок — зависимостей приложения. Этот список называется TPA (Trusted Platform Assemblies) и передается Core CLR для настройки AppDomain. Также составляется список абсолютных путей директорий, в которых находятся остальных файлы зависимостей (кроме coreclr, corejit).



Определение абсолютных путей управляемых сборок происходит путем поиска файлов в Probe paths (путей зондирования). По умолчанию их два — папка фреймворка и папка приложения, и они основаны на расположении deps-файлов. Также можно добавить дополнительные пути:



1) передав аргумент --additionalprobingpath, например

--additionalprobingpath %UserProfile%.nugetpackages



2) указав в файле [AppName].runtimeconfig.json (приоритет ниже, чем у аргумента), например



{
  "runtimeOptions": {
    "additionalProbingPaths": [
      "C:Usersusername.nugetpackages"
    ]
  }
}


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



Очерёдность поиска:




  • папка приложения;

  • папка фреймворка

  • Probe paths



Если deps-файл приложения отсутствует, то в TPA попадают все файлы с расширением .ni.dll, .dll, .ni.exe, .exe из папки приложения.



После составления списка TPA, определяются пути CoreCLR и CLRJit.



При отсутствии deps-файла приложения, dotnet.exe вначале попытается найти эти библиотеки в [app directory]lib. При обычном выполнении пути берутся из папки фреймворка (отбросив относительный путь и взяв только имя файла).



Устанавливаются следующие настройки CoreCLR:




  • TRUSTED_PLATFORM_ASSEMBLIES — список обсолютных путей всех управляемых библиотек приложения.

  • NATIVE_DLL_SEARCH_DIRECTORIES — абсолютные пути директорий, где найдены нативные зависимости.

  • PLATFORM_RESOURCE_ROOTS — абсолютные пути директорий, где найдены зависимости-ресурсы

  • AppDomainCompatSwitch — константа «UseLatestBehaviorWhenTFMNotSpecified».

  • APP_CONTEXT_BASE_DIRECTORY — папка приложения.

  • APP_CONTEXT_DEPS_FILES — абсолютные пути deps-файлов приложения и фреймворка.

  • FX_DEPS_FILE — абсолютный путь deps-файла фреймворка.

  • PROBING_DIRECTORIES — дополнительные пути зондирования (если они были указаны).



Далее управление переходит к coreclr.dll.



7. Процесс запуска Standalone (SCD) .NET Core приложения



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



7.1. Запуск приложения

выполняется с помощью запуска собственного мультиплексора MyApp.exe. В .NET Core = 2.0 вначале проверяется эта «привязка».



7.2. Процесс запуска

происходит так же, как у Portable-приложения, за исключением того, что существует только один deps-файл и все зависимости ищутся в папке приложения или по указанным --additionalprobepaths.



8. Подведем итоги




  • Компонентная модель .NET Core (Runtime, BCL) полностью состоит из NuGet-пакетов.

  • Существует два типа развертывания — FDD и SCD. По возможности рекомендуется использовать Framework Dependent-развертывание, чтобы избежать сложностей с платформозависимыми компонентами и не поставлять лишние зависимости.

  • Как мы могли убедится, есть достаточно много возможностей повлиять на процесс запуска на целевой машине, и при необходимости переопределить/пропатчить файлы зависимостей, а также добавить неявные (динамически запускаемые) зависимости.

  • Не рекомендуется без особых причин удалять или изменять файл Dependency manifest (*.deps.json) .

  • Используя --additional-deps и --additionalprobepaths мы можем размещать runtime-компоненты в нужной нам файловой структуре.

  • Используя Exec mode можно переопределить файлы конфигурации приложения.

  • Посмотреть Trace-лог процесса запуска можно, установив переменную среды COREHOST_TRACE=1



Источник: Хабрахабр

Теги: net core, net

Категория: Программирование

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *