@mark1russell7/kind 0.11.12 → 0.12.0

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/build/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { $ as rulesOf, A as RELATIVE, B as dropOf, C as observationEnd, D as reusesReset, E as referenceOf, F as TCP_STATE, G as kernelCounts, H as failuresOf, I as backendsAt, J as podsOf, K as outcomesOf, L as byClients, M as middle, N as quantileOf, O as unitOf, P as HBONE_PORT, Q as restartsAt, R as byNode, S as observationAfter, St as showBand, T as propagationSource, U as intakeOf, V as emptyOf, W as isTunnelState, X as requestsOf, Y as recordsOf, Z as restartOf, _ as checkRun, _t as REDIS, a as calibrationOf, at as sliceReadyOf, b as factsOf, bt as seconds, c as predictionText, ct as stoppingOf, d as FORMAT_V1, dt as ztunnelPodsOf, et as signalOf, f as judge, ft as ztunnelReplaced, g as LAST_SLACK, gt as PORT, h as KIND_NOISE, ht as LOG_LABEL, i as readRecord, it as sliceReadyAt, j as medianInterval, k as updatesOf, l as timeSpread, lt as transitionOf, m as upgradeCell, mt as HEALTH_PORT, n as kindCellRecordSchema, nt as sliceDropOf, o as cellsTable, ot as slicesOf, p as upgrade, pt as CLIENT, q as podsAt, r as kindCellRecordV1Schema, rt as sliceEmptyOf, s as medianText, st as socketsOf, t as STAGES, tt as signalsOf, u as FORMAT, ut as tunnelsOf, v as cyclesOf, vt as SERVER, w as propagationOf, x as judgeKind, xt as secondsOf, y as deletionsOf, yt as bandOf, z as deletionOf } from "./judge-n0rL_RR0.js";
1
+ import { $ as rulesOf, A as RELATIVE, B as dropOf, C as observationEnd, D as reusesReset, E as referenceOf, F as TCP_STATE, G as kernelCounts, H as failuresOf, I as backendsAt, J as podsOf, K as outcomesOf, L as byClients, M as middle, N as quantileOf, O as unitOf, P as HBONE_PORT, Q as restartsAt, R as byNode, S as observationAfter, St as showBand, T as propagationSource, U as intakeOf, V as emptyOf, W as isTunnelState, X as requestsOf, Y as recordsOf, Z as restartOf, _ as checkRun, _t as REDIS, a as calibrationOf, at as sliceReadyOf, b as factsOf, bt as seconds, c as predictionText, ct as stoppingOf, d as FORMAT_V1, dt as ztunnelPodsOf, et as signalOf, f as judge, ft as ztunnelReplaced, g as LAST_SLACK, gt as PORT, h as KIND_NOISE, ht as LOG_LABEL, i as readRecord, it as sliceReadyAt, j as medianInterval, k as updatesOf, l as timeSpread, lt as transitionOf, m as upgradeCell, mt as HEALTH_PORT, n as kindCellRecordSchema, nt as sliceDropOf, o as cellsTable, ot as slicesOf, p as upgrade, pt as CLIENT, q as podsAt, r as kindCellRecordV1Schema, rt as sliceEmptyOf, s as medianText, st as socketsOf, t as STAGES, tt as signalsOf, u as FORMAT, ut as tunnelsOf, v as cyclesOf, vt as SERVER, w as propagationOf, x as judgeKind, xt as secondsOf, y as deletionsOf, yt as bandOf, z as deletionOf } from "./judge-CMQeiw8k.js";
2
2
  import { valueIn } from "@mark1russell7/quantity";
3
- import { execFile, execFileSync } from "node:child_process";
4
3
  import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
4
  import { dirname, join } from "node:path";
5
+ import { execFile, execFileSync } from "node:child_process";
6
6
  import { promisify } from "node:util";
7
7
  import { createHash } from "node:crypto";
8
8
  import { fileURLToPath } from "node:url";
