Docker + Spring Boot – zamykamy aplikację w kontenerze Dockerowym

blog.mloza.pl 3 lat temu

Kontenery Dockera stały się bardzo popularne. Bardzo ułatwiają tworzenie i deployment aplikacji. gwałtownie możemy zbudować kontener wraz z wszystkimi zależnościami projektu i zdeployować go na środowisku testowym/produkcyjnym lub wysłać do innych zespołów, które korzystają z nasze aplikacji. jeżeli pracujemy z mikroserwisami, jest to idealne rozwiązanie, aby uruchomić wszystkie potrzebne serwisy, a następnie skupić się na rozwijaniu swojego. Pozwala to też bardzo gwałtownie uruchamiać środowiska testowe całej naszej aplikacji, na przykład na potrzeby CI/CD.

W tym wpisie pokażę jak zamknąć prostą aplikację napisaną w Spring Boocie w kontenerze. Dodatkowo pokażę też kilka sztuczek, przydatnych w sprawdzaniu co się dzieje z naszym kontenerem, oraz jak wysłać nasz kontener do zewnętrznego repozytorium Docker Hub. Zaczynajmy!


Kod do postu znajduje się w repozytorium na GitHubie: https://github.com/mloza/spring-boot-docker


Aplikacja Spring Boot

Zacznijmy od stworzenia prostej aplikacji w Spring Boocie. Będzie ona miała jedno zadanie, wyświetlić w przeglądarce napis Hello World!.

Maven i potrzebne zależności

Na początek tworzę nowy projekt Mavenowy i wrzucam tam zależności Springa do pom.xml. Całość wygląda tak jak poniżej.

View this code snippet on GitHub.

Nie używam tutaj parent poma od Springa, zamiast tego importuję go w sekcji dependencyManagement. W zależnościach dodaję tylko Spring Boot Starter Web. Aplikacja ma tylko wyświetlić „Hello World!” więc to wystarczy. Ważną rzeczą jest spring-boot-maven-plugin! Dzięki niemu nasza aplikacja zostanie zapakowana do wykonywalnego jara. W konfiguracji tej wtyczki wskazujemy, która klasa powinna zostać uruchomiona po starcie aplikacji.

Aplikacja – jedna prosta klasa

Cała nasza aplikacja może zostać zamknięta w jednej klasie. Będziemy potrzebowali jeden kontroler z jedną metodą, która zwróci nam odpowiedni napis. Dodatkowo metodę main, która odpali Spring Boota. Całość wygląda jak poniżej.

View this code snippet on GitHub.

Jeśli potrzebujesz objaśnienia poszczególnych elementów, zapraszam do wpisu o podstawach Spring Boota, tam wyjaśniam, do czego służą poszczególne adnotacje i w jaki sposób zbudować aplikację.


W tym momencie możemy uruchomić naszą aplikację, przejść pod adres http://localhost:8080 w przeglądarce i naszym oczom powinien się ukazać napis Hello World!

Pozostaje nam już tylko zbudowanie archiwum jar. Aby to zrobić, wpisujemy komendę mvn package. W katalogu target pojawi nam się plik spring-docker.jar. Możesz w tym momencie odpalić aplikację, aby się upewnić, iż wszystko poszło dobrze. Wystarczy uruchomić polecenie java -jar target/spring-docker.jar i przejść przeglądarką na stronę. Pamiętaj, aby wcześniej zabić aplikację uruchomioną w IDE. jeżeli tego nie zrobisz, otrzymasz błąd mówiący o zajętym porcie 8080.

Docker – tworzymy kontener

Mając już gotową aplikację, czas ją zapakować do kontenera. Aby to zrobić, będziemy potrzebowali Dockerfile. Jest to zwykły plik tekstowy mówiący Dockerowi, w jaki sposób ma być, zbudować kontener. Plik ten tworzymy w głównym katalogu projektu. Dla naszej aplikacji wystarczy kilka linii, aby całość zadziałała.

Dockerfile

View this code snippet on GitHub.

