[FIX] imbrication : dimensionner les étages depuis le bas, vCPU compris
Le plan cédait à l'enfant ce que le parent pouvait céder. Descente réelle à dix étages : l'étage 4 a reçu 44 Go et 2 vCPU sur un hôte qui en avait 2 — cent pour cent de surengagement, à chaque étage. Son installation dure 2 h 52 et n'est pas finie, contre 793 s pour l'étage 3. Extrapolé, le dixième demandait des années. Le plus profond reçoit désormais ce qu'un Proxmox de test demande, et chaque parent ajoute son seul surcoût : un vCPU, 2 Gio, 10 Go. Dix étages tiennent sur 11 vCPU et 22 Go au premier, contre 50 Go avant. Corrige aussi l'explication du gel à 12 vCPU : cette VM avait douze vCPU sur un hôte qui en avait deux. C'est le surengagement qui gèle, pas le douze. --- EN --- The plan handed the child whatever the parent could spare. Real ten-level descent: level 4 got 44 GB and 2 vCPU on a host that had 2 — a hundred percent overcommit, at every level. Its install has run 2h52 and is not done, against 793 s for level 3. Extrapolated, the tenth wanted years. The deepest level now gets what a test Proxmox asks for, and each parent adds its own overhead only: one vCPU, 2 GiB, 10 GB. Ten levels fit in 11 vCPU and 22 GB at the first, against 50 GB before. Also corrects the account of the 12-vCPU freeze: that VM had twelve vCPU on a host with two. Overcommit freezes, not the twelve. Assisted-by: claude-opus-5 (cherry picked from commit 7cda84bf391ee3ed36c5953ca4b86df44bd09e3b)
This commit is contained in:
parent
667842b832
commit
469a5fde9e
5 changed files with 294 additions and 197 deletions
|
|
@ -48,27 +48,42 @@ the repository: it is our code we want to exercise, and the remote is often
|
|||
behind the checkout — a fix absent from the remote made the same defect "come
|
||||
back" on three VMs in a row.
|
||||
|
||||
### The resource algorithm
|
||||
### The resource algorithm — sized from the bottom up
|
||||
|
||||
Two things run out going down, and a third degrades. What runs out is
|
||||
arithmetic, and `script/proxmox/nesting.py` computes it:
|
||||
The first version handed down whatever the parent could spare, and a real
|
||||
descent showed what that costs. Level 4 ended up with 44 GB of memory and
|
||||
2 vCPU **on a host that had 2** — a hundred percent overcommit, at every
|
||||
level, with the hypervisor itself to serve on top. Its install ran past two
|
||||
and a half hours against thirteen minutes for level 3, and extrapolating that
|
||||
ratio gave five years for the tenth.
|
||||
|
||||
* **memory** — each level keeps what its own daemons need (`pve-cluster`,
|
||||
`pvestatd`, `pvedaemon`, `pveproxy`) before handing the rest down;
|
||||
So the direction is reversed. The deepest level gets what a test Proxmox
|
||||
actually asks for — 4 GB of memory, 25 GB of disk, 2 vCPU — and every parent
|
||||
above it adds its own overhead and nothing else: one vCPU, 2 GiB, 10 GB. A
|
||||
ten-level descent therefore asks its first level for 11 vCPU, 22 GB and
|
||||
115 GB, where handing resources down wanted 50 GB of memory for the same
|
||||
depth.
|
||||
|
||||
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
||||
one that ran out:
|
||||
|
||||
* **memory** — every level must run its own daemons (`pve-cluster`,
|
||||
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
||||
* **disk** — the child's disk lives *inside* the parent's, which must also
|
||||
hold its own system.
|
||||
hold its own system;
|
||||
* **processor** — each level wants one vCPU more than its child, so ten levels
|
||||
ask eleven of the first. Half the physical cores is the ceiling: the
|
||||
orchestrator runs on that machine too.
|
||||
|
||||
What degrades is measured, not assumed: past the second level, vendors
|
||||
document nothing. Hence one capped number — **2 vCPU** for every nested
|
||||
level. Twelve vCPU at the fourth level froze the guest kernel in early boot;
|
||||
the same two progressed. Bringing twelve processors online costs as many
|
||||
round trips through the whole stack.
|
||||
That third budget is measured, not assumed. Twelve vCPU at the fourth level
|
||||
froze the guest kernel in early boot — same instruction pointer at three
|
||||
readings two minutes apart — while two progressed. The number was not the
|
||||
culprit: that VM had twelve vCPU on a host with two, six times wider than its
|
||||
own machine. Overcommit freezes, not the twelve.
|
||||
|
||||
Memory is **not** capped. On that one manual VM, dropping it from 9 GB to
|
||||
2 GB moved nothing — it stopped after reading the same 32 MiB, which is simply
|
||||
the size of the boot files. Memory was not the lever; the vCPU count was. And
|
||||
trimming memory would starve the level below, which needs it to host the
|
||||
next.
|
||||
Memory is not the lever. On that same manual VM, dropping it from 9 GB to 2 GB
|
||||
moved nothing — it stopped after reading the same 32 MiB, which is simply the
|
||||
size of the boot files.
|
||||
|
||||
The plan is printed **before** anything is created, and the script never
|
||||
promises a depth it knows will not fit — better to announce six levels and
|
||||
|
|
@ -123,28 +138,44 @@ cloner le dépôt : c'est notre code qu'on veut éprouver, et le dépôt distant
|
|||
est souvent en retard sur le checkout — un correctif absent du distant a fait
|
||||
« revenir » le même défaut sur trois VM de suite.
|
||||
|
||||
### L'algorithme de ressources
|
||||
### L'algorithme de ressources — dimensionné depuis le bas
|
||||
|
||||
Deux choses s'épuisent en descendant, et une troisième se dégrade. Ce qui
|
||||
s'épuise est de l'arithmétique, et `script/proxmox/nesting.py` la calcule :
|
||||
La première version cédait à l'enfant ce que le parent pouvait céder, et une
|
||||
descente réelle a montré ce que cela coûte. L'étage 4 se retrouvait avec 44 Go
|
||||
de mémoire et 2 vCPU **sur un hôte qui en avait 2** — cent pour cent de
|
||||
surengagement, à chaque étage, avec l'hyperviseur lui-même à servir par-dessus.
|
||||
Son installation dépassait deux heures et demie contre treize minutes pour
|
||||
l'étage 3, et l'extrapolation de ce rapport donnait cinq ANS pour le dixième.
|
||||
|
||||
* **la mémoire** — chaque étage garde de quoi faire tourner ses propres démons
|
||||
(`pve-cluster`, `pvestatd`, `pvedaemon`, `pveproxy`) avant de céder le
|
||||
reste ;
|
||||
Le sens est donc inversé. Le plus profond reçoit ce qu'un Proxmox de test
|
||||
demande vraiment — 4 Go de mémoire, 25 Go de disque, 2 vCPU — et chaque parent
|
||||
au-dessus ajoute son propre surcoût, rien d'autre : un vCPU, 2 Gio, 10 Go. Une
|
||||
descente à dix étages demande ainsi 11 vCPU, 22 Go et 115 Go à son premier
|
||||
étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la même
|
||||
profondeur.
|
||||
|
||||
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
||||
nomme celui qui a manqué :
|
||||
|
||||
* **la mémoire** — chaque étage doit faire tourner ses propres démons
|
||||
(`pve-cluster`, `pvestatd`, `pvedaemon`, `pveproxy`) *et* héberger son
|
||||
enfant ;
|
||||
* **le disque** — le disque de l'enfant vit *dans* celui du parent, qui doit
|
||||
aussi contenir son propre système.
|
||||
aussi contenir son propre système ;
|
||||
* **le processeur** — chaque étage en veut un de plus que son enfant, donc dix
|
||||
étages en demandent onze au premier. La moitié des cœurs physiques est le
|
||||
plafond : l'orchestrateur tourne sur cette machine lui aussi.
|
||||
|
||||
Ce qui se dégrade est mesuré, pas supposé : au-delà du deuxième étage, les
|
||||
fabricants ne documentent rien. D'où un seul nombre borné — **2 vCPU** pour
|
||||
tout étage imbriqué. Douze vCPU au quatrième étage ont gelé le noyau invité en
|
||||
tout début de démarrage ; les mêmes deux avançaient. Amener douze processeurs
|
||||
en ligne coûte autant d'allers-retours à travers toute la pile.
|
||||
Ce troisième budget est mesuré, pas supposé. Douze vCPU au quatrième étage ont
|
||||
gelé le noyau invité en tout début de démarrage — même pointeur d'instruction à
|
||||
trois relevés, deux minutes d'écart — quand deux avançaient. Le nombre n'était
|
||||
pas le fautif : cette VM avait douze vCPU sur un hôte qui en avait deux, six
|
||||
fois plus large que sa propre machine. C'est le surengagement qui gèle, pas le
|
||||
douze.
|
||||
|
||||
La mémoire n'est **pas** bornée. Sur cette unique VM examinée à la main, la
|
||||
faire passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu
|
||||
les mêmes 32 Mio, c'est-à-dire simplement la taille des fichiers d'amorçage.
|
||||
La mémoire n'était pas le levier ; le nombre de vCPU l'était. Et la rogner
|
||||
priverait l'étage du dessous, qui en a besoin pour héberger le suivant.
|
||||
La mémoire n'est pas le levier. Sur cette même VM examinée à la main, la faire
|
||||
passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu les
|
||||
mêmes 32 Mio, c'est-à-dire simplement la taille des fichiers d'amorçage.
|
||||
|
||||
Le plan est affiché **avant** que quoi que ce soit ne soit créé, et le script
|
||||
ne promet jamais une profondeur qu'il sait irréalisable — mieux vaut annoncer
|
||||
|
|
|
|||
|
|
@ -47,28 +47,44 @@ cloner le dépôt : c'est notre code qu'on veut éprouver, et le dépôt distant
|
|||
est souvent en retard sur le checkout — un correctif absent du distant a fait
|
||||
« revenir » le même défaut sur trois VM de suite.
|
||||
|
||||
### L'algorithme de ressources
|
||||
### L'algorithme de ressources — dimensionné depuis le bas
|
||||
|
||||
Deux choses s'épuisent en descendant, et une troisième se dégrade. Ce qui
|
||||
s'épuise est de l'arithmétique, et `script/proxmox/nesting.py` la calcule :
|
||||
La première version cédait à l'enfant ce que le parent pouvait céder, et une
|
||||
descente réelle a montré ce que cela coûte. L'étage 4 se retrouvait avec 44 Go
|
||||
de mémoire et 2 vCPU **sur un hôte qui en avait 2** — cent pour cent de
|
||||
surengagement, à chaque étage, avec l'hyperviseur lui-même à servir par-dessus.
|
||||
Son installation dépassait deux heures et demie contre treize minutes pour
|
||||
l'étage 3, et l'extrapolation de ce rapport donnait cinq ANS pour le dixième.
|
||||
|
||||
* **la mémoire** — chaque étage garde de quoi faire tourner ses propres démons
|
||||
(`pve-cluster`, `pvestatd`, `pvedaemon`, `pveproxy`) avant de céder le
|
||||
reste ;
|
||||
Le sens est donc inversé. Le plus profond reçoit ce qu'un Proxmox de test
|
||||
demande vraiment — 4 Go de mémoire, 25 Go de disque, 2 vCPU — et chaque parent
|
||||
au-dessus ajoute son propre surcoût, rien d'autre : un vCPU, 2 Gio, 10 Go. Une
|
||||
descente à dix étages demande ainsi 11 vCPU, 22 Go et 115 Go à son premier
|
||||
étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la même
|
||||
profondeur.
|
||||
|
||||
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
||||
nomme celui qui a manqué :
|
||||
|
||||
* **la mémoire** — chaque étage doit faire tourner ses propres démons
|
||||
(`pve-cluster`, `pvestatd`, `pvedaemon`, `pveproxy`) *et* héberger son
|
||||
enfant ;
|
||||
* **le disque** — le disque de l'enfant vit *dans* celui du parent, qui doit
|
||||
aussi contenir son propre système.
|
||||
aussi contenir son propre système ;
|
||||
* **le processeur** — chaque étage en veut un de plus que son enfant, donc dix
|
||||
étages en demandent onze au premier. La moitié des cœurs physiques est le
|
||||
plafond : l'orchestrateur tourne sur cette machine lui aussi.
|
||||
|
||||
Ce qui se dégrade est mesuré, pas supposé : au-delà du deuxième étage, les
|
||||
fabricants ne documentent rien. D'où un seul nombre borné — **2 vCPU** pour
|
||||
tout étage imbriqué. Douze vCPU au quatrième étage ont gelé le noyau invité en
|
||||
tout début de démarrage ; les mêmes deux avançaient. Amener douze processeurs
|
||||
en ligne coûte autant d'allers-retours à travers toute la pile.
|
||||
Ce troisième budget est mesuré, pas supposé. Douze vCPU au quatrième étage ont
|
||||
gelé le noyau invité en tout début de démarrage — même pointeur d'instruction à
|
||||
trois relevés, deux minutes d'écart — quand deux avançaient. Le nombre n'était
|
||||
pas le fautif : cette VM avait douze vCPU sur un hôte qui en avait deux, six
|
||||
fois plus large que sa propre machine. C'est le surengagement qui gèle, pas le
|
||||
douze.
|
||||
|
||||
La mémoire n'est **pas** bornée. Sur cette unique VM examinée à la main, la
|
||||
faire passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu
|
||||
les mêmes 32 Mio, c'est-à-dire simplement la taille des fichiers d'amorçage.
|
||||
La mémoire n'était pas le levier ; le nombre de vCPU l'était. Et la rogner
|
||||
priverait l'étage du dessous, qui en a besoin pour héberger le suivant.
|
||||
La mémoire n'est pas le levier. Sur cette même VM examinée à la main, la faire
|
||||
passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu les
|
||||
mêmes 32 Mio, c'est-à-dire simplement la taille des fichiers d'amorçage.
|
||||
|
||||
Le plan est affiché **avant** que quoi que ce soit ne soit créé, et le script
|
||||
ne promet jamais une profondeur qu'il sait irréalisable — mieux vaut annoncer
|
||||
|
|
|
|||
|
|
@ -43,27 +43,42 @@ the repository: it is our code we want to exercise, and the remote is often
|
|||
behind the checkout — a fix absent from the remote made the same defect "come
|
||||
back" on three VMs in a row.
|
||||
|
||||
### The resource algorithm
|
||||
### The resource algorithm — sized from the bottom up
|
||||
|
||||
Two things run out going down, and a third degrades. What runs out is
|
||||
arithmetic, and `script/proxmox/nesting.py` computes it:
|
||||
The first version handed down whatever the parent could spare, and a real
|
||||
descent showed what that costs. Level 4 ended up with 44 GB of memory and
|
||||
2 vCPU **on a host that had 2** — a hundred percent overcommit, at every
|
||||
level, with the hypervisor itself to serve on top. Its install ran past two
|
||||
and a half hours against thirteen minutes for level 3, and extrapolating that
|
||||
ratio gave five years for the tenth.
|
||||
|
||||
* **memory** — each level keeps what its own daemons need (`pve-cluster`,
|
||||
`pvestatd`, `pvedaemon`, `pveproxy`) before handing the rest down;
|
||||
So the direction is reversed. The deepest level gets what a test Proxmox
|
||||
actually asks for — 4 GB of memory, 25 GB of disk, 2 vCPU — and every parent
|
||||
above it adds its own overhead and nothing else: one vCPU, 2 GiB, 10 GB. A
|
||||
ten-level descent therefore asks its first level for 11 vCPU, 22 GB and
|
||||
115 GB, where handing resources down wanted 50 GB of memory for the same
|
||||
depth.
|
||||
|
||||
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
||||
one that ran out:
|
||||
|
||||
* **memory** — every level must run its own daemons (`pve-cluster`,
|
||||
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
||||
* **disk** — the child's disk lives *inside* the parent's, which must also
|
||||
hold its own system.
|
||||
hold its own system;
|
||||
* **processor** — each level wants one vCPU more than its child, so ten levels
|
||||
ask eleven of the first. Half the physical cores is the ceiling: the
|
||||
orchestrator runs on that machine too.
|
||||
|
||||
What degrades is measured, not assumed: past the second level, vendors
|
||||
document nothing. Hence one capped number — **2 vCPU** for every nested
|
||||
level. Twelve vCPU at the fourth level froze the guest kernel in early boot;
|
||||
the same two progressed. Bringing twelve processors online costs as many
|
||||
round trips through the whole stack.
|
||||
That third budget is measured, not assumed. Twelve vCPU at the fourth level
|
||||
froze the guest kernel in early boot — same instruction pointer at three
|
||||
readings two minutes apart — while two progressed. The number was not the
|
||||
culprit: that VM had twelve vCPU on a host with two, six times wider than its
|
||||
own machine. Overcommit freezes, not the twelve.
|
||||
|
||||
Memory is **not** capped. On that one manual VM, dropping it from 9 GB to
|
||||
2 GB moved nothing — it stopped after reading the same 32 MiB, which is simply
|
||||
the size of the boot files. Memory was not the lever; the vCPU count was. And
|
||||
trimming memory would starve the level below, which needs it to host the
|
||||
next.
|
||||
Memory is not the lever. On that same manual VM, dropping it from 9 GB to 2 GB
|
||||
moved nothing — it stopped after reading the same 32 MiB, which is simply the
|
||||
size of the boot files.
|
||||
|
||||
The plan is printed **before** anything is created, and the script never
|
||||
promises a depth it knows will not fit — better to announce six levels and
|
||||
|
|
|
|||
|
|
@ -24,8 +24,11 @@ Deux nombres viennent de la même mesure, et méritent d'être dits :
|
|||
|
||||
* 12 vCPU au quatrième étage ont GELÉ le noyau invité en tout début de
|
||||
démarrage — même RIP à trois relevés, deux minutes d'écart, pas un octet lu
|
||||
de plus. Les mêmes 2 vCPU avançaient. D'où VCPU_IMBRIQUE = 2 : amener douze
|
||||
processeurs en ligne demande autant d'allers-retours à travers la pile ;
|
||||
de plus. Les mêmes 2 vCPU avançaient. Le nombre n'était pas le fautif : cette
|
||||
VM avait douze vCPU sur un hôte qui en avait DEUX, six fois plus large que sa
|
||||
propre machine. C'est le surengagement qui gèle, pas le douze — d'où le
|
||||
dimensionnement par étage plus bas, qui donne à chaque parent un vCPU de plus
|
||||
qu'à son enfant ;
|
||||
* cette VM-là s'arrêtait après avoir lu 33 682 432 octets — 32 Mio, soit
|
||||
simplement la taille de ses fichiers d'amorçage — et le chiffre ne bougeait
|
||||
pas quand on lui retirait de la mémoire. La mémoire n'était donc pas le
|
||||
|
|
@ -47,21 +50,49 @@ HOTE_RESERVE_RAM_MO = 4096
|
|||
HOTE_RESERVE_PART = 8 # un huitième
|
||||
HOTE_RESERVE_DISQUE_GO = 20
|
||||
|
||||
# Ce qu'un étage garde pour lui avant de céder le reste. La RAM vient de
|
||||
# l'observation d'un Proxmox imbriqué au repos ; le disque, de la mesure d'un
|
||||
# système installé (5,6 Go) plus de la place pour écrire.
|
||||
# Ce qu'un étage garde pour LUI, en plus de ce qu'il cède à son enfant. La
|
||||
# RAM vient de l'observation d'un Proxmox imbriqué au repos ; le disque, de la
|
||||
# mesure d'un système installé (5,6 Go) plus de la place pour écrire.
|
||||
PVE_RAM_MO = 2048
|
||||
PVE_DISQUE_GO = 10
|
||||
|
||||
# Ce dont le PLUS PROFOND a besoin — et c'est de là qu'on part.
|
||||
#
|
||||
# Le dimensionnement allait d'abord de haut en bas : chaque étage recevait tout
|
||||
# ce que son parent pouvait céder. Mesuré sur une descente réelle, l'étage 4 se
|
||||
# retrouvait avec 44 Go — onze millions de pages à cartographier, chaque défaut
|
||||
# traversant les quatre hyperviseurs empilés. Son installation dépassait deux
|
||||
# heures et demie là où l'étage 3 mettait treize minutes, et l'extrapolation
|
||||
# donnait cinq ANS pour le dixième étage.
|
||||
#
|
||||
# On part donc du bas : le plus profond reçoit ce qu'un Proxmox de test demande
|
||||
# vraiment, et chaque parent ajoute seulement son propre surcoût. Pour dix
|
||||
# étages, le premier a besoin de 4 + 9×2 = 22 Go au lieu de cinquante — et
|
||||
# chaque étage est PETIT, donc rapide.
|
||||
PVE_RAM_CIBLE_MO = 4096
|
||||
PVE_DISQUE_CIBLE_GO = 25
|
||||
|
||||
# En dessous, un Proxmox ne démarre pas ses démons ou n'a plus la place
|
||||
# d'importer une image cloud.
|
||||
RAM_MIN_MO = 2048
|
||||
DISQUE_MIN_GO = 15
|
||||
|
||||
# Le premier étage tourne sur la machine physique : il peut être large. Les
|
||||
# suivants non — voir la mesure dans l'en-tête.
|
||||
VCPU_NIVEAU1_MAX = 4
|
||||
# Le processeur se dimensionne DEPUIS LE BAS lui aussi, et pour la même
|
||||
# raison que la mémoire — mais celle-là s'est vue à l'usage.
|
||||
#
|
||||
# Avec deux vCPU à chaque étage imbriqué, l'étage 3 avait deux vCPU pour
|
||||
# héberger un invité qui en demandait deux : cent pour cent de surengagement,
|
||||
# et l'hyperviseur lui-même à servir par-dessus. À chaque étage. Mesuré sur une
|
||||
# descente réelle : une seconde VM démarrée au quatrième étage a lu DEUX
|
||||
# KILO-OCTETS en onze minutes, affamée par l'installation qui tournait à côté.
|
||||
#
|
||||
# Chaque étage reçoit donc UN vCPU de plus que son enfant : le plus profond en
|
||||
# a deux, son parent trois, et ainsi de suite. Le premier étage d'une descente
|
||||
# à dix en demande onze — sur vingt-huit cœurs réels, cela passe.
|
||||
VCPU_IMBRIQUE = 2
|
||||
# Ce qu'on accepte de prendre à la machine physique : la moitié de ses cœurs.
|
||||
# L'orchestrateur tourne dessus, et la suite de tests aussi.
|
||||
VCPU_HOTE_PART = 2
|
||||
|
||||
# Au-delà, l'imbrication n'est pas un terrain documenté par les fabricants.
|
||||
# On ne refuse pas — on le DIT.
|
||||
|
|
@ -74,59 +105,81 @@ def nesting_plan(
|
|||
ram_dispo_mo: int,
|
||||
disque_libre_go: int,
|
||||
) -> dict:
|
||||
"""Les ressources de chaque étage, et jusqu'où l'arithmétique va.
|
||||
"""Les ressources de chaque étage, dimensionnées DEPUIS LE BAS.
|
||||
|
||||
Rend {"demandee", "atteignable", "niveaux": [...], "arret"}. `arret`
|
||||
nomme ce qui a manqué — « ram » ou « disque » — quand la profondeur
|
||||
demandée n'est pas atteinte, sinon "".
|
||||
Rend {"demandee", "atteignable", "niveaux": [...], "arret"}. `arret` nomme
|
||||
ce qui a manqué — « ram » ou « disque » — quand la profondeur demandée
|
||||
n'est pas atteinte, sinon "".
|
||||
|
||||
Depuis le bas, et c'est tout le sujet. De haut en bas, chaque étage
|
||||
recevait ce que son parent pouvait céder : mesuré, l'étage 4 se retrouvait
|
||||
avec 44 Go de RAM et son installation dépassait deux heures et demie
|
||||
contre treize minutes pour l'étage 3. Sous pagination imbriquée, un gros
|
||||
invité coûte cher à cartographier, et le coût se multiplie par étage.
|
||||
|
||||
Le plus profond reçoit donc ce qu'un Proxmox de test demande, et chaque
|
||||
parent ajoute son surcoût — rien de plus. Une descente à dix étages
|
||||
demande alors 22 Go au premier au lieu de cinquante, et chaque étage est
|
||||
petit.
|
||||
|
||||
On ne rend jamais un plan qu'on sait impossible : mieux vaut annoncer six
|
||||
étages et en réussir six que d'en promettre dix et mourir au septième
|
||||
sans savoir pourquoi.
|
||||
étages et en réussir six que d'en promettre dix et mourir au septième sans
|
||||
savoir pourquoi.
|
||||
"""
|
||||
# Arrondi au gibioctet inférieur : « --memory 25203 » marche, mais un
|
||||
# nombre rond se relit, se compare d'un étage à l'autre, et évite de
|
||||
# traîner les kibioctets du hasard de la mesure jusqu'au dixième étage.
|
||||
reserve = max(HOTE_RESERVE_RAM_MO, int(ram_dispo_mo) // HOTE_RESERVE_PART)
|
||||
ram = ((int(ram_dispo_mo) - reserve) // 1024) * 1024
|
||||
disque = int(disque_libre_go) - HOTE_RESERVE_DISQUE_GO
|
||||
niveaux, arret = [], ""
|
||||
# « max(1, …) » forçait un tour : profondeur 0 rendait un plan d'UN
|
||||
# étage, et « --depth 0 » créait donc une VM. range(1, 1) est déjà vide,
|
||||
# et un plan vide est la bonne réponse à une demande vide.
|
||||
for niveau in range(1, int(profondeur) + 1):
|
||||
if niveau > 1:
|
||||
ram -= PVE_RAM_MO
|
||||
disque -= PVE_DISQUE_GO
|
||||
if ram < RAM_MIN_MO:
|
||||
arret = "ram"
|
||||
break
|
||||
if disque < DISQUE_MIN_GO:
|
||||
arret = "disque"
|
||||
break
|
||||
niveaux.append(
|
||||
{
|
||||
"niveau": niveau,
|
||||
# Le plancher est VCPU_IMBRIQUE et non 1 : sur un hôte de
|
||||
# quatre cœurs, « // 4 » donnait UN vCPU au premier étage —
|
||||
# l'hyperviseur parent — alors que son invité en recevait
|
||||
# deux. Un parent plus étroit que son enfant est absurde, et
|
||||
# c'est tout l'inverse de ce que ce module raconte.
|
||||
"vcpu": (
|
||||
max(
|
||||
VCPU_IMBRIQUE,
|
||||
min(VCPU_NIVEAU1_MAX, int(cpu_hote) // 4),
|
||||
)
|
||||
if niveau == 1
|
||||
else VCPU_IMBRIQUE
|
||||
),
|
||||
"ram": ram,
|
||||
"disque": disque,
|
||||
}
|
||||
budget_ram = ((int(ram_dispo_mo) - reserve) // 1024) * 1024
|
||||
budget_disque = int(disque_libre_go) - HOTE_RESERVE_DISQUE_GO
|
||||
|
||||
def besoin(d):
|
||||
"""Ce que le PREMIER étage doit avoir pour qu'une descente de `d`
|
||||
étages tienne : la cible du bas, plus un surcoût par étage au-dessus.
|
||||
|
||||
Le processeur en fait partie : chaque étage en veut un de plus que son
|
||||
enfant, donc le premier en veut VCPU_IMBRIQUE + d - 1. Sans cette
|
||||
condition, on annonçait dix étages sur une machine à quatre cœurs.
|
||||
"""
|
||||
return (
|
||||
PVE_RAM_CIBLE_MO + (d - 1) * PVE_RAM_MO,
|
||||
PVE_DISQUE_CIBLE_GO + (d - 1) * PVE_DISQUE_GO,
|
||||
VCPU_IMBRIQUE + d - 1,
|
||||
)
|
||||
|
||||
budget_vcpu = max(VCPU_IMBRIQUE, int(cpu_hote) // VCPU_HOTE_PART)
|
||||
atteignable, arret = 0, ""
|
||||
for d in range(max(0, int(profondeur)), 0, -1):
|
||||
ram1, disque1, vcpu1 = besoin(d)
|
||||
if (
|
||||
ram1 <= budget_ram
|
||||
and disque1 <= budget_disque
|
||||
and vcpu1 <= budget_vcpu
|
||||
):
|
||||
atteignable = d
|
||||
break
|
||||
if atteignable < int(profondeur):
|
||||
# Nommer CE qui a manqué, à la profondeur demandée.
|
||||
ram1, disque1, vcpu1 = besoin(max(1, int(profondeur)))
|
||||
if ram1 > budget_ram:
|
||||
arret = "ram"
|
||||
elif disque1 > budget_disque:
|
||||
arret = "disque"
|
||||
else:
|
||||
arret = "vcpu"
|
||||
niveaux = [
|
||||
{
|
||||
"niveau": niveau,
|
||||
# UN de plus que son enfant. Un parent aussi étroit que son
|
||||
# enfant, c'est cent pour cent de surengagement — et l'hyperviseur
|
||||
# à servir en plus.
|
||||
"vcpu": VCPU_IMBRIQUE + (atteignable - niveau),
|
||||
"ram": PVE_RAM_CIBLE_MO + (atteignable - niveau) * PVE_RAM_MO,
|
||||
"disque": PVE_DISQUE_CIBLE_GO
|
||||
+ (atteignable - niveau) * PVE_DISQUE_GO,
|
||||
}
|
||||
for niveau in range(1, atteignable + 1)
|
||||
]
|
||||
return {
|
||||
"demandee": int(profondeur),
|
||||
"atteignable": len(niveaux),
|
||||
"atteignable": atteignable,
|
||||
"niveaux": niveaux,
|
||||
"arret": arret,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,97 +18,96 @@ from script.proxmox import nesting # noqa: E402
|
|||
|
||||
|
||||
class TestLePlanDesEtages(unittest.TestCase):
|
||||
"""Deux ressources s'épuisent en descendant, et le plan doit le dire
|
||||
AVANT de créer quoi que ce soit."""
|
||||
"""Le plan se dimensionne DEPUIS LE BAS, et c'est une correction.
|
||||
|
||||
De haut en bas, chaque étage recevait ce que son parent pouvait céder.
|
||||
Mesuré sur une descente réelle : l'étage 4 se retrouvait avec 44 Go de RAM
|
||||
et deux vCPU sur un hôte qui en avait deux — cent pour cent de
|
||||
surengagement, à chaque étage. Son installation dépassait deux heures et
|
||||
demie contre treize minutes pour l'étage 3, et l'extrapolation donnait cinq
|
||||
ANS pour le dixième.
|
||||
|
||||
Le plus profond reçoit donc ce qu'un Proxmox de test demande, et chaque
|
||||
parent ajoute son propre surcoût — un vCPU, deux gibioctets, dix
|
||||
gigaoctets. Rien de plus."""
|
||||
|
||||
# La machine réelle sur laquelle l'algorithme a été réglé.
|
||||
HOTE = dict(cpu_hote=28, ram_dispo_mo=29549, disque_libre_go=139)
|
||||
HOTE = dict(cpu_hote=28, ram_dispo_mo=58000, disque_libre_go=165)
|
||||
|
||||
def test_ten_levels_fit_on_this_machine(self):
|
||||
plan = nesting.nesting_plan(10, **self.HOTE)
|
||||
self.assertEqual(plan["atteignable"], 10)
|
||||
self.assertEqual(plan["arret"], "")
|
||||
|
||||
def test_every_level_shrinks(self):
|
||||
# Le disque de l'enfant vit DANS celui du parent, qui doit aussi
|
||||
# contenir son propre système : rien ne peut rester constant.
|
||||
niveaux = nesting.nesting_plan(6, **self.HOTE)["niveaux"]
|
||||
for precedent, suivant in zip(niveaux, niveaux[1:]):
|
||||
self.assertLess(suivant["ram"], precedent["ram"])
|
||||
self.assertLess(suivant["disque"], precedent["disque"])
|
||||
def test_the_deepest_level_gets_exactly_the_target(self):
|
||||
"""C'est de là qu'on part : ce qu'un Proxmox de test demande, pas ce
|
||||
qui reste."""
|
||||
plan = nesting.nesting_plan(10, **self.HOTE)
|
||||
fond = plan["niveaux"][-1]
|
||||
self.assertEqual(fond["ram"], nesting.PVE_RAM_CIBLE_MO)
|
||||
self.assertEqual(fond["disque"], nesting.PVE_DISQUE_CIBLE_GO)
|
||||
self.assertEqual(fond["vcpu"], nesting.VCPU_IMBRIQUE)
|
||||
|
||||
def test_the_first_level_may_be_wide_the_others_not(self):
|
||||
"""12 vCPU au quatrième étage ont GELÉ le noyau invité en tout début
|
||||
de démarrage ; les mêmes 2 vCPU avançaient. Amener douze processeurs
|
||||
en ligne demande autant d'allers-retours à travers la pile."""
|
||||
niveaux = nesting.nesting_plan(4, **self.HOTE)["niveaux"]
|
||||
# STRICTEMENT plus grand. « > VCPU_IMBRIQUE - 1 » était satisfait par
|
||||
# la valeur imbriquée elle-même : remplacer tout le calcul du premier
|
||||
# étage par VCPU_IMBRIQUE laissait les tests verts, donc cpu_hote
|
||||
# n'était couvert par rien.
|
||||
self.assertGreater(niveaux[0]["vcpu"], nesting.VCPU_IMBRIQUE)
|
||||
for n in niveaux[1:]:
|
||||
self.assertEqual(n["vcpu"], nesting.VCPU_IMBRIQUE)
|
||||
def test_each_parent_adds_exactly_its_own_overhead(self):
|
||||
# Ni plus ni moins : un parent plus large que nécessaire ralentit tout
|
||||
# ce qu'il héberge, un parent trop juste ne le fait pas tourner.
|
||||
niveaux = nesting.nesting_plan(8, **self.HOTE)["niveaux"]
|
||||
for parent, enfant in zip(niveaux, niveaux[1:]):
|
||||
self.assertEqual(parent["ram"] - enfant["ram"], nesting.PVE_RAM_MO)
|
||||
self.assertEqual(
|
||||
parent["disque"] - enfant["disque"], nesting.PVE_DISQUE_GO
|
||||
)
|
||||
self.assertEqual(parent["vcpu"] - enfant["vcpu"], 1)
|
||||
|
||||
def test_a_parent_is_never_narrower_than_its_child(self):
|
||||
"""Sur un hôte de quatre cœurs, « // 4 » donnait UN vCPU au premier
|
||||
étage — l'hyperviseur — alors que son invité en recevait deux."""
|
||||
for coeurs in (2, 4, 8, 12, 28):
|
||||
"""Deux vCPU hébergeant deux vCPU, c'est cent pour cent de
|
||||
surengagement — et l'hyperviseur à servir en plus. Mesuré : une VM
|
||||
démarrée au quatrième étage a lu DEUX KILO-OCTETS en onze minutes,
|
||||
affamée par l'installation qui tournait à côté."""
|
||||
for coeurs in (4, 8, 12, 28):
|
||||
with self.subTest(coeurs=coeurs):
|
||||
niveaux = nesting.nesting_plan(
|
||||
3,
|
||||
6,
|
||||
cpu_hote=coeurs,
|
||||
ram_dispo_mo=32768,
|
||||
disque_libre_go=300,
|
||||
ram_dispo_mo=64000,
|
||||
disque_libre_go=400,
|
||||
)["niveaux"]
|
||||
self.assertGreaterEqual(
|
||||
niveaux[0]["vcpu"], niveaux[1]["vcpu"], f"{coeurs} cœurs"
|
||||
)
|
||||
for parent, enfant in zip(niveaux, niveaux[1:]):
|
||||
self.assertGreater(parent["vcpu"], enfant["vcpu"])
|
||||
self.assertGreater(parent["ram"], enfant["ram"])
|
||||
self.assertGreater(parent["disque"], enfant["disque"])
|
||||
|
||||
def test_the_cpu_budget_can_bound_the_depth(self):
|
||||
"""Sur une petite machine, c'est le PROCESSEUR qui borne, pas la
|
||||
mémoire : chaque étage en veut un de plus que son enfant, donc dix
|
||||
étages demandent onze vCPU au premier."""
|
||||
plan = nesting.nesting_plan(
|
||||
10, cpu_hote=8, ram_dispo_mo=64000, disque_libre_go=400
|
||||
)
|
||||
self.assertEqual(plan["arret"], "vcpu")
|
||||
self.assertLess(plan["atteignable"], 10)
|
||||
# Et le premier étage ne dépasse pas la part concédée à l'hôte.
|
||||
self.assertLessEqual(
|
||||
plan["niveaux"][0]["vcpu"], 8 // nesting.VCPU_HOTE_PART
|
||||
)
|
||||
|
||||
def test_running_out_of_ram_is_named(self):
|
||||
plan = nesting.nesting_plan(
|
||||
10, cpu_hote=8, ram_dispo_mo=12288, disque_libre_go=500
|
||||
10, cpu_hote=28, ram_dispo_mo=12288, disque_libre_go=500
|
||||
)
|
||||
self.assertEqual(plan["arret"], "ram")
|
||||
self.assertLess(plan["atteignable"], 10)
|
||||
# Aucun étage sous le plancher : un Proxmox sous 2 Go ne démarre pas
|
||||
# ses démons.
|
||||
for n in plan["niveaux"]:
|
||||
self.assertGreaterEqual(n["ram"], nesting.RAM_MIN_MO)
|
||||
|
||||
def test_running_out_of_disk_is_named(self):
|
||||
plan = nesting.nesting_plan(
|
||||
10, cpu_hote=8, ram_dispo_mo=200000, disque_libre_go=60
|
||||
10, cpu_hote=28, ram_dispo_mo=200000, disque_libre_go=60
|
||||
)
|
||||
self.assertEqual(plan["arret"], "disque")
|
||||
for n in plan["niveaux"]:
|
||||
self.assertGreaterEqual(n["disque"], nesting.DISQUE_MIN_GO)
|
||||
|
||||
def test_the_host_keeps_a_share_not_just_a_floor(self):
|
||||
"""Quatre gigaoctets sur une machine de soixante, c'est 6 % laissés à
|
||||
l'hôte : le jour où les invités touchent vraiment leur mémoire, c'est
|
||||
lui qui part en swap — et la mesure serait celle du swap, pas de
|
||||
l'imbrication."""
|
||||
for dispo in (60000, 260000):
|
||||
with self.subTest(dispo=dispo):
|
||||
plan = nesting.nesting_plan(
|
||||
1, cpu_hote=28, ram_dispo_mo=dispo, disque_libre_go=500
|
||||
)
|
||||
reserve = dispo - plan["niveaux"][0]["ram"]
|
||||
self.assertGreater(reserve, nesting.HOTE_RESERVE_RAM_MO)
|
||||
self.assertGreaterEqual(
|
||||
reserve, dispo // nesting.HOTE_RESERVE_PART
|
||||
)
|
||||
|
||||
def test_a_small_host_keeps_the_floor(self):
|
||||
# Sur une petite machine, la part serait dérisoire : le plancher tient.
|
||||
plan = nesting.nesting_plan(
|
||||
1, cpu_hote=4, ram_dispo_mo=16384, disque_libre_go=200
|
||||
)
|
||||
self.assertEqual(
|
||||
16384 - plan["niveaux"][0]["ram"], nesting.HOTE_RESERVE_RAM_MO
|
||||
)
|
||||
|
||||
def test_a_depth_of_zero_asks_for_nothing(self):
|
||||
for profondeur in (0, -1, -7):
|
||||
with self.subTest(profondeur=profondeur):
|
||||
|
|
@ -122,34 +121,17 @@ class TestLePlanDesEtages(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(plan["atteignable"], 0)
|
||||
self.assertEqual(plan["niveaux"], [])
|
||||
self.assertEqual(plan["arret"], "ram")
|
||||
self.assertTrue(plan["arret"])
|
||||
|
||||
def test_a_plan_is_never_promised_beyond_what_fits(self):
|
||||
# Mieux vaut annoncer six étages et en réussir six que d'en promettre
|
||||
# dix et mourir au septième sans savoir pourquoi.
|
||||
# Depuis 0 et depuis les négatifs : « max(1, …) » forçait un tour,
|
||||
# donc « --depth 0 » rendait un plan d'UN étage et créait une VM.
|
||||
for profondeur in range(-2, 13):
|
||||
plan = nesting.nesting_plan(profondeur, **self.HOTE)
|
||||
self.assertEqual(len(plan["niveaux"]), plan["atteignable"])
|
||||
# « max(0, …) » : une demande négative ne peut pas donner un
|
||||
# nombre d'étages négatif, elle donne zéro.
|
||||
self.assertLessEqual(plan["atteignable"], max(0, profondeur))
|
||||
|
||||
|
||||
class TestLaProfondeurDUnHote(unittest.TestCase):
|
||||
"""Comptée depuis la chaîne de rebonds : c'est la seule mesure dont on
|
||||
dispose de l'extérieur, et elle est exacte pour les hôtes que nous avons
|
||||
nous-mêmes déployés — c'est nous qui écrivons ces entrées."""
|
||||
|
||||
def test_no_jump_is_the_first_level(self):
|
||||
self.assertEqual(nesting.depth_from_jumps(0), 1)
|
||||
|
||||
def test_each_jump_adds_a_level(self):
|
||||
for sauts, attendu in ((1, 2), (2, 3), (3, 4), (9, 10)):
|
||||
self.assertEqual(nesting.depth_from_jumps(sauts), attendu)
|
||||
|
||||
|
||||
class TestCompterLesRebonds(unittest.TestCase):
|
||||
"""La profondeur se lit dans ~/.ssh/config : un ProxyJump par étage.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue