Budowanie frontendu przez Mavena w Dockerze

1

Nie miałem pomysłu jak lepiej nazwać ten temat, postaram się więć wytłumaczyć.
Mam aplikację która składa się ze Springa oraz Angulara, całość znajduje się w jednym projekcie Mavenowym, podzielonym na dwa moduły. Dodatkowo nad wszystkim stoi Docker.
Mam więc folder parenta, który ma swój pom.xml

    <groupId>com.example</groupId>
    <artifactId>parent-app</artifactId>
    <packaging>pom</packaging>
    <version>1.0</version>

    <parent>
       <groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modules>
        <module>app-ui</module>
        <module>app-server</module>
    </modules>

No i schodząc niżej, app-ui to wspomniany frontend Angularowy, który również ma swojego pom.xml, w którym odpalany jest plugin do zbudowania tego frontendu jak i zainstalowania node, npm, itp.

	<parent>
		<groupId>com.example</groupId>
		<artifactId>parent-app</artifactId>
		<version>1.0</version>
	</parent>

     <artifactId>app-ui</artifactId>
     <version>1.0</version>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>1.3</version>
                <configuration>
                    <nodeVersion>v12.16.3</nodeVersion>
                    <npmVersion>6.12.1</npmVersion>
                    <workingDirectory>src/main/web/app-ui</workingDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>npm run build</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>run build</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>prod</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>run-script build</arguments>
                        </configuration>
                        <phase>generate-resources</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Jak się można domyśleć, w moim app-server jest plugin który kopiuje zbudowany /dist i wrzuca jako static resources mojej aplikacji Springowej.
No i to działa, gdy odpalam to bez dockera. Problem pojawia gdy wszystko odpalam poprzez Dockera, którego Dockerfile wygląda tak

FROM maven:latest AS MAVEN_BUILD
COPY pom.xml /build/
COPY app-server/pom.xml /build/app-server/
COPY app-server/src /build/app-server/src
COPY app-ui/pom.xml /build/app-ui/
COPY app-ui/src /build/app-ui/src
WORKDIR /build
RUN mvn clean install

FROM openjdk:latest
WORKDIR /app
COPY --from=MAVEN_BUILD /build/app-server/target/app-server-1.0.war /app
EXPOSE 8080
CMD java -jar app-server-1.0.war

Problem zaczyna się pojawiać już przy budowaniu pierwszej częsci, czyli app-ui, w logach widzę że instalowany jest node i npm, czyli wszystko ok

[INFO] Installing node version v12.16.3
[INFO] Downloading https://nodejs.org/dist/v12.16.3/node-v12.16.3-linux-x64.tar.gz to /root/.m2/repository/com/github/eirslett/node/12.16.3/node-12.16.3-linux-x64.tar.gz

Ale potem, odpalane jest npm run build a pod spodem ng build, i to kończy się już błędem...

[INFO] > ng build
[INFO]
[ERROR] sh: ng: command not found
[ERROR] npm ERR! code ELIFECYCLE
[ERROR] npm ERR! syscall spawn
[ERROR] npm ERR! file sh
[ERROR] npm ERR! errno ENOENT
[ERROR] npm ERR! app-ui@0.0.0 build: ng build
[ERROR] npm ERR! spawn ENOENT
[ERROR] npm ERR!
[ERROR] npm ERR! Failed at the app-ui@0.0.0 build script.
[ERROR] npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

0

wewnątrz kontenera nie masz zainstalowanego angular cli globalnie (tak, żeby odpalać je z shella w dowolnym miejscu), możesz spróbować zainstalować go tak:

npm i -g @Angular/cli

0

@Nevaan:
Jestem prawie pewien, że ten plugin mavenowy powinien to zrobić. W każdym razie, dorzuciłem takie coś na początku Dockerfile

FROM node:latest
RUN npm i -g @angular/cli

Niestety, efekt jest taki sam jak wcześniej

0