@@ -27,8 +27,10 @@ const hexIp = (hex) => [
27
27
  * - iptables: the `KUBE-SVC` chain of the Service jumps to a `KUBE-SEP` chain for each backend, and that chain does the DNAT.
28
28
  * - nftables: the `service-` chain of the Service does the DNAT to each backend, or it names an `endpoint-` chain for each one.
29
29
  * - IPVS: `/proc/net/ip_vs` has the line of the virtual service, then a line for each real server.
30
+ * - Cilium: the JSON of the services of the agent (`ciliumBackendsOf`).
30
31
  */
31
32
  const backendsOf = (mode, text, rule) => {
33
+ if (mode === "cilium") return ciliumBackendsOf(text, rule);
32
34
  const out = /* @__PURE__ */ new Set();
33
35
  const lines = text.split("\n");
34
36
  if (mode === "iptables") {
@@ -70,6 +72,45 @@ const backendsOf = (mode, text, rule) => {
70
72
  }
71
73
  return [...out].sort();
72
74
  };
75
+ /** This function gives the services in a reply of the agent of Cilium. It refuses a reply that is not a JSON list. */
76
+ const ciliumServices = (text) => {
77
+ const data = JSON.parse(text);
78
+ if (!Array.isArray(data)) throw new Error("the reply of the agent of Cilium is not a list of services");
79
+ return data;
80
+ };
81
+ /**
82
+ * This function gives the backends of a Service in the reply of `GET /v1/service` of the agent of
83
+ * Cilium 1.20, as `ip:port`, sorted. The model is `ServiceSpec` of `api/v1/openapi.yaml`. The
84
+ * frontend is the ClusterIP and the port of the rule, with the scope `external` or no scope.
85
+ *
86
+ * The backends are those in the state `active`, or with no state. If no backend is active, the
87
+ * backends are those in the state `terminating`. The BPF reconciler gives the slots of a service
88
+ * in this sequence (`pkg/loadbalancer/reconciler/bpf_reconciler.go`). Thus a new connection goes
89
+ * only to them. kube-proxy does the same with the endpoints that serve and stop. The other
90
+ * states take no new connection: `terminating-not-serving`, `quarantined` and `maintenance`.
91
+ */
92
+ const ciliumBackendsOf = (text, rule) => {
93
+ const backends = ciliumServices(text).filter((service) => {
94
+ const front = service.spec?.["frontend-address"];
95
+ const protocol = (front?.protocol ?? "tcp").toLowerCase();
96
+ return front?.ip === rule.ip && front.port === rule.port && (protocol === "tcp" || protocol === "any") && (front.scope ?? "external") === "external";
97
+ }).flatMap((service) => service.spec?.["backend-addresses"] ?? []);
98
+ const of = (states) => [...new Set(backends.filter((b) => states.includes(b.state) && b.ip !== void 0).map((b) => `${b.ip}:${b.port ?? 0}`))].sort();
99
+ const active = of(["active", void 0]);
100
+ return active.length > 0 ? active : of(["terminating"]);
101
+ };
102
+ /**
103
+ * This function tells if a dump of the rules names the Service of a rule. The rules of kube-proxy
104
+ * name it as `namespace/name`. The JSON of Cilium names it in the flags of its frontend.
105
+ */
106
+ const namesService = (mode, text, rule) => {
107
+ if (mode !== "cilium") return text.includes(`${rule.ns}/${rule.svc}`);
108
+ try {
109
+ return ciliumServices(text).some((service) => service.spec?.flags?.namespace === rule.ns && service.spec.flags.name === rule.svc);
110
+ } catch {
111
+ return false;
112
+ }
113
+ };
73
114
  //#endregion
74
115
  //#region src/scenario.ts
75
116
  const KUBE_PROXY_MODES = [
@@ -77,6 +118,9 @@ const KUBE_PROXY_MODES = [
77
118
  "ipvs",
78
119
  "nftables"
79
120
  ];
121
+ const DATAPLANES = [...KUBE_PROXY_MODES, "cilium"];
122
+ /** This function tells if a dataplane is a mode of kube-proxy. */
123
+ const isKubeProxyMode = (mode) => mode !== "cilium";
80
124
  /**
81
125
  * These are the node images of kind v0.33.0, from its release notes. The image of Kubernetes 1.37
82
126
  * is v1.37.0. The research names v1.37.1 for some defaults, thus a default that changed in the
@@ -100,12 +144,16 @@ const ZONE_KEPT = "zone-a";
100
144
  const ZONE_LOST = "zone-b";
101
145
  /** This function gives the zone of the node at the position `at` of `nodeNames`, in a cluster of `count` nodes. */
102
146
  const zoneOf = (at, count) => at === count - 1 && at > 0 ? ZONE_LOST : ZONE_KEPT;
103
- /** This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values. */
147
+ /**
148
+ * This function gives a scenario with the defaults of Kubernetes for kube-proxy, as explicit values.
149
+ * The dataplane `cilium` has no kube-proxy, and its CNI is Cilium.
150
+ */
104
151
  const scenario = (name, mode, workers, kubernetes = "1.37", zoned = false) => ({
105
152
  name,
106
153
  kubernetes,
107
154
  workers,
108
- kubeProxy: {
155
+ dataplane: mode,
156
+ kubeProxy: isKubeProxyMode(mode) ? {
109
157
  mode,
110
158
  minSyncPeriod: {
111
159
  value: 1,
@@ -115,20 +163,27 @@ const scenario = (name, mode, workers, kubernetes = "1.37", zoned = false) => ({
115
163
  value: 30,
116
164
  unit: "s"
117
165
  }
118
- },
119
- cni: "kindnet",
166
+ } : null,
167
+ cni: isKubeProxyMode(mode) ? "kindnet" : "cilium",
120
168
  podSubnet: "10.244.0.0/16",
121
169
  serviceSubnet: "10.96.0.0/16",
122
170
  ...zoned ? { zoned } : {}
123
171
  });
124
172
  const NAME = /^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$/;
125
- /** This function gives the problems of a scenario: a name that kind refuses, no image for the version, or a count out of range. */
173
+ /**
174
+ * This function gives the problems of a scenario. A problem is a name that kind refuses, no image for
175
+ * 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.
176
+ */
126
177
  const scenarioProblems = (s) => {
127
178
  const problems = [];
128
179
  if (!NAME.test(s.name)) problems.push(`the name ${s.name} is not lowercase letters, digits and dashes of 1 to 32 characters`);
129
180
  if (NODE_IMAGES[s.kubernetes] === void 0) problems.push(`no node image is pinned for Kubernetes ${s.kubernetes}`);
130
181
  if (!Number.isInteger(s.workers) || s.workers < 0 || s.workers > 8) problems.push(`${s.workers} workers is not a whole number from 0 to 8`);
131
182
  if (cidrOverlap(s.podSubnet, s.serviceSubnet)) problems.push(`the pod subnet ${s.podSubnet} overlaps the service subnet ${s.serviceSubnet}`);
183
+ if (isKubeProxyMode(s.dataplane)) {
184
+ if (s.kubeProxy?.mode !== s.dataplane) problems.push(`the dataplane ${s.dataplane} needs kube-proxy in the mode ${s.dataplane}`);
185
+ if (s.cni !== "kindnet") problems.push(`the dataplane ${s.dataplane} needs the CNI kindnet`);
186
+ } else if (s.kubeProxy !== null || s.cni !== "cilium") problems.push("the dataplane cilium needs no kube-proxy and the CNI cilium");
132
187
  return problems;
133
188
  };
134
189
  /** This function gives the names of the node containers, as kind names them: the control plane, then each worker. */
@@ -139,17 +194,26 @@ const goDuration = (amount) => {
139
194
  if (!Number.isInteger(ms) || ms < 0) throw new RangeError(`${ms} ms is not a whole number of milliseconds`);
140
195
  return ms % 1e3 === 0 ? `${ms / 1e3}s` : `${ms}ms`;
141
196
  };
142
- /** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. */
143
- const kubeProxyPatch = (s) => [
144
- "apiVersion: kubeproxy.config.k8s.io/v1alpha1",
145
- "kind: KubeProxyConfiguration",
146
- `mode: ${s.kubeProxy.mode}`,
147
- `${s.kubeProxy.mode}:`,
148
- ` minSyncPeriod: ${goDuration(s.kubeProxy.minSyncPeriod)}`,
149
- ` syncPeriod: ${goDuration(s.kubeProxy.syncPeriod)}`,
150
- ""
151
- ].join("\n");
152
- /** This function generates the configuration of kind from a scenario. It refuses a scenario with a problem. */
197
+ /** This function gives the KubeProxyConfiguration patch of a scenario, as kubeadm reads it. It refuses a scenario with no kube-proxy. */
198
+ const kubeProxyPatch = (s) => {
199
+ const proxy = s.kubeProxy;
200
+ if (proxy === null) throw new Error(`the scenario ${s.name} has no kube-proxy`);
201
+ return [
202
+ "apiVersion: kubeproxy.config.k8s.io/v1alpha1",
203
+ "kind: KubeProxyConfiguration",
204
+ `mode: ${proxy.mode}`,
205
+ `${proxy.mode}:`,
206
+ ` minSyncPeriod: ${goDuration(proxy.minSyncPeriod)}`,
207
+ ` syncPeriod: ${goDuration(proxy.syncPeriod)}`,
208
+ ""
209
+ ].join("\n");
210
+ };
211
+ /**
212
+ * This function generates the configuration of kind from a scenario. It refuses a scenario with a
213
+ * problem. The dataplane `cilium` gets no kindnet, as the kind guide of Cilium 1.20.4 tells
214
+ * (`Documentation/installation/kind-configure.rst`). It also gets no kube-proxy, because Cilium
215
+ * replaces it (`Documentation/network/kubernetes/kubeproxy-free.rst`).
216
+ */
153
217
  const kindConfig = (s) => {
154
218
  const problems = scenarioProblems(s);
155
219
  if (problems.length > 0) throw new Error(`the scenario ${s.name}: ${problems.join("; ")}`);
@@ -159,8 +223,8 @@ const kindConfig = (s) => {
159
223
  apiVersion: "kind.x-k8s.io/v1alpha4",
160
224
  networking: {
161
225
  ipFamily: "ipv4",
162
- kubeProxyMode: s.kubeProxy.mode,
163
- disableDefaultCNI: false,
226
+ kubeProxyMode: s.kubeProxy?.mode ?? "none",
227
+ disableDefaultCNI: s.cni !== "kindnet",
164
228
  podSubnet: s.podSubnet,
165
229
  serviceSubnet: s.serviceSubnet
166
230
  },
@@ -172,7 +236,7 @@ const kindConfig = (s) => {
172
236
  ...s.zoned === true ? { [ZONE_LABEL]: zoneOf(at, all.length) } : {}
173
237
  }
174
238
  })),
175
- kubeadmConfigPatches: [kubeProxyPatch(s)]
239
+ kubeadmConfigPatches: s.kubeProxy === null ? [] : [kubeProxyPatch(s)]
176
240
  };
177
241
  };
178
242
  /** This function gives the first and the last address of an IPv4 CIDR, as numbers. */
@@ -266,6 +330,30 @@ const ISTIOCTL = {
266
330
  }
267
331
  };
268
332
  /**
333
+ * This release of Helm is v3.22.1, from 2026-10-15. The lab installs Cilium from its chart with it.
334
+ * The guide of Cilium 1.20.4 installs with Helm 3. The sums are from the `.sha256sum` files of
335
+ * `get.helm.sh`. The tar file for Windows has `helm.exe`, not `helm`, thus Windows has no asset.
336
+ */
337
+ const HELM = {
338
+ name: "helm",
339
+ version: "v3.22.1",
340
+ entry: "helm",
341
+ assets: {
342
+ "linux-x64": {
343
+ url: "https://get.helm.sh/helm-v3.22.1-linux-amd64.tar.gz",
344
+ sha256: "896cf63684a42a723d61c980369d6e588330927deb13676ca0d85006dd6da0ea"
345
+ },
346
+ "linux-arm64": {
347
+ url: "https://get.helm.sh/helm-v3.22.1-linux-arm64.tar.gz",
348
+ sha256: "4c29e286b279d170fa048ddfc3e4e83dcd069ca9c52102766ba672d7149e694d"
349
+ },
350
+ "darwin-arm64": {
351
+ url: "https://get.helm.sh/helm-v3.22.1-darwin-arm64.tar.gz",
352
+ sha256: "e73da8a0b0d5193774fdff17ce6319c7436bb22ee229b45fd64f3c40368c753a"
353
+ }
354
+ }
355
+ };
356
+ /**
269
357
  * This function gives the bytes of one file of a gzipped tar file. A header of the ustar format has
270
358
  * 512 bytes. It has the name at 0, the size in octal at 124, the type at 156 and the prefix of the
271
359
  * name at 345. The data follows the header, in blocks of 512 bytes. The match is on the last part of the
@@ -485,16 +573,17 @@ const createCluster = async (scenario, dir) => {
485
573
  "-o",
486
574
  "json"
487
575
  ]));
488
- const proxy = JSON.parse(await kubectl([
576
+ const config = scenario.kubeProxy === null ? "cilium-config" : "kube-proxy";
577
+ const data = JSON.parse(await kubectl([
489
578
  "-n",
490
579
  "kube-system",
491
580
  "get",
492
581
  "configmap",
493
- "kube-proxy",
582
+ config,
494
583
  "-o",
495
584
  "json"
496
- ]));
497
- const mode = /^mode:\s*(\S*)/m.exec(proxy.data["config.conf"] ?? "")?.[1] ?? "";
585
+ ])).data;
586
+ const mode = scenario.kubeProxy === null ? ciliumMode(data) : /^mode:\s*(\S*)/m.exec(data["config.conf"] ?? "")?.[1] ?? "";
498
587
  const version = JSON.parse(await kubectl([
499
588
  "version",
500
589
  "-o",
@@ -535,6 +624,7 @@ const createCluster = async (scenario, dir) => {
535
624
  ]).catch(() => void 0);
536
625
  }
537
626
  };
627
+ const wait = scenario.cni === "kindnet" ? ["--wait", "180s"] : [];
538
628
  await command(kind, [
539
629
  "create",
540
630
  "cluster",
@@ -544,8 +634,7 @@ const createCluster = async (scenario, dir) => {
544
634
  file,
545
635
  "--kubeconfig",
546
636
  kubeconfig,
547
- "--wait",
548
- "180s"
637
+ ...wait
549
638
  ], {
550
639
  env,
551
640
  timeout: 9e5
@@ -553,6 +642,178 @@ const createCluster = async (scenario, dir) => {
553
642
  return cluster;
554
643
  };
555
644
  //#endregion
645
+ //#region src/cilium.ts
646
+ /**
647
+ * This module installs Cilium 1.20.4 in a kind cluster with Helm, from images pinned by digest.
648
+ * Cilium is the CNI and replaces kube-proxy (the dataplane `cilium`). Each value of the install
649
+ * that is not a default of the chart is in `ciliumValues`, with its reason.
650
+ */
651
+ /** This version is the release of Cilium of the lab, from 2026-10-23: the version of the chart and the tag of each image. */
652
+ const CILIUM_VERSION = "1.20.4";
653
+ /** This chart is the Helm chart of Cilium 1.20.4. The sha256 is of the file on `helm.cilium.io` (read on 2026-10-27). */
654
+ const CILIUM_CHART = {
655
+ url: "https://helm.cilium.io/cilium-1.20.4.tgz",
656
+ sha256: "505abff8385811df1a949776fc42e9f10697989a332acc86d6e8933efcb855ed"
657
+ };
658
+ /**
659
+ * These are the images that the install needs, pinned by the digests that the chart of 1.20.4 names
660
+ * (`values.yaml`). The agent, the operator and Envoy are the only images: the install has no
661
+ * node init, no preflight and no Hubble relay. The lab pulls each one by its digest, tags it
662
+ * (`localPin`), and loads it into each node. Thus no node pulls from a registry.
663
+ */
664
+ const CILIUM_IMAGES = {
665
+ agent: "quay.io/cilium/cilium:v1.20.4@sha256:20fe92e4c86641233a450098c158d2d29085f3419931ab6bf681d2485dedf16e",
666
+ operator: "quay.io/cilium/operator-generic:v1.20.4@sha256:7a7a3489dc66ad67e420d285c8a8d2e169b0957ad15e120f593d27081bf38d82",
667
+ envoy: "quay.io/cilium/cilium-envoy:v1.39.5-1792733054-8c73b2816bc8239e27f1fd53b271884688137459@sha256:547ab45f6092f681d9f54f3be9fa8fb1e705c9c5dc2df388efe3a52aa6965629"
668
+ };
669
+ /**
670
+ * This function gives the Helm values of the install in a cluster. The values that are not the
671
+ * defaults of the chart:
672
+ * - `kubeProxyReplacement: true`, and the address of the API server on the control plane. With no
673
+ * kube-proxy, the agent cannot reach the API through its Service
674
+ * (`Documentation/network/kubernetes/kubeproxy-free.rst`).
675
+ * - `ipam.mode: kubernetes`: the agent takes the pod CIDR of each node from the node, as the kind
676
+ * guide tells (`Documentation/installation/kind.rst`). Thus the pod subnet of the scenario holds.
677
+ * - Each image is an `override`: the helper `cilium.image` of the chart then uses it as it is
678
+ * (`templates/_helpers.tpl`). `pullPolicy: Never`: each image is on each node.
679
+ * - The operator has one replica, not two. Its anti-affinity puts each replica on a different node,
680
+ * and no cell examines a failover of the operator.
681
+ *
682
+ * Socket LB stays at its default. KPR sets it on (`pkg/kpr/kpr.go`). It selects a backend at
683
+ * `connect()`. A connection keeps its backend, as with the conntrack NAT of kube-proxy. On the
684
+ * deletion of a backend, the agent destroys only its UDP sockets, because
685
+ * `LBSockTerminateAllProtos` is off by default (`pkg/loadbalancer/reconciler/termination.go`).
686
+ */
687
+ const ciliumValues = (clusterName, images) => ({
688
+ kubeProxyReplacement: true,
689
+ k8sServiceHost: `${clusterName}-control-plane`,
690
+ k8sServicePort: 6443,
691
+ ipam: { mode: "kubernetes" },
692
+ image: {
693
+ override: images.agent,
694
+ pullPolicy: "Never"
695
+ },
696
+ operator: {
697
+ replicas: 1,
698
+ image: {
699
+ override: images.operator,
700
+ pullPolicy: "Never"
701
+ }
702
+ },
703
+ envoy: { image: {
704
+ override: images.envoy,
705
+ pullPolicy: "Never"
706
+ } }
707
+ });
708
+ /** These are the workloads in kube-system that the install waits for, then the DNS that waits for the CNI. */
709
+ const CILIUM_WORKLOADS = [
710
+ "daemonset/cilium",
711
+ "deployment/cilium-operator",
712
+ "daemonset/cilium-envoy"
713
+ ];
714
+ /** This function gives the path of the chart in the cache, after a download when the cache does not have it. It refuses a file whose sha256 is not the pinned value. */
715
+ const ensureChart = async (cache = cacheFolder()) => {
716
+ const path = join(cache, "charts", `cilium-${CILIUM_VERSION}.tgz`);
717
+ const bytes = existsSync(path) ? readFileSync(path) : await download(CILIUM_CHART.url);
718
+ const found = sha256(bytes);
719
+ if (found !== CILIUM_CHART.sha256) throw new Error(`the chart of Cilium has the sha256 ${found}, not the pinned ${CILIUM_CHART.sha256}`);
720
+ if (!existsSync(path)) {
721
+ mkdirSync(join(cache, "charts"), { recursive: true });
722
+ writeFileSync(path, bytes);
723
+ }
724
+ return path;
725
+ };
726
+ /**
727
+ * This function installs Cilium in a cluster with no CNI and no kube-proxy. Helm keeps its own
728
+ * files in the folder of the cluster. The function waits for each workload, for each node to be
729
+ * ready, and for CoreDNS. If the install fails, the error has the pods of kube-system and the last
730
+ * lines of the log of the agent.
731
+ */
732
+ const installCilium = async (cluster, images) => {
733
+ const helm = await ensureTool(HELM);
734
+ const chart = await ensureChart();
735
+ const file = join(cluster.dir, "cilium-values.json");
736
+ writeFileSync(file, `${JSON.stringify(ciliumValues(cluster.scenario.name, images), null, 2)}\n`);
737
+ const home = join(cluster.dir, "helm");
738
+ const env = {
739
+ HELM_CACHE_HOME: join(home, "cache"),
740
+ HELM_CONFIG_HOME: join(home, "config"),
741
+ HELM_DATA_HOME: join(home, "data")
742
+ };
743
+ try {
744
+ await command(helm, [
745
+ "install",
746
+ "cilium",
747
+ chart,
748
+ "-n",
749
+ "kube-system",
750
+ "--kubeconfig",
751
+ cluster.kubeconfig,
752
+ "-f",
753
+ file,
754
+ "--wait",
755
+ "--timeout",
756
+ "10m"
757
+ ], {
758
+ env,
759
+ timeout: 9e5
760
+ });
761
+ for (const workload of CILIUM_WORKLOADS) await cluster.kubectl([
762
+ "-n",
763
+ "kube-system",
764
+ "rollout",
765
+ "status",
766
+ workload,
767
+ "--timeout=300s"
768
+ ]);
769
+ await cluster.kubectl([
770
+ "wait",
771
+ "--for=condition=Ready",
772
+ "nodes",
773
+ "--all",
774
+ "--timeout=300s"
775
+ ]);
776
+ await cluster.kubectl([
777
+ "-n",
778
+ "kube-system",
779
+ "rollout",
780
+ "status",
781
+ "deployment/coredns",
782
+ "--timeout=300s"
783
+ ]);
784
+ } catch (error) {
785
+ const state = async (args) => (await cluster.kubectl([
786
+ "-n",
787
+ "kube-system",
788
+ ...args
789
+ ]).catch((why) => String(why))).slice(-4e3);
790
+ const pods = await state([
791
+ "get",
792
+ "pods",
793
+ "-o",
794
+ "wide"
795
+ ]);
796
+ const agent = await state([
797
+ "logs",
798
+ "daemonset/cilium",
799
+ "-c",
800
+ "cilium-agent",
801
+ "--tail=80"
802
+ ]);
803
+ throw new Error(`the install of Cilium failed: ${String(error)}\nthe pods of kube-system:\n${pods}\nthe log of the agent:\n${agent}`);
804
+ }
805
+ };
806
+ /** This is the setting of Cilium that a cluster manifest records: the version and the keys of `cilium-config` that the cells depend on. */
807
+ const CILIUM_CONFIG_KEYS = [
808
+ "kube-proxy-replacement",
809
+ "bpf-lb-sock",
810
+ "bpf-lb-algorithm",
811
+ "routing-mode",
812
+ "ipam"
813
+ ];
814
+ /** This function gives the mode of a cluster manifest of the dataplane `cilium`, from the data of the configmap `cilium-config`. */
815
+ const ciliumMode = (data) => `cilium ${CILIUM_VERSION} ${CILIUM_CONFIG_KEYS.map((key) => `${key}=${data[key] ?? "default"}`).join(" ")}`;
816
+ //#endregion
556
817
  //#region src/compose.ts
557
818
  /** This name is the controller of the service, and its one route reads Redis when the service has Redis. */
558
819
  const CONTROLLER = "work";
@@ -3153,7 +3414,7 @@ const deploy = async (lab, cluster, cell, namespace, images) => {
3153
3414
  "--timeout=300s"
3154
3415
  ]);
