(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

Я упомянул в предыдущем посте про Сhaos engineering. Хочу немного развить тему, тем более она стала популярна в последнее время.
Если описать вкратце - это контролируемая поломка системы.
Алгоритм такой:
1) определяем чтобы такого сломать. Очевидно, ломать нужно то, что с большей вероятностью может сломаться в ПРОДе
2) определяем как мы хотим, чтобы система вела себя при поломке
3) определяем как мы будем мониторить ее поведение
4) ломаем систему
5) сравниваем план и факт
6) опциально: заводим задачи на исправление))))
Вот тут подробнее https://habr.com/ru/company/flant/blog/460367/
Что интересно - тема chaos engineering "попала в топ" после того, как начался массовый переход в облака и в частности на k8s. И это неспроста.
Ключевой момент в chaos engineering - контролируемость эксперимента. Да, настоящих bare metal серверов все меньше, все чаще используются виртуалки. VMWare позволяет автоматизировать запуск и остановку отдельного сервера.
Но k8s и особенно Istio благодаря своим прокси на каждом узле (kube-proxy и envoy соответственно) позволяет централизовано управлять маршрутизацией. А это дает нам:
1) эмуляцию потери пакетов
2) эмуляцию увеличенных таймаутов
3) "виртуальное" отключение сервера - отключение трафика на сервер без отключения сервера, т.наз. blackhole. А это ускоряет тестирование.
4) отключение на уровне namespace
и какие-то более сложные сценарии.

В общем унификация в данном случае рулит!)

#haos_engeniring #k8s #istio
Всем привет!

Снова про NoSQL. NoSQL хорошо масштабируется, дает гибкую схему данных, но при этом мы теряем в гибкости запросов. В частности нельзя агрегировать данные - GROUP BY. Там же данные из разных агрегатов, собирать нужно со всех серверов кластера. Так? Нет)
Если RDBMS агрегирует можно сказать "просто" - по одному серверу, то NoSQL системы работают хитрее. Они используют технику MapReduce, автором которой является Google https://ru.wikipedia.org/wiki/MapReduce. Я опишу архитектуру MapReduce на примере Riak, в остальных хранилищах, подозреваю, алгоритм похож.
Для начала нужно задать 2 метода на одном из стандартных языков программирования: Map и Reduce. Их можно задавать при каждом запросе к БД, где нужна агрегация или прихранить на сервере, тогда получится аналог хранимой процедуры. Пусть клиенсткий запрос пришел на один из серверов кластера. Этот сервер пересылает его на другие сервера. Вначале выполняется метод Map на всех "строках" требуемой сущности, преобразуя строку в какое-то представление. Например, если взять агрегат Count - строка преобразует в 1. Код выполняется на параллельно на всех серверах. Далее на каждом сервере выполняется второй метод - reduce, который суммирует все результаты Map. Далее все результаты reduce возвращаются на исходный сервер и там снова выполняется reduce. Ответ возвращается клиенту. Если нужно - есть возможность задать условие фильтра, которое отфильтрует строки до передачи их в Map.
Решение IMHO красивое и быстрое. Кажется, на больших данных и сложном агрегировании может работать быстрее, чем в RDBMS за счет распараллеливания. Конечно зависит от агрегата, объема, СУБД.
Вот реализация Riak https://docs.riak.com/riak/kv/latest/developing/app-guide/advanced-mapreduce/index.html
Вот более сложно, в виде конвейера - Mongo https://www.mongodb.com/docs/manual/core/aggregation-pipeline/
Cassandra имеет ряд базовых функций: COUNT, SUM, MAX, MIN, AVG - из коробки, по позволяет создавать User-Defined Aggregates, работающих по тому же принципу: https://cassandra.apache.org/doc/latest/cassandra/cql/functions.html?highlight=aggregate#aggregate-functions

Из минусов - реализации MapReduce у всех разные, некий vendor-lock присутствует. Но учитывая, что API тоже у всех разное - он в любом случае будет)

#NoSQL
Всем привет!

