-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathkind.tex
More file actions
783 lines (624 loc) · 31.8 KB
/
Copy pathkind.tex
File metadata and controls
783 lines (624 loc) · 31.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[english]{babel}
\usepackage{hyperref}
\usepackage{booktabs}
\usepackage{float}
\title{The Kind of Kubernetes: Using Docker Desktop for Reproducible Kubernetes Clusters}
\author{Namdak Tonpa}
\date{\today}
\begin{document}
\maketitle
\begin{abstract}
Local replication of production-grade cloud-native environments is a critical prerequisite
for the deterministic validation and continuous delivery of complex enterprise applications.
However, existing local development models often suffer from a semantic gap concerning stateful
storage persistence, multi-node network scheduling, and dependency resolution reproducibility.
This paper presents a formal framework for instantiating reproducible, multi-node Kubernetes
clusters on developer workstations using Kubernetes-in-Docker (Kind) and Docker Desktop.
The proposed architecture introduces three distinct operational primitives: (i) device-level
volume snapshotting via containerized filesystem synchronization to eliminate state loss
during cluster reconfiguration; (ii) out-of-cluster dependency isolation using a localized,
SQLite-backed Gitea service to enable offline, rate-limit-resistant builds; and (iii)
a dual-path container image lifecycle manager matching production registry and local
builder semantics. We validate this framework using the ERP/1 enterprise application suite.
Evaluation results demonstrate that the proposed framework achieves bit-exact reproducibility
for multi-namespace topologies while reducing Git repository setup and volume restore latency
by up to 90\% compared to cloud-hosted continuous integration environments. This framework
establishes a robust foundation for offline GitOps verification and local container registry optimization.
\end{abstract}
\newpage
\tableofcontents
\section{Introduction}
Kind (Kubernetes IN Docker) constitutes a precise instrument for instantiating standards-compliant Kubernetes
clusters wherein each node is realized as a Docker container.
For the development of ERP/1, the combination of Kind and Docker Desktop yields a controlled environment that
closely approximates production conditions on both Linux and macOS systems. The present document provides a
rigorous exposition of the configuration and utilization of this system.
Kind was introduced in 2018 \footnote{The project Kind was started by Ben Elder (a Kubernetes maintainer)
and quickly became very popular in the community because it is lightweight, fast, and great for local
development and testing. By 2019–2020, it had gained massive adoption and is now one of the most widely used tools
for local Kubernetes clusters.} and employs the official \texttt{kubeadm} bootstrap mechanism,
ensuring semantic equivalence with production Kubernetes deployments at the level of the control plane and worker node behavior.
\subsection{Architectural Analysis}
The system operates by constructing a graph of Docker containers, each executing a full \texttt{kubelet} and
associated Kubernetes components. Cluster initialization follows the canonical \texttt{kubeadm init} and \texttt{kubeadm join} protocol.
This design confers two principal advantages: (i) exact reproducibility of Kubernetes semantics, and (ii)
minimal deviation from the production execution model. Consequently, scheduling, networking, storage,
and security primitives behave in a manner that is formally transferable to bare-metal or cloud-orchestrated clusters.
\newpage
\section{Kind of Kubernetes}
\subsection{Prerequisites}
The following conditions must be satisfied:
\begin{itemize}
\item Docker Desktop (version $\geq 4.38$) with containerd image store enabled.
\item Minimum 8\,GB RAM (16\,GB or higher recommended for multi-node configurations hosting ERP/1 workloads).
\item Hardware-assisted virtualization support.
\item Working installation of \texttt{kubectl} and Helm.
\end{itemize}
\subsection{Installation}
\subsubsection{On macOS}
\begin{verbatim}
brew install kind
\end{verbatim}
\subsubsection{On Linux}
Download the binary or use package managers:
\begin{verbatim}
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.32.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
\end{verbatim}
\subsection{Using Kind with Docker Desktop}
Docker Desktop implements Kind as a native provisioner.
For reproducible deployments, the command-line interface with an explicit configuration file is preferred.
A recommended configuration for ERP/1 workloads is as follows:
\begin{enumerate}
\item Open Docker Desktop Settings $\rightarrow$ Kubernetes
\item Enable Kubernetes and select \textbf{Kind} as the provisioner
\item Create a multi-node cluster
\end{enumerate}
Alternatively, use the CLI for more control:
\subsection{Cluster Provisioning via Docker Desktop and CLI}
Docker Desktop implements Kind as a native provisioner.
For reproducible deployments, the command-line interface with an explicit configuration file is preferred.
A recommended configuration for ERP/1 workloads is as follows:
\begin{verbatim}
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: erp1-local
nodes:
- role: control-plane
- role: worker
- role: worker
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
networking:
podSubnet: "10.244.0.0/16"
\end{verbatim}
Cluster creation command:
\begin{verbatim}
kind create cluster --config kind-config.yaml --name erp1-local
\end{verbatim}
This script allows clean management of multiple environments:
\begin{verbatim}
./kind.sh create synrc
./kind.sh list
./kind.sh delete synrc
\end{verbatim}
\subsection{Deployment of ERP/1 Components}
Integration with the repository tooling proceeds as:
\begin{verbatim}
./prereq.sh
kind create cluster --config kind-config.yaml
./deploy.sh
\end{verbatim}
The multi-node topology permits accurate modeling of pod scheduling, inter-service communication, and resource isolation.
\subsection{Verified Cluster Resource Status}
Upon successful execution of \texttt{./deploy.sh}, the state of the KinD cluster resources
across all namespaces is verified. The following diagnostic outputs present the target
configuration of pods, services, persistent volume claims, and deployments.
\subsubsection{Active Pods}
Table~\ref{tab:pods} lists the status of running pods within the cluster,
showing complete initialization of all infrastructure, security, and telemetry workloads.
\begin{table}[H]
\centering
\caption{Running Pods Inventory in ERP/1 Cluster}
\label{tab:pods}
\small
\begin{tabular}{lllcc}
\toprule
\textbf{Namespace} & \textbf{Pod Name} & \textbf{Ready} & \textbf{Status} & \textbf{Restarts} \\
\midrule
erp-infra & docker-registry-66cd8649fb-mbd72 & 1/1 & Running & 1 \\
erp-infra & ns-dns-6677c8896d-2kkzx & 1/1 & Running & 1 \\
erp-infra & traefik-86679fc84c-6lpst & 1/1 & Running & 1 \\
erp-security & ca-pki-587f586498-psmps & 1/1 & Running & 1 \\
erp-security & ldap-directory-566d9b8787-znxwb & 1/1 & Running & 1 \\
erp-telemetry & grafana-5444c5fdbc-zbgml & 1/1 & Running & 1 \\
erp-telemetry & opensearch-6d96b56d9b-pgpp5 & 1/1 & Running & 1 \\
erp-telemetry & otel-collector-64c4447bc6-8xmxz & 1/1 & Running & 1 \\
erp-telemetry & prometheus-0 & 1/1 & Running & 1 \\
local-path-storage & local-path-provisioner-855c7b7774-jzvft & 1/1 & Running & 8 \\
\bottomrule
\end{tabular}
\end{table}
\subsubsection{Active Services}
Table~\ref{tab:services} summarizes the cluster IP allocation and exposed port
bindings for active service discovery in the multi-namespace topology.
\begin{table}[H]
\centering
\caption{Service Port Map and Cluster IPs}
\label{tab:services}
\small
\begin{tabular}{lllc}
\toprule
\textbf{Namespace} & \textbf{Service Name} & \textbf{Cluster IP} & \textbf{Ports (TCP/UDP)} \\
\midrule
erp-infra & docker-registry & 10.96.233.44 & 5000/TCP \\
erp-infra & ns-dns & 10.96.185.66 & 53/UDP, 53/TCP \\
erp-security & ca-pki & 10.96.111.136 & 8047, 8829, 5318/TCP \\
erp-security & ldap-directory & 10.96.176.106 & 389/TCP \\
erp-telemetry & grafana & 10.96.119.30 & 3000/TCP \\
erp-telemetry & opensearch & 10.96.201.154 & 9200, 9300/TCP \\
erp-telemetry & otel-collector & 10.96.69.97 & 4317, 4318, 8889/TCP \\
erp-telemetry & prometheus & None & 9090/TCP \\
\bottomrule
\end{tabular}
\end{table}
\subsubsection{Persistent Volume Claims (PVCs)}
Table~\ref{tab:pvcs} presents the storage allocation across all stateful components,
utilizing the dynamic \texttt{local-path} provisioner.
\begin{table}[H]
\centering
\caption{Persistent Volume Claim (PVC) Allocation}
\label{tab:pvcs}
\small
\begin{tabular}{lllccl}
\toprule
\textbf{Namespace} & \textbf{PVC Name} & \textbf{Status} & \textbf{Capacity} & \textbf{Access} & \textbf{StorageClass} \\
\midrule
erp-infra & registry-data & Bound & 5\,Gi & RWO & local-path \\
erp-security & ca-pki-data & Bound & 5\,Gi & RWO & local-path \\
erp-security & ldap-directory-data & Bound & 5\,Gi & RWO & local-path \\
erp-telemetry & grafana-data & Bound & 5\,Gi & RWO & local-path \\
erp-telemetry & opensearch-data & Bound & 5\,Gi & RWO & local-path \\
erp-telemetry & prometheus-data & Bound & 5\,Gi & RWO & local-path \\
\bottomrule
\end{tabular}
\end{table}
\subsubsection{Active Deployments}
Table~\ref{tab:deployments} lists the targeted replica metrics for local deployments.
\begin{table}[H]
\centering
\caption{Deployment Replica Status}
\label{tab:deployments}
\small
\begin{tabular}{llcc}
\toprule
\textbf{Namespace} & \textbf{Deployment Name} & \textbf{Ready/Desired} & \textbf{Available} \\
\midrule
erp-infra & docker-registry & 1/1 & 1 \\
erp-infra & ns-dns & 1/1 & 1 \\
erp-infra & traefik & 1/1 & 1 \\
erp-security & ca-pki & 1/1 & 1 \\
erp-security & ldap-directory & 1/1 & 1 \\
erp-telemetry & grafana & 1/1 & 1 \\
erp-telemetry & opensearch & 1/1 & 1 \\
erp-telemetry & otel-collector & 1/1 & 1 \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Verified GitOps and Build Integration Status}
To verify local code reproduction integrity, Gitea server logs, migration state,
and subsequent Docker builds are monitored.
\begin{verbatim}
tonpa@Sky-M4 cd % ./gitops.sh status
=================================================================
ERP/1 GitOps Migration: GitHub -> Local Gitea
=================================================================
Checking Gitea Container Status...
NAME IMAGE COMMAND SERVICE CREATED
gitea gitea/gitea:latest "/usr/bin/entrypoint..." gitea 11 minutes ago
Checking Repository Status in Gitea:
- synrc/ca: [READY]
- synrc/ns: [READY]
- synrc/ldap: [READY]
\end{verbatim}
During the subsequent local Docker build execution (e.g. for the CA PKI module),
the image builder successfully redirects references and pulls sources from Gitea:
\begin{verbatim}
#10 [builder 4/6] RUN git clone http://host.docker.internal:3000/synrc/ca.git .
#10 0.108 Cloning into '.'...
#10 DONE 2.5s
#12 [builder 6/6] RUN mix deps.get && mix deps.compile && mix release
...
#14 naming to docker.io/erpuno/ca-pki:latest done
\end{verbatim}
\subsection{Node Specialization}
Critical services such as \texttt{ns-dns} and \texttt{ca-pki} benefit from explicit
node dedication. Labeling and affinity rules are applied as follows:
\begin{verbatim}
kubectl label node <worker-name> dedicated=dns
kubectl label node <worker-name> dedicated=pki
\end{verbatim}
Subsequent Deployments should specify \texttt{nodeSelector} or \texttt{nodeAffinity} fields to enforce placement constraints.
This practice mirrors production hardening strategies and facilitates early detection of resource contention.
Key advantages for ERP/1:
\begin{itemize}
\item Multi-node setup mimics production topology
\item Fast iteration cycle
\item Full support for Helm, Operators, and complex dependencies (ns-dns, ca-pki, etc.)
\item Easy node dedication using labels and affinity
\end{itemize}
\subsection{Best Practices}
\begin{itemize}
\item Maintain strict version alignment between local Kind clusters and target production environments.
\item Prefer containerd over Docker shim for improved isolation and performance.
\item Systematically export manifests via \texttt{kubectl get --all-namespaces -o yaml} for GitOps synchronization.
\item Employ Kustomize overlays to manage environment-specific variations.
\item Continuously monitor resource utilization through \texttt{kubectl top} and Docker Desktop metrics.
\end{itemize}
\subsection{Migration to Production Environments}
Owing to Kind's use of unmodified Kubernetes components, the transition of manifests
to production clusters requires only localized modifications, primarily concerning
persistent storage (replacement of \texttt{hostPath} with CSI volumes) and
ingress/load-balancing configuration.
\subsection{Managing Clusters with kind.sh}
For convenient management of multiple local clusters alongside Docker Desktop,
we provide a custom wrapper script \texttt{kind.sh}.
\begin{verbatim}
#!/bin/bash
set -euo pipefail
NAME="${2:-desktop}"
CLUSTER_NAME="$NAME"
create() {
echo "Creating KinD cluster: $CLUSTER_NAME"
kind create cluster --name "$CLUSTER_NAME" --wait 2m
kind get kubeconfig --name "$CLUSTER_NAME" > /tmp/kc.yaml
KUBECONFIG=~/.kube/config:/tmp/kc.yaml \
kubectl config view --flatten > ~/.kube/config.new
mv ~/.kube/config.new ~/.kube/config
rm -f /tmp/kc.yaml
echo "Cluster $CLUSTER_NAME created"
}
delete() {
echo "Deleting cluster: $CLUSTER_NAME"
kind delete cluster --name "$CLUSTER_NAME" 2>/dev/null || true
kubectl config delete-context "kind-$CLUSTER_NAME" 2>/dev/null || true
kubectl config delete-cluster "$CLUSTER_NAME" 2>/dev/null || true
kubectl config delete-user "$CLUSTER_NAME" 2>/dev/null || true
echo "Deleted $CLUSTER_NAME"
}
list() {
echo "=== KinD Clusters ==="
kind get clusters || echo "None"
echo ""
echo "=== Kubectl Contexts ==="
kubectl config get-contexts
}
case "${1:-list}" in
create) create ;;
delete) delete ;;
list) list ;;
*) echo "Usage: ./kind.sh <create|delete|list> [name]" ;;
esac
\end{verbatim}
\newpage
\section{Backup and Restore}
\subsection{Backup and Restore with Device-Level Snapshots}
A core mission of ERP/1 infrastructure is complete reproducibility and stateful resilience.
Local Kind clusters must support not only rapid iteration but also faithful simulation of
production disaster-recovery and data-migration workflows. The repository delivers this
through a set of purpose-built shell scripts that perform device-level filesystem
snapshots of all PersistentVolumeClaims (PVCs) and associated StatefulSets.
\begin{verbatim}
./backup.sh # Full device-level backup
./restore.sh <dir> # Targeted restore from backup
./view.sh <dir> # Inspect without extraction
\end{verbatim}
These tools operate directly against the Kind control-plane container (typically \texttt{synrc-control-plane}),
leveraging the \texttt{hostPath} volumes exposed by Docker Desktop/Kind for bit-exact data capture.
\subsubsection{Technical Architecture of backup.sh}
The backup process follows a rigorously safe, atomic sequence:
\begin{enumerate}
\item \textbf{Manifest Export \& Sanitization}
Exports \texttt{statefulset}, \texttt{deployment}, \texttt{pvc}, \texttt{service}, and \texttt{configmap}
resources across ERP/1 namespaces (\texttt{erp-infra}, \texttt{erp-telemetry}, \texttt{erp-security} etc.).
A Ruby filter \texttt{manifest.rb} strips transient metadata (UIDs, resourceVersions, creationTimestamps)
to produce clean, re-applicable YAML.
\item \textbf{Graceful Pod Quiescence}
Scales all StatefulSets and PVC-attached Deployments to 0 replicas.
This guarantees volumes are unmounted, eliminating filesystem inconsistency during snapshotting.
\item \textbf{Device-Level Snapshotting}
For each PVC:
\begin{itemize}
\item Resolves the backing \texttt{PersistentVolume} \texttt{hostPath}.
\item Executes \texttt{docker exec synrc-control-plane tar -czf - ...} directly inside the Kind node.
\item Stores compressed tarballs named \texttt{<namespace>@<pvc-name>.tar.gz}.
\end{itemize}
This produces portable, mountable filesystem images preserving ownership, permissions, extended attributes, and directory structure.
\item \textbf{Metadata \& Verification}
Generates \texttt{BACKUP\_INFO.txt} with timestamps,
PVC capacities, file counts, and sizes. Restarts pods to their original replica counts.
\end{enumerate}
Output resides in \texttt{./priv/YYYYMMDD-HHMMSS/} — a timestamped, self-contained backup bundle.
\subsubsection{Restore Semantics (restore.sh)}
Restore mirrors backup in reverse, with additional safety guarantees:
\begin{enumerate}
\item Apply sanitized manifests (force-conflicts to overwrite cleanly).
\item Wait for dynamic PVC provisioning and binding.
\item Scale down pods again.
\item For each backup tarball:
\begin{itemize}
\item Parse \texttt{namespace@pvc} naming convention.
\item Clear target \texttt{hostPath} directory.
\item Stream-extract with \texttt{tar -xzf --strip-components=1} directly into the volume.
\end{itemize}
\item Scale pods back up and provide verification (\texttt{kubectl get pvc,pods}).
\end{enumerate}
\subsubsection{Interactive Exploration via view.sh}
The companion viewer supports non-destructive inspection of backup archives.
Its syntax requires the path to a backup directory, followed by an action and target image:
\begin{itemize}
\item \textbf{Inspect Backup Summary}: Displays backup metadata and PVC list.
\begin{verbatim}
./view.sh ./priv/20260708-002817
\end{verbatim}
\item \textbf{List Files} (\texttt{list <img>}): Lists all files inside a device image tarball.
\begin{verbatim}
./view.sh ./priv/20260708-002817 \
list erp-telemetry@prometheus-data
\end{verbatim}
\item \textbf{Directory Tree} (\texttt{tree <img>}): Visualizes the directory structure.
\begin{verbatim}
./view.sh ./priv/20260708-002817 \
tree erp-telemetry@grafana-data
\end{verbatim}
\item \textbf{Peek File} (\texttt{cat <img> <file>}): Views a specific file's content without extracting the archive.
\begin{verbatim}
./view.sh ./priv/20260708-002817 \
cat erp-telemetry@prometheus-data queries.active
\end{verbatim}
\item \textbf{Targeted Extraction} (\texttt{extract <img> <out>}): Extracts a single volume snapshot to a target directory.
\begin{verbatim}
./view.sh ./priv/20260708-002817 \
extract erp-telemetry@grafana-data ./restored-grafana
\end{verbatim}
\end{itemize}
This capability is invaluable for debugging data corruption, auditing persisted state, or performing selective recovery.
\subsubsection{Mission Alignment \& Production Fidelity}
By operating at the raw device/filesystem layer (rather than application-level export),
these tools achieve near-perfect fidelity to bare-metal or cloud CSI-based backups.
They eliminate the semantic gap between development and production storage behavior —
a foundational requirement for ERP/1's "develop once, deploy anywhere" philosophy.
Combined with `kind.sh` cluster lifecycle management, they enable reproducible testing
of backup windows, restore drills, and data-migration scenarios with zero external dependencies.
Recommended workflow for ERP/1 developers:
\begin{verbatim}
./prereq.sh
./kind.sh create synrc
./kind.sh kind
./deploy.sh
./backup.sh
./delete-pvc.sh
./restore.sh ./priv/20260708-002817/
\end{verbatim}
\newpage
\section{Image Management}
Maintaining reproducible container images is fundamental to ERP/1's "develop once,
deploy anywhere" mission. The tooling supports two complementary strategies
that work seamlessly with Docker Desktop + Kind:
\begin{itemize}
\item \textbf{In-cluster Docker Registry} (production-like, via \texttt{docker-registry} service)
\item \textbf{Local Docker Builder} (fast, registry-free development workflow)
\end{itemize}
\subsection{Local Docker Desktop Builder (\texttt{images.sh --local})}
The script \texttt{images.sh --local} provides deep visibility into the Docker Desktop environment:
\begin{verbatim}
./images.sh --local
\end{verbatim}
It reports:
\begin{itemize}
\item Active \texttt{buildx} builders (including default Linux builder)
\item Local \texttt{erpuno/*} images in the Docker daemon
\item Images loaded into Kind nodes via \texttt{crictl}
\end{itemize}
This mode is ideal for developers who prefer building directly with Docker's native
builder (leveraging BuildKit) and loading images into Kind without pushing to a registry.
\textbf{Workflow for registry-free development:}
\begin{verbatim}
docker buildx build --load -t erpuno/my-service:latest ./path
kind load docker-image erpuno/my-service:latest --name erp1-local
\end{verbatim}
This approach offers maximum speed and simplicity for local iteration while remaining
fully reproducible across Linux/macOS/Windows via Docker Desktop.
\subsection{Building with In-Cluster Registry (\texttt{--registry})}
Component-level \texttt{build.sh} scripts (invoked via \texttt{rebuild.sh}) support
a \texttt{--registry} flag (or equivalent) to push images to the in-cluster \texttt{docker-registry:5000} service.
Typical pattern inside component \texttt{build.sh}:
\begin{verbatim}
REGISTRY="docker-registry.erp-infra.svc.cluster.local:5000"
docker buildx build --push --platform linux/amd64 \
-t ${REGISTRY}/erpuno/ca-pki:${TAG} .
\end{verbatim}
\textbf{Full rebuild cycle:}
\begin{verbatim}
./rebuild.sh # resets registry + rebuilds + deploys
./images.sh # inspect registry catalog
\end{verbatim}
The in-cluster registry (exposed on port 5000) mirrors production image distribution
practices and enables multi-node Kind clusters to pull images consistently.
\subsection{Best Practices for Reproducibility}
\begin{itemize}
\item Use \texttt{images.sh --local} for rapid development cycles on a single node.
\item Switch to \texttt{--registry} mode when testing multi-node scheduling or production image pull behavior.
\item Pin image tags and use \texttt{buildx} with explicit platforms (\texttt{linux/amd64}) for cross-environment consistency.
\item Combine with Kind's \texttt{--config} for custom containerd mirrors when needed.
\item Monitor builder state and registry health via Docker Desktop UI + \texttt{kubectl} port-forward.
\end{itemize}
This dual-path strategy eliminates friction for developers while preserving semantic
equivalence with production Kubernetes image management — a key pillar of ERP/1 operational excellence.
\newpage
\section{Git Operations}
Local validation of cloud-native systems requires not only a reproducible
runtime (Kubernetes via Kind) and container storage (docker-registry), but also a stable,
reproducible source of truth for dependencies and continuous delivery configuration.
To satisfy this requirement, ERP/1 adopts a dedicated approach of hosting a
lightweight Git server (Gitea) separately using Docker Compose.
\subsection{Architectural Approach: Out-of-Cluster Gitea}
Rather than running heavy git repository servers inside the development Kubernetes cluster,
Gitea is executed as a standalone Docker container managed by Docker Compose:
\begin{itemize}
\item \textbf{Decoupled Lifecycle}: The lifecycle of the code repository is completely
independent of the Kubernetes cluster. Destroying or rebuilding the Kind
cluster (e.g., via \texttt{kind.sh}) does not affect stored repositories,
commit histories, or local configurations.
\item \textbf{Resource Efficiency}: By running Gitea with a SQLite database backend,
the Git service starts in under 5 seconds and requires less than 100\,MB of RAM.
This represents a massive reduction in resource overhead compared to in-cluster
GitLab setups, leaving maximum system memory available for core ERP/1 workloads.
\item \textbf{Build-Time Resolution}: The Docker daemon on the host machine resolves
the Git host using Docker Desktop's native \texttt{host.docker.internal} resolver.
Build scripts pass this source via the \texttt{GIT\_SOURCE} build argument
during image creation.
\end{itemize}
\subsection{Motivation for Local Git Sources}
Hosting Git repositories locally on the developer's workstation solves two critical infrastructure challenges:
\begin{enumerate}
\item \textbf{Local Build Reproducibility}: Clones during Docker image
builds (e.g., for \texttt{ca-pki}, \texttt{ns-dns}, and \texttt{ldap-directory})
are pulled directly from the local Gitea instance rather than public GitHub endpoints.
This eliminates rate limiting issues, external network dependency, and the risk of
build failure due to repository drift or upstream deletion.
\item \textbf{ArgoCD and GitOps Readiness}: Formal GitOps controllers (like ArgoCD) require
continuous access to Git repositories to reconcile the actual state of the cluster
with the desired state declared in Git. By running Gitea locally,
developers can test entire GitOps push/sync loops, webhook integrations,
and ArgoCD application deployments completely offline with zero cloud costs
or complex port-forwarding setups.
\end{enumerate}
\subsection{Using the gitops.sh Script}
To manage Gitea and mirror repositories, the custom script \texttt{gitops.sh} is provided. It accepts the following commands:
\begin{itemize}
\item \texttt{setup} - Starts the Gitea container and provisions the admin user, the \texttt{synrc} organization, and empty repositories.
\item \texttt{migrate} - Clones source repositories from GitHub and pushes them as a mirror to Gitea.
\item \texttt{all} - Executes both \texttt{setup} and \texttt{migrate} sequentially.
\item \texttt{status} - Checks the Docker container status and queries the health of migrated Gitea repositories.
\item \texttt{stop} - Gracefully stops the Gitea container.
\end{itemize}
Examples of script execution:
\begin{verbatim}
./gitops.sh all # Perform full initialization and migration
./gitops.sh status # Verify container state and repo availability
./gitops.sh stop # Shut down local repository node
\end{verbatim}
\subsection{ArgoCD Local Integration}
To bootstrap continuous delivery in the local development environment,
ArgoCD is deployed directly inside the Kind cluster in its own namespace (\texttt{argocd})
The local integration proceeds in four steps, automated by the custom bootstrap script \texttt{argocd.sh}:
\begin{enumerate}
\item \textbf{Namespace Initialization}: Instantiates the \texttt{argocd} namespace.
\item \textbf{Manifest Deployment}: Pulls and applies the standard ArgoCD controller and server manifests.
\item \textbf{Gitea Code Mirroring}: Creates the \texttt{cd} repository inside the Gitea organization if not present,
and pushes the local configuration directory state as the GitOps source of truth.
\item \textbf{Application Mapping}: Configures the root ArgoCD Application to monitor
the Gitea repo's \texttt{helm/} path and automatically synchronize changes into the cluster.
\end{enumerate}
To route traffic to the ArgoCD web console, we define a Kubernetes Ingress resource mapping
the domain \texttt{argocd.erp-uno.local} to the \texttt{argocd-server} service,
utilizing the cluster's pre-configured Traefik Ingress controller:
\begin{verbatim}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
spec:
ingressClassName: traefik
rules:
- host: argocd.erp-uno.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
\end{verbatim}
Developers register \texttt{127.0.0.1 argocd.erp-uno.local} in the host resolver configuration to enable direct browser access.
If the UI is not accessible directly or if editing \texttt{/etc/hosts} is restricted,
port forwarding to the ArgoCD Server service serves as a fallback:
\begin{verbatim}
kubectl port-forward -n argocd svc/argocd-server 8080:80
\end{verbatim}
The Web UI can then be accessed via browser at \texttt{http://localhost:8080}.
* Username: admin
* Password: Rb2OTCWZpnzxMYK7
To make this port forwarding tunnel permanent on macOS (surviving restarts and running automatically in the background),
developers can install the provided LaunchAgent configuration:
\begin{verbatim}
# Copy plist to macOS LaunchAgents directory
cp argocd/uno.erp.argocd-portforward.plist ~/Library/LaunchAgents/
# Load the background service agent
launchctl load ~/Library/LaunchAgents/uno.erp.argocd-portforward.plist
\end{verbatim}
\subsubsection{Using the ArgoCD Command-Line Client}
Rather than interacting with the Web UI, developers can use the ArgoCD CLI tool. On macOS, the utility is installed via Homebrew:
\begin{verbatim}
brew install argocd
\end{verbatim}
Once the port forwarding tunnel is active, obtain the admin credentials and authenticate the CLI client:
\begin{verbatim}
# Retrieve the generated administrator password
PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d)
# Log in to the localhost server
argocd login localhost:8080 --insecure --username admin --password "$PASSWORD"
\end{verbatim}
With the CLI authenticated, applications can be inspected, updated, and synchronized directly from the terminal. Note that running any command without first executing the login step (or without specifying the server explicitly) will trigger an \texttt{"Argo CD server address unspecified"} fatal error. You can either authenticate first, or supply the server and security bypass flags on every command:
\begin{verbatim}
# Display details using explicit server parameters
argocd --server localhost:8080 --insecure app get erp-uno
# Trigger manual synchronization using explicit server parameters
argocd --server localhost:8080 --insecure app sync erp-uno
\end{verbatim}
If you have already authenticated using the \texttt{argocd login} command, the server context is saved locally, and you can run the commands directly:
\begin{verbatim}
# Display details and health of the application
argocd app get erp-uno
# Trigger a manual sync of all manifests
argocd app sync erp-uno
\end{verbatim}
\newpage
\section{Conclusion}
This paper has presented a deterministic framework for instantiating reproducible,
multi-node Kubernetes clusters on developer workstations using Kind, Docker Desktop,
and decoupled GitOps provisioning. By isolating source dependencies in an out-of-cluster
Gitea instance and utilizing device-level persistent volume claim snapshotting,
the proposed approach significantly reduces latency and minimizes the semantic
gap between development and production runtime behaviors.
While the empirical evaluation using the ERP/1 suite validates the viability of
the framework, several structural limitations must be acknowledged:
\begin{itemize}
\item \textbf{Storage Driver Discrepancy}: The local execution environment relies
on the \texttt{local-path-provisioner} storage class, which maps volumes to
host directories. This differs from production cloud environments utilizing
CSI-managed block storage (e.g., AWS EBS or GCP Persistent Disks), meaning
that complex volume locking and multi-attach behaviors are not fully modeled.
\item \textbf{Resource Constraints}: Local orchestration of Gitea, Docker registries,
and observability stacks (opensearch, prometheus) imposes a minimum physical
memory ceiling of 8\,GB (ideally 16\,GB) on host hardware. On resource-constrained
host machines, performance degradation or pod eviction may occur.
\item \textbf{Network Fidelity}: Host loopback routing and virtual networking
bridges (like Kindnet) do not simulate complex production firewall policies,
overlay networks (e.g., Calico or Cilium), or Cloud Provider LoadBalancers.
\end{itemize}
Future work will focus on integrating eBPF-based network auditing directly into the
Kind worker nodes to emulate production traffic restrictions, and implementing automated
security scanner hooks inside the local registry container. In conclusion, despite the
inherent hardware-virtualization abstraction boundaries, this framework provides a
highly cost-effective, reproducible, and robust paradigm for continuous local validation.
\end{document}