JavaRush /Курси /Docker for Spring /jarmode=tools і шари...

jarmode=tools і шари Spring Boot

Docker for Spring
Рівень 8 , Лекція 1
Відкрита

1. Що таке jarmode=tools

Якщо ви колись думали: «Чому не можна просто взяти jar і запустити?», — вітаю, це цілком нормальна думка. Але Docker-збирання любить, коли ми вміємо не лише запускати, а й розбирати артефакт на частини. jarmode=tools — це як сервісний режим в автомобілі: двигун той самий, але замість поїздки ми відкриваємо капот і проводимо техобслуговування.

Spring Boot уміє пакувати застосунок так, що всередині jar з’являється інформація про шари (layers index), а разом із цим до архіву додається підтримка спеціального jar modetools. Це дає змогу запускати jar не як «підняти сервер на порту», а як «виконати службову команду»: показати шари або вилучити їх у файлову систему. Саме так і працює jarmode=tools, і саме це є канонічним підходом в актуальній документації Spring Boot 4.0.x.

Важливо одразу зафіксувати одну думку — вона потім неабияк економить нерви: jarmode=tools — це не якась окрема магія, вигадана в Docker. Це звичайний запуск Java-процесу із системною властивістю -D.... Магії тут рівно стільки ж, скільки у фразі «запусти Java з параметром».

2. tools — режим jar, а не утиліта

Психологічно новачкові простіше уявити окрему утиліту, ніж jar, який уміє працювати в різних режимах. Тому часто виникає очікування, що tools — це якась зовнішня програма на кшталт gradle чи docker. Насправді все навпаки: jar — той самий, а спосіб його запуску змінюється завдяки параметру JVM -Djarmode=tools.

Технічно це виглядає так: ми запускаємо java -jar ..., але додаємо системну властивість jarmode. Boot loader — той самий завантажувач, який зазвичай запускає застосунок, — бачить, що треба не запускати ваш main(), а виконати службову команду. У результаті jar починає поводитися як міні-CLI. Документація прямо показує такий запуск як базовий вхід до режиму tools:

java -Djarmode=tools -jar my-app.jar

І так, це той рідкісний випадок, коли «запусти jar без аргументів» — корисна команда. Бо саме вона показує підказку, які команди доступні.

Окремо корисно не плутати дві схожі на вигляд речі: jarmode=tools і профілі Spring (spring.profiles.active). Профілі впливають на поведінку застосунку під час запуску. А jarmode=tools впливає на те, чи запускається застосунок узагалі. У режимі tools сервер не підіймається, тож жодні server.port тут поки що не мають сенсу.

3. Підготовка: правильний jar

Дуже легко потрапити в пастку: «Ну я ж просто подивлюся шари». Java, звісно, не проти, але шари з’являться лише тоді, коли jar зібрано як Spring Boot executable jar і він містить індекс шарів. У межах курсу ми працюємо з наскрізним проєктом Container-Ready Catalog Service, і його базовий артефакт для Docker — це результат bootJar.

У Gradle-проєкті це зазвичай виглядає так — показую в максимально «копійованому» вигляді:

# 1) Збираємо саме bootJar, щоб отримати виконуваний Spring Boot jar із шарами
./gradlew clean bootJar

# 2) Перевіряємо, що jar справді з’явився там, де ми очікуємо
ls -la build/libs

# побачите щось на кшталт:
# docker-java-catalog-service-0.0.1-SNAPSHOT.jar