Pierwsza linia pliku mówi, z jakiego obrazu kontenera powinien skorzystać Docker jako bazy dla naszego obrazu. W zależności od tego co wybierzemy, taki obraz będzie miał zainstalowane inne pakiety i będzie zajmował więcej lub mniej miejsca. Ja wybrałem obraz od Adopt OpenJDK z zainstalowanym JRE 14 opartym na Alpine Linux (jeden z mniejszych obrazów z zainstalowanym JRE).

Następnie określamy katalog roboczy. Kolejne polecenia będą traktowały ten katalog jako katalog bazowy. Przykładowo, następnym poleceniem jest kopiowanie pliku jar z naszego komputera do kontenera. Zostanie on skopiowany właśnie do tego katalogu.

Ostatnia linijka określa polecenie, które ma zostać wykonane przy uruchomieniu kontenera. Przyjmuje ono tablicę argumentów. Dla nas jest to oczywiście java -jar application.jar.

Budujemy kontener

Mając już wszystko przygotowane, możemy zbudować kontener. W głównym katalogu aplikacji wydajemy komendę:

docker build .

Jako wyjście powinieneś ujrzeć coś w tym rodzaju (może się różnić u Ciebie):

Sending build context to Docker daemon 17.6MB Step 1/4 : FROM adoptopenjdk/openjdk14:alpine-jre alpine-jre: Pulling from adoptopenjdk/openjdk14 df20fa9351a1: Already exists 600e2408a8d5: Pull complete 59570a1fe844: Pull complete Digest: sha256:1847d76ac5ff2a19856ffe05aab4601eaebf668f47f232864084cc7fdf3aa402 Status: Downloaded newer image for adoptopenjdk/openjdk14:alpine-jre ---> 13f4bc5a87c3 Step 2/4 : WORKDIR /opt ---> Running in 7c24b64b058e Removing intermediate container 7c24b64b058e ---> 2bcba4470168 Step 3/4 : COPY target/spring-docker.jar application.jar ---> 24600e60d332 Step 4/4 : ENTRYPOINT ["java", "-jar", "application.jar"] ---> Running in 152cf7fcb2da Removing intermediate container 152cf7fcb2da ---> 447767e52cc2 Successfully built 447767e52cc2

Interesuje nas ostatnia linia mówiąca o sukcesie i podaje id obrazu (447767e52cc2). Za jego pomocą możemy uruchomić kontener.

Uruchamiamy kontener

Uruchomienie kontenera jest bardzo proste, wpisujemy po prostu docker run id-obrazu. W naszym przypadku musimy jeszcze przekierować port lokalny do kontenera. Robimy to dzięki opcji -p.

docker run -p 8080:8080 447767e52cc2

Na wyjściu powinny zostać wypisane logi z aplikacji. jeżeli chcemy uruchomić kontener w tle, wystarczy dodać modyfikator -d. Wtedy zostanie nam zwrócone na wyjście id kontenera i wrócimy do wiersza poleceń. dzięki opcji -p mapujemy porty. W naszym przypadku mówimy, iż port lokalny 8080 ma zostać przekierowany na port 8080 w kontenerze.

Jeżeli wszystko zostało wykonane poprawnie to pod adresem http://localhost:8080, powinniśmy ujrzeć napis Hello World!.

Możesz spokojnie uruchomić więcej instancji kontenera. Pamiętaj, aby zmienić port hosta, z którego mapujesz do kontenera, ponieważ kilka aplikacji nie może słuchać na jednym porcie.

Podstawy pracy z Dockerem

Aby wyświetlić aktualnie działające kontenery, wpisujemy docker ps. jeżeli chcemy zobaczyć też kontenery, które zakończyły pracę, dodajemy flagę -a.

$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e6687130b97e 447767e52cc2 "java -jar applicati…" 5 minutes ago Up 5 minutes nervous_gates $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e6687130b97e 447767e52cc2 "java -jar applicati…" 5 minutes ago Up 5 minutes nervous_gates e388017bbd0e 447767e52cc2 "java -jar applicati…" 5 minutes ago Exited (130) 5 minutes ago elated_banzai

