Source code of my website
1
fork

Configure Feed

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

✨ : finalize timbernetes article

+102 -47
content/posts/2026/2026-03-20-timbernetes/console-top.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/console-top.webp

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/cover.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/cover.webp

This is a binary file and will not be displayed.

+102 -47
content/posts/2026/2026-03-20-timbernetes/index.md
··· 6 6 - kubernetes 7 7 - java 8 8 - scaleway 9 - draft: true 10 9 --- 11 10 12 11 La version 1.35 de Kubernetes, nommée "Timbernetes", est sortie le 17 décembre dernier (ça passe vite !) et est déjà disponible sur toutes les bonnes plateformes de Cloud. ··· 17 16 18 17 <!--more--> 19 18 20 - ## Une appli simple pour faire un bench 19 + ## Une appli Java simple pour faire un bench 21 20 22 - Pour pouvoir tester cette feature, je veux pouvoir charger un peu le CPU et la Heap d'une JVM. J'ai donc demandé à OpenCode / DevStral de me générer une petite appli qui utilise JMH pour bencher un bon vieux _fibonnaci_ : 21 + Pour pouvoir tester cette feature, je veux pouvoir charger un peu le CPU et la Heap d'une JVM. J'ai donc codé une petite appli qui utilise JMH pour bencher un bon vieux _fiboacci_ : 23 22 24 23 ```java 25 24 public class CPUStress { 26 25 27 26 @Benchmark 28 - public void fibonacciCalculation(Blackhole blackhole) { 27 + public void fibonacciBench(Blackhole blackhole) { 29 28 for (int i = 0; i < 50; i++) { 30 29 var result = fibonacci(i); 31 30 blackhole.consume(result); ··· 33 32 } 34 33 35 34 private long fibonacci(int n) { 36 - if (n <= 1) return n; 37 - long a = 0, b = 1; 35 + if (n <= 1) { 36 + return n; 37 + } 38 + long a = 0; 39 + long b = 1; 38 40 for (int i = 2; i <= n; i++) { 39 41 long temp = a + b; 40 42 a = b; ··· 56 58 private final List<byte[]> memory = new ArrayList<>(); 57 59 58 60 @Benchmark 59 - public void allocateMemory(Blackhole blackhole) { 61 + public void memoryBench(Blackhole blackhole) { 60 62 try { 61 63 memory.add(new byte[MB_TO_ALLOCATE * 1024 * 1024]); 62 64 blackhole.consume(memory); ··· 98 100 ``` 99 101 100 102 Le bench est lancé sans forker la JVM, ce qui va me permettre de voir quel est l'impact d'un redimensionnement de la JVM pendant son exécution. 101 - J'ai exposé le démarrage du bench dans un endpoint HTTP `/stress/start` et la récupération des résultats dans `/stress/results`. 103 + J'ai aussi positionné 2 threads, histoire de voir les impacts lorsque le nombre de CPU disponibles sera supérieur à 1. 104 + J'ai exposé le démarrage du bench dans un endpoint HTTP `/stress/start`. 102 105 103 106 ## Exposer quelques métriques avec Micrometer 104 107 ··· 204 207 En faisant un test rapide avec Docker, je peux vérifier que mes métriques sont correctes, en contraignant le nombre de CPU et la RAM visibles par le container : 205 208 206 209 ```shell 207 - docker image build -t timbernetes-demo . 210 + $ docker image build -t timbernetes-demo . 208 211 209 - docker container run --rm --cpus=2 --memory=512m -p 8080:8080 timbernetes-demo 212 + $ docker container run --rm --cpus=2 --memory=512m -p 8080:8080 timbernetes-demo 210 213 ``` 211 214 212 215 ```http request ··· 218 221 jvm_memory_used_mb 4.075630187988281 219 222 ``` 220 223 221 - ## Instancier un cluster sur Scaleway 224 + Le code est prêt, je peux maintenant le déployer sur un cluster Kubernetes. 222 225 223 - Pour pouvoir expérimenter et jouer avec ces features, j'ai choisi d'utiliser un cluster que j'instancie sur Scaleway. 226 + ## Instancier un cluster Kapsule sur Scaleway 227 + 228 + Pour pouvoir expérimenter et jouer avec ces features, j'ai choisi d'utiliser un cluster Kapsule sur Scaleway. 224 229 Ça me permet de valider un vrai comportement de production, là où utiliser un _minikube_ ou un _kind_ en local pourrait avoir des comportements différents. 225 230 226 231 Armé de mon meilleur _CLI_, j'enchaine donc les commandes. ··· 239 244 La version 1.35.2 est celle qui m'intéresse aujourd'hui, je vais donc pouvoir déployer un cluster avec cette version : 240 245 241 246 ```bash 242 - # création du cluster 243 247 $ scw k8s cluster create name=timbernetes-demo version=1.35.2 244 248 245 249 ID 100d3564-66b2-4439-bcc2-b5e76cd6d1fb ··· 266 270 267 271 Le cluster apparaît dans la console : 268 272 269 - ![scaleway-console-cluster-starting](scaleway-console-cluster-starting.png) 273 + ![Console Scaleway : Cluster en cours de création](scaleway-console-cluster-starting.webp) 270 274 271 275 Une fois le cluster créé, il faut lui ajouter un _node-pool_, avec une petite machine _DEV1-M_ (3CPU et 4G de RAM) qui sera bien suffisante pour mes test : 272 276 ··· 298 302 299 303 Après quelques minutes, le cluster est dispo : 300 304 301 - ![img.png](scaleway-console-cluster-up.png) 305 + ![Console Scaleway : Cluster Kubernetes opérationnel](scaleway-console-cluster-up.webp) 302 306 303 - ![img.png](scaleway-console-nodepool-up.png) 307 + ![Console Scaleway : Pool de nœuds opérationnel](scaleway-console-nodepool-up.webp) 304 308 305 309 Je peux générer mon fichier `kubeconfig`, et vérifier que tout fonctionne bien : 306 310 307 311 ```bash 308 312 $ scw k8s kubeconfig get 100d3564-66b2-4439-bcc2-b5e76cd6d1fb > kubeconfig.yaml 309 313 310 - kubectl get nodes 314 + $ kubectl get nodes 311 315 312 316 NAME STATUS ROLES AGE VERSION 313 317 scw-timbernetes-dem-timbernetes-demo-po-fd96dc Ready <none> 1m55s v1.35.2 ··· 331 335 Region fr-par 332 336 ``` 333 337 334 - ![img.png](scaleway-console-registry.png) 338 + ![Console Scaleway : Registre de conteneurs créé](scaleway-console-registry.webp) 335 339 336 340 J'authentifie mon CLI Docker au registry avec un `docker login` : 337 341 ··· 347 351 $ docker push rg.fr-par.scw.cloud/timbernetes-demo/java:latest 348 352 ``` 349 353 350 - ![img.png](scaleway-console-image.png) 354 + ![Console Scaleway : Image Docker poussée sur le registre](scaleway-console-image.webp) 351 355 352 356 Tout est prêt pour pouvoir déployer l'application et lancer les tests. 353 357 ··· 421 425 * Modifier la taille du pod pour le passer à 2CPU et 1Go de RAM 422 426 * Relancer un stress-test 423 427 * Remodifier la taille du pod pour revenir à 1 CPU et 512Mo de RAM 424 - * Relancer un dernier stress-test 425 428 426 429 Je m'attends à voir le nombre de CPU modifiés, et les résultats des tests adaptés en fonction. Par contre, pour la RAM, je m'attends à ce qu'il ne se passe rien, puisque la RAM consommée par la JVM est fixée au redémarrage, allouer de la RAM supplémentaire sera donc inutile. 427 430 ··· 436 439 Started benchmark 437 440 ``` 438 441 439 - Pendant le premier Benchmark, CPUStress, le CPU est bien chargé, la RAM ne bouge pas : 442 + Pendant le premier Benchmark, CPUStress, le CPU est bien chargé, on voit le load qui est proche de 1, la RAM ne bouge pas : 443 + 444 + ```http request 445 + GET localhost:8080/metrics 440 446 441 - ```shell 442 447 cpu_count 1.0 443 - process_cpu_load 0.80859375 448 + process_cpu_load 0.90859375 449 + 444 450 jvm_memory_max_mb 396.375 445 451 jvm_memory_used_mb 5.6450958251953125 446 452 ``` 447 453 448 454 Pendant le second Benchmark, on voit que la RAM se rempli, et est nettoyée une fois pleine : 449 455 450 - ```shell 456 + ```http request 457 + GET localhost:8080/metrics 458 + 451 459 cpu_count 1.0 452 460 process_cpu_load 0.99267578125 461 + 453 462 jvm_memory_max_mb 396.375 454 463 jvm_memory_used_mb 353.49063873291016 455 464 ``` 456 465 457 - Le benchmark donne les résultats suivants : 458 - 459 - ```text 460 - Benchmark Mode Cnt Score Error Units 461 - CPUStress.fibonacciCalculation thrpt 5 1853767.056 ± 303895.231 ops/s 462 - MemoryStress.allocateMemory thrpt 5 44.607 ± 2.511 ops/s 463 - ``` 464 - 465 466 Cela nous fait un point de départ. 466 467 467 468 ### Redimensionnement du pod et deuxième tir ··· 500 501 501 502 cpu_count 2.0 502 503 process_cpu_load 0.04296875 504 + 503 505 jvm_memory_max_mb 396.375 504 506 jvm_memory_used_mb 362.24312591552734 505 507 ``` ··· 514 516 515 517 cpu_count 2.0 516 518 process_cpu_load 1.90654296875 519 + 517 520 jvm_memory_max_mb 396.375 518 521 jvm_memory_used_mb 355.3716583251953 519 522 ``` 520 523 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) 524 + 525 + Le load est maintenant proche de 2. 526 + 527 + Un `top` dans le container permet de confirmer ce qu'on voit avec la métrique, le process utilise 200% de CPU, les 2 cœurs sont bien exploités par la JVM. 528 + ![Commande top dans le conteneur montrant l'utilisation des deux cœurs CPU par la JVM](console-top.webp) 523 529 524 530 ### Redimensionnement et dernier tir 525 531 526 532 Pour compléter les tests, je redimensionne à nouveau le pod, cette fois-ci avec des valeurs à la baisse, pour revenir aux valeurs initiales : 527 533 528 534 ```bash 529 - kubectl patch pod timbernetes-demo --subresource resize --patch \ 535 + $ kubectl patch pod timbernetes-demo --subresource resize --patch \ 530 536 '{"spec":{"containers":[{"name":"timbernetes-demo","resources":{"limits":{"cpu":"1","memory":"512Mi"},"requests":{"cpu":"1","memory":"512Mi"}}}]}}' 531 537 532 538 pod/timbernetes-demo patched ··· 535 541 Les évènements sur le pod affichent bien que le resizing a été exécuté : 536 542 537 543 ```bash 538 - kubectl events --for pod/timbernetes-demo 544 + $ kubectl events --for pod/timbernetes-demo 545 + 539 546 LAST SEEN TYPE REASON OBJECT MESSAGE 540 547 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 548 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} ··· 548 555 549 556 cpu_count 1.0 550 557 process_cpu_load 0.03466796875 558 + 551 559 jvm_memory_max_mb 396.375 552 560 jvm_memory_used_mb 206.46312713623047 553 561 ``` 554 562 555 563 Pas de surprise non plus sur ce redimensionnement qui est aussi effectué à chaud. 556 564 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. 565 + Enfin, pour observer ce qu'il se passerait avec un redimensionnement sur une RAM déjà consommée, j'opère un redimensionnement à une valeur de RAM inférieure à celle que consomme déjà le pod. 559 566 560 567 ```bash 561 568 $ 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 569 pod/timbernetes-demo patched 563 570 ``` 564 571 565 - Cette fois-ci, lorsque je regarde les évènements du pod, j'observe une erreur : 572 + Cette fois-ci, lorsque je regarde les évènements du pod, j'observe une erreur _cannot decrease memory limits_: 566 573 567 574 ```bash 568 575 $ kubectl events --for pod/timbernetes-demo ··· 572 579 573 580 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 581 575 - ## Conclusion 582 + ## Intégration avec les VPA 576 583 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. 584 + Ces mécanismes sont déjà chouettes par eux-même, mais vont prendre une toute autre dimension avec leur intégration dans les _VPA_ (_Vertical Pod Autoscaler_). 578 585 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. 586 + ### InPlaceOrRecreate 580 587 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. 588 + Les VPA, depuis leur version 1.6, ont également un nouveau mode appelé `InPlaceOrRecreate` qui permet de redimensionner les pods sans les redémarrer, et de forcer une recréation du Pod 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 verrait son nombre de CPU ajusté à chaud, plutôt que de faire de la scalabilité horizontale. 582 589 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. 590 + Il faut cependant limiter cet usage au CPU (en tout cas pour des applis Java), et utiliser un VPA est toujours incompatible avec un HPA, donc l'intérêt reste encore un peu limité. 584 591 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 : 592 + Voici un exemple de VPA pour mon application Java, tirant parti de cette feature : 588 593 589 594 ```yaml 590 595 apiVersion: autoscaling.k8s.io/v1 ··· 610 615 controlledValues: RequestsAndLimits 611 616 ``` 612 617 618 + > Les VPA ne semblent pas disponibles sur les clusters Kapsule, donc je n'ai pas pu tester cette partie (et installer un VPA est au-delà de mes compétences ahaha). 619 + 620 + ### CPU Startup Boost 621 + 622 + La feature des VPA nommée CPU Startup Boost a pour objectif de donner accès à un pod un peu plus de ressources pendant son démarrage, et de réduire ces ressources une fois que le pod est au statut _Ready_. 623 + 624 + Dans l'exemple ci-dessous, on double le nombre de CPU disponibles, et on le repasse au nombre de CPU initialement alloués, 10 secondes après que le pod soit _Ready_ : 625 + 626 + ```yaml 627 + apiVersion: "autoscaling.k8s.io/v1" 628 + kind: VerticalPodAutoscaler 629 + metadata: 630 + name: timbernetes-demo-vpa 631 + spec: 632 + targetRef: 633 + apiVersion: "apps/v1" 634 + kind: Deployment 635 + name: timbernetes-demo 636 + startupBoost: 637 + cpu: 638 + type: "Factor" 639 + factor: 2 640 + durationSeconds: 10 641 + ``` 642 + 643 + Cette feature est pour l'instant disponible en version alpha dans les VPA 1.7. 644 + 645 + C'est clairement une feature qui va être très utile pour les applications Java, en particulier les applications Spring Boot, qui nécessitent beaucoup de CPU au démarrage pour analyser le classpath et configurer l'application context. 646 + 647 + ## Conclusion 648 + 649 + 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. 650 + 651 + Concernant la RAM, étant donné que la JVM fixe 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. 652 + 653 + 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. 654 + 613 655 ## Liens et références 614 656 657 + * Le repo Github avec lequel j'ai expérimenté : https://github.com/juwit/timbernetes-demo 658 + 615 659 Kubernetes : 616 660 * [Release](https://kubernetes.io/blog/2025/12/17/kubernetes-v1-35-release/) note de Kubernetes 1.35 : Timbernetes 617 661 * La [KEP #1287](https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1287-in-place-update-pod-resources) In-Place Update of Pod Resources 618 662 * L'article de blog pour promouvoir la feature : [Kubernetes 1.35: In-Place Pod Resize Graduates to Stable](https://kubernetes.io/blog/2025/12/19/kubernetes-v1-35-in-place-pod-resize-ga/) 619 663 * La page de documentation [Resize CPU and Memory Resources assigned to Containers](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/) 620 664 * La page de documentation [Resize CPU and Memory Resources assigned to Pods](https://kubernetes.io/docs/tasks/configure-pod-container/resize-pod-resources/) 665 + * Vertical Pod Autoscaling : 666 + * [Installation of the Vertical Pod Autoscaler](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/docs/installation.md) 667 + * [InPlaceOrRecreate Update mode](https://kubernetes.io/docs/concepts/workloads/autoscaling/vertical-pod-autoscale/#updateMode-InPlaceOrRecreate) 668 + * Vertical Pod Autoscaler - [AEP-4016: Support for in place updates in VPA](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler/enhancements/4016-in-place-updates-support) 669 + * Vertical Pod Autoscaler - [AEP-7862: CPU Startup Boost](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler/enhancements/7862-cpu-startup-boost) 670 + 621 671 Scaleway : 672 + 622 673 * La documentation Scaleway : [Kapsule & Kosmos release calendar](https://www.scaleway.com/en/docs/kubernetes/reference-content/version-support-policy/#scaleway-kubernetes-kapsule--kosmos-release-calendar) 623 674 * 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/) 624 675 * [Scaleway Instances datasheet](https://www.scaleway.com/en/docs/instances/reference-content/instances-datasheet/) 676 + 625 677 JMH : 678 + 626 679 * Le tuto de Baeldung [Microbenchmarking with Java](https://www.baeldung.com/java-microbenchmark-harness) 680 + 627 681 Java : 682 + 628 683 * [JEP draft: Automatic Heap Sizing for G1](https://openjdk.org/jeps/8359211) 629 684 * [JEP draft: Automatic Heap Sizing for ZGC](https://openjdk.org/jeps/8377305) 630 685 * [JEP draft: Automatic Heap Sizing for the Serial Garbage Collector](https://openjdk.org/jeps/8350152)
content/posts/2026/2026-03-20-timbernetes/scaleway-console-cluster-starting.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-cluster-starting.webp

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-cluster-up.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-cluster-up.webp

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-image.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-image.webp

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-nodepool-up.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-nodepool-up.webp

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-registry.png

This is a binary file and will not be displayed.

content/posts/2026/2026-03-20-timbernetes/scaleway-console-registry.webp

This is a binary file and will not be displayed.