Сегодня побуду немного вашим кэпом, он же капитан очевидность))))
Не так давно писал про то, как важна "гигиена" в настройках с точки зрения сопровождения - неочевидные названия, дублирование, неиспользуемые настройки могут ввести в заблуждение и привести к ошибкам на ПРОМе.
Хочется развить эту тему.
На самом деле "костыли" и "мусор" вредны не только в настройках, но и в коде, в документации, в тестах. Даже если единственный потребитель - это сам разработчик, который их написал.
Представьте, что код, написанный сегодня, вы не будете трогать полгода. А через полгода прилетит большая задача, требующая этот код серьезно отрефакторить. Разберетесь в своем коде?))) Или проще сразу выкинуть и переписать?)
Ясно, что задача поддерживать код в чистоте, нелегка. Но в отличии от природы, сам код не очистится)))
Нужно выделять время на чистку. Нужно, чтобы "причесывание" кода вошло в привычку.
Небольшое отступление - выделять фиксированное время. "Причесывание", которое занимает больше времени, чем написание кода - не айс.
На какие вещи стоит обратить особое внимание:
1) неиспользуемый код и настройки, ненужные зависимости. IDEA вам в помощь в их поиске.
2) дублирование кода. Тут поможет SonarQube
3) неочевидные названия классов, полей, методов, настроек
4) уже не актуальные названия: класс изменил свой функционал, к классу добавился функционал и название его не отражает
5) код, по которому не непонятно, что он делает. Здесь конечно важен взгляд со стороны. Но если код непонятен внешнему наблюдателю, то через полгода он будет непонятен и вам. Ну и обычно это огромные методы, при просмотре сложно в голове удержать весь алгоритм.
6) рассинхрон кода и документации. Лучше всего, если документации будет мало или она будет генерироваться по коду. Имеет смысл описывать или общие вещи, например, как использовать и собирать ваш сервис, или код, назначение которого неочевидно, например, из-за бизнес-требований.
7) хардкод. Иногда можно, особенно как временное решение, главное не забывать о рефакторинге.

Что я забыл?

А так как я люблю читать и советовать книги - не могу не вспомнить и не порекомендовать классику: Чистый код Роберта Мартина https://www.litres.ru/book/chistyy-kod-sozdanie-analiz-i-refaktoring-6444478
Конспект по книге: https://habr.com/ru/post/485118/

#clean_code
Всем привет!

Сегодня хочу рассказать про полезную библиотеку для Unit-тестирования - Instancio.
Ее цель - заполнение произвольными данными тестовых объектов. По умолчанию - произвольными и not null.
Но можно настройть селекторы для выбора определенных полей по маске (регулярке) и заполнении их данными по определенному алгоритму, определенными константами или null. Умеет заполнять коллекции и вложенные объекты.
Плюса я вижу два:
1) один очевидный - убрать boiler-plate код из тестового кода
2) второй не такой очевидный - при создании тестовых объектов руками часто во всех тестах используются одни и те же константы. Очевидные, типовые. Есть вероятность не попасть с выбранной тестовой константой в какие-то ветки тестовой логики, и т.об. не протестировать часть логики. Instancio же генерирует произвольные данные и может при очередном запуске помочь поймать редкую ошибку до того, как она проявится на ПРОМе.

Вот неплохая статья с примерами использования https://www.baeldung.com/java-test-data-instancio

#unittests #rare_test_libs
Всем привет!

Иногда проект нужно мигрировать - перейти на новую версию платформы, фреймворк, новый формат конфигов. Для преобразований XML есть XSLT. Для JSON - целый зоопарк тулов - https://stackoverflow.com/questions/1618038/xslt-equivalent-for-json
А если нужно преобразовать Java? Есть библиотеки для семанитического анализа кода - вот неплохой список https://stackoverflow.com/questions/2261947/are-there-alternatives-to-cglib
Но анализ - это лишь часть миграции, да и как известно всегда можно добавить новый уровень абстракции)
Сегодня хочу рассказать про OpenRewrite - библиотеку, созданную специально для миграции или масштабного рефаторинга кода.
Введение: https://docs.openrewrite.org/running-recipes/getting-started
Пример кода миграции для произвольного Java класса: https://docs.openrewrite.org/authoring-recipes/writing-a-java-refactoring-recipe
В результате разбора кода строятся Lossless Semantic Trees (LSTs) - ациклические деревья с вложенными элементами, например: CompilationUnit -> Class -> Block of code. Код мигратора напоминает по принципу работы SAX парсер - если кто еще помнит такой)
Не обязательно самому писать код миграции, вот список готовых "рецептов":
https://docs.openrewrite.org/reference/recipes Там есть и преобразование json, xml, yml, maven pom, замена System.out на логгер, миграция с JUnit 4 на JUnit 5, миграция на проверок в модульных тестах с JUnit asserts на AssertJ. И даже миграция на Spring Boot https://www.infoq.com/news/2022/09/spring-boot-migrator
Важно - фреймворк содержит проверки корректности синтаксиса у получаемого кода, но не гарантирует, что код после миграции скомпилируется и будет работать как нужно.
Еще важно - для миграций можно и нужно писать модульные тесты.

