Контейнеризация Angular 6 SPA Template ASP .NET Core 2.1 приложения

    UPDATE от 24.07.2019 смотрите в конце статьи.

    UPDATE от 01.11.2018 {
    Благодаря помощи в комментариях, ниже представлена более правильная с идеологической точки зрения версия docker файла.

    В стандартном шаблоне проекта Angular SPA Template содержится ошибка. Разработчики в версии net .core 2.1. удалили из образа microsoft/dotnet:2.1-sdk используемого для сборки nodejs, но в файле проекта остался код, его использующий. Подробнее здесь github.com/aspnet/Announcements/issues/298
    Необходимо отредактировать вручную файл проекта *.csproj, удалив следующий код
    Код для удаления из *.csproj
      <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
        <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
        <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
        <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
        <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
    
        <!-- Include the newly-built files in the publish output -->
        <ItemGroup>
          <DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
          <DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
          <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
            <RelativePath>%(DistFiles.Identity)</RelativePath>
            <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
          </ResolvedFileToPublish>
        </ItemGroup>
      </Target>
    


    Правильный dockerfile
    ARG NODE_IMAGE=node:8.12
    
    FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
    WORKDIR /app
    EXPOSE 80
    
    FROM microsoft/dotnet:2.1-sdk AS build
    WORKDIR /src
    COPY ["AngularWebApp/AngularWebApp.csproj", "AngularWebApp/"]
    RUN dotnet restore "AngularWebApp/AngularWebApp.csproj"
    COPY . .
    WORKDIR "/src/AngularWebApp"
    
    FROM ${NODE_IMAGE} as node-build
    WORKDIR /src
    COPY AngularWebApp/ClientApp .
    RUN npm install
    RUN npm run build -- --prod
    
    FROM build AS publish
    RUN dotnet publish "AngularWebApp.csproj" -c Release -o /app
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app .
    COPY --from=node-build /src/dist ./ClientApp/dist
    ENTRYPOINT ["dotnet", "AngularWebApp.dll"]


    }

    К сожалению готового решения нигде не нашел. Пришлось компилировать из нескольких источников информацию. Чтобы запустить в докере Angular 6/7 приложение в виде проекта на ASP .NET Core.

    Если мы включим стандартными средствами поддержку докер для проекта с Angular приложением, то докер файл создаст образ приложения на базе microsoft/dotnet:2.1-aspnetcore-runtime образа. В этот базовый образ не включен сервер node.js. И в результаты выполнения будет выходить ошибка ASP .NET Core


    An unhandled exception occurred while processing the request.
    The NPM script ‘start’ exited without indicating that the Angular CLI was listening for requests

    image

    Чтобы разрешить эту ошибку, необходимо обновить Docker файл, добавив установку node.js и angular.
    Кроме того, поскольку разработка идет под Windows, а запускается это все в контейнере Docker на базе Ubunty, в случае если используются scss или sass файлы стилей, они компилируются Angular CLI с помощью node-sass, и при запуске выдавало ошибку “Node Sass does not yet support your current environment: Windows 64-bit with Unsupported runtime”. Необходимо выполнить команду:

    npm rebuild node-sass

    npm скачает необходимые скрипты для текущей платформы и перестроит css файлы.

    Итоговый Docker файл для Angular приложения на базе проекта ASP .NET Core 2.1 выглядит следующим образом

    FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
    # Setup NodeJs
    RUN apt-get update && \
        apt-get install -y wget && \
        apt-get install -y gnupg2 && \
        wget -qO- https://deb.nodesource.com/setup_8.x | bash - && \
        apt-get install -y build-essential nodejs
    RUN npm install @angular/cli -g
    # End setup
    
    WORKDIR /app
    EXPOSE 80
    
    FROM microsoft/dotnet:2.1-sdk AS build
    WORKDIR /src
    COPY ["AngularWebApp/AngularWebApp.csproj", "AngularWebApp/"]
    RUN dotnet restore "AngularWebApp/AngularWebApp.csproj"
    COPY . .
    WORKDIR "/src/AngularWebApp"
    RUN dotnet build "AngularWebApp.csproj" -c Release -o /app
    
    FROM build AS publish
    RUN dotnet publish "AngularWebApp.csproj" -c Release -o /app
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app .
    WORKDIR /app/ClientApp
    RUN npm install
    RUN npm rebuild node-sass
    WORKDIR /app
    ENTRYPOINT ["dotnet", "AngularWebApp.dll"]

    Я использовал стабильный LTS релиз Node.js, на данный момент 8.x, но можно изменить на любой более актуальный.

    Кроме того, еще замечу, что Windows прощает ошибки связанные с различным регистром символов, но вот Linux этого не прощает. И проект который работал при разработке, откажется компилироваться в контейнере, выдавая ошибку

    docker ERROR in error TS1149: File name 'filename.ts' differs from already included file name 'FileNames.ts' only in casing

    Для того чтобы избежать таких ошибок еще на этапе компиляции, рекомендую добавить соответствующую опцию “forceConsistentCasingInFileNames”: true в файл tsconfig.json в секцию compilerOptions

    {
      "compilerOptions": {
        "forceConsistentCasingInFileNames": true
        }
      }
    }
    


    UPDATE от 24.07.2019 {
    Опять же, благодаря помощи в комментариях, ниже представлена оптимизированная версия docker файла.
    Оптимизированный dockerfile
    ARG NODE_IMAGE=node:10.16.0
    
    FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
    WORKDIR /src
    COPY ["AngularWebApp/AngularWebApp.csproj", "AngularWebApp/"]
    RUN dotnet restore "AngularWebApp/AngularWebApp.csproj"
    COPY . .
    WORKDIR "/src/AngularWebApp"
    RUN dotnet build "AngularWebApp.csproj" -c Release -o /app
    RUN dotnet publish "AngularWebApp.csproj" -c Release -o /app
    
    FROM ${NODE_IMAGE} as node-build
    WORKDIR /src
    COPY AngularWebApp/ClientApp .
    RUN npm install
    RUN npm rebuild node-sass
    RUN npm run build -- --prod
    
    FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base
    WORKDIR /app
    EXPOSE 80
    COPY --from=build /app .
    COPY --from=node-build /src/dist ./ClientApp/dist
    ENTRYPOINT ["dotnet", "AngularWebApp.dll"]
    


    }

    angular, angular6/7, asp .net core 2.1, docker
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 26

      +1
      pprometey, не могли бы вы подробнее рассказать о цели данного контейнера?
      Вы делаете контейнер для сборки приложения? Либо же для его запуска?
      Если первое, не совсем понятно, зачем вообще нужен контейнер — собрал один раз, сделал пакет (или несколько) и вперед, деплой его налево и направо.
      Во втором же случае, у меня такое чувство, что приложение не должно каждый раз собираться заново при развертывании нового контейнера.
      Возможно, я не совсем точно понимаю саму суть контейнеризации?
        0
        Я лично его использую для CI\CD на базе Gitlab, т.е. автоматического деплоя.
        В целом контейнеры очень удобно как для разработки, так и для дальнейшего развертывания. Особенно когда у тебя крупное приложение состоящее из множества проектов (микросервисная архитектура), использующее различные технологии, и весь этот зоопарк надо заставить слаженно работать. Об этом много написано. Надо немного перестроить мозги, чтобы научится работать с контейнерами. Но как «вкуришь», дальнейшей жизни без них уже просто не представляешь. Это чертовски удобно.
          0
          «приложение не должно каждый раз собираться заново при развертывании нового контейнера.»
          Оно и не собирается каждый раз. Оно собирается один раз при сборке ОБРАЗА. Который уже множество раз разворачивается как КОНТЕЙНЕР одной командой docker run
            0
            Можно без проблем настроить процесс так, когда не надо пересоздавать образ каждый раз и видеть изменения интерпретируемого кода «на лету». Плюс Visual Studio довольно хорошо умеет работать с контейнерами, отслеживает изменения чтобы не пересоздавать образ с нуля, и позволяет производить отладку в контейнерах тоже.
            0
            Хорошая статья!
            Два вопроса
            1) Как работать с HTTPS redirection в docker?
            2) Стоит ли использовать образ alpine для сборки приложения?
              0
              Спасибо за положительный отзыв.

              1. Подключить папку с сертификатом в контейнеру как volumes. и без проблем. Все прекрасно работает.
              Но я использую Traefik — превосходный обратный прокси-сервер и балансировщик для облачной инфраструктуры. Сами контейнеры работают на 80 порту, без https, а вот traefik автоматически получает и продлевает Letsencrypt сертификаты и расшаривает эти контейнеры для мира уже по https.

              2. Я стараюсь именно alpine образ и использовать везде, где это только можно, как наиболее легковесный.
              +2
              В итоговом образе конечно не должно быть ноды. Ангуляр приложение — это скомпилированная статика, которая раздается веб сервером и делает вызовы к АПИ, для какой цели нам там нода?

              Надо взять контейнер с нодой, сбилдить в нем ангуляр приложение (АСП для этого не нужен), потом полученную статику переложить в контейнер с веб сервером, который ее будет раздавать — это вообще может быть отдельный контейнер от бекенда. Плюс делаем конейнер с беком (АСП приложением). Можно и объеденить АСП + статику в один контейнер, но тогда деплоиться будут всегди фронт и бек, а это не всегда удобно.
              Но золотое правило такое — в итоговых контейнерах не должно быть ничего, кроме минимума необходимого для его работы (например, никаких тулзов для сборки).
                0
                Если я правильно понял, то в статье используется multistage-сборка, и там как раз только статика+api.
                  0
                  Мультистейдж да, в показанном докер файле 4 стеджа. Во 2 и 3 ноды нет, а в 1 и 4 есть.
                  0
                  Бакенд отдельно работает. В отдельном проекте, в отдельном контейнере. Ангулар это статика, но тут речь идет о шаблоне проекта для Visual Studio и когда запускается в среде Development на ветке develop нужен node, чтобы развернуть сервер разработки Angular
                  			
                  app.UseSpa(spa =>
                  {
                  	spa.Options.SourcePath = "ClientApp";
                  
                  	if (env.IsDevelopment())
                  	{
                  		spa.UseAngularCliServer(npmScript: "start");
                  		spa.Options.StartupTimeout = TimeSpan.FromSeconds(60);
                  	}
                  });
                  
                    +1
                    Тогда нужно больше деталей. Для чего нужен приведенный в примере докер файл — для деплоя на тетсинг? Зачем dotnet publish делается в отдельном стейдже? Зачем в контейнере запускать if (env.IsDevelopment()) нельзя ли обойтись без него (запускать тестинг или стейджинг)? Какого размера получется dev контейнер? Нужно ли отдельно запускать RUN npm install angular/cli -g ведь для билда вроде должно быть достаточно RUN npm install, который запускается позже.
                      0
                      Да, все нужно что есть в Dockerfile, без этого в работать не будет при заданных исходных условиях. Единственное, я «подстраховался» с npm install перед rebuild node-sass.
                      Зачем в контейнере запускать if (env.IsDevelopment())

                      Я использую Git-flow, это ветка develop, автоматически разворачивающаяся на dev сервер. Ветка master полуавтоматически развертывается в продакшн, там все так как говорите, ничего лишнего, без env.IsDevelopment().
                        0

                        Вот только то, что у вас прописано в env.IsDevelopment, не предназначено для сервера, даже для dev-сервера!


                        Эта штука предназначена исключительно для горячей сборки измененных модулей. Откуда эти самые измененные модули появятся у вас в контейнере?

                          0
                          Предлагаешь всем разработчикам перед каждым пушем в ветку дев, закоментировать эти строки, а после пуша, раскоментировать и работать дальше?

                          Или есть другие способы решения данной проблемы?
                            +1
                            Конечно же другие способы есть! Использовать другое значение ASPNETCORE_ENVIRONMENT на дев-сервере.
                              0
                              Тогда много отладочной информации, например страницы ошибок и т.д будут недоступны на дев сервере, и тестеры не будут получать полной информации об ошибках и по сути не будут работать все ветки кода, с if (env.IsDevelopment()).
                                0
                                Мне тоже кажется вам стоит как-то разделить — Local от Development, и не надо будет ангуляр-нод сервер в контйенер пихать. Например выкатывать можно staging, а локально использовать Development. Я почему-то не могу спать спокойно, когда знаю что в контейнере есть что-то лишнее :)
                                  0
                                  Да, согласен. Лишнее в контейнере не есть хорошо. Можно решить эту проблему реорганизовав процесс. Я просто не стал дальше заморачиваться. Такой компромисс меня устроил.
                                    +1
                                    Да, вы правы. Я переработал подход и на самом деле в том варианте Docker файл который сейчас — вообще не должен собираться (хотя собирался каким-то чудом и не только у меня одного).

                                    Доработал. Теперь контейнер чистый как детская слеза.

                                    ARG NODE_IMAGE=node:8.12
                                    
                                    FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
                                    WORKDIR /app
                                    EXPOSE 80
                                    
                                    FROM microsoft/dotnet:2.1-sdk AS build
                                    WORKDIR /src
                                    COPY ["AngularWebApp/AngularWebApp.csproj", "AngularWebApp/"]
                                    RUN dotnet restore "AngularWebApp/AngularWebApp.csproj"
                                    COPY . .
                                    WORKDIR "/src/AngularWebApp"
                                    
                                    FROM ${NODE_IMAGE} as node-build
                                    WORKDIR /src
                                    COPY AngularWebApp/ClientApp .
                                    RUN npm install
                                    RUN npm run build -- --prod
                                    
                                    FROM build AS publish
                                    RUN dotnet publish "AngularWebApp.csproj" -c Release -o /app
                                    
                                    FROM base AS final
                                    WORKDIR /app
                                    COPY --from=publish /app .
                                    COPY --from=node-build /src/dist ./ClientApp/dist
                                    ENTRYPOINT ["dotnet", "AngularWebApp.dll"]
                                    
                                      0
                                      1. Для чего строка «FROM build AS publish»? Можно же сделать publish прямо в фазе использования образа build, я правильно понимаю?
                                      2. Для чего строка «FROM base AS final»? Можно же в самом конце взять образ runtime, сделать EXPOSE, копирование файлов и установку entrypoint, так?
                                        0
                                        Я использовал стандартный Docker файл который по умолчанию генерирует Visual Studio. В целом это вопросы к MS. Ну а дальше по старому шаманскому обычаю — работает — не трожь) Единственное — модифицировал его, добавив сборку Angular приложения образом node.
                                          0
                                          Попробую, спасибо. Посмотрим.
                                            +1
                                            Попробовал. Да действительно. Вы правы. Обновил статью, оптимизировал файл.
                                            Возможно MS генерирует докер-файл с излишней избыточностью для наглядности процесса мультистейдж сборки докер файла. Это мое предположение.
                                        0
                                        Так напишите условия таким образом, чтобы они были доступны и работали! Не понимаю, в чем проблема-то?
                                          0
                                          Спасибо большое. В том числе и благодаря вашим замечаниям я изменил подход и теперь получил в итоге более правильный контейнер, плюс включил поддержку переменных среды. Выше в комментариях я включил обновленный код dockerfile.
                                          ARG BUILD_ARGS
                                          RUN npm run build${BUILD_ARGS}
                                          


                                          Правда пришлось обновить код файла проекта *.csproj, так как шаблон проекта содержал команды на стадии publish, которые используют nodejs, а в версии 2.1 .net core, nodejs был исключен из образа microsoft/dotnet:2.1-sdk используемого для сборки. Подробнее можно прочитать здесь: github.com/aspnet/Announcements/issues/298
                                          Вот только nodejs из образа убрали, а из шаблона проекта забыли.
                                            0
                                            22.07.2019 Проблема актуальна. Приходится вручную править csproj

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

                        Самое читаемое