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
    }
  }
}

angular, angular6/7, asp .net core 2.1, docker

Комментарии (21)


  1. zhaparoff
    29.10.2018 14:38
    +1

    pprometey, не могли бы вы подробнее рассказать о цели данного контейнера?
    Вы делаете контейнер для сборки приложения? Либо же для его запуска?
    Если первое, не совсем понятно, зачем вообще нужен контейнер — собрал один раз, сделал пакет (или несколько) и вперед, деплой его налево и направо.
    Во втором же случае, у меня такое чувство, что приложение не должно каждый раз собираться заново при развертывании нового контейнера.
    Возможно, я не совсем точно понимаю саму суть контейнеризации?


    1. pprometey Автор
      29.10.2018 14:56

      Я лично его использую для CI\CD на базе Gitlab, т.е. автоматического деплоя.
      В целом контейнеры очень удобно как для разработки, так и для дальнейшего развертывания. Особенно когда у тебя крупное приложение состоящее из множества проектов (микросервисная архитектура), использующее различные технологии, и весь этот зоопарк надо заставить слаженно работать. Об этом много написано. Надо немного перестроить мозги, чтобы научится работать с контейнерами. Но как «вкуришь», дальнейшей жизни без них уже просто не представляешь. Это чертовски удобно.


    1. pprometey Автор
      29.10.2018 15:00

      «приложение не должно каждый раз собираться заново при развертывании нового контейнера.»
      Оно и не собирается каждый раз. Оно собирается один раз при сборке ОБРАЗА. Который уже множество раз разворачивается как КОНТЕЙНЕР одной командой docker run


    1. pprometey Автор
      29.10.2018 18:53

      Можно без проблем настроить процесс так, когда не надо пересоздавать образ каждый раз и видеть изменения интерпретируемого кода «на лету». Плюс Visual Studio довольно хорошо умеет работать с контейнерами, отслеживает изменения чтобы не пересоздавать образ с нуля, и позволяет производить отладку в контейнерах тоже.


  1. denismaster
    29.10.2018 15:17

    Хорошая статья!
    Два вопроса
    1) Как работать с HTTPS redirection в docker?
    2) Стоит ли использовать образ alpine для сборки приложения?


    1. pprometey Автор
      29.10.2018 18:43

      Спасибо за положительный отзыв.

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

      2. Я стараюсь именно alpine образ и использовать везде, где это только можно, как наиболее легковесный.


  1. hummerd
    29.10.2018 19:02

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

    Надо взять контейнер с нодой, сбилдить в нем ангуляр приложение (АСП для этого не нужен), потом полученную статику переложить в контейнер с веб сервером, который ее будет раздавать — это вообще может быть отдельный контейнер от бекенда. Плюс делаем конейнер с беком (АСП приложением). Можно и объеденить АСП + статику в один контейнер, но тогда деплоиться будут всегди фронт и бек, а это не всегда удобно.
    Но золотое правило такое — в итоговых контейнерах не должно быть ничего, кроме минимума необходимого для его работы (например, никаких тулзов для сборки).


    1. denismaster
      29.10.2018 20:22

      Если я правильно понял, то в статье используется multistage-сборка, и там как раз только статика+api.


      1. hummerd
        29.10.2018 22:12

        Мультистейдж да, в показанном докер файле 4 стеджа. Во 2 и 3 ноды нет, а в 1 и 4 есть.


    1. pprometey Автор
      29.10.2018 20:42

      Бакенд отдельно работает. В отдельном проекте, в отдельном контейнере. Ангулар это статика, но тут речь идет о шаблоне проекта для 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. hummerd
        29.10.2018 22:25

        Тогда нужно больше деталей. Для чего нужен приведенный в примере докер файл — для деплоя на тетсинг? Зачем dotnet publish делается в отдельном стейдже? Зачем в контейнере запускать if (env.IsDevelopment()) нельзя ли обойтись без него (запускать тестинг или стейджинг)? Какого размера получется dev контейнер? Нужно ли отдельно запускать RUN npm install angular/cli -g ведь для билда вроде должно быть достаточно RUN npm install, который запускается позже.


        1. pprometey Автор
          30.10.2018 06:02

          Да, все нужно что есть в Dockerfile, без этого в работать не будет при заданных исходных условиях. Единственное, я «подстраховался» с npm install перед rebuild node-sass.

          Зачем в контейнере запускать if (env.IsDevelopment())

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


          1. mayorovp
            30.10.2018 08:01

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


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


            1. pprometey Автор
              30.10.2018 08:56

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

              Или есть другие способы решения данной проблемы?


              1. mayorovp
                30.10.2018 09:01

                Конечно же другие способы есть! Использовать другое значение ASPNETCORE_ENVIRONMENT на дев-сервере.


                1. pprometey Автор
                  30.10.2018 10:42

                  Тогда много отладочной информации, например страницы ошибок и т.д будут недоступны на дев сервере, и тестеры не будут получать полной информации об ошибках и по сути не будут работать все ветки кода, с if (env.IsDevelopment()).


                  1. hummerd
                    30.10.2018 11:33

                    Мне тоже кажется вам стоит как-то разделить — Local от Development, и не надо будет ангуляр-нод сервер в контйенер пихать. Например выкатывать можно staging, а локально использовать Development. Я почему-то не могу спать спокойно, когда знаю что в контейнере есть что-то лишнее :)


                    1. pprometey Автор
                      30.10.2018 12:21

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


                    1. pprometey Автор
                      01.11.2018 06:48

                      Да, вы правы. Я переработал подход и на самом деле в том варианте 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"]
                      


                  1. mayorovp
                    30.10.2018 11:45

                    Так напишите условия таким образом, чтобы они были доступны и работали! Не понимаю, в чем проблема-то?


                    1. pprometey Автор
                      01.11.2018 06:55

                      Спасибо большое. В том числе и благодаря вашим замечаниям я изменил подход и теперь получил в итоге более правильный контейнер, плюс включил поддержку переменных среды. Выше в комментариях я включил обновленный код 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 из образа убрали, а из шаблона проекта забыли.