#java #migration #refactoring
Всем привет!

Лайфхак для IntelliJ IDEA.
Оказывается Terminal в IDEA умный. В заголовке окна справа есть выдающее меню New Predefined Session, где можно выбрать тип терминала.
У меня на Windows выбор из:
1) PowerShell - по умолчанию
2) cmd
3) Windows Subsystem for Linux (WSL) - если в Windows установлен соответствующий компонент.
4) Git Bash
5) New ssh session - в настройках IDEA есть отдельное окно для управления ssh.
Для каждого типа терминала - один шаблон.
Опция новая, в документации ее описание не нашел, только тикеты в таск трекере IDEA.
Полезна она в первую очередь для тех, кто использует Windows.
Но не только - видел в таск-трекере запрос, на несколько шаблонов для одного типа терминала, например, с разными начальными путями или переменными среды.

Да, терминал по умолчанию, который открывается по кнопке +, можно переопределять на одноименной вкладке в настройках. Но набор предопределенных шаблонов все же удобнее.

Вдогонку еще про одну малоизвестную фичу: терминал может работать в режиме Run Anything https://www.jetbrains.com/help/idea/running-anything.html - т.е. команда будет запускаться не в консоли, а в IDEA. Само собой только та команда, которую IDEA распознала.
Детали см. https://www.jetbrains.com/help/idea/terminal-emulator.html#smart-command-execution

Учитывая наличие хорошего git client, http client, maven\Gradle интеграции - из IDEA во время работы можно не выходить)))

#IDEA
Всем привет!

Есть куча способов работать с реляционными СУБД в Java приложениях.
JPA, JPA JPQL, JPA Native Query, JPA Criteria API, Spring Data JDBC, Spring Data JPA, MyBatis, Jooq. Нативный JDBC в конце концов. Чистый Hibernate API не беру в расчет, т.к. подозреваю, что JPA его заборол)

А недавно я узнал про еще один - в нем соединяются JPA и стримы.
Речь о JPAStreamer https://jpastreamer.org/
Плюс статья на хабре https://habr.com/ru/post/568794/

Идея кажется интересной, т.к. благодаря автогенерации недостающих классов получаем строгую проверку типов на этапе компиляции. И запрос в теле метода, а не в аннотации.
При этом код выглядит более читаемый по сравнению с Criteria API, который так и не взлетел.
Если попытаться сравнить еще с чем-то - код похож на Jooq, но на основе JPA и со стримами. И на .NET LINQ.

Возникла мысль: неплохо бы сделать сравнение вышеперечисленных технологий. Имеет смысл?

#jpa #jdbc #rdbms
Всем привет!

Короткий пост про DDD. На мой взгляд это полезная технология, имеет смысл ее изучить. Но есть важный момент. Доменный слой это в первую очередь доменная логика. Доменные объекты нужны для использования в доменной логике, DTO и @Entity там быть не должно. Но есть куча приложений, где нет доменной логики, или она минимальная. Например, в middleware слое. Или реально микро микросервис) А доменный слой - это в нагрузку mapper-ы из/в DTO.

Поэтому вывод: не надо создавать доменный слой и использовать DDD везде и всегда. Во главу угла ставим целесообразность.

#ddd
Всем привет!

Наверняка все видели псевдографический баннер в логах при запуске Spring приложений. Так вот его текст можно менять, использовать в тексте переменные среды или даже написать логику его генерации на Java. И наконец его можно просто отключить. Делали: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.banner Генератор псевдографики: https://devops.datenkollektiv.de/banner.txt/index.html

Фича наверное малополезная, но забавная)
#spring
Всем привет!

У любого Spring Boot приложения есть настройки. В базовом варианте они хранятся в application.yaml файле. Но вообще говоря алгоритм, по которым Spring ищет эти настройки достаточно сложный. Вот тут https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config и вот тут https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files он описан детальнее.
Когда это "тайное знание" может быть полезно - если нужно задать настройки по умолчанию или, наоборот, переопределить их. Или разобраться - откуда тянется та или иная настройка.

