In Teil I haben wir uns mit Ende zu Ende Tests von Kubernetes Apps auf Gitlab-CI beschäftigt. Nach den letzten Erfahrungen auf gehärteten AWS Sysbox Runnern (siehe auch Sysbox) muss man sagen:
Es ist alles noch viel schlimmer
Wie im Teil 1 zu sehen, die Installation von K3D oder Kind-Cluster klappt zunächst, wenn man nicht ganz so aktuelle Docker DIND Versionen nimmt. In der Vierfachverschachtelung von Sysbox-VM, Docker Dind, Kind Node und Pod Netzwerk gibt es aber ein Problem: Die Verbindung vom Pod Netzwerk zum Cluster Netzwerk über die veth Bridge und eingerichteten DNAT vo kube-proxy, ist untersagt! Die typische Meldung ist “no route to host” und betrifft alle Pods, die ausgehende Netzwerkverbindungen aufbauen, sei es etwa zur Kubernetes API, um dort eine Configmap abzurufen. Deswegen scheitert die local-provisioner CSI Installation vom Kind Cluster und wir haben keinen Storage.
Der Hack ist die Streichung des vierten Levels und Verbindung solcher Pods über das HostNetwork:
kubectl -n local-path-storage patch deployment local-path-provisioner \
--type=json \
-p='[
{"op":"add","path":"/spec/template/spec/hostNetwork","value":true},
{"op":"add","path":"/spec/template/spec/dnsPolicy","value":"ClusterFirstWithHostNet"}
]'
Das geht. Der Pos startet mit HostNetwork, DNS beziehen wir auch von dort, um die Kubernetes API als Hostnamen auflösen zu können. Wir kriegen eine Verbindung, können die Configmap abrufen, das Ding läuft.
Genauso müssen wir es mit allen anderen Deployments handhaben, die ähnliche Probleme haben, etwa auch in unserer App. Ich weiss nicht, ob das dann noch ein valider Ende zu Ende Test wäre.
Ausserdem: Das funktioniert nur bis Kind v0.30.0 (Kubernetes 1.34). Bei v0.31.0 (Kubernetes 1.35), crasht das Ding total beim Installieren unserer Helm App:
5s Warning FailedCreatePodSandBox pod/my-app-74c4d4f4c-8fj6w Failed to create pod sandbox: rpc error: code = Unknown desc = failed to start sandbox "0be3636d18f46927397dcd6238ace8570ebbade55c0992e32e2f913bf5e94de1": failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open sysctl net.ipv4.ip_unprivileged_port_start file: unsafe procfs detected: openat2 /proc/./sys/net/ipv4/ip_unprivileged_port_start: invalid cross-device link
Also man könnte es mit Hacks noch eine Weile nutzen, aber gerade in den Ende zu Ende Tests will man ja neue Kubernetes Versionen testen: 1.35, 1.36, 1.37 … unbrauchbar, in dem Zusammenhang.
Auch andere CNI Treiber brachten keinen Erfolg (getestet mit Cilium eBPF, Calico, kube-proxy ipvs, nftables)
Intressant ist auch die Issue Liste auf Github. Dort gibt es auch immer wieder Probleme mit neueren Kubernetes-Versionen und es wird auch schon mal nach dem Projektstatus gefragt, da das letzte Release mehr als ein halbes Jahr zurückliegt.
Besonders tragisch für die Leute, die von Kaniko zu Sysbox gewechselt sind, um ein besseres Leben in CI/CD zu haben.
Zwei weitere Sachen, die NICHT funktionieren, auch nicht mit priveligierten eigenen Runner mit Host-Pid mount.
k3s als sidecar service:
k3s_sidecar:
stage: k3s
services:
- name: docker.io/rancher/k3s:v1.29.1-k3s1
alias: k3s
command:
- server
- --cluster-init
- --disable=traefik
- --disable=servicelb
- --disable-network-policy
- --flannel-backend=none
- --tls-san=k3s
k3s rootless:
k3s_rootless:
stage: k3s
services:
- name: docker.io/rancher/k3s:v1.29.1-k3s1
alias: k3s
command:
- server
- --rootless
- --cluster-init
- --disable=traefik
- --disable=servicelb
- --disable-network-policy
- --flannel-backend=none
- --tls-san=k3s
Die Ursache liegt im cgroup mount. Mit Host Pid mount, kriegt man diesen zwar in den Runner:
2026-02-15T16:39:00.494458Z 01O 0::/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pode80cf426_b ║ ║
║ ║ c4d_4e25_ab0e_9f7ad4276746.slice/cri-containerd-e4a4116b393ae98ac3943bf080a779c8d92b9967bda46afdab2386631f1276 ║ ║
║ ║ 16.scope
Aber es ist nur der Mount vom Pod, in dem der Runner läuft. Um den Kubernetes Server zu starten, bräuchte es
0::/
was quasi vollen Node-Zugriff bedeutet, also könnte man es gleich auf einer VM starten, also ein Shell-Runner.
Brauch man gar nicht probieren, es setzt auf die gleiche Logik auf, brauch privilegierten Zugriff und ist so für die meisten Umgebungen ungeeignet.
Damit haben wir auch alle Möglichkeiten ausgeschöpft. Es gibt keine brauchbare Lösungen, um Kubernetes in der Pipeline zu betreiben. Dort wo es noch funktioniert, ist es ein aussterbendes Feature. Also wie geht es weiter?
Kwok ist ein Simulator. der so tut, als wäre er ein Kubernetes Cluster. Wenn man also keinen in der Gitlabä-Ci-Pipeline installieren kann, muss man wenigstens so tun als ob:
Wir können hier mit einem Ubuntu Container Image in einem Pipeline Stage anfangen und ein paar Sachen installieren:
```yaml
deploy-kwok:
stage: deploy
variables:
# Version pinning for stability
K8S_VERSION: "v1.34.3"
KWOK_REPO: "kubernetes-sigs/kwok"
KWOK_VERSION: "v0.7.0"
ETCD_VERSION: "v3.5.9"
HELM_VERSION: "v4.1.0"
# KWOK Configuration
KWOK_KUBE_CONFIG: /tmp/kubeconfig
KUBECONFIG: /tmp/kubeconfig
image: dockerhub.devops.telekom.de/ubuntu:24.04
tags:
- aws_run_sysbox
parallel:
matrix:
- K8S_VERSION: ["v1.34.3", "v1.35.0"]
before_script:
# 1. Isolate from Host Cluster (Critical for Sysbox/Runner environments)
- unset KUBERNETES_SERVICE_HOST
- unset KUBERNETES_SERVICE_PORT
- echo ${K8S_VERSION}
# 2. Install System Dependencies
- apt-get update && apt-get install -y curl ca-certificates git socat jq
# 3. Install KWOK (Kubernetes WithOut Kubelet)
- curl -L -o /usr/local/bin/kwokctl "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwokctl-linux-amd64"
- curl -L -o /usr/local/bin/kwok "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwok-linux-amd64"
- chmod +x /usr/local/bin/kwokctl /usr/local/bin/kwok
# 4. Install kubectl
- curl -LO "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl"
- chmod +x kubectl && mv kubectl /usr/local/bin/
# 5. Install Kubernetes Control Plane Binaries (API, Controller, Scheduler)
# KWOK uses these to simulate a full cluster in user-mode
- curl -L -o /usr/local/bin/kube-apiserver https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-apiserver
- curl -L -o /usr/local/bin/kube-controller-manager https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-controller-manager
- curl -L -o /usr/local/bin/kube-scheduler https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-scheduler
- chmod +x /usr/local/bin/kube-apiserver /usr/local/bin/kube-controller-manager /usr/local/bin/kube-scheduler
# 6. Install Etcd (Required for Binary Runtime)
- curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz -o etcd.tar.gz
- tar xzf etcd.tar.gz && mv etcd-${ETCD_VERSION}-linux-amd64/etcd /usr/local/bin/ && rm -rf etcd*
# 7. Install Helm
- curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
script:
- echo "=== Starting Fake Kubernetes Cluster (KWOK) ==="
# Define Stage Policy: Simulate Pods as Running immediately
- |
cat <<EOF > kwok.yaml
apiVersion: kwok.x-k8s.io/v1alpha1
kind: Stage
metadata:
name: fast-forward
spec:
resourceRef:
apiGroup: v1
kind: Pod
selector:
matchExpressions:
- key: .metadata.deletionTimestamp
operator: DoesNotExist
next:
statusTemplate: |
phase: Running
conditions:
- type: Ready
status: "True"
lastProbeTime: null
lastTransitionTime:
EOF
# Start Cluster in Binary Mode (No Docker required!)
- kwokctl create cluster --runtime binary --kubeconfig $KWOK_KUBE_CONFIG --config kwok.yaml
- echo "=== Configuring Cluster ==="
# Create a Fake Node (Required for Scheduling)
- |
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Node
metadata:
name: kwok-node-0
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: kwok-node-0
kubernetes.io/os: linux
kubernetes.io/role: agent
node-role.kubernetes.io/agent: ""
type: kwok
annotations:
node.alpha.kubernetes.io/ttl: "0"
kwok.x-k8s.io/node: fake
status:
allocatable:
cpu: 32
memory: 256Gi
pods: 110
capacity:
cpu: 32
memory: 256Gi
pods: 110
conditions:
- lastHeartbeatTime: "2023-01-01T00:00:00Z"
lastTransitionTime: "2023-01-01T00:00:00Z"
message: kubelet is posting ready status
reason: KubeletReady
status: "True"
status: "True"
type: Ready
EOF
# Ensure Stage CRD is present (Fallback for older KWOK versions)
- kubectl apply -f "https://github.com/kubernetes-sigs/kwok/raw/main/kustomize/crd/bases/kwok.x-k8s.io_stages.yaml" || true
- kubectl apply -f kwok.yaml || true
- echo "=== Cluster Ready ==="
- kubectl get nodes
# EXAMPLE: Installing other controllers (e.g. Ingress, Prometheus)
# If your chart needs CRDs (e.g. ServiceMonitor), install them here:
# - kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.68.0/bundle.yaml
# If you need an Ingress Controller fake:
# - kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# (Note: Ingress pods will stay Pending/Running fake, but API resources will be accepted)
- echo "=== Running E2E Test ==="
- helm install e2e-test charts/my-app --wait --timeout 60s || { kubectl get pods; kubectl get events; exit 1; }
- echo "=== Verification ==="
- kubectl get all -A
- kubectl describe pod -l app=my-app
```Ganz schön viel Zeug. Im Detail installieren und starten wir kwok. Das würde ein Kubernetes Cluster ohne alles sein. Um mit dem sprechen zu können, brauchen wir das API-Server Binary. Damit der sowas wie Deployment und ReplicaSet versteht, brauchen wir Controller Binary. Für andere Features an der Stelle natürlich auch noch mehr. Damit wir die Pods auch irgendwo schedulen können, brauchen wir Fake Nodes, die wir natürlich auch noch definieren müssen. Letztlich dann der Lebenszyklus des Pods. Damit der von Creating, Pending nach Running kommt, brauchen wir noch Stages, die wir im Kwok definieren müssen. Und so sieht das dann aus:
=== Cluster Ready ===
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kwok-node-0 Ready agent 2s
$ echo "=== Running E2E Test ==="
=== Running E2E Test ===
$ helm install e2e-test charts/my-app --wait --timeout 60s || { kubectl get pods; kubectl get events; exit 1; }
NAME: e2e-test
LAST DEPLOYED: Sat Feb 14 22:37:09 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
$ echo "=== Verification ==="
=== Verification ===
$ kubectl get all -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default pod/my-app-757664f475-znl7b 0/1 Running 0 2s
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default service/kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 3s
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
default deployment.apps/my-app 1/1 1 1 2s
NAMESPACE NAME DESIRED CURRENT READY AGE
default replicaset.apps/my-app-757664f475 1 1 1 2s
$ kubectl describe pod -l app=my-app
Name: my-app-757664f475-znl7b
Namespace: default
Priority: 0
Service Account: default
Node: kwok-node-0/
Labels: app=my-app
pod-template-hash=757664f475
Annotations: <none>
Status: Running
IP:
IPs: <none>
Controlled By: ReplicaSet/my-app-757664f475
Containers:
nginx:
Image: dockerhub.devops.telekom.de/nginx:1.14.2
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-4db5g (ro)
Conditions:
Type Status
Ready True
PodScheduled True
Volumes:
kube-api-access-4db5g:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2s default-scheduler Successfully assigned default/my-app-757664f475-znl7b to kwok-node-0
Cleaning up project directory and file based variables
Job succeeded
Ja, okay, es läuft, auch mit Kubernetes 1.35. Und da es ein Kubernetes Sig Projekt ist, wird es auch mit zukünftigen Versionen noch laufen. Dennoch bleiben meine Zweifel, ob das der richtige Weg ist.
Man kann auch Kwok weglassen und Kubernetes Vanilla mit den notwendigen Binaries installieren. Hier nochmal ein volles Beispiel mit Versions-Matrix für 4 Kubernetes-Versionen:
```yaml
deploy-kwok:
stage: deploy
variables:
# Version pinning for stability
KWOK_REPO: "kubernetes-sigs/kwok"
KWOK_VERSION: "v0.7.0"
ETCD_VERSION: "v3.6.8"
HELM_VERSION: "v4.1.0"
# KWOK Configuration
KWOK_KUBE_CONFIG: /tmp/kubeconfig
KUBECONFIG: /tmp/kubeconfig
image: docker.io/ubuntu:24.04
tags:
- aws_run_sysbox
parallel:
matrix:
- K8S_VERSION: ["v1.32.12", "v1.33.8", "v1.34.3", "v1.35.1"]
before_script:
# 1. Isolate from Host Cluster (Critical for Sysbox/Runner environments)
- unset KUBERNETES_SERVICE_HOST
- unset KUBERNETES_SERVICE_PORT
- echo ${K8S_VERSION}
# 2. Install System Dependencies
- apt-get update && apt-get install -y curl ca-certificates git socat jq
# 3. Install KWOK (Kubernetes WithOut Kubelet)
- curl -L -o /usr/local/bin/kwokctl "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwokctl-linux-amd64"
- curl -L -o /usr/local/bin/kwok "https://github.com/kubernetes-sigs/kwok/releases/download/${KWOK_VERSION}/kwok-linux-amd64"
- chmod +x /usr/local/bin/kwokctl /usr/local/bin/kwok
# 4. Install kubectl
- curl -LO "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl"
- chmod +x kubectl && mv kubectl /usr/local/bin/
# 5. Install Kubernetes Control Plane Binaries (API, Controller, Scheduler)
# KWOK uses these to simulate a full cluster in user-mode
- curl -L -o /usr/local/bin/kube-apiserver https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-apiserver
- curl -L -o /usr/local/bin/kube-controller-manager https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-controller-manager
- curl -L -o /usr/local/bin/kube-scheduler https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-scheduler
- chmod +x /usr/local/bin/kube-apiserver /usr/local/bin/kube-controller-manager /usr/local/bin/kube-scheduler
# 6. Install Etcd (Required for Binary Runtime)
- curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz -o etcd.tar.gz
- tar xzf etcd.tar.gz && mv etcd-${ETCD_VERSION}-linux-amd64/etcd /usr/local/bin/ && rm -rf etcd*
# 7. Install Helm
- curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
script:
- echo "=== Starting Fake Kubernetes Cluster (KWOK/${K8S_VERSION}) ==="
# Define Stage Policy: Simulate Pods as Running immediately
- |
cat <<EOF > kwok.yaml
apiVersion: kwok.x-k8s.io/v1alpha1
kind: Stage
metadata:
name: fast-forward
spec:
resourceRef:
apiGroup: v1
kind: Pod
selector:
matchExpressions:
- key: .metadata.deletionTimestamp
operator: DoesNotExist
next:
statusTemplate: |
phase: Running
conditions:
- type: Ready
status: "True"
lastProbeTime: null
lastTransitionTime:
EOF
# Start Cluster in Binary Mode (No Docker required!)
- kwokctl create cluster --runtime binary --kubeconfig $KWOK_KUBE_CONFIG --config kwok.yaml
- echo "=== Configuring Cluster ==="
# Create a Fake Node (Required for Scheduling)
- |
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Node
metadata:
name: kwok-node-0
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: kwok-node-0
kubernetes.io/os: linux
kubernetes.io/role: agent
node-role.kubernetes.io/agent: ""
type: kwok
annotations:
node.alpha.kubernetes.io/ttl: "0"
kwok.x-k8s.io/node: fake
status:
allocatable:
cpu: 32
memory: 256Gi
pods: 110
capacity:
cpu: 32
memory: 256Gi
pods: 110
conditions:
- lastHeartbeatTime: "2023-01-01T00:00:00Z"
lastTransitionTime: "2023-01-01T00:00:00Z"
message: kubelet is posting ready status
reason: KubeletReady
status: "True"
type: Ready
EOF
# Ensure Stage CRD is present (Fallback for older KWOK versions)
- kubectl apply -f "https://github.com/kubernetes-sigs/kwok/raw/main/kustomize/crd/bases/kwok.x-k8s.io_stages.yaml" || true
- kubectl apply -f kwok.yaml || true
- echo "=== Cluster Ready ==="
- kubectl get nodes
# EXAMPLE: Installing other controllers (e.g. Ingress, Prometheus)
# If your chart needs CRDs (e.g. ServiceMonitor), install them here:
# - kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.68.0/bundle.yaml
# If you need an Ingress Controller fake:
# - kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# (Note: Ingress pods will stay Pending/Running fake, but API resources will be accepted)
- echo "=== Running E2E Test ==="
- helm install e2e-test charts/my-app --wait --timeout 60s || { kubectl get pods; kubectl get events; exit 1; }
- echo "=== Verification ==="
- kubectl get all -A
- kubectl describe pod -l app=my-app
deploy-vanilla:
stage: deploy
image: docker.io/ubuntu:24.04
tags:
- aws_run_sysbox
parallel:
matrix:
- K8S_VERSION: ["v1.32.12", "v1.33.8", "v1.34.3", "v1.35.1"]
variables:
ETCD_VERSION: "v3.6.8"
KUBECONFIG: /tmp/kubeconfig
before_script:
- unset KUBERNETES_SERVICE_HOST
- unset KUBERNETES_SERVICE_PORT
- apt-get update && apt-get install -y curl ca-certificates git socat jq
# Install kubectl & k8s binaries
- curl -LO "https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kubectl"
- chmod +x kubectl && mv kubectl /usr/local/bin/
- curl -L -o /usr/local/bin/kube-apiserver https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-apiserver
- curl -L -o /usr/local/bin/kube-controller-manager https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-controller-manager
- curl -L -o /usr/local/bin/kube-scheduler https://dl.k8s.io/release/${K8S_VERSION}/bin/linux/amd64/kube-scheduler
- chmod +x /usr/local/bin/kube-apiserver /usr/local/bin/kube-controller-manager /usr/local/bin/kube-scheduler
# Install etcd
- curl -L https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz -o etcd.tar.gz
- tar xzf etcd.tar.gz --no-same-owner
- mv etcd-${ETCD_VERSION}-linux-amd64/etcd /usr/local/bin/
- rm -rf etcd*
# Install Helm
- curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
script:
- echo "=== Starting Vanilla Kubernetes Control Plane (${K8S_VERSION}) ==="
- ls -la /usr/local/bin/
# 1. Start Etcd
- mkdir -p /tmp/etcd-data
- nohup /usr/local/bin/etcd --data-dir /tmp/etcd-data --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 > /tmp/etcd.log 2>&1 &
- echo "Wait on Etcd..."
- |
timeout 30s bash -c 'until curl -s http://127.0.0.1:2379/health; do echo "Warte..."; sleep 1; done' || (cat /tmp/etcd.log && exit 1)
# 2. Start API Server
# Generate CA and Client Certs properly
- mkdir -p /tmp/certs
# 2.1 CA Authority
- openssl genrsa -out /tmp/certs/ca.key 2048
- openssl req -x509 -new -nodes -key /tmp/certs/ca.key -subj "/CN=kubernetes-ca" -days 365 -out /tmp/certs/ca.crt
# 2.2 Service Account Key (for signing tokens)
- openssl genrsa -out /tmp/certs/service-account.key 2048
- openssl req -new -key /tmp/certs/service-account.key -out /tmp/certs/service-account.csr -subj "/CN=service-account"
- openssl x509 -req -in /tmp/certs/service-account.csr -CA /tmp/certs/ca.crt -CAkey /tmp/certs/ca.key -CAcreateserial -out /tmp/certs/service-account.crt -days 365
# 2.3 Admin User (CN=admin, O=system:masters)
- openssl genrsa -out /tmp/certs/admin.key 2048
- openssl req -new -key /tmp/certs/admin.key -out /tmp/certs/admin.csr -subj "/CN=admin/O=system:masters"
- openssl x509 -req -in /tmp/certs/admin.csr -CA /tmp/certs/ca.crt -CAkey /tmp/certs/ca.key -CAcreateserial -out /tmp/certs/admin.crt -days 365
# 2.4 API Server Start (One-Liner for robustness)
- nohup /usr/local/bin/kube-apiserver --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/16 --cert-dir=/tmp/certs --secure-port=6443 --bind-address=127.0.0.1 --service-account-key-file=/tmp/certs/service-account.key --service-account-signing-key-file=/tmp/certs/service-account.key --service-account-issuer=https://kubernetes.default.svc.cluster.local --client-ca-file=/tmp/certs/ca.crt --disable-admission-plugins=ServiceAccount,NamespaceLifecycle,LimitRanger,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --authorization-mode=Node,RBAC > /tmp/apiserver.log 2>&1 &
- echo "Wait on API Server..."
- |
timeout 30s bash -c 'until curl -k -s https://127.0.0.1:6443/version; do echo "Warte..."; sleep 1; done' || (cat /tmp/apiserver.log && exit 1)
# 2.5 Configure kubectl with Admin Cert
- |
/usr/local/bin/kubectl config set-cluster local --server=https://127.0.0.1:6443 --insecure-skip-tls-verify=true
/usr/local/bin/kubectl config set-credentials admin --client-certificate=/tmp/certs/admin.crt --client-key=/tmp/certs/admin.key
/usr/local/bin/kubectl config set-context local --cluster=local --user=admin
/usr/local/bin/kubectl config use-context local
# 3. Start Controller Manager (creates Pods from Deployments)
- nohup /usr/local/bin/kube-controller-manager --master=https://127.0.0.1:6443 --kubeconfig=/tmp/kubeconfig --service-account-private-key-file=/tmp/certs/service-account.key --root-ca-file=/tmp/certs/service-account.crt > /tmp/controller.log 2>&1 &
# 4. Start Scheduler (assigns Pods to Nodes)
- nohup /usr/local/bin/kube-scheduler --master=https://127.0.0.1:6443 --kubeconfig=/tmp/kubeconfig > /tmp/scheduler.log 2>&1 &
# 5. Start Fake Kubelet (Bash Loop)
- echo "Starting Fake Kubelet..."
- |
(
# Create Node Object
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Node
metadata:
name: fake-node-0
labels:
kubernetes.io/hostname: fake-node-0
kubernetes.io/role: agent
status:
allocatable:
cpu: "32"
memory: "256Gi"
pods: "110"
capacity:
cpu: "32"
memory: "256Gi"
pods: "110"
conditions:
- type: Ready
status: "True"
lastHeartbeatTime: "$(date -u +%FT%TZ)"
lastTransitionTime: "$(date -u +%FT%TZ)"
message: "Fake Kubelet is ready"
reason: "KubeletReady"
EOF
while true; do
# Node Heartbeat (Keep it Ready)
kubectl patch node fake-node-0 --subresource=status --type=merge -p "{\"status\":{\"conditions\":[{\"type\":\"Ready\",\"status\":\"True\",\"lastHeartbeatTime\":\"$(date -u +%FT%TZ)\"}]}}" >/dev/null 2>&1
# Pod Lifecycle (Pending -> Running)
# Find all pods on our node (or pending pods) and set them to Running
# Note: We use -o json and jq to be robust
kubectl get pods --all-namespaces --field-selector=status.phase=Pending -o json | jq -r '.items[] | .metadata.name + " " + .metadata.namespace' | while read name namespace; do
if [ ! -z "$name" ]; then
echo "Fake Kubelet: Starting pod $name in $namespace..."
# Patch status to Running and Ready
kubectl patch pod $name -n $namespace --subresource=status --type=merge -p "{\"status\":{\"phase\":\"Running\",\"conditions\":[{\"type\":\"Ready\",\"status\":\"True\"}],\"containerStatuses\":[{\"name\":\"nginx\",\"ready\":true,\"state\":{\"running\":{\"startedAt\":\"$(date -u +%FT%TZ)\"}}}]}}" >/dev/null 2>&1
fi
done
sleep 2
done
) &
FAKE_PID=$!
- echo "=== Cluster Ready ==="
- kubectl get nodes
- echo "=== Running E2E Test ==="
- helm install e2e-test charts/my-app --wait --timeout 60s
- echo "=== Verification ==="
- kubectl get all -A
- kill $FAKE_PID
```Am Ende vom zweiten Teil bleibt alles nur ein grosser Fake. Mit Kwok/Vanilla kann ich zwar Grundfunktionalitäten testen, wie Pod Starts oder CRD Deployment, bloss was passiert, wenn ein Pod einen Storage braucht? Genau, nichts, ist ja alles nur ein Fake, es gibt ja nicht mal eine StorageClass.
Was als Lösung bleibt, ist also tatsächlich nur ein externer Kubernetes-Cluster, sei es als Kind in einer VM, oder ein dedizierter Cluster, der speziell dieser Gitlab-Pipeline zugeordnet ist und von dieser immer neu bestückt wird.
Wenn das netztechnisch nicht geht, muss man das Gitrepo im Cluster einbinden, etwa über Flux, und dann spezielle Tags oder Branches ausrollen. Dazu vielleicht mehr im dritten Teil.