wiesz co, szczerze mówiąc nie korzystałem z dockerfile'ów które miały wiele komend FROM. z tego co widzę, że są osobne "warstwy". najprostszym rozwiązaniem powinno być dodatnie czegoś takiego do poma (przed npm run build):

 <execution>
                        <id>npm install ng cli globally</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>install -g @angular/cli</arguments>
                        </configuration>
                    </execution>

0

@Nevaan:
Zrobiłem tak jak mówisz

#15 131.8 [INFO] + @Angular/cli@11.0.5
#15 131.8 [INFO] added 254 packages from 201 contributors in 50.825s

A zaraz potem

#15 132.2 [ERROR] sh: ng: command not found

0

Skoro w dockerze odbywa się budowanie to może zmień koncepcję i porzuć budowanie frontendu za pomocą mavena. Maven nadaje się do budowania aplikacji napisanych w javie, npm nadaje się do budowania aplikacji napisanych w angularze.
Przy okazji, każde FROM w Dockerfile to osobny stage w budowaniu dockerem, każdy stage to osobne pudełka, można kopiować pliki pomiędzy nimi, ale aplikacje nie są widoczne pomiędzy nimi.
Wracając do tematu, proponuję, abyś wywalił z pom.xml wszystko do budowania angulara i zrobił to mniej więcej tak:

FROM node:latest AS node
SKOPIUJ PLIKI ANGULAROWE
ODPAL BUDOWANIE ANGULARA

FROM maven:latest AS maven
SKOPIUJ POM ORAZ ŹRÓDŁA JAVY
SKOPIUJ --from=node WYNIKI BUDOWANIA ANGULARA DO src/main/web/app-ui
ODPAL BUDOWANIE MAVEN

FROM openjdk:latest
SKOPIUJ --from=maven WYNIKI BUDOWANIA MAVENA
ODPAL APLIKACJE
0

W końcu tak też zrobiłem, poszedłem nawet o krok dalej, tzn.

FROM alpine:latest
RUN apk add maven
RUN apk add openjdk11
RUN apk add nodejs nodejs-npm
RUN npm install -g @angular/cli

I nawet to działa, w kolejnych krokach po prostu kopiuje /src i wszystko buduje, z Mavena wywaliłem plugin do budowania frontendu. Został jeden problem... odpalenie mongodb. Dorzuciłem coś takiego

RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.6/main' >> /etc/apk/repositories
RUN echo 'http://dl-cdn.alpinelinux.org/alpine/v3.6/community' >> /etc/apk/repositories
RUN apk update
RUN apk add mongodb
RUN mkdir -p data/db
RUN ./mongod &

Potem jest mvn clean install i java -jar ..., niestety problem w tym, że aplikacja nie wstaje, a to z powodu takiego że mongo nie działa. Jak wszedłem na ten kontener to faktycznie, mongo nie jest odpalone. Musiałem z poziomu CLI odpalić mongod i dopiero wtedy udało mi się odpalić aplikację. Czemu jednak nie mogę zrobić tego z poziomu Dockerfile?

Edit#
Jak w Dockerfile dam po prostu RUN ./mongod to wygląda to tak, mongo jest odpalone ale docker build image się po prostu nie kończy
screenshot-20201230225859.png

2

Wydaje mi się, że w dockerze jest przyjęta konwencja, że w kontenerze powinien być odpalony tylko jeden proces. Jak stopujesz kontener to do polecenia odpalonego za pomocą CMD jest wysyłany sygnał zatrzymania aplikacji. Tutaj mongo próbujesz odpalić w tle poza poleceniem CMD.
Jak chcesz mieć aplikację i bazę zdockeryzowaną to pownienieś odpalić dwa kontenery, a do jednego skryptu do odpalania kilku kontenerów może posłużyć docker compose.

Chciałbym jeszcze napisać, że budowanie za pomocą multistage w moim odczuciu jest lepszym rozwiązaniem niż użycie bazowego obrazu i instalowania na nim różnych aplikacji. Obraz puchnie, a przecież nie potrzebujesz nodejs ani mavena do uruchomienia aplikacji. Teraz w obrazie znajduje się wszystko co potrzebowałeś na każdym z kroków. Ale to tylko moja opinia.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.