@mark1russell7/kind 0.11.12 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/build/index.d.ts +105 -5
- package/build/index.js +385 -39
- package/build/index.js.map +1 -1
- package/build/{judge-n0rL_RR0.js → judge-BdykXjF2.js} +73 -12
- package/build/judge-BdykXjF2.js.map +1 -0
- package/build/{judge-BKe6dJ3O.d.ts → judge-Cnn42gih.d.ts} +72 -15
- package/build/judge.d.ts +2 -2
- package/build/judge.js +1 -1
- package/image/Dockerfile +1 -1
- package/image/agent.mjs +20 -6
- package/image/client.mjs +58 -1
- package/package.json +2 -1
- package/src/backends.ts +59 -4
- package/src/place.ts +43 -0
- package/build/judge-n0rL_RR0.js.map +0 -1
|
@@ -4,9 +4,17 @@ import { Coupling, Gate, Poll, Stage, Timing } from "@mark1russell7/service";
|
|
|
4
4
|
import { RedisKnobs } from "@mark1russell7/service/redis";
|
|
5
5
|
import { SessionCheckKnobs } from "@mark1russell7/service/http-client";
|
|
6
6
|
//#region src/scenario.d.ts
|
|
7
|
-
/** A kube-proxy mode is the
|
|
7
|
+
/** A kube-proxy mode is the mode of kube-proxy on each node: iptables (the default of kind), IPVS or nftables. */
|
|
8
8
|
type KubeProxyMode = "iptables" | "ipvs" | "nftables";
|
|
9
9
|
declare const KUBE_PROXY_MODES: readonly KubeProxyMode[];
|
|
10
|
+
/**
|
|
11
|
+
* A dataplane is the path of a Service on each node, and the mode of a cell. It is kube-proxy in one
|
|
12
|
+
* of its modes, or `cilium`: Cilium is the CNI and replaces kube-proxy (`cilium.ts`).
|
|
13
|
+
*/
|
|
14
|
+
type Dataplane = KubeProxyMode | "cilium";
|
|
15
|
+
declare const DATAPLANES: readonly Dataplane[];
|
|
16
|
+
/** This function tells if a dataplane is a mode of kube-proxy. */
|
|
17
|
+
declare const isKubeProxyMode: (mode: Dataplane) => mode is KubeProxyMode;
|
|
10
18
|
/** A node image is the kind node image of one Kubernetes version, pinned by digest. */
|
|
11
19
|
interface NodeImage {
|
|
12
20
|
readonly kubernetes: string;
|
|
@@ -20,8 +28,8 @@ interface NodeImage {
|
|
|
20
28
|
declare const NODE_IMAGES: Readonly<Record<string, NodeImage>>;
|
|
21
29
|
/**
|
|
22
30
|
* A scenario is one kind cluster as data. The lab generates the configuration of kind from it, and
|
|
23
|
-
* nothing comes from a default of kind. The scenario gives the node count, the version, the
|
|
24
|
-
* of kube-proxy
|
|
31
|
+
* nothing comes from a default of kind. The scenario gives the node count, the version, the
|
|
32
|
+
* dataplane, the sync periods of kube-proxy, the CNI and the subnets.
|
|
25
33
|
*/
|
|
26
34
|
interface KindScenario {
|
|
27
35
|
/** This name names the cluster and its Docker network: lowercase letters, digits and dashes. */
|
|
@@ -30,15 +38,18 @@ interface KindScenario {
|
|
|
30
38
|
readonly kubernetes: string;
|
|
31
39
|
/** This count is the number of workers. The cluster also has one control plane. */
|
|
32
40
|
readonly workers: number;
|
|
41
|
+
/** This dataplane is the path of a Service on each node. */
|
|
42
|
+
readonly dataplane: Dataplane;
|
|
43
|
+
/** This is kube-proxy and its sync periods, or null when Cilium replaces kube-proxy. */
|
|
33
44
|
readonly kubeProxy: {
|
|
34
45
|
readonly mode: KubeProxyMode;
|
|
35
46
|
/** This time is the shortest interval between two syncs of the rules. */
|
|
36
47
|
readonly minSyncPeriod: Quantity;
|
|
37
48
|
/** This time is the interval of the full sync of the rules. */
|
|
38
49
|
readonly syncPeriod: Quantity;
|
|
39
|
-
};
|
|
40
|
-
/** This CNI is kindnet, the CNI of kind.
|
|
41
|
-
readonly cni: "kindnet";
|
|
50
|
+
} | null;
|
|
51
|
+
/** This CNI is kindnet, the CNI of kind, beside kube-proxy. The CNI of the dataplane `cilium` is Cilium. */
|
|
52
|
+
readonly cni: "kindnet" | "cilium";
|
|
42
53
|
readonly podSubnet: string;
|
|
43
54
|
readonly serviceSubnet: string;
|
|
44
55
|
/**
|
|
@@ -55,15 +66,21 @@ declare const ZONE_KEPT = "zone-a";
|
|
|
55
66
|
declare const ZONE_LOST = "zone-b";
|
|
56
67
|
/** This function gives the zone of the node at the position `at` of `nodeNames`, in a cluster of `count` nodes. */
|
|
57
68
|
declare const zoneOf: (at: number, count: number) => string;
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
/**
|
|
70
|
+
* This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values.
|
|
71
|
+
* The dataplane `cilium` has no kube-proxy, and its CNI is Cilium.
|
|
72
|
+
*/
|
|
73
|
+
declare const scenario: (name: string, mode: Dataplane, workers: number, kubernetes?: string, zoned?: boolean) => KindScenario;
|
|
74
|
+
/**
|
|
75
|
+
* This function gives the problems of a scenario. A problem is a name that kind refuses, no image for
|
|
76
|
+
* the version, or a count out of range. A kube-proxy or a CNI that does not agree with the dataplane is a problem too.
|
|
77
|
+
*/
|
|
61
78
|
declare const scenarioProblems: (s: KindScenario) => string[];
|
|
62
79
|
/** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */
|
|
63
80
|
declare const nodeNames: (s: KindScenario) => string[];
|
|
64
81
|
/** This function gives a duration as kube-proxy reads it, in whole milliseconds: `1s`, `500ms`. */
|
|
65
82
|
declare const goDuration: (amount: Quantity) => string;
|
|
66
|
-
/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. */
|
|
83
|
+
/** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. It refuses a scenario with no kube-proxy. */
|
|
67
84
|
declare const kubeProxyPatch: (s: KindScenario) => string;
|
|
68
85
|
/** A kind configuration is the cluster file that `kind create cluster --config` reads. JSON is YAML, thus the lab writes it as JSON. */
|
|
69
86
|
interface KindConfig {
|
|
@@ -71,7 +88,8 @@ interface KindConfig {
|
|
|
71
88
|
readonly apiVersion: "kind.x-k8s.io/v1alpha4";
|
|
72
89
|
readonly networking: {
|
|
73
90
|
readonly ipFamily: "ipv4";
|
|
74
|
-
|
|
91
|
+
/** This mode is the mode of kube-proxy, or `none` for no kube-proxy. */
|
|
92
|
+
readonly kubeProxyMode: KubeProxyMode | "none";
|
|
75
93
|
readonly disableDefaultCNI: boolean;
|
|
76
94
|
readonly podSubnet: string;
|
|
77
95
|
readonly serviceSubnet: string;
|
|
@@ -83,7 +101,12 @@ interface KindConfig {
|
|
|
83
101
|
}>;
|
|
84
102
|
readonly kubeadmConfigPatches: readonly string[];
|
|
85
103
|
}
|
|
86
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* This function generates the configuration of kind from a scenario. It refuses a scenario with a
|
|
106
|
+
* problem. The dataplane `cilium` gets no kindnet, as the kind guide of Cilium 1.20.4 tells
|
|
107
|
+
* (`Documentation/installation/kind-configure.rst`). It also gets no kube-proxy, because Cilium
|
|
108
|
+
* replaces it (`Documentation/network/kubernetes/kubeproxy-free.rst`).
|
|
109
|
+
*/
|
|
87
110
|
declare const kindConfig: (s: KindScenario) => KindConfig;
|
|
88
111
|
/** This function gives the first and the last address of an IPv4 CIDR, as numbers. */
|
|
89
112
|
declare const cidrRange: (cidr: string) => [number, number];
|
|
@@ -337,6 +360,12 @@ interface Workload {
|
|
|
337
360
|
* then closes the session, and the next stream opens a new one. Without them, a session has no check.
|
|
338
361
|
*/
|
|
339
362
|
readonly sessionCheck?: SessionCheckKnobs | undefined;
|
|
363
|
+
/**
|
|
364
|
+
* This count is the sessions of all clients that the lab places on the target before each run
|
|
365
|
+
* (`placementOf`). The other sessions go to the other replicas. Each client opens each session again
|
|
366
|
+
* until its first answer comes from its backend. With no value, the rules pick the backend of each session.
|
|
367
|
+
*/
|
|
368
|
+
readonly onTarget?: number | undefined;
|
|
340
369
|
}
|
|
341
370
|
/**
|
|
342
371
|
* A band is a range of times after a reference event of the run. The format `/1` gives each end
|
|
@@ -553,7 +582,8 @@ interface KindCell<A = Quantity> {
|
|
|
553
582
|
readonly mesh?: Mesh | undefined;
|
|
554
583
|
/** This text names a system of the plan that the cell replaces with the service of this repository, or null. */
|
|
555
584
|
readonly substitutes: string | null;
|
|
556
|
-
|
|
585
|
+
/** This mode is the dataplane of the cluster of the cell: a mode of kube-proxy, or Cilium. */
|
|
586
|
+
readonly mode: Dataplane;
|
|
557
587
|
readonly workers: number;
|
|
558
588
|
readonly trigger: Trigger<A>;
|
|
559
589
|
readonly pod: PodSettings;
|
|
@@ -747,6 +777,20 @@ declare const outcomesOf: (requests: readonly Request[]) => Record<string, numbe
|
|
|
747
777
|
declare const byClients: <T extends {
|
|
748
778
|
readonly pod: string;
|
|
749
779
|
}>(replicas: readonly T[], answers: ReadonlyMap<string, string>) => T[];
|
|
780
|
+
/** These are the sessions of the clients at an instant: those on the target, of all, and those on the target by client. */
|
|
781
|
+
interface Sessions {
|
|
782
|
+
readonly onTarget: number;
|
|
783
|
+
readonly of: number;
|
|
784
|
+
readonly byClient: Readonly<Record<string, number>>;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* This function gives the sessions of the clients at an instant (`at`), and how many of them are on
|
|
788
|
+
* the target pod. A client sends each request on its next session in turn. Thus its last `connections`
|
|
789
|
+
* requests that started before the instant name its sessions, each by its local port. The backend of
|
|
790
|
+
* a session is the body of the last answer on its port before the instant: the name of the pod. A
|
|
791
|
+
* session with no such answer is not on the target. A request with no port names no session.
|
|
792
|
+
*/
|
|
793
|
+
declare const sessionsAt: (lines: readonly Line[], at: number, target: string, connections: number) => Sessions;
|
|
750
794
|
/** This function gives the requests of each node, by the name of the node. */
|
|
751
795
|
declare const byNode: (requests: readonly Request[]) => Map<string, Request[]>;
|
|
752
796
|
/** This function gives the time that the watcher first received a deletion timestamp of one of the pods, after a time. */
|
|
@@ -882,6 +926,19 @@ interface RunFacts<A = Quantity> {
|
|
|
882
926
|
readonly empty: A | null;
|
|
883
927
|
readonly ready: A | null;
|
|
884
928
|
}> | undefined;
|
|
929
|
+
/**
|
|
930
|
+
* These are the sessions of the clients at the fault (`sessionsAt`): those on the target, of all,
|
|
931
|
+
* and those on the target by client. A run of a workload with kept connections and a target has them.
|
|
932
|
+
*/
|
|
933
|
+
readonly sessions?: Sessions | undefined;
|
|
934
|
+
/**
|
|
935
|
+
* This is the stall of the target (`app-stall`): its start after the fault, and how long its loop
|
|
936
|
+
* stayed blocked. The app writes a line at each end of the stall. A run with no such line gives null.
|
|
937
|
+
*/
|
|
938
|
+
readonly stall?: {
|
|
939
|
+
readonly start: A | null;
|
|
940
|
+
readonly held: A | null;
|
|
941
|
+
} | undefined;
|
|
885
942
|
}
|
|
886
943
|
/** A deleted replica is a replica of a run that got a deletion. It gives its pod, and the stamps of its intake (or null) and of its deletion. */
|
|
887
944
|
interface Deleted {
|
|
@@ -1156,5 +1213,5 @@ declare const quantileOf: (sorted: readonly number[], p: number) => number;
|
|
|
1156
1213
|
*/
|
|
1157
1214
|
declare const medianInterval: (sorted: readonly number[]) => [number, number];
|
|
1158
1215
|
//#endregion
|
|
1159
|
-
export { unitOf as $,
|
|
1160
|
-
//# sourceMappingURL=judge-
|
|
1216
|
+
export { unitOf as $, bandOf as $t, TriggerV1 as A, KindConfig as An, restartOf as At, RunCheck as B, goDuration as Bn, slicesOf as Bt, KindCellResult as C, Trigger as Cn, isTunnelState as Ct, PredictionV1 as D, DATAPLANES as Dn, podsOf as Dt, LoadSample as E, Workload as En, podsAt as Et, KIND_NOISE as F, ZONE_KEPT as Fn, signalsOf as Ft, factsOf as G, scenario as Gn, ztunnelPodsOf as Gt, checkRun as H, kindConfig as Hn, stoppingOf as Ht, KindDecision as I, ZONE_LABEL as In, sliceDropOf as It, observationEnd as J, HEALTH_PORT as Jt, judgeKind as K, scenarioProblems as Kn, ztunnelReplaced as Kt, KindVerdict as L, ZONE_LOST as Ln, sliceEmptyOf as Lt, upgrade as M, KubeProxyMode as Mn, rulesOf as Mt, upgradeCell as N, NODE_IMAGES as Nn, sessionsAt as Nt, RunCheckV1 as O, Dataplane as On, recordsOf as Ot, Deleted as P, NodeImage as Pn, signalOf as Pt, reusesReset as Q, SERVER as Qt, LAST_SLACK as R, cidrOverlap as Rn, sliceReadyAt as Rt, KindCellRecordV1 as S, Template as Sn, intakeOf as St, KindVerdictV1 as T, WindowPrediction as Tn, outcomesOf as Tt, cyclesOf as U, kubeProxyPatch as Un, transitionOf as Ut, RunFacts as V, isKubeProxyMode as Vn, socketsOf as Vt, deletionsOf as W, nodeNames as Wn, tunnelsOf as Wt, propagationSource as X, PORT as Xt, propagationOf as Y, LOG_LABEL as Yt, referenceOf as Z, REDIS as Zt, ClusterManifest as _, RedisDeclaration as _n, byNode as _t, STAGES as a, Declaration as an, PropagationSource as at, FORMAT_V1 as b, ResetReuse as bn, emptyOf as bt, readRecord as c, KindCell as cn, RulesLine as ct, calibrationOf as d, Mesh as dn, SocketLine as dt, seconds as en, updatesOf as et, cellsTable as f, MeshFlavor as fn, TCP_STATE as ft, BandV1 as g, Prediction as gn, byClients as gt, timeSpread as h, PodSettings as hn, backendsAt as ht, quantileOf as i, Cycle as in, PodLine as it, judge as j, KindScenario as jn, restartsAt as jt, RunFactsV1 as k, KUBE_PROXY_MODES as kn, requestsOf as kt, Judged as l, KindRun as ln, Sessions as lt, predictionText as m, NonePrediction as mn, ZtunnelPodLine as mt, medianInterval as n, showBand as nn, HBONE_PORT as nt, kindCellRecordSchema as o, EdgeProtocol as on, RecordLine as ot, medianText as p, MeshMode as pn, TunnelLine as pt, observationAfter as q, zoneOf as qn, CLIENT as qt, middle as r, Band as rn, Line as rt, kindCellRecordV1Schema as s, Flap as sn, Request as st, RELATIVE as t, secondsOf as tn, Endpoint as tt, TimeSpread as u, KubeletProbe as un, SliceLine as ut, EnvironmentManifest as v, Reference as vn, deletionOf as vt, KindCellV1 as w, TriggerKind as wn, kernelCounts as wt, KindCellRecord as x, Stack as xn, failuresOf as xt, FORMAT as y, Replica as yn, dropOf as yt, Propagated as z, cidrRange as zn, sliceReadyOf as zt };
|
|
1217
|
+
//# sourceMappingURL=judge-Cnn42gih.d.ts.map
|
package/build/judge.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as unitOf, $t as
|
|
2
|
-
export { type Band, type BandV1, CLIENT, type ClusterManifest, type Cycle, type Declaration, type Deleted, type EdgeProtocol, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type Flap, HBONE_PORT, HEALTH_PORT, type Judged, KIND_NOISE, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindDecision, type KindRun, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, type Mesh, type MeshFlavor, type MeshMode, type NonePrediction, PORT, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, PropagationSource, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, SERVER, STAGES, SliceLine, SocketLine, type Stack, TCP_STATE, type Template, type TimeSpread, type Trigger, type TriggerKind, type TriggerV1, TunnelLine, type WindowPrediction, type Workload, ZtunnelPodLine, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cyclesOf, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, intakeOf, isTunnelState, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, medianInterval, medianText, middle, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartOf, restartsAt, reusesReset, rulesOf, seconds, secondsOf, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, stoppingOf, timeSpread, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, ztunnelPodsOf, ztunnelReplaced };
|
|
1
|
+
import { $ as unitOf, $t as bandOf, A as TriggerV1, At as restartOf, B as RunCheck, Bt as slicesOf, C as KindCellResult, Cn as Trigger, Ct as isTunnelState, D as PredictionV1, Dt as podsOf, E as LoadSample, En as Workload, Et as podsAt, F as KIND_NOISE, Ft as signalsOf, G as factsOf, Gt as ztunnelPodsOf, H as checkRun, Ht as stoppingOf, I as KindDecision, It as sliceDropOf, J as observationEnd, Jt as HEALTH_PORT, K as judgeKind, Kt as ztunnelReplaced, L as KindVerdict, Lt as sliceEmptyOf, M as upgrade, Mn as KubeProxyMode, Mt as rulesOf, N as upgradeCell, Nt as sessionsAt, O as RunCheckV1, On as Dataplane, Ot as recordsOf, P as Deleted, Pt as signalOf, Q as reusesReset, Qt as SERVER, R as LAST_SLACK, Rt as sliceReadyAt, S as KindCellRecordV1, Sn as Template, St as intakeOf, T as KindVerdictV1, Tn as WindowPrediction, Tt as outcomesOf, U as cyclesOf, Ut as transitionOf, V as RunFacts, Vt as socketsOf, W as deletionsOf, Wt as tunnelsOf, X as propagationSource, Xt as PORT, Y as propagationOf, Yt as LOG_LABEL, Z as referenceOf, Zt as REDIS, _ as ClusterManifest, _n as RedisDeclaration, _t as byNode, a as STAGES, an as Declaration, at as PropagationSource, b as FORMAT_V1, bn as ResetReuse, bt as emptyOf, c as readRecord, cn as KindCell, ct as RulesLine, d as calibrationOf, dn as Mesh, dt as SocketLine, en as seconds, et as updatesOf, f as cellsTable, fn as MeshFlavor, ft as TCP_STATE, g as BandV1, gn as Prediction, gt as byClients, h as timeSpread, hn as PodSettings, ht as backendsAt, i as quantileOf, in as Cycle, it as PodLine, j as judge, jt as restartsAt, k as RunFactsV1, kt as requestsOf, l as Judged, ln as KindRun, lt as Sessions, m as predictionText, mn as NonePrediction, mt as ZtunnelPodLine, n as medianInterval, nn as showBand, nt as HBONE_PORT, o as kindCellRecordSchema, on as EdgeProtocol, ot as RecordLine, p as medianText, pn as MeshMode, pt as TunnelLine, q as observationAfter, qt as CLIENT, r as middle, rn as Band, rt as Line, s as kindCellRecordV1Schema, sn as Flap, st as Request, t as RELATIVE, tn as secondsOf, tt as Endpoint, u as TimeSpread, un as KubeletProbe, ut as SliceLine, v as EnvironmentManifest, vn as Reference, vt as deletionOf, w as KindCellV1, wn as TriggerKind, wt as kernelCounts, x as KindCellRecord, xn as Stack, xt as failuresOf, y as FORMAT, yn as Replica, yt as dropOf, z as Propagated, zt as sliceReadyOf } from "./judge-Cnn42gih.js";
|
|
2
|
+
export { type Band, type BandV1, CLIENT, type ClusterManifest, type Cycle, type Dataplane, type Declaration, type Deleted, type EdgeProtocol, Endpoint, type EnvironmentManifest, FORMAT, FORMAT_V1, type Flap, HBONE_PORT, HEALTH_PORT, type Judged, KIND_NOISE, type KindCell, type KindCellRecord, type KindCellRecordV1, type KindCellResult, type KindCellV1, type KindDecision, type KindRun, type KindVerdict, type KindVerdictV1, type KubeProxyMode, type KubeletProbe, LAST_SLACK, LOG_LABEL, Line, type LoadSample, type Mesh, type MeshFlavor, type MeshMode, type NonePrediction, PORT, PodLine, type PodSettings, type Prediction, type PredictionV1, type Propagated, PropagationSource, REDIS, RELATIVE, RecordLine, type RedisDeclaration, type Reference, type Replica, Request, type ResetReuse, RulesLine, type RunCheck, type RunCheckV1, type RunFacts, type RunFactsV1, SERVER, STAGES, Sessions, SliceLine, SocketLine, type Stack, TCP_STATE, type Template, type TimeSpread, type Trigger, type TriggerKind, type TriggerV1, TunnelLine, type WindowPrediction, type Workload, ZtunnelPodLine, backendsAt, bandOf, byClients, byNode, calibrationOf, cellsTable, checkRun, cyclesOf, deletionOf, deletionsOf, dropOf, emptyOf, factsOf, failuresOf, intakeOf, isTunnelState, judge, judgeKind, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, medianInterval, medianText, middle, observationAfter, observationEnd, outcomesOf, podsAt, podsOf, predictionText, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, referenceOf, requestsOf, restartOf, restartsAt, reusesReset, rulesOf, seconds, secondsOf, sessionsAt, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, stoppingOf, timeSpread, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, ztunnelPodsOf, ztunnelReplaced };
|
package/build/judge.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { mt as CLIENT, u as FORMAT, d as FORMAT_V1, P as HBONE_PORT, ht as HEALTH_PORT, h as KIND_NOISE, g as LAST_SLACK, gt as LOG_LABEL, _t as PORT, vt as REDIS, A as RELATIVE, yt as SERVER, t as STAGES, F as TCP_STATE, I as backendsAt, bt as bandOf, L as byClients, R as byNode, a as calibrationOf, o as cellsTable, _ as checkRun, v as cyclesOf, z as deletionOf, y as deletionsOf, B as dropOf, V as emptyOf, b as factsOf, H as failuresOf, U as intakeOf, W as isTunnelState, f as judge, x as judgeKind, G as kernelCounts, n as kindCellRecordSchema, r as kindCellRecordV1Schema, j as medianInterval, s as medianText, M as middle, S as observationAfter, C as observationEnd, K as outcomesOf, q as podsAt, J as podsOf, c as predictionText, w as propagationOf, T as propagationSource, N as quantileOf, i as readRecord, Y as recordsOf, E as referenceOf, X as requestsOf, Z as restartOf, Q as restartsAt, D as reusesReset, $ as rulesOf, xt as seconds, St as secondsOf, et as sessionsAt, Ct as showBand, tt as signalOf, nt as signalsOf, rt as sliceDropOf, it as sliceEmptyOf, at as sliceReadyAt, ot as sliceReadyOf, st as slicesOf, ct as socketsOf, lt as stoppingOf, l as timeSpread, ut as transitionOf, dt as tunnelsOf, O as unitOf, k as updatesOf, p as upgrade, m as upgradeCell, ft as ztunnelPodsOf, pt as ztunnelReplaced } from "./judge-BdykXjF2.js";
|
package/image/Dockerfile
CHANGED
|
@@ -14,5 +14,5 @@ RUN for name in quantity algebra service; do \
|
|
|
14
14
|
&& tar -xzf /tmp/workspace/mark1russell7-$name-*.tgz -C "node_modules/@mark1russell7/$name" --strip-components=1; \
|
|
15
15
|
done \
|
|
16
16
|
&& rm -rf /tmp/workspace
|
|
17
|
-
COPY app.mjs client.mjs client-grpc.mjs agent.mjs watch.mjs compose.ts backends.ts conntrack.ts drops.ts eds.ts lds.ts client-lines.ts ./
|
|
17
|
+
COPY app.mjs client.mjs client-grpc.mjs agent.mjs watch.mjs compose.ts backends.ts conntrack.ts drops.ts eds.ts lds.ts client-lines.ts place.ts ./
|
|
18
18
|
CMD ["node", "app.mjs"]
|
package/image/agent.mjs
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// node and the freeze of its cgroup, or the thaw and the end of the drops), and now.
|
|
28
28
|
import { spawn, spawnSync } from "node:child_process";
|
|
29
29
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
30
|
-
import { backendsOf } from "./backends.ts";
|
|
30
|
+
import { backendsOf, namesService } from "./backends.ts";
|
|
31
31
|
import { entriesOf, flowsOf, staleOf, stalledOf } from "./conntrack.ts";
|
|
32
32
|
import { devOf, grownOf, snmpOf, softnetOf, unresolvedOf } from "./drops.ts";
|
|
33
33
|
import { clusterFilter, hostsOf } from "./eds.ts";
|
|
@@ -38,11 +38,16 @@ const node = process.env.NODE_NAME ?? "";
|
|
|
38
38
|
const say = (line) => process.stdout.write(`${JSON.stringify({ ...line, node })}\n`);
|
|
39
39
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
40
40
|
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* These commands give the backends of a Service in each dataplane, as "ip:port" (with the weight for
|
|
43
|
+
* IPVS). The agent of Cilium answers on its socket in the node (a hostPath of its chart), with the
|
|
44
|
+
* services of its load balancer as JSON (`GET /v1/service`).
|
|
45
|
+
*/
|
|
42
46
|
const DUMP = {
|
|
43
47
|
iptables: ["nsenter", "-t", "1", "-m", "-n", "--", "iptables-save", "-t", "nat"],
|
|
44
48
|
nftables: ["nsenter", "-t", "1", "-m", "-n", "--", "nft", "list", "table", "ip", "kube-proxy"],
|
|
45
49
|
ipvs: ["nsenter", "-t", "1", "-n", "--", "cat", "/proc/net/ip_vs"],
|
|
50
|
+
cilium: ["nsenter", "-t", "1", "-m", "-n", "--", "curl", "-sS", "--fail", "--max-time", "2", "--unix-socket", "/var/run/cilium/cilium.sock", "http://localhost/v1/service"],
|
|
46
51
|
};
|
|
47
52
|
|
|
48
53
|
/**
|
|
@@ -185,14 +190,23 @@ const serve = async () => {
|
|
|
185
190
|
const took = now() - t;
|
|
186
191
|
polls += 1;
|
|
187
192
|
spent += took;
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
// a reply that the parser refuses (JSON that is not a list) is a failure, not a Service with no backend
|
|
194
|
+
let backends = null;
|
|
195
|
+
let refused = "";
|
|
196
|
+
if (run.status === 0) {
|
|
197
|
+
try {
|
|
198
|
+
backends = backendsOf(rule.mode, run.stdout, rule);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
refused = `the dump does not parse: ${String(error)}: ${run.stdout.slice(0, 200)}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (backends === null) {
|
|
204
|
+
say({ ev: "rules-fail", t, why: (refused || (run.stderr ?? "")).trim().slice(0, 300) });
|
|
190
205
|
} else {
|
|
191
|
-
const backends = backendsOf(rule.mode, run.stdout, rule);
|
|
192
206
|
const key = backends.join(",");
|
|
193
207
|
// a dump that names the Service but gives no backend: once for each rule, its text, to read the format
|
|
194
208
|
const config = JSON.stringify(rule);
|
|
195
|
-
if (backends.length === 0 && run.stdout
|
|
209
|
+
if (backends.length === 0 && namesService(rule.mode, run.stdout, rule) && dumped !== config) {
|
|
196
210
|
say({ ev: "rules-dump", t, text: run.stdout.slice(0, 30000) });
|
|
197
211
|
dumped = config;
|
|
198
212
|
}
|
package/image/client.mjs
CHANGED
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
// unreachable, or another error) and its local port (`lp`). With PROTOCOL h2c, each connection is a
|
|
7
7
|
// session of HTTP/2, and each request is a stream on the next session in turn. With SESSION_CHECK,
|
|
8
8
|
// each session has the session check of the service: a stream timeout or a PING with no ACK closes it.
|
|
9
|
-
// Each second, one line counts the PINGs of the checks and their ACKs (`pings`).
|
|
9
|
+
// Each second, one line counts the PINGs of the checks and their ACKs (`pings`). With a placement in
|
|
10
|
+
// /tmp/lab-place (place.ts), SIGHUP opens each session of HTTP/2 again until its first answer comes
|
|
11
|
+
// from the backend of its slot. A line `placed` gives the port and the backend of each slot.
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
10
13
|
import http from "node:http";
|
|
11
14
|
import http2 from "node:http2";
|
|
12
15
|
import net from "node:net";
|
|
13
16
|
import { sessionCheck } from "@mark1russell7/service/http-client";
|
|
17
|
+
import { placeSlot, placementFrom } from "./place.ts";
|
|
14
18
|
|
|
15
19
|
const [host, port] = (process.env.TARGET ?? "").split(":");
|
|
16
20
|
const every = Number(process.env.EVERY_MS ?? 20);
|
|
@@ -81,11 +85,64 @@ const slotAt = (at) => {
|
|
|
81
85
|
return slots[at];
|
|
82
86
|
};
|
|
83
87
|
|
|
88
|
+
// the placement of the slots: the backend of each one, or null with no placement. The lab writes it
|
|
89
|
+
// before the SIGHUP, and the requests of the client go on the old slots until each new slot has its backend
|
|
90
|
+
const PLACE_FILE = process.env.LAB_PLACE ?? "/tmp/lab-place";
|
|
91
|
+
const PLACE_TRIES = 64;
|
|
92
|
+
const placement = () => {
|
|
93
|
+
try {
|
|
94
|
+
return placementFrom(readFileSync(PLACE_FILE, "utf8"), connections);
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// the first answer of a new session: the body of a stream of /work, the name of its pod, or null
|
|
101
|
+
const probeOf = (held) =>
|
|
102
|
+
new Promise((resolve) => {
|
|
103
|
+
let body = "";
|
|
104
|
+
let done = false;
|
|
105
|
+
const end = (value) => {
|
|
106
|
+
if (done) return;
|
|
107
|
+
done = true;
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
resolve(value);
|
|
110
|
+
};
|
|
111
|
+
const timer = setTimeout(() => end(null), timeout);
|
|
112
|
+
let stream;
|
|
113
|
+
try {
|
|
114
|
+
stream = held.session.request({ ":path": "/work" });
|
|
115
|
+
} catch {
|
|
116
|
+
end(null);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
stream.setEncoding("utf8");
|
|
120
|
+
stream.on("data", (chunk) => {
|
|
121
|
+
body += chunk;
|
|
122
|
+
});
|
|
123
|
+
stream.on("end", () => end(body === "" ? null : body.slice(0, 64)));
|
|
124
|
+
stream.on("error", () => end(null));
|
|
125
|
+
stream.on("close", () => end(body === "" ? null : body.slice(0, 64)));
|
|
126
|
+
stream.end();
|
|
127
|
+
});
|
|
128
|
+
|
|
84
129
|
// SIGHUP: new agents or sessions for the next requests. The old ones go after the timeout, thus each
|
|
85
130
|
// request on them ends first
|
|
86
131
|
process.on("SIGHUP", () => {
|
|
87
132
|
const old = agents;
|
|
88
133
|
const oldSlots = slots;
|
|
134
|
+
const wanted = h2 ? placement() : null;
|
|
135
|
+
if (wanted !== null) {
|
|
136
|
+
say({ ev: "client", what: "reconnect", t: now(), place: wanted });
|
|
137
|
+
Promise.all(wanted.map((pod) => placeSlot(sessionOf, probeOf, (held) => held.session.destroy(), pod, PLACE_TRIES))).then((placed) => {
|
|
138
|
+
slots = placed.map((each) => each.held);
|
|
139
|
+
say({ ev: "client", what: "placed", t: now(), slots: placed.map((each) => ({ lp: portOf(each.held.socket), b: each.b, tries: each.tries, ok: each.ok })) });
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
for (const held of oldSlots) held.session.destroy();
|
|
142
|
+
}, timeout + 100);
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
89
146
|
if (h2) slots = Array.from({ length: connections }, sessionOf);
|
|
90
147
|
else agents = agentsOf();
|
|
91
148
|
say({ ev: "client", what: "reconnect", t: now() });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/kind",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.1",
|
|
5
5
|
"description": "Kubernetes in Docker as a lab for connection timers: a kind cluster from a scenario, faults in the namespaces of its pods, observations on the one monotonic clock of the VM, and predictions from the ordering law. Its judge entry reads and judges a raw record without Docker.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"kubernetes",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"src/eds.ts",
|
|
59
59
|
"src/lds.ts",
|
|
60
60
|
"src/client-lines.ts",
|
|
61
|
+
"src/place.ts",
|
|
61
62
|
"README.md",
|
|
62
63
|
"LICENSE"
|
|
63
64
|
],
|
package/src/backends.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The parser of the rules of kube-proxy. The agent of each node reads
|
|
2
|
-
// image copies this file, thus it imports nothing.
|
|
1
|
+
// The parser of the rules of kube-proxy and of the services of Cilium. The agent of each node reads
|
|
2
|
+
// the rules with it, and the image copies this file, thus it imports nothing.
|
|
3
3
|
|
|
4
|
-
/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS. */
|
|
4
|
+
/** A service rule names the Service whose backends the agent reads: its namespace, its name, and its ClusterIP and port for IPVS and Cilium. */
|
|
5
5
|
export interface ServiceRule {
|
|
6
|
-
readonly mode: "iptables" | "ipvs" | "nftables";
|
|
6
|
+
readonly mode: "iptables" | "ipvs" | "nftables" | "cilium";
|
|
7
7
|
readonly ns: string;
|
|
8
8
|
readonly svc: string;
|
|
9
9
|
readonly ip: string;
|
|
@@ -20,8 +20,10 @@ const hexIp = (hex: string): string =>
|
|
|
20
20
|
* - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.
|
|
21
21
|
* - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.
|
|
22
22
|
* - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.
|
|
23
|
+
* - Cilium: the JSON of the services of the agent (`ciliumBackendsOf`).
|
|
23
24
|
*/
|
|
24
25
|
export const backendsOf = (mode: ServiceRule["mode"], text: string, rule: Pick<ServiceRule, "ns" | "svc" | "ip" | "port">): string[] => {
|
|
26
|
+
if (mode === "cilium") return ciliumBackendsOf(text, rule);
|
|
25
27
|
const out = new Set<string>();
|
|
26
28
|
const lines = text.split("\n");
|
|
27
29
|
if (mode === "iptables") {
|
|
@@ -68,3 +70,56 @@ export const backendsOf = (mode: ServiceRule["mode"], text: string, rule: Pick<S
|
|
|
68
70
|
}
|
|
69
71
|
return [...out].sort();
|
|
70
72
|
};
|
|
73
|
+
|
|
74
|
+
/** This is one service of the agent of Cilium, as `GET /v1/service` gives it: the fields that the parser reads. */
|
|
75
|
+
interface CiliumService {
|
|
76
|
+
readonly spec?: {
|
|
77
|
+
readonly "frontend-address"?: { readonly ip?: string; readonly port?: number; readonly protocol?: string; readonly scope?: string };
|
|
78
|
+
readonly "backend-addresses"?: ReadonlyArray<{ readonly ip?: string; readonly port?: number; readonly state?: string }> | null;
|
|
79
|
+
readonly flags?: { readonly type?: string; readonly name?: string; readonly namespace?: string };
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** This function gives the services in a reply of the agent of Cilium. It refuses a reply that is not a JSON list. */
|
|
84
|
+
const ciliumServices = (text: string): CiliumService[] => {
|
|
85
|
+
const data: unknown = JSON.parse(text);
|
|
86
|
+
if (!Array.isArray(data)) throw new Error("the reply of the agent of Cilium is not a list of services");
|
|
87
|
+
return data as CiliumService[];
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* This function gives the backends of a Service in the reply of `GET /v1/service` of the agent of
|
|
92
|
+
* Cilium 1.20, as `ip:port`, sorted. The model is `ServiceSpec` of `api/v1/openapi.yaml`. The
|
|
93
|
+
* frontend is the ClusterIP and the port of the rule, with the scope `external` or no scope.
|
|
94
|
+
*
|
|
95
|
+
* The backends are those in the state `active`, or with no state. If no backend is active, the
|
|
96
|
+
* backends are those in the state `terminating`. The BPF reconciler gives the slots of a service
|
|
97
|
+
* in this sequence (`pkg/loadbalancer/reconciler/bpf_reconciler.go`). Thus a new connection goes
|
|
98
|
+
* only to them. kube-proxy does the same with the endpoints that serve and stop. The other
|
|
99
|
+
* states take no new connection: `terminating-not-serving`, `quarantined` and `maintenance`.
|
|
100
|
+
*/
|
|
101
|
+
export const ciliumBackendsOf = (text: string, rule: Pick<ServiceRule, "ip" | "port">): string[] => {
|
|
102
|
+
const ours = ciliumServices(text).filter((service) => {
|
|
103
|
+
const front = service.spec?.["frontend-address"];
|
|
104
|
+
const protocol = (front?.protocol ?? "tcp").toLowerCase();
|
|
105
|
+
return front?.ip === rule.ip && front.port === rule.port && (protocol === "tcp" || protocol === "any") && (front.scope ?? "external") === "external";
|
|
106
|
+
});
|
|
107
|
+
const backends = ours.flatMap((service) => service.spec?.["backend-addresses"] ?? []);
|
|
108
|
+
const of = (states: readonly (string | undefined)[]): string[] =>
|
|
109
|
+
[...new Set(backends.filter((b) => states.includes(b.state) && b.ip !== undefined).map((b) => `${b.ip}:${b.port ?? 0}`))].sort();
|
|
110
|
+
const active = of(["active", undefined]);
|
|
111
|
+
return active.length > 0 ? active : of(["terminating"]);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* This function tells if a dump of the rules names the Service of a rule. The rules of kube-proxy
|
|
116
|
+
* name it as `namespace/name`. The JSON of Cilium names it in the flags of its frontend.
|
|
117
|
+
*/
|
|
118
|
+
export const namesService = (mode: ServiceRule["mode"], text: string, rule: Pick<ServiceRule, "ns" | "svc">): boolean => {
|
|
119
|
+
if (mode !== "cilium") return text.includes(`${rule.ns}/${rule.svc}`);
|
|
120
|
+
try {
|
|
121
|
+
return ciliumServices(text).some((service) => service.spec?.flags?.namespace === rule.ns && service.spec.flags.name === rule.svc);
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
};
|
package/src/place.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The placement of the sessions of the HTTP/2 client of the lab image. The lab writes the backend of
|
|
2
|
+
// each slot of the client (`placementOf`), and the next SIGHUP opens each slot again until its first
|
|
3
|
+
// answer comes from that backend. The client runs this module with Node, thus this module imports nothing.
|
|
4
|
+
|
|
5
|
+
/** A placed slot is the session of a slot with the backend of its first answer (`b`). It also has its tries, and if that backend is the wanted one. */
|
|
6
|
+
export interface Placed<H> {
|
|
7
|
+
readonly held: H;
|
|
8
|
+
readonly b: string | null;
|
|
9
|
+
readonly tries: number;
|
|
10
|
+
readonly ok: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This function opens a session for one slot until its first answer comes from the wanted backend,
|
|
15
|
+
* for `tries` sessions at the most. Each session of the wrong backend closes. The rules pick the backend of each new
|
|
16
|
+
* session, thus each try reaches the wanted backend at random or in turn. After the last try, the slot
|
|
17
|
+
* keeps its last session, and `ok` is false.
|
|
18
|
+
*/
|
|
19
|
+
export const placeSlot = async <H>(
|
|
20
|
+
open: () => H,
|
|
21
|
+
probe: (held: H) => Promise<string | null>,
|
|
22
|
+
close: (held: H) => void,
|
|
23
|
+
wanted: string,
|
|
24
|
+
tries: number,
|
|
25
|
+
): Promise<Placed<H>> => {
|
|
26
|
+
if (!Number.isInteger(tries) || tries < 1) throw new RangeError(`a placement needs one try at the least, not ${tries}`);
|
|
27
|
+
for (let tried = 1; ; tried += 1) {
|
|
28
|
+
const held = open();
|
|
29
|
+
const b = await probe(held);
|
|
30
|
+
if (b === wanted || tried === tries) return { held, b, tries: tried, ok: b === wanted };
|
|
31
|
+
close(held);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** This function reads a placement: a list of the names of the backends of the slots, one for each slot. Any other text gives null. */
|
|
36
|
+
export const placementFrom = (text: string, slots: number): string[] | null => {
|
|
37
|
+
try {
|
|
38
|
+
const parsed: unknown = JSON.parse(text);
|
|
39
|
+
return Array.isArray(parsed) && parsed.length === slots && parsed.every((each) => typeof each === "string" && each !== "") ? (parsed as string[]) : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|