Source code of my website
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

✨ : expand timbernetes draft with JVM resource resizing insights and VPA example

+127 -6
content/posts/2026/2026-03-20-timbernetes/console-top.png

This is a binary file and will not be displayed.

+127 -6
content/posts/2026/2026-03-20-timbernetes/index.md
··· 81 81 .mode(Mode.Throughput) 82 82 .timeUnit(TimeUnit.SECONDS) 83 83 .forks(0) 84 + .threads(2) 84 85 .result(tempFile.getAbsolutePath()) 85 86 .resultFormat(ResultFormatType.TEXT) 86 87 .build(); ··· 403 404 GET localhost:8080/metrics 404 405 405 406 cpu_count 1.0 406 - process_cpu_load 1.4755859375 407 + process_cpu_load 0.18505859375 407 408 jvm_memory_max_mb 396.375 408 409 jvm_memory_used_mb 4.081321716308594 409 410 ``` ··· 448 449 449 450 ```shell 450 451 cpu_count 1.0 451 - process_cpu_load 0.875 452 + process_cpu_load 0.99267578125 452 453 jvm_memory_max_mb 396.375 453 454 jvm_memory_used_mb 353.49063873291016 454 455 ``` ··· 481 482 ```bash 482 483 & kubectl events --for pod/timbernetes-demo 483 484 LAST SEEN TYPE REASON OBJECT MESSAGE 484 - 17m Normal NotTriggerScaleUp Pod/timbernetes-demo pod didn't trigger scale-up: 485 - 17m Warning FailedScheduling Pod/timbernetes-demo no nodes available to schedule pods 486 - 17m (x4 over 17m) Warning FailedScheduling Pod/timbernetes-demo no nodes available to schedule pods 487 485 16m Normal Scheduled Pod/timbernetes-demo Successfully assigned default/timbernetes-demo to scw-timbernetes-dem-timbernetes-demo-po-1b7caa 488 486 16m Normal Pulling Pod/timbernetes-demo Pulling image "rg.fr-par.scw.cloud/timbernetes-demo/java:latest" 489 487 15m Normal Pulled Pod/timbernetes-demo Successfully pulled image "rg.fr-par.scw.cloud/timbernetes-demo/java:latest" in 14.487s (27.916s including waiting). Image size: 109821985 bytes. ··· 493 491 112s Normal ResizeCompleted Pod/timbernetes-demo Pod resize completed: {"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"2","memory":"1Gi"},"requests":{"cpu":"2","memory":"1Gi"}}}],"generation":2} 494 492 ``` 495 493 494 + Les deux dernières lignes font bien état de la modification. 495 + 496 + Quand je requête à nouveau le endpoint `/metrics`, j'obtiens alors la réponse suivante : 497 + 498 + ```http request 499 + GET localhost:8080/metrics 500 + 501 + cpu_count 2.0 502 + process_cpu_load 0.04296875 503 + jvm_memory_max_mb 396.375 504 + jvm_memory_used_mb 362.24312591552734 505 + ``` 506 + 507 + On observe que le nombre de CPU visibles par le JVM a changé, c'est une bonne nouvelle. 508 + Par contre, comme on l'attendait, la Heap maximale que peut consommer la JVM n'a pas changé. La Heap est configurée au démarrage de la JVM et n'est donc pas redimensionnée à chaud, même si le pod a plus de RAM disponible. 509 + 510 + Une fois le stress test lancé, on observe les métriques suivantes : 511 + 512 + ```http request 513 + GET localhost:8080/metrics 514 + 515 + cpu_count 2.0 516 + process_cpu_load 1.90654296875 517 + jvm_memory_max_mb 396.375 518 + jvm_memory_used_mb 355.3716583251953 519 + ``` 520 + 521 + Un `top` dans le container permet de confirmer ce qu'on voit avec la métrique, le process utilise 200% de CPU, les 2 coeurs sont bien exploités par la JVM. 522 + ![img.png](console-top.png) 523 + 524 + ### Redimensionnement et dernier tir 525 + 526 + Pour compléter les tests, je redimensionne à nouveau le pod, cette fois-ci avec des valeurs à la baisse, pour revenir aux valeurs initiales : 527 + 528 + ```bash 529 + kubectl patch pod timbernetes-demo --subresource resize --patch \ 530 + '{"spec":{"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"512Mi"},"requests":{"cpu":"1","memory":"512Mi"}}}]}}' 531 + 532 + pod/timbernetes-demo patched 533 + ``` 534 + 535 + Les évènements sur le pod affichent bien que le resizing a été exécuté : 536 + 537 + ```bash 538 + kubectl events --for pod/timbernetes-demo 539 + LAST SEEN TYPE REASON OBJECT MESSAGE 540 + 53s Normal ResizeStarted Pod/timbernetes-demo Pod resize started: {"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"512Mi"},"requests":{"cpu":"1","memory":"512Mi"}}}],"generation":3} 541 + 52s Normal ResizeCompleted Pod/timbernetes-demo Pod resize completed: {"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"512Mi"},"requests":{"cpu":"1","memory":"512Mi"}}}],"generation":3} 542 + ``` 543 + 544 + La métrique affiche de nouveau 1 CPU disponible, aucun changement au niveau de la RAM comme attendu : 545 + 546 + ```http request 547 + GET localhost:8080/metrics 548 + 549 + cpu_count 1.0 550 + process_cpu_load 0.03466796875 551 + jvm_memory_max_mb 396.375 552 + jvm_memory_used_mb 206.46312713623047 553 + ``` 554 + 555 + Pas de surprise non plus sur ce redimensionnement qui est aussi effectué à chaud. 556 + 557 + Enfin, pour observer ce qu'il se passerai avec un redimensionnement sur une RAM déjà consommé, j'opère un redimensionnement à une valeur de RAM inférieure à celle que consomme déjà le pod. 558 + Je dois m'attendre à un OOMKill, puis un redémarrage du pod, qui reprendra donc un taille de Heap à 80% de la RAM disponible, vu que ce dimensionnement est fait au démarrage de la JVM. 559 + 560 + ```bash 561 + $ kubectl patch pod timbernetes-demo --subresource resize --patch '{"spec":{"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"128Mi"},"requests":{"cpu":"1","memory":"128Mi"}}}]}}' 562 + pod/timbernetes-demo patched 563 + ``` 564 + 565 + Cette fois-ci, lorsque je regarde les évènements du pod, j'observe une erreur : 566 + 567 + ```bash 568 + $ kubectl events --for pod/timbernetes-demo 569 + 32s Normal ResizeStarted Pod/timbernetes-demo Pod resize started: {"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"128Mi"},"requests":{"cpu":"1","memory":"128Mi"}}}],"generation":5} 570 + 32s Warning ResizeError Pod/timbernetes-demo Pod resize error: {"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"128Mi"},"requests":{"cpu":"1","memory":"128Mi"}}}],"generation":5,"error":"cannot decrease memory limits: [attempting to set pod memory limit (134217728) below current usage (418054144), attempting to set container \"timbernetes-demo\" memory limit (134217728) below current usage (418054144)]"} 571 + ``` 572 + 573 + Kubernetes refuse de redimensionner le pod à chaud, car la RAM consommée est supérieure à la nouvelle taille de RAM, ce qui est cohérent. 574 + 575 + ## Conclusion 576 + 577 + Le redimensionnement des ressources à chaud fonctionne à merveille, et le comportement de la JVM est bien celui auquel on s'attendait : le nombre de CPU est détecté dynamiquement, et les threads schedulés par la JVM peuvent exploiter pleinement les coeurs ajoutés. 578 + 579 + Concernant la RAM, étant donné que la JVM fix sa quantité de Heap au démarrage, et que cette valeur ne peut pas être ajustée au runtime, modifier la RAM allouée à un pod Java n'a aucun effet. 580 + 581 + Des [drafts de JEP](https://openjdk.org/jeps/8359211) proposent que les différents _Garbage Collector_ (G1, ZGC et Serial) soient modifiés pour pouvoir ajuster à chaud la taille de la Heap en fonction de l'environnement dans lequel s'exécute la JVM. Ces évolutions permettraient donc à terme de pouvoir bénéficier pleinement de cette feature de Kubernetes. 582 + 583 + Les VPA (_Vertical Pod Autoscaler_) ont également un nouveau mode appelé `InPlaceOrRecreate` qui permet de redimensionner les pods sans les redémarrer, et de forer une recréation si le redimensionnement n'est pas possible. Cette fonctionnalité rend maintenant l'utilisation des VPA pertinentes pour des applications Java. On peut imaginer qu'un pod verrai son nombre de CPU ajusté à chaud, plutôt que de faire de la scalabilité horizontale. 584 + 585 + Il faut cependant limiter cet usage à au CPU, et utiliser un VPA est toujours incompatible avec un HPA, donc l'intérêt reste encore un peu limité. 586 + 587 + Voici un exemple de VPA pour une application Java : 588 + 589 + ```yaml 590 + apiVersion: autoscaling.k8s.io/v1 591 + kind: VerticalPodAutoscaler 592 + metadata: 593 + name: timbernetes-demo-vpa 594 + spec: 595 + targetRef: 596 + apiVersion: "apps/v1" 597 + kind: Deployment 598 + name: timbernetes-demo 599 + updatePolicy: 600 + updateMode: "InPlaceOrRecreate" 601 + resourcePolicy: 602 + containerPolicies: 603 + - containerName: "timbernetes-demo" 604 + minAllowed: 605 + cpu: 100m 606 + maxAllowed: 607 + cpu: 2 608 + controlledResources: 609 + - cpu 610 + controlledValues: RequestsAndLimits 611 + ``` 612 + 496 613 ## Liens et références 497 614 498 615 Kubernetes : ··· 506 623 * La documentation du CLI Scaleway : [Creating and managing a Kubernetes Kapsule with CLI (v2)](https://www.scaleway.com/en/docs/kubernetes/api-cli/creating-managing-kubernetes-lifecycle-cliv2/) 507 624 * [Scaleway Instances datasheet](https://www.scaleway.com/en/docs/instances/reference-content/instances-datasheet/) 508 625 JMH : 509 - * Le tuto de Baeldung [Microbenchmarking with Java](https://www.baeldung.com/java-microbenchmark-harness) 626 + * Le tuto de Baeldung [Microbenchmarking with Java](https://www.baeldung.com/java-microbenchmark-harness) 627 + Java : 628 + * [JEP draft: Automatic Heap Sizing for G1](https://openjdk.org/jeps/8359211) 629 + * [JEP draft: Automatic Heap Sizing for ZGC](https://openjdk.org/jeps/8377305) 630 + * [JEP draft: Automatic Heap Sizing for the Serial Garbage Collector](https://openjdk.org/jeps/8350152)