Kontenery zatrzymujemy poprzez polecenie docker stop id-kontenera. Po zatrzymaniu kontener dalej istnieje. Usunąć go można poleceniem docker rm id-kontenera. Z czasem lista kontenerów robi się dość długa. Aby usunąć wszystkie kontenery, możemy skorzystać z jednolinijkowaca:

docker ps -a | cut -d' ' -f1 | xargs docker rm {}

Wszystkie zainstalowane aktualnie obrazy możemy wyświetlić wpisując docker images.

Interakcja z kontenerem

Jeżeli uruchomimy nasz kontener jako demon (-d) konsola wypisze tylko id kontenera. Możemy jednak podejmować pewne interakcje z naszym kontenerem. Pierwszą z nich jest wypisanie wszystkich logów ze standardowego wyjścia. Służy do tego polecenie docker logs id-kontenera.

$ docker logs e6687130b97e . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.2.4.RELEASE) 2020-09-12 19:27:53.732 INFO 1 --- [ main] pl.mloza.Main : Starting Main on e6687130b97e with PID 1 (/opt/application.jar started by root in /opt) 2020-09-12 19:27:53.737 INFO 1 --- [ main] pl.mloza.Main : No active profile set, falling back to default profiles: default 2020-09-12 19:27:54.937 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) (.....)

Kolejną rzeczą, jaką możemy zrobić to uruchomienie polecenia w kontenerze. Dzięki temu możemy też uruchomić powłokę shellową i wejść do naszego kontenera. Robimy to przez polecenie docker exec id-kontenera polecenie. Na przykład poniższe polecenie uruchomi wspomniany wcześniej wiersz poleceń.

docker exec -ti e6687130b97e /bin/sh

W kontenerze opartym na Alpine Linuxie nie ma basha, więc musi nam wystarczyć powłoka sh. Flaga -i mówi nam, iż ma to być uruchomione interaktywnie – standardowe wejście pozostaje podłączone, oraz -t – aby zaalokować pseudo terminal.

Dzielenie się obrazem

Jak mamy już zbudowany obraz, możemy umieścić go w repozytorium. Najprościej jest skorzystać z darmowego oficjalnego repozytorium Docker Hub. Pozwala on na stworzenie za darmo jednego prywatnego repozytorium oraz nieograniczonej liczby publicznych. Oprócz Docker Huba możemy użyć np. AWS ECR od Amzaona. Często w organizacjach używa się Nexusa jako repozytorium pakietów Mavenowych. Nexus 3 może też być repozytorium obrazów dockerowych.

Aby opublikować obraz w Docker Hub, należy go najpierw odpowiednio otagować. Tag powinien się składać z id-użytkownika/id-repozytorium:wersja. Możemy to zrobić przy budowaniu, przy pomocy opcji -t tag lub poleceniem docker tag id-obrazu tag. Przykładowo:

$ docker build -t mloza/spring-docker:1.0 . $ docker tag 447767e52cc2 mloza/spring-docker:1.0

Mając już obraz, musimy się tylko zalogować i możemy wysłać nasz obraz. Robimy to odpowiednio poleceniami docker login –username=nazwa-użytkownika oraz docker push tag-obrazu.

$ docker login --username=mloza $ docker push mloza/spring-docker:1.0

Teraz nasz obraz jest dostępny dla innych. jeżeli wybraliśmy, aby nasze repozytorium było publiczne, to każdy może wykonać komendę docker pull tag-obrazu i uruchomić u siebie nasz obraz.

$ docker pull mloza/spring-docker:1.0 $ docker run mloza/spring-docker:1.0

Podsumowanie

Zamykanie aplikacji w kontenerze jest bardzo wygodne i ułatwia wielu programistom pracę. W artykule opisałem tylko podstawy. Docker daje nam dużo szersze możliwości. Dodatkowo ogromna ilość narzędzi takich jak docker compose, Kubernetes, AWS ECS pozwala nam uruchamiać i zarządzać aplikacjami złożonymi z wielu kontenerów. Możemy przykładowo automatycznie skalować ilość instancji kontenerów w zależności od obciążenia. Możliwości są ogromne, więc zachęcam do zgłębienia tematu. Na pewno nie jest to też ostatni post, jaki pojawi się na blogu w temacie Dockera.

Idź do oryginalnego materiału