Благодаря помощи в комментариях, ниже представлена более правильная с идеологической точки зрения версия docker файла.
В стандартном шаблоне проекта Angular SPA Template содержится ошибка. Разработчики в версии net .core 2.1. удалили из образа microsoft/dotnet:2.1-sdk используемого для сборки nodejs, но в файле проекта остался код, его использующий. Подробнее здесь github.com/aspnet/Announcements/issues/298
Необходимо отредактировать вручную файл проекта *.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>
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
Чтобы разрешить эту ошибку, необходимо обновить 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)
denismaster
29.10.2018 15:17Хорошая статья!
Два вопроса
1) Как работать с HTTPS redirection в docker?
2) Стоит ли использовать образ alpine для сборки приложения?pprometey Автор
29.10.2018 18:43Спасибо за положительный отзыв.
1. Подключить папку с сертификатом в контейнеру как volumes. и без проблем. Все прекрасно работает.
Но я использую Traefik — превосходный обратный прокси-сервер и балансировщик для облачной инфраструктуры. Сами контейнеры работают на 80 порту, без https, а вот traefik автоматически получает и продлевает Letsencrypt сертификаты и расшаривает эти контейнеры для мира уже по https.
2. Я стараюсь именно alpine образ и использовать везде, где это только можно, как наиболее легковесный.
hummerd
29.10.2018 19:02В итоговом образе конечно не должно быть ноды. Ангуляр приложение — это скомпилированная статика, которая раздается веб сервером и делает вызовы к АПИ, для какой цели нам там нода?
Надо взять контейнер с нодой, сбилдить в нем ангуляр приложение (АСП для этого не нужен), потом полученную статику переложить в контейнер с веб сервером, который ее будет раздавать — это вообще может быть отдельный контейнер от бекенда. Плюс делаем конейнер с беком (АСП приложением). Можно и объеденить АСП + статику в один контейнер, но тогда деплоиться будут всегди фронт и бек, а это не всегда удобно.
Но золотое правило такое — в итоговых контейнерах не должно быть ничего, кроме минимума необходимого для его работы (например, никаких тулзов для сборки).denismaster
29.10.2018 20:22Если я правильно понял, то в статье используется multistage-сборка, и там как раз только статика+api.
hummerd
29.10.2018 22:12Мультистейдж да, в показанном докер файле 4 стеджа. Во 2 и 3 ноды нет, а в 1 и 4 есть.
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); } });
hummerd
29.10.2018 22:25Тогда нужно больше деталей. Для чего нужен приведенный в примере докер файл — для деплоя на тетсинг? Зачем dotnet publish делается в отдельном стейдже? Зачем в контейнере запускать if (env.IsDevelopment()) нельзя ли обойтись без него (запускать тестинг или стейджинг)? Какого размера получется dev контейнер? Нужно ли отдельно запускать RUN npm install angular/cli -g ведь для билда вроде должно быть достаточно RUN npm install, который запускается позже.
pprometey Автор
30.10.2018 06:02Да, все нужно что есть в Dockerfile, без этого в работать не будет при заданных исходных условиях. Единственное, я «подстраховался» с npm install перед rebuild node-sass.
Зачем в контейнере запускать if (env.IsDevelopment())
Я использую Git-flow, это ветка develop, автоматически разворачивающаяся на dev сервер. Ветка master полуавтоматически развертывается в продакшн, там все так как говорите, ничего лишнего, без env.IsDevelopment().mayorovp
30.10.2018 08:01Вот только то, что у вас прописано в env.IsDevelopment, не предназначено для сервера, даже для dev-сервера!
Эта штука предназначена исключительно для горячей сборки измененных модулей. Откуда эти самые измененные модули появятся у вас в контейнере?
pprometey Автор
30.10.2018 08:56Предлагаешь всем разработчикам перед каждым пушем в ветку дев, закоментировать эти строки, а после пуша, раскоментировать и работать дальше?
Или есть другие способы решения данной проблемы?mayorovp
30.10.2018 09:01Конечно же другие способы есть! Использовать другое значение ASPNETCORE_ENVIRONMENT на дев-сервере.
pprometey Автор
30.10.2018 10:42Тогда много отладочной информации, например страницы ошибок и т.д будут недоступны на дев сервере, и тестеры не будут получать полной информации об ошибках и по сути не будут работать все ветки кода, с if (env.IsDevelopment()).
hummerd
30.10.2018 11:33Мне тоже кажется вам стоит как-то разделить — Local от Development, и не надо будет ангуляр-нод сервер в контйенер пихать. Например выкатывать можно staging, а локально использовать Development. Я почему-то не могу спать спокойно, когда знаю что в контейнере есть что-то лишнее :)
pprometey Автор
30.10.2018 12:21Да, согласен. Лишнее в контейнере не есть хорошо. Можно решить эту проблему реорганизовав процесс. Я просто не стал дальше заморачиваться. Такой компромисс меня устроил.
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"]
mayorovp
30.10.2018 11:45Так напишите условия таким образом, чтобы они были доступны и работали! Не понимаю, в чем проблема-то?
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 из образа убрали, а из шаблона проекта забыли.
zhaparoff
pprometey, не могли бы вы подробнее рассказать о цели данного контейнера?
Вы делаете контейнер для сборки приложения? Либо же для его запуска?
Если первое, не совсем понятно, зачем вообще нужен контейнер — собрал один раз, сделал пакет (или несколько) и вперед, деплой его налево и направо.
Во втором же случае, у меня такое чувство, что приложение не должно каждый раз собираться заново при развертывании нового контейнера.
Возможно, я не совсем точно понимаю саму суть контейнеризации?
pprometey Автор
Я лично его использую для CI\CD на базе Gitlab, т.е. автоматического деплоя.
В целом контейнеры очень удобно как для разработки, так и для дальнейшего развертывания. Особенно когда у тебя крупное приложение состоящее из множества проектов (микросервисная архитектура), использующее различные технологии, и весь этот зоопарк надо заставить слаженно работать. Об этом много написано. Надо немного перестроить мозги, чтобы научится работать с контейнерами. Но как «вкуришь», дальнейшей жизни без них уже просто не представляешь. Это чертовски удобно.
pprometey Автор
«приложение не должно каждый раз собираться заново при развертывании нового контейнера.»
Оно и не собирается каждый раз. Оно собирается один раз при сборке ОБРАЗА. Который уже множество раз разворачивается как КОНТЕЙНЕР одной командой docker run
pprometey Автор
Можно без проблем настроить процесс так, когда не надо пересоздавать образ каждый раз и видеть изменения интерпретируемого кода «на лету». Плюс Visual Studio довольно хорошо умеет работать с контейнерами, отслеживает изменения чтобы не пересоздавать образ с нуля, и позволяет производить отладку в контейнерах тоже.