#spring_boot #spring
Всем привет!

Помнится лет 10 назад был такой легендарный вопрос на баше: как пропатчить KDE2 под FreeBSD)
У меня канал немного про другое, но при прочтении данной статьи https://telegra.ph/Kak-sdelat-ssylki-na-metody-druzhelyubnymi-dlya-otladki-07-23 вспомнилось)
А вообще интересен тот факт, что пропатчить JDK как выясняется не так уж и сложно.

#jdk
Всем привет!

Есть такой класс библиотек в Java как mappers. Служат для преобразования одного класса в другой без написания boiler plate кода, в котором:
а) легко допустить ошибку
б) сложно тестировать. Сложно не в плане сложности теста, а из-за того, что постоянно возникает вопрос - а надо ли это тестировать?)

Самым известным mapper-ом в Java является MapStruct. Альтернативы: ModelMapper, Dozer, JMapper, Orica. Если знаете еще - пишите в комментах.

Главная фишка любого mapper - он автоматически копирует значения совпадающих по имени полей, тем самым избавляя от boiler plate кода и потенциальных ошибок.
Также все вышеперечисленные библиотеки умеют работать с вложенными объектами, в частности мапить коллекцию на коллекцию.
Но что делать, если поля не совпадают по имени или по типу? Для этого есть возможность переопределения маппинга для отдельных полей, с указанием нужного имени и преобразования типа.
Но ближе к делу, вот пример маппинга с использованием MapStruct как самой распространенной библиотеки:

@Mapper(componentModel = "spring")
public interface MapStructConverter extends Converter {
@Mapping(source = "status", target = "orderStatus")
Order convert(SourceOrder sourceOrder);
}

Здесь все поля SourceOrder будут скопированы в одноименные поля Order, за исключением поля status, которое станет orderStatus.
Может возникнуть вопрос - это же интерфейс, где исполняемый код?
Код будет сгенерирован при сборке в виде реализации данного интерфейса. Другие вышеперечисленные библиотеки используют либо рефлексию, либо генерация байткода для маппинга.

Главный вопрос - когда это все нужно?
Общий ответ - маппинг модели в DTO или DTO в DTO.
Более конкретный:
1) Полностью совпадающие по полям объекты. Например, это могут быть сгенерированные по разным версиям xsd или Json Schema DTO, хранящие одни и те же данные.
Или если ваши доменные объекты совпадают с DTO. Идеальный кейс для применения mappers.
2) Подвид варианта 1 когда в объектах различаются пару полей.
3) Маппинг коллекций похожих объектов, это тоже boiler plate код. Тут надо смотреть насколько похожи объекты.

Что важно - не использовать библиотеки маппинга для достаточно сильно различающихся объектов, а делать его руками.
Т.к. в таком случае проявляются минусы данных библиотек:
1) этап кодогенерации сам по себе не является злом, но увеличивает сложность сборки. А если вместо генерации используется рефлексия - уменьшается производительность.
2) когда кастомного маппинга становится слишком много, то это усложняет чтение кода. Ты видишь код маппинга, а половины полей там нет...
3) часто кастомный маппинг делается в аннотациях, а это значит что IDEA не подсветит ошибки, они будут видны только во время сборки

P.S. Есть идея сделать статью по сравнению всех вышеперечисленных библиотек.

#java #mappers
Всем привет!

Как я писал ранее - паттерны полезны для понимания кода, т.к. облегчают чтение кода, формирую некий язык.
Но этот язык должен быть достаточно четким.
Так вот - есть такие структурные паттерны. https://ru.wikipedia.org/wiki/Структурные_шаблоны_проектирования
И среди них есть много на первый взгляд похожих паттернов для подмены одного объекта\сервиса другим. Да и на второй тоже сложно понять где что использовать)
Как же их различать?
Здесь и далее я употребляю слово интерфейс, но если паттерн используется для внешних интеграций, то его можно заменить на API.

Декоратор - добавляет поведение не меняя исходный интерфейс. Пример: добавить к сервису логирование, метрики или аудит.
Прокси - обеспечивает контроль доступа не меняя исходный интерфейс. Пример: проверка доступа, rate limiting.
Фасад - упрощает интерфейс для работы с объектом. Пример: SLF4
Адаптер - адаптирует один существующий интерфейс к другому. Пример: реализация Iterable для объекта, чтобы по нему можно было итерироваться
Gateway (шлюз) - паттерн уровня приложения https://martinfowler.com/articles/gateway-pattern.html в отличие от указанных выше паттернов проектирования, служит для добавления единого интерфейса к внешней системе. Пример: шлюз для доступа к любому внешнему сервису.

Если я что-то забыл или не согласны с чем-то - пишите.
Да, еще важный момент. Лично меня бесит, когда все классы с логикой в приложении называются Service. Не забывайте про 5 шаблонов, описанных выше!

#patterns
Всем привет!

Пару слов про gRPC - новый модный молодежный протокол для интеграции приложений.
Лично я всегда считал основных его преимуществом бинарный формат данных, а, следовательно, быстродействие.
Но это один из трех китов - трех преимуществ gRPC.
Какие же два других?

2) проблема общепринятого сейчас REST-а - в нем не было и нет встроенной схемы данных. Да, есть JSON Schema, OpenAPI и Consumer Driven Contracts. Но где-то они есть, а где-то - нет, причем это могут быть работающие вместе клиент и сервер) Можно же просто получить строку ответа и распарсить ее самостоятельно. И чем больше компания, чем больше у нее микросервисов внутри - тем сложнее будет поддержка и обновление зоопарка REST сервисов со временем. С этим столкнулись Google - разработчик gRPC, Netflix, Dropbox, Facebook - разработчик Thrift, аналога gRPC. В gRPC она есть, из нее генерируется код сервера и клиента. Не весь конечно, сервисная часть - без инфраструктурной и бизнес-логики. Schema first подход, без вариантов)

3) в схему gRPC изначально встроена возможность стриминга. Т.е. можно работать в режиме запрос-ответ, а можно использовать такие комбинации как:
а) запрос - несколько ответов
б) несколько запросов - один ответ
в) двунаправленный стриминг, где логика последовательности запросов и ответов определяется бизнес-процессом.
REST такое не умеет.
Причем схема со стримингом отличается от схемы запроса-ответа буквально одним словом. Код сервера\клиента конечно отличается сильнее)

Из минусов я бы отметил применимость в первую очередь для внутренних взаимодействий, наружу лучше выставлять REST или GraphQL, т.к. потребителям они понятнее. Также могут быть проблемы при изменениях, ломающих обратную совместимость, т.к. из-за бинарности и компактности формата данных жестко зафиксирован порядок полей в запросе\ответе. Возможно где-то будет проблемой то, что gRPC требует HTTP/2, в том же k8s\Openshift траффик HTTP и gRPC нужно разводить по разным портам. Ну и лично меня очень удивляет использование термина Stub в сгенерированном клиенте. Stub и в "боевом" коде... выглядит странно)))

#gRPC #integration #performance
Всем привет!

Внимание, короткий пост-вопрос.
Кто для чего использует аспекты (AspectJ) в Java? И использует ли?) Если нет, то почему?
Я вижу одно "красивое" применение - реализация шаблона Декоратор, о котором я упоминал в https://t.me/javaKotlinDevOps/124
Самые очевидные примеры, чем можно "декорировать" метод - журналирование, мониторинг, аудит.
Напишите в комментах, плиз)

#java #aspects
Всем привет!

Я уже писал про библиотеку с улучшенными assert-ами - Truth https://t.me/javaKotlinDevOps/51
Вот пример вывода ошибки в лог для такой банальной вещи, как проверка результата функции на true.
"Ванильный" JUnit 5:

java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:87)
at org.junit.Assert.assertTrue(Assert.java:42)
at org.junit.Assert.assertTrue(Assert.java:53)

Понятно, что проверка не прошла, не понятно - где, пока не посмотришь код теста. Дело упрощается, если в методе один assertTrue, есть рекомендации так делать - один тест = одна проверка. Но это не всегда удобно. И в любом случае если код написан какое-то время назад - придется лезть в тест.

JUnit5 + Truth:

value of: needFilter(...)
expected to be true

Совсем другое дело!)))

#unittests
Всем привет!

Заметка из серии "хозяйке на заметку" про Docker.
При сборке docker образа мы можем передать в команду docker build контекст сборки.
Как правило во всех примерах это последний аргумент в виде точки. Т.е. текущего каталога.
Что это такое?
Это каталог в хостовой системе, из которого можно копировать данные в образ командами COPY в Dockerfile. Да, Dockerfile тоже должен лежать в контексте
Как его можно задать при сборке:
1) указав каталог
2) указав архив с нужными файлами
3) указав git репозиторий (!)
4) указать только Dockerfile как контекст, если копировать в образ ничего не надо
5) указать только Dockerfile как контекст, и с помощью команд RUN wget или RUN curl в нем выкачать данные для образа по http

Что важно - docker client при запуске сборки упаковывает все содержимое контекста и оправляет его docker daemon. Т.е. указывать корневую папку точно не стоит) Лучше создать новую папку и положить туда только необходимое. Альтернатива - использовать файл .dockerignore
Детали - https://docs.docker.com/engine/reference/commandline/build/

P.S Оффтопик - похоже создатели Docker вдохновлялись git-ом. docker push\pull\commit И упомянутый выше .dockerignore

#docker
Всем привет!

В протоколе HTTP, а также gRPC, основанном на HTTP/2, есть возможность передачи параметров запроса разными способами:
1) в заголовках
2) в параметрах
3) в path части url
4) в теле запроса

Когда что использовать? Ниже будут типовые варианты, в реальных кейсах конечно же возможны отклонения.

1) headers - не бизнесовые, технические данные. Примеры: id для tracing, для логирования, маршрутизации, токены\куки для авторизации. Ключевой момент - в бизнес-процессе эти данные не используются
2) params - как правило используются в GET запросах, по сути фильтр для выборки данных
3) path - как правило в REST запросе, построенном на концепции REST ресурсов, для идентификации ресурса: GET\PUT\DELETE .../order/{nnn}
4) body - атрибуты объекта для создания\обновления в REST. Любая закрытая информация - https шифрует заголовки и тело запроса, но само собой не шифрует параметры и путь. Большие объемы данных, в URL есть ограничения на длину, причем в разных браузерах и проксях они разные, лучше к ним не приближаться). По объему данных в заголовках также есть ограничения на веб-серверах, привет "413 Entity Too Large". Размер можно подкрутить в большую сторону, но опять же это не наш метод)

#rest #api
Всем привет!
При использовании JPA в проекте может возникнуть вопрос - нужно ли JPA Entity мапить на DTO, а точнее когда это нужно делать?
Вот хорошая статья с ответом на этот вопрос: https://thorben-janssen.com/object-mapper-dto/
#jpa #mappers
Всем привет!

При создании образа Docker нужно выбрать базовый образ - образ содержащий минимальный набор необходимых CLI утилит, сервисов, конфигураций и фреймворков необходимых для того, чтобы заработало ваше приложение. Какой минимальный размер этого образа?
Ответ - нулевой.
Есть специальное зафиксированное имя scratch - в DockerFile будет выглядеть как:
FROM scratch
https://docs.docker.com/build/building/base-images/#create-a-simple-parent-image-using-scratch
Но сразу скажу - там не будет даже shell - sh, bash. Вообще ничего.
Все необходимые утилиты нужно будет добавлять руками в процессе сборки образа.
Путь для сильных духом) Или случай каких-то очень специфичных образов.

Что же должно быть в образе для Java приложения:
1) JRE и все необходимое для того, чтобы она запустилась)
Далее все зависит от необходимости в отладке внутри образа. По моему опыту она нужна, а значит нужны:
2) Linux shell
3) CLI утилиты для работы с файловой системой
4) curl\wget - для проверки маршрутизации в k8s
5) CLI редактор - vi, nano - для правки конфигов "на ходу"
6) возможно ssh сервер
7) возможно утилиты из состава JDK

Минималистичные образы можно поискать по словам busybox и alpine.
Размер - до 5 Мб(!) Само собой они без Java. Но есть основанные на них образы с Java размером порядка 55 Мб с JRE (Linux Temurin Java 11). Для сравнения обычные образы c JRE - 85-150 Мб. С JDK размер можно умножать на 3.
Что важно - в Linux куча настроек, поэтому важно выбрать проверенный и оптимизированный для работы с Java базовый образ.

P.S. Точно не стоит использовать образы с полноценным Linux - Debian, Ubuntu... Размер будет порядка 500+ Мб, а пользы - ноль.

P.P.S. Еще нужно помнить, что активное использование Spring Framework легко добавляет в размер образа ~ 100 Мб)

#docker