Якщо ви на Windows і ls вам не звичний — не страшно, сенс той самий: jar лежить у build/libs. У документації Spring Boot навіть окремо нагадують, що приклад із target/*.jar треба замінити на build/libs/*.jar, якщо ви працюєте з Gradle.

Справжня назва bootJar на диску може містити версію, і це нормально. Щоб далі не танцювати навколо версії, будемо тримати просте правило: зовні беремо реальний файл із build/libs, а всередині Docker-стейджів одразу називаємо його application.jar. Це локальний псевдонім для extract-команд, а не нова «офіційна» назва артефакту проєкту. Тому в shell-командах нижче фігуруватиме build/libs/<ваш-bootJar>.jar, а в Dockerfile — стабільний application.jar.

4. Запуск tools-режиму і підказка

У нас є jar. Тепер найкорисніша вправа для мозку — запустити tools без команди й подивитися, що він узагалі вміє. Саме це — той випадок, коли «прочитати help» не занудство, а економія часу.

Команда така:

# Запускаємо jar у спеціальному режимі tools (застосунок при цьому НЕ стартує)
java -Djarmode=tools -jar build/libs/<ваш-bootJar>.jar

# Підказка: якщо все гаразд, ви побачите usage і список команд (extract/list-layers/help)

Документація Spring Boot 4.0.3 показує, що у відповідь ви побачите usage і список доступних команд. Там є щонайменше три: extract, list-layers, help.

Сенс кожної простий:

list-layers — подивитися, які шари взагалі є у вашому jar і як вони називаються. Це інспекція: вона нічого не розпаковує.

extract — розкласти jar по каталогах за шарами, щоб потім ці каталоги можна було копіювати окремими COPY у Dockerfile і тим самим отримувати точніші Docker-шари. Документація прямо каже, що extract потрібен, щоб «split the application into layers» для Dockerfile.

help — підказка щодо конкретної команди, корисна тоді, коли ви забули аргументи.

І тут з’являється важлива звичка професіонала: перед тим як писати Dockerfile навмання, ми спочатку читаємо, які команди доступні й як вони називаються. Це суттєво знижує шанс, що ви почнете гуглити «чому не працює layertools», хоча насправді ви вже на Boot 4.

5. list-layers: спочатку побачити назви шарів, потім писати Dockerfile

Коли студент уперше бачить layered Dockerfile, у нього зазвичай дві реакції. Перша: «О, так можна було?». Друга: «Я все одно не розумію, звідки беруться ці дивні папки». Команда list-layers — це якраз відповідь на другу реакцію: ви не вірите Dockerfile на слово, а спочатку запитуєте шари в jar.

Запускається це так:

# Команда нічого не розпаковує: вона лише друкує назви шарів із layer index
java -Djarmode=tools -jar build/libs/<ваш-bootJar>.jar list-layers

Що ви отримуєте на виході? Список назв шарів, які jar готовий вилучати. У типовому Spring Boot-застосунку ви побачите знайомий набір на кшталт dependencies, spring-boot-loader, snapshot-dependencies, application. Ключове тут інше: list-layers дає вам точні рядки, які потім фігуруватимуть у файлах і шляхах.

Педантичний, але дуже практичний момент: назви шарів краще сприймати як «API». Якщо команда вивела spring-boot-loader, значить у результаті вилучення ви отримаєте директорію spring-boot-loader/. Не loader/, не boot-loader/, а рівно те, що сказав інструмент. Чим менше ви «здогадуєтеся», тим менше потім дебажите.

Ще один приємний момент: list-layers — це швидкий спосіб перевірити, що ваш jar справді підтримує шари. Якщо ви з якоїсь причини вимкнули layering під час збирання, тут усе стане ясно ще до того, як ви напишете Dockerfile на 40 рядків.

6. extract: розкладаємо jar за шарами

Тепер ми переходимо від «подивитися» до «зробити». Команда extract — це робоча конячка layered-підходу. Вона бере ваш jar і розкладає його вміст у директорію, яку ви вкажете, причому так, щоб кожен шар опинився окремою папкою. Документація Spring Boot прямо показує канонічну форму команди для Dockerfile: extract --layers --destination layers.

Ось базовий варіант для локального запуску — без Dockerfile, просто щоб зрозуміти, що вийде:

# Очищаємо попередній результат, щоб не сплутати вивід і не отримати "змішані" файли
rm -rf layers
mkdir -p layers

# Вилучаємо jar за шарами в явно задану директорію
java -Djarmode=tools -jar build/libs/<ваш-bootJar>.jar extract \
  --layers \
  --destination layers

Зверніть увагу на два прапорці, які тут зовсім не «косметика».

--destination layers робить результат передбачуваним. У навчальному проєкті та в Dockerfile ми не хочемо «десь воно розпакувалося, не знаю де». Ми хочемо точний шлях, який зручно потім копіювати. Документація теж робить акцент на --destination.

--layers каже: «розкладай не абияк, а за шарами». Тобто це не просто «unzip jar», а саме «дотримуйся layer index».

Після виконання команди ви побачите, що всередині layers/ з’явилися піддиректорії шарів. Якщо ви любите дивитися очима, можна зробити так:

# Швидка перевірка: чи з’явилися директорії шарів
ls -la layers

# очікувано побачите папки на кшталт:
# dependencies/
# spring-boot-loader/
# snapshot-dependencies/
# application/

І ось тепер відбувається важлива зміна картини світу: ваш jar перестає бути «одним файлом» з точки зору Docker build. Він стає набором директорій, які можна копіювати окремими командами COPY, перетворюючи кожну з них на окремий шар образу.

Якщо уявити це схемою — без спроби намалювати шедевр, — вийде приблизно так:

flowchart TD
  %% Ліворуч — один JAR, праворуч — каталоги за шарами після extract
  A["bootJar: один файл .jar"] --> B["java -Djarmode=tools -jar ... list-layers"]
  B --> C["назви шарів (список)"]
  A --> D["java -Djarmode=tools -jar ... extract --layers --destination layers"]
  D --> E["layers/dependencies"]
  D --> F["layers/spring-boot-loader"]
  D --> G["layers/snapshot-dependencies"]
  D --> H["layers/application"]

Ця схема хороша тим, що в ній немає Docker. Бо зараз ми навчаємося саме «отримувати правильні каталоги», а не «збирати фінальний образ». Але саме ці каталоги Docker потім використовуватиме як будівельні блоки.

7. Що відбувається після extract і навіщо тут application.jar

На цьому місці в новачків часто виникає мініпаніка: «Зачекайте, ми ж бачили application.jar. Це і є фінальний runtime-артефакт?» Важливо розділити дві ролі.

У stage extract application.jar — це просто зручна локальна назва для вихідного bootJar, який ми взяли з build/libs. Вона потрібна, щоб команди list-layers і extract були короткими та не залежали від версії в імені файлу.

Після extract головним результатом стають уже не jar-файли, а каталоги шарів: layers/dependencies, layers/spring-boot-loader, layers/snapshot-dependencies, layers/application. Саме їх потім копіюють у runtime окремими COPY, щоб Docker отримав кілька незалежних шарів замість одного COPY app.jar.

Тому фінальний ручний layered-шлях зазвичай закінчується не повторним java -jar application.jar, а запуском через org.springframework.boot.loader.launch.JarLauncher. Це важливе розрізнення: application.jar живе як зручний вхідний артефакт stage extract, а runtime уже працює з розкладеною exploded-структурою застосунку.

Якщо згорнути це в короткий ланцюжок, вийде так:

- build/libs/<ваш-bootJar>.jar — реальний executable jar після bootJar;
- application.jar — його стабільна назва всередині stage extract;
- layers/... — результат extract, який Docker копіює частинами;
- JarLauncher — спосіб запуску вже зібраної exploded-структури в runtime.

Саме цей ланцюжок перетворює layering із «подивитися список шарів» на нормальний спосіб збирання Docker-образу.

8. Зв’язок із Dockerfile

На цьому місці дуже хочеться одразу зібрати весь layered Dockerfile — руки сверблять, Dockerfile кличе, а COPY просить бути розумним. Але спочатку важливо втримати дві опори: побачити назви шарів і навчитися їх вилучати. Без цього runtime-фрагмент лишається просто набором рядків.

Втім, корисно знати, як це виглядає в мінімальному вигляді з боку Dockerfile, бо команда extract найчастіше виконуватиметься саме під час docker build. Документація Spring Boot показує multi-stage патерн, де в builder stage робиться extract, а потім у runtime stage каталоги шарів копіюються окремо.

Мініфрагмент — лише як «скелет», без усього іншого:

FROM bellsoft/liberica-openjre-debian:25-cds AS extract

# У цьому stage ми лише "розпаковуємо за шарами", а не запускаємо застосунок
WORKDIR /builder

# Шаблон шляху зручний у навчальному проєкті: версія jar змінюється, а Dockerfile — ні
ARG JAR_FILE=build/libs/*.jar

# Кладемо jar під стабільним іменем, щоб команда RUN була передбачуваною
COPY ${JAR_FILE} application.jar

# Вилучаємо вміст за шарами в папку layers (її потім будемо COPY'ити по шарах)
RUN java -Djarmode=tools -jar application.jar extract --layers --destination layers

Тут є три принципові ідеї, які ви вже повинні впізнавати за попередніми темами курсу. По-перше, це окремий stage, тобто runtime-образ ми не «забруднюємо» логікою збирання. По-друге, jar перейменовується на стабільний application.jar, щоб команди були короткими і не залежали від версії. По-третє, --destination задає стабільний шлях, і Dockerfile стає читабельним.

Назва каталогу тут не декоративна: важливий саме стабільний шлях. Далі runtime-контейнер забирає вміст layers/... окремими COPY і стартує через JarLauncher, бо після extraction головним результатом уже стають каталоги шарів, а не вихідний файл application.jar.

Тут важливо не завчити Dockerfile напам’ять, а зрозуміти, що саме робить jarmode=tools всередині stage extract.

9. Шпаргалка за командами

Після всієї цієї теорії хочеться мати щось на кшталт «шпаргалки, але без сорому». Таблиця нижче — це саме вона: мінімум команд, максимум сенсу.

Дія Команда Що отримуємо на виході Навіщо нам це в layered image
Увійти в tools-режим і побачити підказку java -Djarmode=tools -jar application.jar список команд (extract, list-layers, help) швидко зрозуміти, що jar підтримує tools і як це використовувати
Подивитися назви шарів ... list-layers список назв шарів щоб не «вгадувати» папки в Dockerfile, а працювати за фактом
Вилучити шари в директорію ... extract --layers --destination layers папки layers/<layer>/... щоб копіювати шари окремими COPY і точніше використовувати кеш Docker

Якщо ви зможете відтворити ці три рядки й пояснити, що вони роблять, ви вже «в темі» і готові до наступного інженерного кроку — збирати layered Dockerfile не навмання, а з розумінням.

10. Типові помилки під час роботи з jarmode=tools

Помилка №1: запускати tools до того, як jar зібрано.
У голові це звучить логічно: «Я ж просто подивитися». На практиці java -Djarmode=tools -jar build/libs/*.jar не знайде файл або знайде не той, і ви отримаєте помилку рівня «Unable to access jarfile». Лікується не магією, а звичайною дисципліною: спочатку ./gradlew bootJar, потім tools.

Помилка №2: переплутати Maven-шлях target/*.jar із Gradle-шляхом build/libs/*.jar.
Ця помилка особливо підступна, бо ви можете копіювати приклад зі статті, і він виглядає «схожим на правду». У документації Spring Boot прямо сказано, що target/*.jar треба замінити на build/libs/*.jar, якщо ви використовуєте Gradle. Якщо цього не зробити, Docker build буде чесно шукати jar там, де його немає.

Помилка №3: забути, що list-layers нічого не розпаковує.
Інколи очікують, що після list-layers уже з’являться папки. Не з’являться. list-layers — це інспекція, читання «карти шарів». Папки створює лише extract. Добра перевірка: після list-layers у файловій системі не повинно змінитися майже нічого.

Помилка №4: не вказати --destination і потім шукати файли «десь у контейнері».
Це класична проблема не лише tools-режиму, а й будь-якого build-кроку: якщо шлях нестабільний, ви не зможете нормально писати COPY --from=.... Тому --destination layers — не прикраса, а спосіб зробити Dockerfile читабельним і передбачуваним. Документація використовує саме таку форму.

Помилка №5: сприймати jarmode=tools як runtime-налаштування застосунку.
Інколи намагаються «запустити контейнер у tools-режимі», очікуючи, що це якось прискорить або покращить роботу сервісу. Ні: tools-режим потрібен на етапі збирання образу та підготовки шарів. У runtime контейнер має запускати застосунок, а не розпаковувати самого себе. Це вже звучить як сюжет із фільму жахів про jar, який їсть сам себе.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