Budując projekty napisane w Javie pewnie wielu z nas chociaż raz użyło Mavena. Jeśli nie to pokrótce jest to narzędzie, które pozwala na zarządzanie cyklami życia aplikacji takimi jak kompilacja, testowanie, budowanie, pobieranie zależności czy generowanie dokumentacji. Możemy również rozszerzyć jego działanie wykorzystując wszelkiej maści pluginy. Jednym z nich jest docker-maven-plugin od fabric8io, który pozwala nam na budowanie obrazów dockerowych oraz zarządzanie kontenerami podczas testów integracyjnych. Na ten moment chciałbym przyjrzeć się temu pierwszemu aspektowi.

Aplikacja do testów

Jak zawsze najprościej będzie stworzyć aplikację webową w Spring Boot, którą wykorzystamy do celów artykułu. Oczywiście wybieramy Mavena oraz zależność do Spring Web.

Spring na potrzeby Docker Maven plugin

Następnie tworzymy prosty kontroler z jednym endpointem. Aby było ciekawiej dodamy w nim jeden parametr w ścieżce.

1
2
3
4
5
6
7
8
@RestController
class MavenDockerController {

  @GetMapping("/hello/{text}")
  String hello(@PathVariable String text) {
    return "Hello from app using docker-maven-plugin, message: " + text;
  }
}

Jesteśmy teraz gotowi, aby stworzyć obraz dockerowy z naszą aplikacją wykorzystując plugin dla Mavena.

Zaglądamy do pom.xml

Na samym początku utwórzmy profil, który będzie porządkował naszą konfigurację. Robimy to poprzez utworzenie węzła profiles, a w nim dodajemy profile z wybranym przez nas id - maven-docker. Następnie w build podajemy z jakiego pluginu chcemy skorzystać. W naszym przypadku będzie to wcześniej wspomniany docker-maven-plugin znajdujący się w artifactId, a w groupId podajemy io.fabric8. Na chwilę obecną najbardziej aktualną wersją jest 0.37.0, którą również zapisujemy explicite. Po tym wstępie przejdźmy do głównej konfiguracji.

1
2
3
4
5
6
<plugin>
  <groupId>io.fabric8</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>0.37.0</version>
  ...
</plugin>

Musimy ustalić na początku z jakiego obrazu z Docker HUB będziemy korzystać. Ja użyłem w tym celu openjdk w wersji 16.0.2, ponieważ niezbędna jest tylko Java do uruchomienia aplikacji Spring Boot’owej. Następnie określamy co ma być załączone do budowanego obrazu Dockerowego. W tym celu podajemy węzeł assembly z wartością artifact pod hasłem descriptorRef. Oznacza to, że do obrazu zostanie dodany tylko artefakt projektowy bez żadnych zależności.

Następnie możemy zdefiniować port, który będzie formą dokumentacji dla osoby uruchamiającej nasz obraz, aby poprawnie z niego korzystała. Właśnie do tego służy instrukcja EXPOSE znajdująca się w Dockerfile: “It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published.”. Dlaczego wspominam o EXPOSE? Ponieważ w późniejszym etapie jak już zbudujemy nasz projekt wygenerowany zostanie właśnie Dockerfile na podstawie, którego zostanie stworzony obraz Dockerowy.

Komenda uruchamiająca aplikację

Na koniec w definicji budowania obrazu musimy jeszcze zdefiniować komendę jaka zostanie wywołana przy uruchamianiu kontenera dla stworzonego obrazu. Wykorzystamy w tym celu węzeł cmd i podamy w nim java -jar maven/${project.name}-${project.version}.jar. Jest to najzwyklejsza komenda uruchamiająca aplikację Javową spakowaną do pliku o rozszerzeniu jar. Warto zwrócić uwagę, że podaliśmy katalog maven przed nazwą naszego artefaktu. Musieliśmy tak zrobić, ponieważ jednym z kroków w Dockerfile jest kopiowanie jarki do właśnie tego katalogu. Dodatkowo użyliśmy zmiennych Mavena, które czynią naszą definicję elastyczniejszą.

Zdefiniowaliśmy również węzeł run, który przyda się w przypadku, gdy będziemy od razu chcieli stworzyć kontener z naszym obrazem. Dlatego tutaj dodajemy mapowanie localhost:8080:8080, które odpowiada fladze -p podczas uruchamiania kontenera z konsoli. Cała konfiguracja wygląda następująco.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<configuration>
  <images>
    <image>
      <name>devcezz/maven-docker-demo:${project.version}</name>
      <build>
        <from>openjdk:16.0.2-jdk-buster</from>
        <assembly>
          <descriptorRef>artifact</descriptorRef>
        </assembly>
        <ports>
          <port>8080</port>
        </ports>
        <cmd>java -jar maven/${project.name}-${project.version}.jar</cmd>
      </build>
      <run>
        <ports>
          <port>localhost:8080:8080</port>
        </ports>
      </run>
    </image>
  </images>
</configuration>

Zapomniałbym, że w węźle name musimy podać nazwę dla naszego tworzonego obrazu. Ja w tym celu wykorzystałem nazwę devcezz/maven-docker-demo i dałem tag określający aktualną wersję programu. W tym przypadku będzie to po prostu 0.0.1-SNAPSHOT.

Podpięcie tworzenia obrazu pod komendę Mavena

Należałoby jeszcze zdefiniować w jakim dokładnie momencie budowania naszej aplikacji chcemy tworzyć obraz Dockerowy. Zdecydowałem, że będzie to podczas wykorzystywania komendy package. W tym celu należy zdefiniować węzeł w pom.xml o nazwie executions i w nim umieścić definicję dla naszego wykonania. Nadajemy mu id o nazwie building-image, następnie w phase podajemy fazę cyklu życia budowania projektu o nazwie package. Na koniec w goals definiujemy ‘cel’ z pluginu docker-maven-plugin. Będzie to build odpowiedzialny za tworzenie obrazu Dockerowego, czyli to o co nam chodzi.

1
2
3
4
5
6
7
8
9
<executions>
  <execution>
    <id>building-image</id>
    <phase>package</phase>
    <goals>
      <goal>build</goal>
    </goals>
  </execution>
</executions>

Cała konfiguracja w pliku pom.xml prezentuje się następująco.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<project>
  ...
  <profiles>
    <profile>
      <id>maven-docker</id>
      <build>
        <plugins>
          <plugin>
            <groupId>io.fabric8</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>0.37.0</version>

            <configuration>
              <images>
                <image>
                  <name>devcezz/maven-docker-demo:${project.version}</name>
                  <build>
                    <from>openjdk:16.0.2-jdk-buster</from>
                    <assembly>
                      <descriptorRef>artifact</descriptorRef>
                    </assembly>
                    <ports>
                      <port>8080</port>
                    </ports>
                    <cmd>java -jar maven/${project.name}-${project.version}.jar</cmd>
                  </build>
                  <run>
                    <ports>
                      <port>localhost:8080:8080</port>
                    </ports>
                  </run>
                </image>
              </images>
            </configuration>
            <executions>
              <execution>
                <id>building-image</id>
                <phase>package</phase>
                <goals>
                  <goal>build</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
</project>

Pora przejść do konsoli!

Wpisując następującą komendę do konsoli tworzymy docelowy obraz Dockerowy.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mvn package -Pmaven-docker
...
[INFO] --- docker-maven-plugin:0.37.0:build (building-image) @ maven-docker ---
[INFO] Copying files to D:\git\maven-docker\target\docker\devcezz\maven-docker-demo\0.0.1-SNAPSHOT\build\maven
[INFO] Building tar: D:\git\maven-docker\target\docker\devcezz\maven-docker-demo\0.0.1-SNAPSHOT\tmp\docker-build.tar
[INFO] DOCKER> [devcezz/maven-docker-demo:0.0.1-SNAPSHOT]: Created docker-build.tar in 773 milliseconds
[INFO] DOCKER> [devcezz/maven-docker-demo:0.0.1-SNAPSHOT]: Built image sha256:c1cf3
...
$ docker image ls
REPOSITORY          TAG         IMAGE ID     CREATED     SIZE
devcezz/maven-docker-demo   0.0.1-SNAPSHOT    c1cf388db202   6 seconds ago   667MB
openjdk           16.0.2-jdk-buster   408a85222357   5 days ago    650MB
...

Wykorzystaliśmy polecenia package Mavena i użyliśmy naszego profilu maven-docker. W logach otrzymaliśmy informację, że obraz został utworzony. Wpisując komendę docker image ls przekonujemy się o tym fakcie - otrzymaliśmy obraz o nazwie devcezz/maven-docker-demo i tagu 0.0.1-SNAPSHOT.

Wracając na chwilę do IDE możemy zobaczyć, że w katalogu target powstał folder docker, który zawiera wszelkie niezbędne pliki do utworzenia naszego obrazu z aplikacją. Pośród nich został zdefiniowany Dockerfile z niezbędnymi instrukcjami, które sami musielibyśmy napisać, gdyby nie plugin.

Wygenerowane katalogi i pliki przez plugin
Wygenerowane katalogi i pliki przez plugin

Teraz wpisując w konsoli docker container run -d -p 8080:8080 devcezz/maven-docker-demo:0.0.1-SNAPSHOT uruchomimy naszą aplikację. Wchodząc w przeglądarkę i podając adres http://localhost:8080/hello/Maven%20Docker%20rulz!!! dostajemy poniższy komunikat. Świadczy to o tym, że wszystko działa jak chcieliśmy.

Hello from app using docker-maven-plugin, message: Maven Docker rulz!!!

Oczywiście tworzenie obrazu i uruchamianie kontenera możemy zawrzeć w jednej komendzie. Wystarczy przy package dopisać docker:start -Ddocker.follow=false, aby wszystko zadziało się automatycznie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mvn package -Pmaven-docker docker:start -Ddocker.follow=false
...
[INFO] --- docker-maven-plugin:0.37.0:build (building-image) @ maven-docker ---
[INFO] Copying files to D:\git\maven-docker\target\docker\devcezz\maven-docker-demo\0.0.1-SNAPSHOT\build\maven
[INFO] Building tar: D:\git\maven-docker\target\docker\devcezz\maven-docker-demo\0.0.1-SNAPSHOT\tmp\docker-build.tar
[INFO] DOCKER> [devcezz/maven-docker-demo:0.0.1-SNAPSHOT]: Created docker-build.tar in 662 milliseconds
[INFO] DOCKER> [devcezz/maven-docker-demo:0.0.1-SNAPSHOT]: Built image sha256:c84ca
[INFO]
[INFO] --- docker-maven-plugin:0.37.0:start (default-cli) @ maven-docker ---
[INFO] DOCKER> [devcezz/maven-docker-demo:0.0.1-SNAPSHOT]: Start container c35819543705
...
$ docker container ls
CONTAINER ID   IMAGE                    COMMAND          CREATED        STATUS        PORTS            NAMES
c35819543705   devcezz/maven-docker-demo:0.0.1-SNAPSHOT   "/bin/sh -c 'java -jÔÇŽ"   About a minute ago   Up About a minute   127.0.0.1:8080->8080/tcp   maven-docker-demo-1

Flaga -Ddocker.follow=false ustawiona na false pozwala nam uruchomić kontener w detached mode. Jeżeli teraz wejdziemy do przeglądarki uzyskamy ten sam rezultat co poprzednio.

Podsumowanie

Uważam, że ten plugin może nam oszczędzić co i raz wpisywanie tych samych komend w celu utworzenia obrazu Dockerowego z interesującą nas aplikacją. Jeżeli zainteresowała Cię ta koncepcja i jesteś zwolennikiem Gradle to powinieneś przyjrzeć się projektowi bmuschko/gradle-docker-plugin, który pozwala nam stosować takie same zabiegi co w Mavenie.

Mam nadzieję, że ten artykuł był dla Ciebie interesujący i pokazał Ci kolejną możliwość poszerzenia swojego warsztatu programisty!