3155
3416
  await lab.rules({
3156
- mode: cluster.scenario.kubeProxy.mode,
3417
+ mode: cluster.scenario.dataplane,
3157
3418
  ns: namespace,
3158
3419
  svc: "lab",
3159
3420
  ip: clusterIp,
@@ -3388,6 +3649,7 @@ const doStep = async (ctx, step, replicas, background) => {
3388
3649
  pod: redis
3389
3650
  };
3390
3651
  }
3652
+ if (cluster.scenario.kubeProxy === null) throw new Error(`${ctx.cell.name}: the target kube-proxy needs kube-proxy, and the dataplane ${cluster.scenario.dataplane} has none`);
3391
3653
  const worker = cluster.nodes[1] ?? cluster.nodes[0];
3392
3654
  const proxy = JSON.parse(await cluster.kubectl([
3393
3655
  "-n",
@@ -3817,16 +4079,22 @@ const runKindCells = async (cells, options) => {
3817
4079
  const flavors = new Set(cells.flatMap((cell) => cell.mesh === void 0 ? [] : [cell.mesh.flavor]));
3818
4080
  const pinned = /* @__PURE__ */ new Map();
3819
4081
  for (const name of new Set([...flavors].flatMap((flavor) => MESH_IMAGES[flavor]))) pinned.set(name, await localPin(ISTIO_IMAGES[name]));
4082
+ const meshImages = flavors.size === 0 ? void 0 : {
4083
+ pilot: pinned.get("pilot"),
4084
+ proxy: pinned.get("proxy"),
4085
+ cni: pinned.get("cni"),
4086
+ ztunnel: pinned.get("ztunnel")
4087
+ };
4088
+ const cilium = cells.some((cell) => cell.mode === "cilium") ? {
4089
+ agent: await localPin(CILIUM_IMAGES.agent),
4090
+ operator: await localPin(CILIUM_IMAGES.operator),
4091
+ envoy: await localPin(CILIUM_IMAGES.envoy)
4092
+ } : void 0;
3820
4093
  const images = {
3821
4094
  lab: image,
3822
4095
  redis: redisImage,
3823
4096
  grpc: grpcImage,
3824
- mesh: flavors.size === 0 ? void 0 : {
3825
- pilot: pinned.get("pilot"),
3826
- proxy: pinned.get("proxy"),
3827
- cni: pinned.get("cni"),
3828
- ztunnel: pinned.get("ztunnel")
3829
- }
4097
+ mesh: meshImages
3830
4098
  };
3831
4099
  const groups = /* @__PURE__ */ new Map();
3832
4100
  for (const cell of cells) groups.set(clusterKey(cell), [...groups.get(clusterKey(cell)) ?? [], cell]);
@@ -3837,11 +4105,22 @@ const runKindCells = async (cells, options) => {
3837
4105
  const s = scenario(`lab-${key}-${env}`, group[0].mode, group[0].workers, void 0, zoned(group[0]));
3838
4106
  const dir = join(options.work ?? join(tmpdir(), "systems-kind-work"), s.name);
3839
4107
  const mesh = group[0].mesh;
3840
- options.log(`cluster ${s.name}: ${s.kubeProxy.mode}, ${s.workers} workers${mesh === void 0 ? "" : `, Istio ${mesh.flavor}`}`);
4108
+ options.log(`cluster ${s.name}: ${s.dataplane}, ${s.workers} workers${mesh === void 0 ? "" : `, Istio ${mesh.flavor}`}`);
3841
4109
  const cluster = await createCluster(s, dir);
3842
4110
  let lab = null;
3843
4111
  let loader = null;
3844
4112
  try {
4113
+ if (s.cni === "cilium") {
4114
+ if (cilium === void 0) throw new Error(`${group[0].name}: the run has no images of Cilium`);
4115
+ for (const each of [
4116
+ cilium.agent,
4117
+ cilium.operator,
4118
+ cilium.envoy
4119
+ ]) await cluster.load(each);
4120
+ const began = Date.now();
4121
+ await installCilium(cluster, cilium);
4122
+ options.log(` Cilium ${CILIUM_VERSION} installed in ${Math.round((Date.now() - began) / 100) / 10} s`);
4123
+ }
3845
4124
  await cluster.load(image);
3846
4125
  await cluster.load(redisImage);
3847
4126
  if (grpcImage !== void 0 && group.some((cell) => stackOf(cell) === "grpc")) await cluster.load(grpcImage);
@@ -3912,7 +4191,8 @@ const runKindCells = async (cells, options) => {
3912
4191
  lab: image,
3913
4192
  redis: redisImage,
3914
4193
  ...stackOf(cell) === "grpc" ? { grpc: grpcImage } : {},
3915
- ...cell.mesh === void 0 ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]]))
4194
+ ...cell.mesh === void 0 ? {} : Object.fromEntries(MESH_IMAGES[cell.mesh.flavor].map((name) => [`istio-${name}`, ISTIO_IMAGES[name]])),
4195
+ ...s.cni === "cilium" ? Object.fromEntries(Object.entries(CILIUM_IMAGES).map(([name, ref]) => [`cilium-${name}`, ref])) : {}
3916
4196
  }
3917
4197
  });
3918
4198
  entry.loads.push(...loads);
@@ -4017,6 +4297,6 @@ const shardOf = (cells, index, count) => {
4017
4297
  return out;
4018
4298
  };
4019
4299
  //#endregion
4020
- export { ACCESS_LOG_FORMAT, CHANNEL_USE, CLIENT, CLIENT_ACCOUNT, CLOSE_LEAD, CONTROLLER, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LDS_EVERY, LDS_FILTER, LOG_LABEL, MESH_IMAGES, MESH_WORKLOADS, MIDDLEBOX, NETEM_DRAIN, NETNS_DEFAULTS, NODE_IMAGES, NODE_LIFECYCLE, O04, O09, O10, O16, POD_LINK, POLICY, PORT, QUIET_LEAD, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, SPREAD_LEAD, STAGES, SYSCTL, TCP_STATE, WATCHDOG, ZONE_KEPT, ZONE_LABEL, ZONE_LOST, ZTUNNEL_PODS, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, certificateArgs, checkRun, cidrOverlap, cidrRange, clientObjects, clusterFilter, compose, createCluster, cyclesOf, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dropOf, emptyOf, ensureTool, estimatedTime, factsOf, failuresOf, faultSpan, followsZtunnel, goDuration, hostsOf, installMesh, intakeAfter, intakeOf, isTunnelState, istioOperator, judge, judgeKind, keptKernel, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, labSource, latchBand, leafCertificates, learnWindow, lintCell, listenerCountsOf, localPin, medianInterval, medianText, meshKey, middle, middleboxCommands, namespaceObject, namespaceOf, nodeLossBand, nodeNames, nodeReturnBand, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartOf, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, setZtunnelEnv, sha256, shardOf, sharedGate, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, stackOf, stageLabImage, stallRemovalBand, stopOf, stoppingOf, sysctlCommand, tarEntry, timeSpread, toolPath, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult, zoneOf, ztunnelCertificates, ztunnelEnvArgs, ztunnelPodsOf, ztunnelReplaced };
4300
+ export { ACCESS_LOG_FORMAT, CHANNEL_USE, CILIUM_CHART, CILIUM_CONFIG_KEYS, CILIUM_IMAGES, CILIUM_VERSION, CILIUM_WORKLOADS, CLIENT, CLIENT_ACCOUNT, CLOSE_LEAD, CONTROLLER, DATAPLANES, DOCUMENTED, DRAIN_RULES, EDS_EVERY, FLAG_CHECK, FORMAT, FORMAT_V1, HBONE_PORT, HEALTH_PORT, HELM, ISTIOCTL, ISTIO_IMAGES, ISTIO_VERSION, KEPT_KERNEL, KIND, KIND_NOISE, KUBECTL, KUBE_PROXY_MODES, LAST_SLACK, LDS_EVERY, LDS_FILTER, LOG_LABEL, MESH_IMAGES, MESH_WORKLOADS, MIDDLEBOX, NETEM_DRAIN, NETNS_DEFAULTS, NODE_IMAGES, NODE_LIFECYCLE, O04, O09, O10, O16, POD_LINK, POLICY, PORT, QUIET_LEAD, REDIS, REDIS_IMAGE, RELATIVE, ROUTE, RULES_EVERY, SERVER, SPREAD_LEAD, STAGES, SYSCTL, TCP_STATE, WATCHDOG, ZONE_KEPT, ZONE_LABEL, ZONE_LOST, ZTUNNEL_PODS, backendsAt, backendsOf, bandOf, byClients, byNode, cacheFolder, calibrationOf, capOf, cellsTable, certificateArgs, checkRun, cidrOverlap, cidrRange, ciliumBackendsOf, ciliumMode, ciliumValues, clientObjects, clusterFilter, compose, createCluster, cyclesOf, deletes, deletionOf, deletionWindow, deletionsOf, describeCell, dropOf, emptyOf, ensureChart, ensureTool, estimatedTime, factsOf, failuresOf, faultSpan, followsZtunnel, goDuration, hostsOf, installCilium, installMesh, intakeAfter, intakeOf, isKubeProxyMode, isTunnelState, istioOperator, judge, judgeKind, keptKernel, kernelCounts, kindCellRecordSchema, kindCellRecordV1Schema, kindConfig, kubeProxyPatch, kubeletBand, labImage, labSource, latchBand, leafCertificates, learnWindow, lintCell, listenerCountsOf, localPin, medianInterval, medianText, meshKey, middle, middleboxCommands, namesService, namespaceObject, namespaceOf, nodeLossBand, nodeNames, nodeReturnBand, observationAfter, observationEnd, openKindLab, outboundCluster, outcomesOf, pausedWindow, planOf, platformKey, podsAt, podsOf, policyObject, pollsListeners, predictionText, probeOf, propagationOf, propagationSource, quantileOf, readRecord, recordsOf, redisObjects, referenceOf, removalBand, requestsOf, restartBand, restartOf, restartsAt, reusesReset, rulesOf, runKindCells, scenario, scenarioOf, scenarioProblems, seconds, secondsOf, serverCluster, serverObjects, setZtunnelEnv, sha256, shardOf, sharedGate, showBand, signalOf, signalsOf, sliceDropOf, sliceEmptyOf, sliceReadyAt, sliceReadyOf, slicesOf, socketsOf, stackOf, stageLabImage, stallRemovalBand, stopOf, stoppingOf, sysctlCommand, tarEntry, timeSpread, toolPath, transitionOf, tunnelsOf, unitOf, updatesOf, upgrade, upgradeCell, verdictsOf, writeResult, zoneOf, ztunnelCertificates, ztunnelEnvArgs, ztunnelPodsOf, ztunnelReplaced };
4021
4301
 
4022
4302
  //# sourceMappingURL=index.js.map