fix(proxmox): Task-Status unter Last weiter abfragen, nesting an
Im Feldtest wurde ein LXC fertig angelegt, während ProxDeploy nach drei gescheiterten Statusabfragen aufgab (Verbindungsaufbau > 10 s bei 76 % I/O-Last). Statusabfragen werden jetzt bis zum Task-Zeitlimit wiederholt, der Verbindungsaufbau darf 30 s dauern. nesting ist für Container standardmäßig an wie in der Proxmox-Oberfläche – Proxmox warnt sonst bei systemd 249/252. Refs #020 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,10 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
- Standardwerte je Server (Node, Storage, Bridge) im Server-Formular; der Assistent belegt sie vor, die CLI nutzt sie für kurze Befehle (#023)
|
||||
|
||||
### Geändert
|
||||
- LXC: `nesting` ist für Container standardmäßig an, wie in der Proxmox-Oberfläche – Debian 12 und Ubuntu 22.04 (systemd 252/249) brauchen es; abwählbar im Assistenten und mit `--nesting=false` (#020)
|
||||
|
||||
### Behoben
|
||||
- Warten auf Proxmox-Tasks bricht bei vorübergehend fehlschlagenden Statusabfragen nicht mehr ab, sondern fragt bis zum Zeitlimit des Tasks weiter; Verbindungsaufbau darf 30 s statt 10 s dauern (#020)
|
||||
- Gast-Benutzer, der wie eine vorhandene Gruppe heißt (z. B. „admin“ auf Ubuntu 22.04), ließ die Provisionierung mit Code 9 abbrechen; die Gruppe wird jetzt Primärgruppe (#020)
|
||||
- `pnpm test:container` lief seit pnpm 12 nicht mehr (`pnpm -s` gibt es dort nicht) (#020)
|
||||
- Auf ausgelasteten Nodes brachen Aufträge beim ersten Verbindungswackler ab („Broken pipe“, „other side closed“, Zeitüberschreitung). Lesende Proxmox-Aufrufe werden jetzt bis zu dreimal wiederholt, schreibende nur, wenn die Anfrage sicher nicht ankam; Schreibaufrufe dürfen 120 s dauern; Wiederholungen stehen im Detail-Log (#020)
|
||||
|
||||
@@ -503,13 +503,13 @@
|
||||
},
|
||||
"features": {
|
||||
"default": {
|
||||
"nesting": false,
|
||||
"nesting": true,
|
||||
"keyctl": false
|
||||
},
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nesting": {
|
||||
"default": false,
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"keyctl": {
|
||||
|
||||
@@ -49,7 +49,7 @@ func (g *gastFlags) registrieren(f *pflag.FlagSet, typ string) {
|
||||
if typ == "lxc" {
|
||||
f.IntVar(&g.swap, "swap", 0, "Swap in MB (Standard: 512)")
|
||||
f.BoolVar(&g.privilegiert, "privileged", false, "privilegierter Container (Standard: unprivilegiert)")
|
||||
f.BoolVar(&g.nesting, "nesting", false, "Feature nesting (das Profil docker setzt es selbst)")
|
||||
f.BoolVar(&g.nesting, "nesting", true, "Feature nesting (Standard an wie in Proxmox; --nesting=false schaltet es ab)")
|
||||
} else {
|
||||
f.BoolVar(&g.templateBauen, "build-template", false, "fehlt das Template, vorher bauen")
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ export function Assistent({
|
||||
storage: vor.storage,
|
||||
swapMb: "512",
|
||||
unprivilegiert: true,
|
||||
nesting: false,
|
||||
nesting: true,
|
||||
bridge: vor.bridge,
|
||||
vlan: "",
|
||||
modus: "dhcp",
|
||||
@@ -817,7 +817,7 @@ export function Assistent({
|
||||
hilfe={
|
||||
werte.profile.includes("docker")
|
||||
? "Das Profil docker schaltet es ohnehin ein."
|
||||
: "Für Docker oder systemd-Dienste mit eigenem Namensraum."
|
||||
: "Standard an, wie in Proxmox: neuere systemd-Versionen brauchen es im unprivilegierten Container."
|
||||
}
|
||||
checked={werte.nesting || werte.profile.includes("docker")}
|
||||
disabled={werte.profile.includes("docker")}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { istTrockenUpid, pfad, type ProxmoxClient } from "./client";
|
||||
import { istTrockenUpid, pfad, stoerung, type ProxmoxClient } from "./client";
|
||||
import { ProxmoxFehler } from "./fehler";
|
||||
|
||||
/**
|
||||
@@ -53,10 +53,19 @@ export async function aufgabeAbwarten(
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
const status = await client.get<TaskStatus>(pfad`/nodes/${node}/tasks/${upid}/status`);
|
||||
if (optionen.beiLog) {
|
||||
const neu = await logLesen();
|
||||
if (neu.length > 0) optionen.beiLog(neu);
|
||||
// Nachsehen ist harmlos: scheitert eine Abfrage vorübergehend (ausgelasteter Node), wird bis
|
||||
// zum Zeitlimit des Tasks weiter gefragt – der Task läuft auf dem Node ja weiter.
|
||||
let status: TaskStatus;
|
||||
try {
|
||||
status = await client.get<TaskStatus>(pfad`/nodes/${node}/tasks/${upid}/status`);
|
||||
if (optionen.beiLog) {
|
||||
const neu = await logLesen();
|
||||
if (neu.length > 0) optionen.beiLog(neu);
|
||||
}
|
||||
} catch (fehler) {
|
||||
if (stoerung(fehler) === null || Date.now() - beginn > zeitlimit) throw fehler;
|
||||
await warten(Math.max(intervall, 5000));
|
||||
continue;
|
||||
}
|
||||
if (status.status === "stopped") {
|
||||
if (status.exitstatus === "OK" || status.exitstatus?.startsWith("WARNINGS")) return;
|
||||
|
||||
@@ -54,6 +54,9 @@ const VERSUCHE = 3;
|
||||
export function stoerung(fehler: unknown): "nicht_angekommen" | "unklar" | null {
|
||||
if (fehler instanceof ProxmoxFehler) {
|
||||
const status = fehler.details.status;
|
||||
// Schon übersetzte Netzwerkfehler (ohne HTTP-Status); TLS und Fingerprint sind nie vorübergehend.
|
||||
if (status === undefined && (fehler.art === "zeitueberschreitung" || fehler.art === "verbindung"))
|
||||
return "unklar";
|
||||
if (status === 595 || status === 596) return "unklar";
|
||||
if (
|
||||
(status === 500 || status === 502 || status === 503) &&
|
||||
@@ -122,7 +125,7 @@ export class ProxmoxClient {
|
||||
);
|
||||
}
|
||||
this.basis = verbindung.url.replace(/\/+$/, "") + "/api2/json";
|
||||
this.agent = new Agent({ connect: tlsConnector(verbindung), keepAliveTimeout: 2_000 });
|
||||
this.agent = new Agent({ connect: tlsConnector(verbindung), keepAliveTimeout: 4_000 });
|
||||
this.kopf = `PVEAPIToken=${verbindung.tokenId}=${verbindung.tokenGeheimnis}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,12 @@ export function tlsConnector(einstellung: TlsEinstellung): buildConnector.connec
|
||||
}
|
||||
// Ohne Sitzungs-Cache: eine wiederaufgenommene TLS-Sitzung schickt kein Zertifikat mit,
|
||||
// und der Pin ließe sich dann nicht prüfen.
|
||||
const basis = buildConnector({ rejectUnauthorized: einstellung.tlsPruefen, maxCachedSessions: 0 });
|
||||
// 30 s statt der 10 s von undici: ein ausgelasteter Node nimmt neue Verbindungen nur zögerlich an.
|
||||
const basis = buildConnector({
|
||||
rejectUnauthorized: einstellung.tlsPruefen,
|
||||
maxCachedSessions: 0,
|
||||
timeout: 30_000,
|
||||
});
|
||||
if (!pin) return basis;
|
||||
|
||||
return (optionen, rueckruf) => {
|
||||
|
||||
@@ -98,8 +98,10 @@ export const AusrollAnfrage = z
|
||||
swapMb: z.coerce.number().int().min(0).max(1_048_576).default(512),
|
||||
unprivilegiert: z.boolean().default(true),
|
||||
features: z
|
||||
.object({ nesting: z.boolean().default(false), keyctl: z.boolean().default(false) })
|
||||
.default({ nesting: false, keyctl: false }),
|
||||
// nesting an wie in der Proxmox-Oberfläche: neuere systemd-Versionen brauchen es im
|
||||
// unprivilegierten Container („Systemd 252 detected. You may need to enable nesting.“).
|
||||
.object({ nesting: z.boolean().default(true), keyctl: z.boolean().default(false) })
|
||||
.default({ nesting: true, keyctl: false }),
|
||||
netz: Netz,
|
||||
zugang: Zugang,
|
||||
profile: z.array(z.string()).default(["base"]),
|
||||
|
||||
@@ -275,3 +275,31 @@ describe("Proxmox-Tasks (UPID)", () => {
|
||||
expect(fehler.hinweis).toContain("TASK ERROR");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Task abwarten unter Last", () => {
|
||||
it("fragt nach vorübergehenden Fehlern der Statusabfrage weiter, bis der Task fertig ist", async () => {
|
||||
let n = 0;
|
||||
mock.route("GET", /\/nodes\/pve09\/tasks\/[^/]+\/status$/, () => {
|
||||
n += 1;
|
||||
if (n <= 4) return { status: 596, statusText: "Broken pipe" };
|
||||
return { data: { status: "stopped", exitstatus: "WARNINGS: 1" } };
|
||||
});
|
||||
await aufgabeAbwarten(client(), "UPID:pve09:00000001:00000002:00000003:vzcreate:114:root@pam:", {
|
||||
warten: async () => {},
|
||||
});
|
||||
expect(n).toBe(5);
|
||||
});
|
||||
|
||||
it("bricht bei fachlichen Fehlern sofort ab", async () => {
|
||||
mock.route("GET", /\/nodes\/pve08\/tasks\/[^/]+\/status$/, () => ({
|
||||
status: 403,
|
||||
statusText: "Permission check failed (/nodes/pve08, Sys.Audit)",
|
||||
}));
|
||||
const fehler = await fehlerVon(
|
||||
aufgabeAbwarten(client(), "UPID:pve08:00000001:00000002:00000003:vzcreate:114:root@pam:", {
|
||||
warten: async () => {},
|
||||
}),
|
||||
);
|
||||
expect(fehler.art).toBe("recht");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user