Долго ли, коротко ли, служил я java-разработчиком, да судьба-злодейка, крутанула меня в Platform Engineer’ы. Овладел я ремеслом devops’ским да сисадминским, но по ночам снится мне low-level Java, но быль моя совсем чуть-чуть об этом будет - поглаголю о JVM опциях, дабы приложение в k8s без дури запускать. Расскажу, как доблестно (а может, и безрассудно) SeaweedFS S3 storage выбирал, как кластер k8s поднимал, не щадя живота своего. Читай сии записки, запивая иван-чаем или медовухой: авось, умная мысль глянет меж строк. Не глянет — так хоть посмеёшься над моим devops экспириенсом.
Сказ первый: как java-дев k8s-град каменный возводил (и что из этого вышло)
Из заморских далей вернувшись, пришлось доблестному ex-java-деву кластер k8sный возводить. Да не managed-облачный, подачку чужую, а свой - рукотворный, кровью и потом выкованный.
Но не робкого десятка наш молодец - не раз уже kops'ом кластера на облаках заморских разворачивал. Стоит теперь на распутье, как богатырь у камня былинного, и три пути перед ним яснеют:
Kubeadm- легковесная приблуда от самогоk8s, поднимает тот самый ванильныйk8sкластер, да его аддоны. Помним, что в таком виде -k8sкластер, является кластером в вакууме, требующим всевозможной “донастройки” (от сontainer runtime to cni). Отсюда вытекает необходимость в большом количестве ansible playbook.Kubespray- по сути упомянутый kubeadm + содержит в себе все ansible плейбуки необходимые для установки и обновления (предустановка и обновление всех компонентов - runc, containerd и т.д), плейбуки для любых cni. Часть компонентов опциональна и конфигурируема в конфигурационном файле. В этом разнообразии ansible playbook и сила, и слабостьkubespay- инсталляции большого количества лишних утилит/компонентов и т.д, отчего время разворачивания/инициализации новой ноды критично увеличивается. Также, как и у всех фактических компоновщиков ansible-playbook дляk8sкластера (сравниваю с небезызвестным kops), имеется проблема с поддержкой новых версии того или иного тулинга или его конфигурации. Например, чтобы включить фичу cilliumgateway - пришлось хардкодить в конфигмапе (сорцах)kubespray, такая же проблема/решение нашлись для bgp анонсов vip-адресов кластера.Rancher- не ansible-like подход к инсталляции. Предоставляет не только спектр разнообразных возможностей дляk8sкластера, а целую экосистему - там же и веб-интрефейс, и block storage - сeph. Те же проблемы с небольшим отставанием по наличию тех или иных фич. Не очень подходит для бюджетных инсталляций.
Сердце витязя чует, что грядёт время, когда быстрота присоединения нод станет мерилом силы кластерной - ибо autoscaling, словно вихрь весенний, потребует, дабы новые воины - виртуалки восставали из цифрового небытия в мгновение ока. Для сего замыслил devops-молодец cloudinit-заклятье — дабы едва родившись, виртуалки сами kubeadm’ом крестились, плейбуками доспехи надевали и в строй безропотно вставали. Ныне же путь Kubespray’ем держится, ибо стезя сия хоть и долгая, да проверенная.
Избрав kubespray пришлось повоевать devops молодцу с плейбуками, нужды-то были — свежие фичи Ciliumовые во град Kubernetes’ский ввести: и BGP-announcement, и CiliumGateway, а хардкодить в сорцах не гоже. Решил молодец со иглы готовых плейбуков соскочить — взял Helm, да как сокол на добычу пикирует, уставил Cilium свежий, ядрёный, да завелся кластер могучий.
Однако не безмятежно выдался путь представления кластера миру - сети корпоративные, что драконы лютые, козни строили. По замыслу чистому, Cilium BGP Control Plane должен был верой и правдой служить, да вот беда - роутеры внешние, как стены каменные, BGP не ведали (хотя в планах великих их к разуму привести). Пришлось горемычному девопсу на каждую ноду Bird'а мудрого посадить - принимает тот BGP-весты от Cilium, да перешептывает их в сеть корпоративную через RIP, словно гонец усталый.
Много еще подвигов предстоит витязю нашему: то динамический провижининг нод k8s на виртуалках организовать, то роутеры корпоративные к BGP приобщить, то да се.... много чего еще в свитке задач нераскрытом значится!
Сказ второй: как Java-витязь S3-кладовую выбирал да обустраивал
Minio — как старый друг: и кривоват, но знаком. SeaweedFS — как русалка: манит, да кто знает, что на глубине?
Явились как-то к нашему молодцу собратья из соседнего подцарства IT-шного. "Дай нам, - молят, - сторадж вороватый: чт��б и быстрый был, и копеечный, а сколько добра в него складывать будем - и сами не ведаем. Может, архивные дани скопим, может, свежие потоки данных пустим".
Почесал молодец голову русистую, да и отринул Ceph сходу: не по силам витязю-одиночке сего чудища многокомпонентного содержать - и ресурсов не хватит, и подмоги нет.
Задумался молодец меж двух стезей: Minio, кое хоть и с документацией кривобокой, да знакомое, и SeaweedFS неведомый, где лишь README на GitHub’е, словно свеча во тьме, светит. Но приглянулся SeaweedFS манерой хитрою — файлы воедино сливать, да ноды добавлять без лишней кручины и ребалансировочных мук. Так и решили.
Пока летит, как сокол ясный. Коли ж баги нежданные нагрянут — пути три предстанут: либо на Ceph с пулами разными для hot да cold данных перейти, либо Minio кластерами раскидать, а то и вовсе на S3 облачный сдаться... Но то — сказ уже иной.
Сказ третий: как Java-витязь JVM-опции в k8s доспехи обряжал
Disclaimer. Актуально для версии
jdk >= 8u131
Узрел наш молодец шаблоны для запуска JVM-сервисов - и взгрустнулось ему: все как под копирку писано, да к тому же явно не времен JDK 11. "Не дело это!" - молвил он, засучив рукава цифровые, и принялся за перепись устаревших скрижалей.
Разные memory limits/requests - коль любишь unpredictable утечки memory исследовать

Предположил наш молодец, что разработчики голову чесали, когда OOMKiller поды Java’шные косил, а память из heap’а не утекала (не ведали холопы про non heap и специфику OOMKillera). И ведь правда — нашёл витязь в трекере пару инцидентов плавающих, уравнил все, и дальше править пошел.
Limits cpu - что ж экономим, батюшка?

Что ж, ты брат, девелопер, экономишь, троттлинга али боишься иль думаешь, что потоки к ядрам гвоздями окаянными прибиваешь? Так, чтоб с троттлингом бороться реквестов достаточно, ну а планировщики, что ядер, что потоков по-другому работают. А коль cpu на ноде хватать не будет, так под твой все равно заэвикчен не будет. Ведомо, что в большинстве случаев, Java-матушке cpu больше всего на старте надобно.
В 99% случаев не нужны тебе cpu лимиты в проде, может для тестов нагрузочных актуально будет в окружении каком-то.
За хардкод в аргументах нечаянный - бьют отчаянно

О Xmx речь отдельная будет, но вот окаянный хардкод - зачем? Ведь коли в контроллере память увеличишь/уменьшишь, сии цифры в Xmx уже не сменишь (разве что контроллер удалять да заново разворачивать). Пусть хоть так (хотя, вестимо, забудешь поменять при смене ресурсов - но об том в следующем абзаце). Зато когда гореть начнёт — хоть потушить быстро сможешь!
command: ["java"]
args: ["-jar", "app.jar" , $(JVM_ARGS)]
- name: JVM_ARGS
value:”-Xmx2128"На heap и gc надейся, но и сам не плошай
Что только не узрел девопс-молодец в тех пропертях: и Xmx одинокий без пары Xmn, и Xmx с Xmn разногласные, и вовсе пустоту - словно поле без ограды, где хип-память вольно гулять растекается. И молвил девопс молодец истины:
Heap ограничивать разработчик должен. Ведь Java работает не только с хипом, а всяко разное и в non-heap хранит.
XmncXmxодинаковые ставь - зачем ресурсы тратить на resize?Ну а мы же в k8s царстве живем, так вообще не надо тебе
XmncXmx. ИспользуйInitialRAMPercentageиMaxRAMPercentage, так хоть когда значение памяти меняешь, в одном месте достаточно контроллер редактировать. Коль heap у тебя маленький где-то будет(меньше 200Мб), не забудь поставитьMinRAMPercentage. Значения эти никак не 100% должны быть, чем меньше memory даешь - тем больше процентов на non-heap оставлять надо. (Худо-бедно универсальное значение 70%, но каждый случай уникальный)
- name: JVM_ARGS
value: "-XX:InitialRAMPercentage=60.0 -XX:MinRAMPercentage=50.0 -XX:MaxRAMPercentage=60.0"
/*
MinRAMPercentage устанавливает максимальный heap size для малого количества memory.
Не��минг не самая сильная сторона Java
*/А коли heap у тебя небольшой, не чурайся
AlwaysPreTouchопцию попробовать, которая физически выделяет все страницы памяти, отведенные под heap
Старовером быть негоже витязю молодому!
С заменой Xms и Xmn/Xmx на InitialRAMPercentage и MinRAMPercentage/MaxRAMPercentage уже порешали. Но помнить хорошо, что Java на месте не стоит, и коль версию Java обновили, загляни в release notes, да попробуй (с прода не начинай) фичи свежевпиленные, ибо пользу принести они могут хорошую. Например, UseStringDeduplication доступный с Java >=8u20 (но имеет смысл только для больших Heap, так как работает с G1 GC) может impact помочь тебе сыскать.
GC — поле битвы для отважного Java-витязя
GC тропы оказались нехоженными в этом царстве-государстве. Поэтому разгулялся наш devops молодец с экспериментами над GC, но помнил:
G1хорош для больших heap . Коль JDK > 8 у тебя и heap скромный (точно <=2GB, а дальше смотреть надо), попробуй переключи GCА если heap большой и JDK поновее (>=15) не чурайся
ZGC(но вернее с JDK >=21, когда ZGC лик дженеративный обрел)У каждого
GCкуча опций, разбирайся, экспериментируй - и получай профит на метриках
Коль heap большой, то попробуй включить THP
Тропа сия избита, да не всяк разработчик памятует: когда память великими объёмами оперируешь, можно попытаться аллоцировать её крупными страницами — а именно, THP (Transparent Huge Pages) включить. Да вот незадача, часто выключена опция эта на уровне ядра, поэтому для экспериментов не достаточно Java опций, базовый image с JDK правильный собрать надобно. Однако редко такое встречается в ныне разрабатываемых приложениях корпоративных, пожалуй только в монолитах древних. Коли разуму не достает, в скрижали вечные загляни.
Негоже про non-heap забывать.
Память то коварная - течь может не только в heap. Смотри и non heap опции тоже. Для оптимизаций тоже полюшко не пахано - тот же codecache.
Далеко не все описал я, ибо точно уже не эксперт, но что сказать хотел, коль пишешь на Java, то не зазорно одним глазком на JVM проперти по��матривать, да оптимизировать пытаться. Да и на девопса надейся, но и сам не плошай - проверяй чарты и темплейты сервисов своих, дабы онколл звонким не был.
Чтец благодарный! Спасибо, что прошёл сей путь до конца. Коли бредятину в тексте узришь — милости просим в комменты: поспорим, посмеёмся. А ежели ошибку найдёшь или мудрую мысль подкинешь — низко кланяюсь в пояс. До новых сказов!
