[FIX] imbrication : aucun étage imbriqué n'est large, le gel le dit
Ma propre conclusion de ce matin était fausse, et une mesure l'a réfutée. J'avais écrit — code, README, commit — que le gel à 12 vCPU venait du SURENGAGEMENT : cette VM avait douze vCPU sur un hôte qui en avait deux. Descente réelle : l'étage 4 à huit vCPU, sur un parent qui en avait NEUF, charge 1,47, aucun surengagement. Gelé pareil. 32 Mio lus en 106 minutes, même RIP à trois relevés espacés de cinq minutes. C'est le nombre de vCPU de l'invité imbriqué, et rien d'autre. Le dimensionnement de bas en haut donnait 8 vCPU à l'étage 4, 7 au 5 : il rendait larges précisément les étages qui doivent rester étroits. Trois largeurs fixes le remplacent — métal, intermédiaire, fond. La mémoire et le disque, eux, restent dimensionnés depuis le bas. VCPU_INTERMEDIAIRE = 3 est une hypothèse assumée : deux démarre au quatrième étage, huit gèle, rien n'est mesuré entre les deux. --- EN --- My own conclusion from this morning was wrong, and a measurement refuted it. I had written — code, README, commit — that the 12-vCPU freeze came from OVERCOMMIT: that VM had twelve vCPU on a host with two. Real descent: level 4 at eight vCPU, on a parent with NINE, load 1.47, no overcommit whatsoever. Frozen all the same. 32 MiB read in 106 minutes, same RIP at three readings five minutes apart. It is the nested guest's vCPU count, nothing else. Bottom-up sizing gave level 4 eight vCPU and level 5 seven: it made wide exactly the levels that must stay narrow. Three fixed widths replace it — metal, intermediate, floor. Memory and disk stay sized from the bottom. VCPU_INTERMEDIAIRE = 3 is an owned hypothesis: two boots at the fourth level, eight freezes, nothing is measured in between. Assisted-by: claude-opus-5 (cherry picked from commit ee45ff4f333c69e3862e277334cb34efa39bbd57)
This commit is contained in:
parent
006f4d271b
commit
b2b3f36026
5 changed files with 265 additions and 97 deletions
|
|
@ -57,12 +57,12 @@ 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
|
and a half hours against thirteen minutes for level 3, and extrapolating that
|
||||||
ratio gave five years for the tenth.
|
ratio gave five years for the tenth.
|
||||||
|
|
||||||
So the direction is reversed. The deepest level gets what a test Proxmox
|
So the direction is reversed for **memory and disk**. The deepest level gets
|
||||||
actually asks for — 4 GB of memory, 25 GB of disk, 2 vCPU — and every parent
|
what a test Proxmox actually asks for — 4 GB of memory, 25 GB of disk — and
|
||||||
above it adds its own overhead and nothing else: one vCPU, 2 GiB, 10 GB. A
|
every parent above it adds its own overhead and nothing else: 2 GiB and 10 GB.
|
||||||
ten-level descent therefore asks its first level for 11 vCPU, 22 GB and
|
A ten-level descent therefore asks its first level for 22 GB and 115 GB, where
|
||||||
115 GB, where handing resources down wanted 50 GB of memory for the same
|
handing resources down wanted 50 GB of memory for the same depth. The
|
||||||
depth.
|
processor follows a different rule; see below.
|
||||||
|
|
||||||
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
||||||
one that ran out:
|
one that ran out:
|
||||||
|
|
@ -71,15 +71,33 @@ one that ran out:
|
||||||
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
||||||
* **disk** — the child's disk lives *inside* the parent's, which must also
|
* **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
|
* **processor** — it does *not* grow with depth. Every nested level keeps a
|
||||||
ask eleven of the first. Half the physical cores is the ceiling: the
|
fixed, narrow width; only the first level counts against the physical cores.
|
||||||
orchestrator runs on that machine too.
|
Either the machine can carry that first level or it can carry nothing.
|
||||||
|
|
||||||
That third budget is measured, not assumed. Twelve vCPU at the fourth level
|
That third rule is measured, and it cost two descents to get right. A nested
|
||||||
froze the guest kernel in early boot — same instruction pointer at three
|
guest at the **fourth** level freezes in early boot as soon as it is wide:
|
||||||
readings two minutes apart — while two progressed. The number was not the
|
twelve vCPU the first time, eight the second — same instruction pointer at
|
||||||
culprit: that VM had twelve vCPU on a host with two, six times wider than its
|
three readings five minutes apart, 32 MiB read and not one byte more for 106
|
||||||
own machine. Overcommit freezes, not the twelve.
|
minutes. Two vCPU boots.
|
||||||
|
|
||||||
|
The first freeze was blamed on **overcommit**: that VM had twelve vCPU on a
|
||||||
|
host with two. The second measurement refuted it — eight vCPU on a parent with
|
||||||
|
**nine**, load 1.47, no overcommit at all, and the same freeze. It is the
|
||||||
|
nested guest's vCPU count, not its ratio to its host's.
|
||||||
|
|
||||||
|
At the third level, 9 vCPU boots in 117 s. The threshold sits between the
|
||||||
|
third and fourth level, so no nested level is ever made wide. An earlier
|
||||||
|
version of this algorithm gave each parent one vCPU more than its child, which
|
||||||
|
made level 4 eight wide — exactly the frozen case. The rule made wide what
|
||||||
|
must stay narrow.
|
||||||
|
|
||||||
|
Hence three fixed widths: `VCPU_METAL` for level 1 (on bare metal, no freeze
|
||||||
|
risk — eleven vCPU booted there in 42 s), `VCPU_IMBRIQUE` for the deepest, and
|
||||||
|
`VCPU_INTERMEDIAIRE` in between, wide enough to host its child without being
|
||||||
|
as narrow as it. That middle number is a **hypothesis**: two is proven to boot
|
||||||
|
at the fourth level and eight is proven to freeze, with nothing measured in
|
||||||
|
between. The descent decides.
|
||||||
|
|
||||||
Memory is not the lever. On that same manual VM, dropping it from 9 GB to 2 GB
|
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
|
moved nothing — it stopped after reading the same 32 MiB, which is simply the
|
||||||
|
|
@ -147,12 +165,12 @@ surengagement, à chaque étage, avec l'hyperviseur lui-même à servir par-dess
|
||||||
Son installation dépassait deux heures et demie contre treize minutes pour
|
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.
|
l'étage 3, et l'extrapolation de ce rapport donnait cinq ANS pour le dixième.
|
||||||
|
|
||||||
Le sens est donc inversé. Le plus profond reçoit ce qu'un Proxmox de test
|
Le sens est donc inversé pour la **mémoire et le disque**. Le plus profond
|
||||||
demande vraiment — 4 Go de mémoire, 25 Go de disque, 2 vCPU — et chaque parent
|
reçoit ce qu'un Proxmox de test demande vraiment — 4 Go de mémoire, 25 Go de
|
||||||
au-dessus ajoute son propre surcoût, rien d'autre : un vCPU, 2 Gio, 10 Go. Une
|
disque — et chaque parent au-dessus ajoute son propre surcoût, rien d'autre :
|
||||||
descente à dix étages demande ainsi 11 vCPU, 22 Go et 115 Go à son premier
|
2 Gio et 10 Go. Une descente à dix étages demande ainsi 22 Go et 115 Go à son
|
||||||
étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la même
|
premier étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la
|
||||||
profondeur.
|
même profondeur. Le processeur, lui, suit une autre règle — voir plus bas.
|
||||||
|
|
||||||
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
||||||
nomme celui qui a manqué :
|
nomme celui qui a manqué :
|
||||||
|
|
@ -162,16 +180,35 @@ nomme celui qui a manqué :
|
||||||
enfant ;
|
enfant ;
|
||||||
* **le disque** — le disque de l'enfant vit *dans* celui du parent, qui doit
|
* **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
|
* **le processeur** — il ne croît *pas* avec la profondeur. Tout étage
|
||||||
étages en demandent onze au premier. La moitié des cœurs physiques est le
|
imbriqué garde une largeur fixe et étroite ; seul le premier compte sur les
|
||||||
plafond : l'orchestrateur tourne sur cette machine lui aussi.
|
cœurs physiques. Ou la machine peut porter ce premier étage, ou elle ne peut
|
||||||
|
rien.
|
||||||
|
|
||||||
Ce troisième budget est mesuré, pas supposé. Douze vCPU au quatrième étage ont
|
Cette troisième règle est mesurée, et il a fallu deux descentes pour la poser
|
||||||
gelé le noyau invité en tout début de démarrage — même pointeur d'instruction à
|
juste. Un invité imbriqué au **quatrième** étage gèle en tout début de
|
||||||
trois relevés, deux minutes d'écart — quand deux avançaient. Le nombre n'était
|
démarrage dès qu'il est large : douze vCPU la première fois, huit la seconde —
|
||||||
pas le fautif : cette VM avait douze vCPU sur un hôte qui en avait deux, six
|
même pointeur d'instruction à trois relevés espacés de cinq minutes, 32 Mio lus
|
||||||
fois plus large que sa propre machine. C'est le surengagement qui gèle, pas le
|
et plus un octet pendant 106 minutes. Deux vCPU démarrent.
|
||||||
douze.
|
|
||||||
|
Le premier gel avait été imputé au **surengagement** : cette VM à douze vCPU
|
||||||
|
tournait sur un hôte qui en avait deux. La seconde mesure l'a réfuté — huit
|
||||||
|
vCPU sur un parent qui en avait **neuf**, charge 1,47, aucun surengagement, et
|
||||||
|
le même gel. C'est le nombre de vCPU de l'invité imbriqué, et non son rapport à
|
||||||
|
celui de son hôte.
|
||||||
|
|
||||||
|
Au troisième étage, 9 vCPU démarrent en 117 s. Le seuil est entre le troisième
|
||||||
|
et le quatrième étage : aucun étage imbriqué n'est donc rendu large. Une version
|
||||||
|
précédente de cet algorithme donnait un vCPU de plus à chaque parent, ce qui
|
||||||
|
rendait l'étage 4 large de huit — exactement le cas gelé. La règle rendait large
|
||||||
|
ce qui doit rester étroit.
|
||||||
|
|
||||||
|
D'où trois largeurs fixes : `VCPU_METAL` pour l'étage 1 (sur le métal, aucun
|
||||||
|
risque de gel — onze vCPU y ont démarré en 42 s), `VCPU_IMBRIQUE` pour le plus
|
||||||
|
profond, et `VCPU_INTERMEDIAIRE` entre les deux, juste assez large pour héberger
|
||||||
|
son enfant sans être aussi étroit que lui. Ce nombre du milieu est une
|
||||||
|
**hypothèse** : deux démarre au quatrième étage, huit gèle, et rien n'est mesuré
|
||||||
|
entre les deux. C'est la descente qui tranche.
|
||||||
|
|
||||||
La mémoire n'est pas le levier. Sur cette même VM examinée à la main, la faire
|
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
|
passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu les
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,12 @@ surengagement, à chaque étage, avec l'hyperviseur lui-même à servir par-dess
|
||||||
Son installation dépassait deux heures et demie contre treize minutes pour
|
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.
|
l'étage 3, et l'extrapolation de ce rapport donnait cinq ANS pour le dixième.
|
||||||
|
|
||||||
Le sens est donc inversé. Le plus profond reçoit ce qu'un Proxmox de test
|
Le sens est donc inversé pour la **mémoire et le disque**. Le plus profond
|
||||||
demande vraiment — 4 Go de mémoire, 25 Go de disque, 2 vCPU — et chaque parent
|
reçoit ce qu'un Proxmox de test demande vraiment — 4 Go de mémoire, 25 Go de
|
||||||
au-dessus ajoute son propre surcoût, rien d'autre : un vCPU, 2 Gio, 10 Go. Une
|
disque — et chaque parent au-dessus ajoute son propre surcoût, rien d'autre :
|
||||||
descente à dix étages demande ainsi 11 vCPU, 22 Go et 115 Go à son premier
|
2 Gio et 10 Go. Une descente à dix étages demande ainsi 22 Go et 115 Go à son
|
||||||
étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la même
|
premier étage, là où la cession de haut en bas voulait 50 Go de mémoire pour la
|
||||||
profondeur.
|
même profondeur. Le processeur, lui, suit une autre règle — voir plus bas.
|
||||||
|
|
||||||
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
Trois budgets peuvent borner la profondeur, et `script/proxmox/nesting.py`
|
||||||
nomme celui qui a manqué :
|
nomme celui qui a manqué :
|
||||||
|
|
@ -71,16 +71,35 @@ nomme celui qui a manqué :
|
||||||
enfant ;
|
enfant ;
|
||||||
* **le disque** — le disque de l'enfant vit *dans* celui du parent, qui doit
|
* **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
|
* **le processeur** — il ne croît *pas* avec la profondeur. Tout étage
|
||||||
étages en demandent onze au premier. La moitié des cœurs physiques est le
|
imbriqué garde une largeur fixe et étroite ; seul le premier compte sur les
|
||||||
plafond : l'orchestrateur tourne sur cette machine lui aussi.
|
cœurs physiques. Ou la machine peut porter ce premier étage, ou elle ne peut
|
||||||
|
rien.
|
||||||
|
|
||||||
Ce troisième budget est mesuré, pas supposé. Douze vCPU au quatrième étage ont
|
Cette troisième règle est mesurée, et il a fallu deux descentes pour la poser
|
||||||
gelé le noyau invité en tout début de démarrage — même pointeur d'instruction à
|
juste. Un invité imbriqué au **quatrième** étage gèle en tout début de
|
||||||
trois relevés, deux minutes d'écart — quand deux avançaient. Le nombre n'était
|
démarrage dès qu'il est large : douze vCPU la première fois, huit la seconde —
|
||||||
pas le fautif : cette VM avait douze vCPU sur un hôte qui en avait deux, six
|
même pointeur d'instruction à trois relevés espacés de cinq minutes, 32 Mio lus
|
||||||
fois plus large que sa propre machine. C'est le surengagement qui gèle, pas le
|
et plus un octet pendant 106 minutes. Deux vCPU démarrent.
|
||||||
douze.
|
|
||||||
|
Le premier gel avait été imputé au **surengagement** : cette VM à douze vCPU
|
||||||
|
tournait sur un hôte qui en avait deux. La seconde mesure l'a réfuté — huit
|
||||||
|
vCPU sur un parent qui en avait **neuf**, charge 1,47, aucun surengagement, et
|
||||||
|
le même gel. C'est le nombre de vCPU de l'invité imbriqué, et non son rapport à
|
||||||
|
celui de son hôte.
|
||||||
|
|
||||||
|
Au troisième étage, 9 vCPU démarrent en 117 s. Le seuil est entre le troisième
|
||||||
|
et le quatrième étage : aucun étage imbriqué n'est donc rendu large. Une version
|
||||||
|
précédente de cet algorithme donnait un vCPU de plus à chaque parent, ce qui
|
||||||
|
rendait l'étage 4 large de huit — exactement le cas gelé. La règle rendait large
|
||||||
|
ce qui doit rester étroit.
|
||||||
|
|
||||||
|
D'où trois largeurs fixes : `VCPU_METAL` pour l'étage 1 (sur le métal, aucun
|
||||||
|
risque de gel — onze vCPU y ont démarré en 42 s), `VCPU_IMBRIQUE` pour le plus
|
||||||
|
profond, et `VCPU_INTERMEDIAIRE` entre les deux, juste assez large pour héberger
|
||||||
|
son enfant sans être aussi étroit que lui. Ce nombre du milieu est une
|
||||||
|
**hypothèse** : deux démarre au quatrième étage, huit gèle, et rien n'est mesuré
|
||||||
|
entre les deux. C'est la descente qui tranche.
|
||||||
|
|
||||||
La mémoire n'est pas le levier. Sur cette même VM examinée à la main, la faire
|
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
|
passer de 9 Go à 2 Go n'a rien déplacé : elle s'arrêtait après avoir lu les
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,12 @@ 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
|
and a half hours against thirteen minutes for level 3, and extrapolating that
|
||||||
ratio gave five years for the tenth.
|
ratio gave five years for the tenth.
|
||||||
|
|
||||||
So the direction is reversed. The deepest level gets what a test Proxmox
|
So the direction is reversed for **memory and disk**. The deepest level gets
|
||||||
actually asks for — 4 GB of memory, 25 GB of disk, 2 vCPU — and every parent
|
what a test Proxmox actually asks for — 4 GB of memory, 25 GB of disk — and
|
||||||
above it adds its own overhead and nothing else: one vCPU, 2 GiB, 10 GB. A
|
every parent above it adds its own overhead and nothing else: 2 GiB and 10 GB.
|
||||||
ten-level descent therefore asks its first level for 11 vCPU, 22 GB and
|
A ten-level descent therefore asks its first level for 22 GB and 115 GB, where
|
||||||
115 GB, where handing resources down wanted 50 GB of memory for the same
|
handing resources down wanted 50 GB of memory for the same depth. The
|
||||||
depth.
|
processor follows a different rule; see below.
|
||||||
|
|
||||||
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
Three budgets can bound the depth, and `script/proxmox/nesting.py` names the
|
||||||
one that ran out:
|
one that ran out:
|
||||||
|
|
@ -66,15 +66,33 @@ one that ran out:
|
||||||
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
`pvestatd`, `pvedaemon`, `pveproxy`) *and* hold its child;
|
||||||
* **disk** — the child's disk lives *inside* the parent's, which must also
|
* **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
|
* **processor** — it does *not* grow with depth. Every nested level keeps a
|
||||||
ask eleven of the first. Half the physical cores is the ceiling: the
|
fixed, narrow width; only the first level counts against the physical cores.
|
||||||
orchestrator runs on that machine too.
|
Either the machine can carry that first level or it can carry nothing.
|
||||||
|
|
||||||
That third budget is measured, not assumed. Twelve vCPU at the fourth level
|
That third rule is measured, and it cost two descents to get right. A nested
|
||||||
froze the guest kernel in early boot — same instruction pointer at three
|
guest at the **fourth** level freezes in early boot as soon as it is wide:
|
||||||
readings two minutes apart — while two progressed. The number was not the
|
twelve vCPU the first time, eight the second — same instruction pointer at
|
||||||
culprit: that VM had twelve vCPU on a host with two, six times wider than its
|
three readings five minutes apart, 32 MiB read and not one byte more for 106
|
||||||
own machine. Overcommit freezes, not the twelve.
|
minutes. Two vCPU boots.
|
||||||
|
|
||||||
|
The first freeze was blamed on **overcommit**: that VM had twelve vCPU on a
|
||||||
|
host with two. The second measurement refuted it — eight vCPU on a parent with
|
||||||
|
**nine**, load 1.47, no overcommit at all, and the same freeze. It is the
|
||||||
|
nested guest's vCPU count, not its ratio to its host's.
|
||||||
|
|
||||||
|
At the third level, 9 vCPU boots in 117 s. The threshold sits between the
|
||||||
|
third and fourth level, so no nested level is ever made wide. An earlier
|
||||||
|
version of this algorithm gave each parent one vCPU more than its child, which
|
||||||
|
made level 4 eight wide — exactly the frozen case. The rule made wide what
|
||||||
|
must stay narrow.
|
||||||
|
|
||||||
|
Hence three fixed widths: `VCPU_METAL` for level 1 (on bare metal, no freeze
|
||||||
|
risk — eleven vCPU booted there in 42 s), `VCPU_IMBRIQUE` for the deepest, and
|
||||||
|
`VCPU_INTERMEDIAIRE` in between, wide enough to host its child without being
|
||||||
|
as narrow as it. That middle number is a **hypothesis**: two is proven to boot
|
||||||
|
at the fourth level and eight is proven to freeze, with nothing measured in
|
||||||
|
between. The descent decides.
|
||||||
|
|
||||||
Memory is not the lever. On that same manual VM, dropping it from 9 GB to 2 GB
|
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
|
moved nothing — it stopped after reading the same 32 MiB, which is simply the
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,19 @@ documente l'imbrication qu'à DEUX niveaux.
|
||||||
|
|
||||||
Deux nombres viennent de la même mesure, et méritent d'être dits :
|
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
|
* au QUATRIÈME étage, un invité large GÈLE en tout début de démarrage. Mesuré
|
||||||
démarrage — même RIP à trois relevés, deux minutes d'écart, pas un octet lu
|
deux fois, à douze vCPU puis à huit : même RIP à trois relevés espacés de
|
||||||
de plus. Les mêmes 2 vCPU avançaient. Le nombre n'était pas le fautif : cette
|
cinq minutes, 32 Mio lus et plus un octet — 106 minutes durant, pour le
|
||||||
VM avait douze vCPU sur un hôte qui en avait DEUX, six fois plus large que sa
|
second. Les mêmes 2 vCPU démarrent.
|
||||||
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
|
On avait d'abord imputé le premier gel au SURENGAGEMENT : cette VM à douze
|
||||||
qu'à son enfant ;
|
vCPU tournait sur un hôte qui en avait deux. La seconde mesure l'a réfuté —
|
||||||
|
huit vCPU sur un parent qui en avait NEUF, charge 1,47, aucun surengagement,
|
||||||
|
et le même gel. C'est bien le nombre de vCPU de l'invité imbriqué, et non son
|
||||||
|
rapport à celui de son hôte.
|
||||||
|
|
||||||
|
Au TROISIÈME étage, 9 vCPU démarrent en 117 s. Le seuil est donc entre le
|
||||||
|
troisième et le quatrième étage ; tout étage imbriqué reste étroit ;
|
||||||
* cette VM-là s'arrêtait après avoir lu 33 682 432 octets — 32 Mio, soit
|
* 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
|
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
|
pas quand on lui retirait de la mémoire. La mémoire n'était donc pas le
|
||||||
|
|
@ -74,22 +80,40 @@ PVE_DISQUE_CIBLE_GO = 25
|
||||||
|
|
||||||
# En dessous, un Proxmox ne démarre pas ses démons ou n'a plus la place
|
# En dessous, un Proxmox ne démarre pas ses démons ou n'a plus la place
|
||||||
# d'importer une image cloud.
|
# d'importer une image cloud.
|
||||||
|
# Une profondeur qu'aucun budget ne borne. Grand, mais fini : « inf » se
|
||||||
|
# propagerait dans min() et rendrait un float là où tout le reste compte des
|
||||||
|
# étages entiers.
|
||||||
|
PLAFOND_LIBRE = 10**6
|
||||||
RAM_MIN_MO = 2048
|
RAM_MIN_MO = 2048
|
||||||
DISQUE_MIN_GO = 15
|
DISQUE_MIN_GO = 15
|
||||||
|
|
||||||
# Le processeur se dimensionne DEPUIS LE BAS lui aussi, et pour la même
|
# Le processeur NE se dimensionne PAS depuis le bas, contrairement à la
|
||||||
# raison que la mémoire — mais celle-là s'est vue à l'usage.
|
# mémoire et au disque. Trois nombres fixes, et la mesure les impose.
|
||||||
#
|
#
|
||||||
# Avec deux vCPU à chaque étage imbriqué, l'étage 3 avait deux vCPU pour
|
# Une première version donnait à chaque parent un vCPU de plus qu'à son enfant,
|
||||||
# héberger un invité qui en demandait deux : cent pour cent de surengagement,
|
# pour supprimer le surengagement : le plus profond deux, son parent trois, et
|
||||||
# et l'hyperviseur lui-même à servir par-dessus. À chaque étage. Mesuré sur une
|
# ainsi de suite jusqu'à onze au premier. Elle rendait donc LARGES les étages
|
||||||
# descente réelle : une seconde VM démarrée au quatrième étage a lu DEUX
|
# du milieu — huit au quatrième, sept au cinquième. Or c'est exactement là que
|
||||||
# KILO-OCTETS en onze minutes, affamée par l'installation qui tournait à côté.
|
# l'invité gèle : mesuré, l'étage 4 à huit vCPU n'a pas passé son amorçage en
|
||||||
|
# 106 minutes, sur un parent à neuf vCPU parfaitement sain. La règle rendait
|
||||||
|
# large ce qui doit rester étroit.
|
||||||
#
|
#
|
||||||
# Chaque étage reçoit donc UN vCPU de plus que son enfant : le plus profond en
|
# Le plus profond : deux, le seul chiffre dont on ait la preuve qu'il démarre
|
||||||
# a deux, son parent trois, et ainsi de suite. Le premier étage d'une descente
|
# au quatrième étage.
|
||||||
# à dix en demande onze — sur vingt-huit cœurs réels, cela passe.
|
|
||||||
VCPU_IMBRIQUE = 2
|
VCPU_IMBRIQUE = 2
|
||||||
|
# Les étages imbriqués intermédiaires : un de plus, pour héberger leur enfant
|
||||||
|
# sans être aussi étroits que lui. Deux hébergeant deux, c'est cent pour cent
|
||||||
|
# de surengagement — et l'installation de l'étage 4 dépassait alors 2 h 50
|
||||||
|
# contre 793 s pour l'étage 3.
|
||||||
|
#
|
||||||
|
# Trois est une HYPOTHÈSE : on a la preuve que deux démarre au quatrième étage
|
||||||
|
# et que huit gèle, rien entre les deux. C'est la descente qui tranchera.
|
||||||
|
VCPU_INTERMEDIAIRE = 3
|
||||||
|
# Le premier étage tourne sur le MÉTAL : aucun risque de gel, et son amorçage
|
||||||
|
# est rapide — onze vCPU y ont démarré en 42 s. Il n'a pourtant qu'un enfant à
|
||||||
|
# trois vCPU à servir ; quatre suffisent, et laissent la machine physique aux
|
||||||
|
# autres.
|
||||||
|
VCPU_METAL = 4
|
||||||
# Ce qu'on LAISSE à la machine physique. L'orchestrateur tourne dessus, son
|
# Ce qu'on LAISSE à la machine physique. L'orchestrateur tourne dessus, son
|
||||||
# ssh vers chaque étage aussi, et la suite de tests avec.
|
# ssh vers chaque étage aussi, et la suite de tests avec.
|
||||||
#
|
#
|
||||||
|
|
@ -153,14 +177,14 @@ def nesting_plan(
|
||||||
"""Ce que le PREMIER étage doit avoir pour qu'une descente de `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.
|
é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
|
Le processeur n'en fait PAS partie de la même façon : il ne croît
|
||||||
enfant, donc le premier en veut VCPU_IMBRIQUE + d - 1. Sans cette
|
pas avec la profondeur, puisque tout étage imbriqué reste étroit. Le
|
||||||
condition, on annonçait dix étages sur une machine à quatre cœurs.
|
premier étage demande VCPU_METAL, quelle que soit la profondeur.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
PVE_RAM_CIBLE_MO + (d - 1) * PVE_RAM_MO,
|
PVE_RAM_CIBLE_MO + (d - 1) * PVE_RAM_MO,
|
||||||
PVE_DISQUE_CIBLE_GO + (d - 1) * PVE_DISQUE_GO,
|
PVE_DISQUE_CIBLE_GO + (d - 1) * PVE_DISQUE_GO,
|
||||||
VCPU_IMBRIQUE + d - 1,
|
VCPU_METAL if d > 1 else VCPU_IMBRIQUE,
|
||||||
)
|
)
|
||||||
|
|
||||||
budget_vcpu = int(cpu_hote) - HOTE_RESERVE_VCPU
|
budget_vcpu = int(cpu_hote) - HOTE_RESERVE_VCPU
|
||||||
|
|
@ -171,7 +195,10 @@ def nesting_plan(
|
||||||
plafonds = {
|
plafonds = {
|
||||||
"ram": (budget_ram - PVE_RAM_CIBLE_MO) // PVE_RAM_MO + 1,
|
"ram": (budget_ram - PVE_RAM_CIBLE_MO) // PVE_RAM_MO + 1,
|
||||||
"disque": (budget_disque - PVE_DISQUE_CIBLE_GO) // PVE_DISQUE_GO + 1,
|
"disque": (budget_disque - PVE_DISQUE_CIBLE_GO) // PVE_DISQUE_GO + 1,
|
||||||
"vcpu": budget_vcpu - VCPU_IMBRIQUE + 1,
|
# Le processeur ne borne plus la profondeur : les étages imbriqués
|
||||||
|
# gardent une largeur fixe, seul le premier compte sur le métal. Il
|
||||||
|
# borne encore à ZÉRO une machine trop petite pour ce premier étage.
|
||||||
|
"vcpu": PLAFOND_LIBRE if budget_vcpu >= VCPU_METAL else 0,
|
||||||
}
|
}
|
||||||
plafonds = {nom: max(0, valeur) for nom, valeur in plafonds.items()}
|
plafonds = {nom: max(0, valeur) for nom, valeur in plafonds.items()}
|
||||||
demandee = max(0, int(profondeur))
|
demandee = max(0, int(profondeur))
|
||||||
|
|
@ -185,10 +212,18 @@ def nesting_plan(
|
||||||
niveaux = [
|
niveaux = [
|
||||||
{
|
{
|
||||||
"niveau": niveau,
|
"niveau": niveau,
|
||||||
# UN de plus que son enfant. Un parent aussi étroit que son
|
# Trois largeurs, et aucune ne dépend de la profondeur : le
|
||||||
# enfant, c'est cent pour cent de surengagement — et l'hyperviseur
|
# métal en premier, le fond au plus étroit, les intermédiaires
|
||||||
# à servir en plus.
|
# juste assez larges pour héberger leur enfant.
|
||||||
"vcpu": VCPU_IMBRIQUE + (atteignable - niveau),
|
"vcpu": (
|
||||||
|
VCPU_METAL
|
||||||
|
if niveau == 1
|
||||||
|
else (
|
||||||
|
VCPU_IMBRIQUE
|
||||||
|
if niveau == atteignable
|
||||||
|
else VCPU_INTERMEDIAIRE
|
||||||
|
)
|
||||||
|
),
|
||||||
# Les planchers ne sont pas décoratifs : ils tiennent même si
|
# Les planchers ne sont pas décoratifs : ils tiennent même si
|
||||||
# quelqu'un baisse une CIBLE un jour. Sans eux, ils n'étaient plus
|
# quelqu'un baisse une CIBLE un jour. Sans eux, ils n'étaient plus
|
||||||
# lus par personne et les tests qui les vérifiaient passaient
|
# lus par personne et les tests qui les vérifiaient passaient
|
||||||
|
|
|
||||||
|
|
@ -50,21 +50,64 @@ class TestLePlanDesEtages(unittest.TestCase):
|
||||||
|
|
||||||
def test_each_parent_adds_exactly_its_own_overhead(self):
|
def test_each_parent_adds_exactly_its_own_overhead(self):
|
||||||
# Ni plus ni moins : un parent plus large que nécessaire ralentit tout
|
# 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.
|
# ce qu'il héberge, un parent trop juste ne le fait pas tourner. Vaut
|
||||||
|
# pour la mémoire et le disque — le processeur, lui, ne croît pas avec
|
||||||
|
# la profondeur, voir test_no_nested_level_is_ever_wide.
|
||||||
niveaux = nesting.nesting_plan(8, **self.HOTE)["niveaux"]
|
niveaux = nesting.nesting_plan(8, **self.HOTE)["niveaux"]
|
||||||
for parent, enfant in zip(niveaux, niveaux[1:]):
|
for parent, enfant in zip(niveaux, niveaux[1:]):
|
||||||
self.assertEqual(parent["ram"] - enfant["ram"], nesting.PVE_RAM_MO)
|
self.assertEqual(parent["ram"] - enfant["ram"], nesting.PVE_RAM_MO)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
parent["disque"] - enfant["disque"], nesting.PVE_DISQUE_GO
|
parent["disque"] - enfant["disque"], nesting.PVE_DISQUE_GO
|
||||||
)
|
)
|
||||||
self.assertEqual(parent["vcpu"] - enfant["vcpu"], 1)
|
|
||||||
|
def test_no_nested_level_is_ever_wide(self):
|
||||||
|
"""MESURÉ, deux fois : au quatrième étage un invité large GÈLE.
|
||||||
|
|
||||||
|
Douze vCPU d'abord, sur un parent qui en avait deux : on avait imputé
|
||||||
|
le gel au surengagement. Puis huit vCPU sur un parent qui en avait
|
||||||
|
NEUF, charge 1,47, aucun surengagement — 32 Mio lus en 106 minutes,
|
||||||
|
même RIP à trois relevés espacés de cinq minutes. C'est le nombre de
|
||||||
|
vCPU de l'invité imbriqué, et rien d'autre.
|
||||||
|
|
||||||
|
Une version de ce module donnait un vCPU de plus à chaque parent, ce
|
||||||
|
qui rendait l'étage 4 large de huit : exactement le cas gelé. Aucun
|
||||||
|
étage imbriqué ne doit dépasser VCPU_INTERMEDIAIRE, à AUCUNE
|
||||||
|
profondeur demandée."""
|
||||||
|
for profondeur in range(1, 13):
|
||||||
|
niveaux = nesting.nesting_plan(profondeur, **self.HOTE)["niveaux"]
|
||||||
|
for n in niveaux[1:]:
|
||||||
|
self.assertLessEqual(
|
||||||
|
n["vcpu"],
|
||||||
|
nesting.VCPU_INTERMEDIAIRE,
|
||||||
|
f"profondeur {profondeur}, étage {n['niveau']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_the_three_widths_are_where_they_belong(self):
|
||||||
|
niveaux = nesting.nesting_plan(6, **self.HOTE)["niveaux"]
|
||||||
|
# Le métal : aucun risque de gel, onze vCPU y ont démarré en 42 s.
|
||||||
|
self.assertEqual(niveaux[0]["vcpu"], nesting.VCPU_METAL)
|
||||||
|
# Le fond : le seul chiffre dont on ait la preuve qu'il démarre au
|
||||||
|
# quatrième étage.
|
||||||
|
self.assertEqual(niveaux[-1]["vcpu"], nesting.VCPU_IMBRIQUE)
|
||||||
|
for n in niveaux[1:-1]:
|
||||||
|
self.assertEqual(n["vcpu"], nesting.VCPU_INTERMEDIAIRE)
|
||||||
|
|
||||||
|
def test_a_single_level_descent_runs_on_metal(self):
|
||||||
|
niveaux = nesting.nesting_plan(1, **self.HOTE)["niveaux"]
|
||||||
|
self.assertEqual(len(niveaux), 1)
|
||||||
|
self.assertEqual(niveaux[0]["vcpu"], nesting.VCPU_METAL)
|
||||||
|
|
||||||
def test_a_parent_is_never_narrower_than_its_child(self):
|
def test_a_parent_is_never_narrower_than_its_child(self):
|
||||||
"""Deux vCPU hébergeant deux vCPU, c'est cent pour cent de
|
"""Deux vCPU hébergeant deux vCPU, c'est cent pour cent de
|
||||||
surengagement — et l'hyperviseur à servir en plus. Mesuré : une VM
|
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,
|
démarrée au quatrième étage a lu DEUX KILO-OCTETS en onze minutes,
|
||||||
affamée par l'installation qui tournait à côté."""
|
affamée par l'installation qui tournait à côté. L'installation de
|
||||||
for coeurs in (4, 8, 12, 28):
|
l'étage 4 dépassait alors 2 h 50 contre 793 s pour l'étage 3.
|
||||||
|
|
||||||
|
« Jamais plus étroit », et non « toujours plus large » : deux étages
|
||||||
|
imbriqués voisins ont la même largeur, ce que le gel du quatrième
|
||||||
|
étage impose. C'est le PLUS PROFOND qui descend à deux."""
|
||||||
|
for coeurs in (6, 8, 12, 28):
|
||||||
with self.subTest(coeurs=coeurs):
|
with self.subTest(coeurs=coeurs):
|
||||||
niveaux = nesting.nesting_plan(
|
niveaux = nesting.nesting_plan(
|
||||||
6,
|
6,
|
||||||
|
|
@ -73,22 +116,38 @@ class TestLePlanDesEtages(unittest.TestCase):
|
||||||
disque_libre_go=400,
|
disque_libre_go=400,
|
||||||
)["niveaux"]
|
)["niveaux"]
|
||||||
for parent, enfant in zip(niveaux, niveaux[1:]):
|
for parent, enfant in zip(niveaux, niveaux[1:]):
|
||||||
self.assertGreater(parent["vcpu"], enfant["vcpu"])
|
self.assertGreaterEqual(parent["vcpu"], enfant["vcpu"])
|
||||||
self.assertGreater(parent["ram"], enfant["ram"])
|
self.assertGreater(parent["ram"], enfant["ram"])
|
||||||
self.assertGreater(parent["disque"], enfant["disque"])
|
self.assertGreater(parent["disque"], enfant["disque"])
|
||||||
|
|
||||||
def test_the_cpu_budget_can_bound_the_depth(self):
|
def test_the_cpu_only_ever_refuses_outright(self):
|
||||||
"""Sur une petite machine, c'est le PROCESSEUR qui borne, pas la
|
"""Le processeur ne borne plus une profondeur INTERMÉDIAIRE : les
|
||||||
mémoire : chaque étage en veut un de plus que son enfant, donc dix
|
étages imbriqués gardent une largeur fixe, seul le premier compte sur
|
||||||
étages demandent onze vCPU au premier."""
|
le métal. Ou la machine peut le porter, ou elle ne peut rien.
|
||||||
plan = nesting.nesting_plan(
|
|
||||||
|
Une version d'avant faisait croître la demande avec la profondeur —
|
||||||
|
onze vCPU pour dix étages — et bornait donc à trois étages sur huit
|
||||||
|
cœurs. Elle rendait aussi le quatrième étage large de huit, ce qui le
|
||||||
|
gelait : la borne cachait un défaut."""
|
||||||
|
large = nesting.nesting_plan(
|
||||||
10, cpu_hote=8, ram_dispo_mo=64000, disque_libre_go=400
|
10, cpu_hote=8, ram_dispo_mo=64000, disque_libre_go=400
|
||||||
)
|
)
|
||||||
self.assertEqual(plan["arret"], "vcpu")
|
self.assertEqual(large["atteignable"], 10)
|
||||||
self.assertLess(plan["atteignable"], 10)
|
self.assertEqual(large["arret"], "")
|
||||||
|
# Trop petite pour le premier étage : zéro, et le dire.
|
||||||
|
for coeurs in (1, 2, 4):
|
||||||
|
with self.subTest(coeurs=coeurs):
|
||||||
|
petite = nesting.nesting_plan(
|
||||||
|
10,
|
||||||
|
cpu_hote=coeurs,
|
||||||
|
ram_dispo_mo=64000,
|
||||||
|
disque_libre_go=400,
|
||||||
|
)
|
||||||
|
self.assertEqual(petite["atteignable"], 0)
|
||||||
|
self.assertEqual(petite["arret"], "vcpu")
|
||||||
# Et le premier étage laisse à l'hôte ce qui lui est réservé.
|
# Et le premier étage laisse à l'hôte ce qui lui est réservé.
|
||||||
self.assertLessEqual(
|
self.assertLessEqual(
|
||||||
plan["niveaux"][0]["vcpu"], 8 - nesting.HOTE_RESERVE_VCPU
|
large["niveaux"][0]["vcpu"], 8 - nesting.HOTE_RESERVE_VCPU
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_the_named_resource_is_the_one_that_really_binds(self):
|
def test_the_named_resource_is_the_one_that_really_binds(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue