Organize zero-scale-platform project with standard layout, go.mod, Makefile, deployment manifests, CLI, and docs
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
gateway
|
||||
mock-app
|
||||
/gateway
|
||||
/mock-app
|
||||
|
||||
27
Makefile
Normal file
27
Makefile
Normal file
@@ -0,0 +1,27 @@
|
||||
IMAGE ?= localhost:32000/systemd-runner:latest
|
||||
NAMESPACE ?= default
|
||||
|
||||
.PHONY: all build image push deploy restart clean
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o gateway ./cmd/gateway
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o mock-app ./cmd/mock-app
|
||||
|
||||
image: build
|
||||
docker build -t $(IMAGE) -f Dockerfile.systemd .
|
||||
|
||||
push: image
|
||||
docker push $(IMAGE)
|
||||
|
||||
deploy:
|
||||
kubectl apply -f deploy/deployment.yaml -n $(NAMESPACE)
|
||||
kubectl apply -f deploy/service.yaml -n $(NAMESPACE)
|
||||
kubectl apply -f deploy/ingress.yaml -n $(NAMESPACE)
|
||||
|
||||
restart:
|
||||
kubectl rollout restart deployment/zero-scale-gateway -n $(NAMESPACE)
|
||||
|
||||
clean:
|
||||
rm -f gateway mock-app
|
||||
106
README.md
Normal file
106
README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Zero-Scale Platform
|
||||
|
||||
A lightweight, high-density serverless execution runtime and dynamic reverse-proxy for Kubernetes.
|
||||
|
||||
Rather than maintaining dedicated Pods and container runtimes for infrequently accessed services, the **Zero-Scale Platform** multiplexes stateless and microservice binaries inside a single sandboxed container using transient `systemd-run` execution units.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Client[HTTP Client] -->|1. Incoming Request :80| Ingress[Kubernetes Ingress]
|
||||
Ingress -->|2. Route Host Header| Gateway[Gateway Reverse Proxy]
|
||||
|
||||
subgraph "Zero-Scale Gateway Pod (systemd-runner)"
|
||||
Gateway -->|3. Check /opt/apps/<host>| Volume[(Longhorn Binaries Volume)]
|
||||
Gateway -->|4. Fetch DB Secret & ConfigMap| K8sAPI[Kubernetes InCluster API]
|
||||
Gateway -->|5. systemd-run with dynamic PORT & DB env| Systemd[systemd PID 1]
|
||||
Systemd -->|6. Spawns sandboxed transient service| App[Dynamic App Process]
|
||||
Gateway -->|7. Reverse Proxy to 127.0.0.1:PORT| App
|
||||
Reaper[Idle Reaper Timer] -->|8. Idle > 15m: systemctl stop| App
|
||||
end
|
||||
```
|
||||
|
||||
### Core Features
|
||||
1. **True Scale-to-Zero**: Idle apps consume **zero memory and zero CPU**.
|
||||
2. **Sub-Second Cold Starts**: Because apps launch as local native Linux processes via `systemd-run`, cold starts complete in **~500 ms** (warm requests respond in **~50–80 ms**).
|
||||
3. **Dynamic Credential Injection**: Automatically reads Kubernetes `ConfigMap` and `Secret` resources to pass database credentials (`DATABASE_HOST`, `DATABASE_USER`, `DATABASE_PASSWORD`, etc.) to the binary on startup.
|
||||
4. **Storage Integration**: Seamlessly bind-mounts Longhorn shared RWX volumes (e.g. `/app/uploads`) into the sandboxed process.
|
||||
5. **Automated Idle Reaper**: Monitors application activity; after 15 minutes of inactivity, the gateway terminates the process and reclaims the ephemeral port.
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
zero-scale-platform/
|
||||
├── Makefile # Standardized build, image, and deploy commands
|
||||
├── go.mod # Go module definition
|
||||
├── Dockerfile.systemd # Container image (Debian Bookworm + systemd + wasmtime)
|
||||
├── cmd/
|
||||
│ ├── gateway/
|
||||
│ │ └── main.go # The core zero-scale proxy daemon
|
||||
│ └── mock-app/
|
||||
│ └── main.go # Reference mock HTTP service
|
||||
├── deploy/ # Kubernetes manifests
|
||||
│ ├── deployment.yaml # Gateway Deployment (replaces manual pods)
|
||||
│ ├── service.yaml # Gateway ClusterIP Service (:80)
|
||||
│ └── ingress.yaml # Ingress routing configuration
|
||||
├── scripts/
|
||||
│ └── zero-scale-ctl.sh # CLI tool for deploying and monitoring apps
|
||||
└── services/ # Systemd service unit templates
|
||||
├── gateway.service
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Management CLI (`zero-scale-ctl.sh`)
|
||||
|
||||
The helper script in `scripts/zero-scale-ctl.sh` simplifies managing zero-scale applications:
|
||||
|
||||
### Deploy an Application
|
||||
```bash
|
||||
./scripts/zero-scale-ctl.sh deploy-app \
|
||||
--name bdash2 \
|
||||
--host bdash2.kube.fairfaxmedia.net \
|
||||
--url http://minio.minio-operator/binaries/bdash-x86_64 \
|
||||
--db \
|
||||
--mount /app/uploads
|
||||
```
|
||||
|
||||
### List Configured Applications
|
||||
```bash
|
||||
./scripts/zero-scale-ctl.sh list-apps
|
||||
```
|
||||
|
||||
### Check Application Status & Logs
|
||||
```bash
|
||||
# Check systemd status inside the gateway
|
||||
./scripts/zero-scale-ctl.sh status bdash2.kube.fairfaxmedia.net
|
||||
|
||||
# Tail systemd journal logs
|
||||
./scripts/zero-scale-ctl.sh logs bdash2.kube.fairfaxmedia.net
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building and Deploying
|
||||
|
||||
### 1. Compile Binaries
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
### 2. Build and Push Container Image
|
||||
```bash
|
||||
make image
|
||||
make push
|
||||
```
|
||||
|
||||
### 3. Deploy to Kubernetes
|
||||
```bash
|
||||
make deploy
|
||||
```
|
||||
56
deploy/deployment.yaml
Normal file
56
deploy/deployment.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
labels:
|
||||
app: zero-scale-gateway
|
||||
app.kubernetes.io/name: zero-scale-gateway
|
||||
app.kubernetes.io/part-of: zero-scale-platform
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: zero-scale-gateway
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: zero-scale-gateway
|
||||
app.kubernetes.io/name: zero-scale-gateway
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: didi-macbookpro14-3
|
||||
containers:
|
||||
- name: systemd-runner
|
||||
image: localhost:32000/systemd-runner:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- mountPath: /run
|
||||
name: run
|
||||
- mountPath: /run/lock
|
||||
name: run-lock
|
||||
- mountPath: /opt/apps
|
||||
name: apps-volume
|
||||
- mountPath: /app/uploads
|
||||
name: bdash-uploads
|
||||
volumes:
|
||||
- name: run
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
- name: run-lock
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
- name: apps-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: zero-scale-binaries-pvc
|
||||
- name: bdash-uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: builds-dash-uploads-longhorn-rwx
|
||||
45
deploy/ingress.yaml
Normal file
45
deploy/ingress.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
labels:
|
||||
app: zero-scale-gateway
|
||||
app.kubernetes.io/name: zero-scale-gateway
|
||||
app.kubernetes.io/part-of: zero-scale-platform
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
spec:
|
||||
ingressClassName: public
|
||||
rules:
|
||||
- host: bdash2.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
- host: bdash2.kube.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
- host: hopscotch.kube.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
18
deploy/service.yaml
Normal file
18
deploy/service.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
labels:
|
||||
app: zero-scale-gateway
|
||||
app.kubernetes.io/name: zero-scale-gateway
|
||||
app.kubernetes.io/part-of: zero-scale-platform
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: zero-scale-gateway
|
||||
3
go.mod
Normal file
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module git.fairfaxmedia.net/open-cream-cheese/zero-scale-platform
|
||||
|
||||
go 1.26
|
||||
155
scripts/zero-scale-ctl.sh
Executable file
155
scripts/zero-scale-ctl.sh
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
NAMESPACE="${NAMESPACE:-default}"
|
||||
GATEWAY_POD_LABEL="app.kubernetes.io/name=zero-scale-platform"
|
||||
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
Zero-Scale Platform Management CLI
|
||||
|
||||
Usage:
|
||||
$0 [command] [options]
|
||||
|
||||
Commands:
|
||||
deploy-app Deploy or update a zero-scale application
|
||||
list-apps List all configured zero-scale applications
|
||||
delete-app <name> Delete an application ConfigMap
|
||||
status <host> Check the systemd status of an app inside the gateway
|
||||
logs <host> View the systemd journal logs of an app inside the gateway
|
||||
install-chart Install or upgrade the full Helm chart
|
||||
|
||||
Options for deploy-app:
|
||||
--name <name> Application identifier (e.g., bdash2) [Required]
|
||||
--host <host> Hostname to route (e.g., bdash2.kube.fairfaxmedia.net) [Required]
|
||||
--url <url> Binary download URL (S3 / MinIO / HTTP) [Required]
|
||||
--type <elf|wasm> Runtime type (default: elf)
|
||||
--db Enable automated PostgreSQL schema provisioning
|
||||
--mount <path> Container directory to bind-mount from Longhorn (e.g. /app/uploads)
|
||||
--no-scale-to-zero Keep application running warm permanently
|
||||
|
||||
Examples:
|
||||
# Deploy a new application
|
||||
$0 deploy-app --name bdash2 --host bdash2.kube.fairfaxmedia.net --url http://minio.minio-operator/binaries/bdash-x86_64 --db --mount /app/uploads
|
||||
|
||||
# List active apps
|
||||
$0 list-apps
|
||||
|
||||
# Check logs of a running systemd unit
|
||||
$0 logs bdash2.kube.fairfaxmedia.net
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
function get_gateway_pod() {
|
||||
local pod
|
||||
pod=$(kubectl get pods -n "$NAMESPACE" -l "$GATEWAY_POD_LABEL" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
if [ -z "$pod" ]; then
|
||||
pod=$(kubectl get pods -n "$NAMESPACE" -l app=zero-scale-gateway -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$pod" ]; then
|
||||
echo "Error: No running zero-scale gateway pod found in namespace $NAMESPACE" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$pod"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
deploy-app)
|
||||
shift
|
||||
NAME=""
|
||||
HOST=""
|
||||
URL=""
|
||||
TYPE="elf"
|
||||
DB="false"
|
||||
MOUNTS=""
|
||||
SCALE_TO_ZERO="true"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) NAME="$2"; shift 2 ;;
|
||||
--host) HOST="$2"; shift 2 ;;
|
||||
--url) URL="$2"; shift 2 ;;
|
||||
--type) TYPE="$2"; shift 2 ;;
|
||||
--db) DB="true"; shift 1 ;;
|
||||
--mount) MOUNTS="$2"; shift 2 ;;
|
||||
--no-scale-to-zero) SCALE_TO_ZERO="false"; shift 1 ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$NAME" ] || [ -z "$HOST" ] || [ -z "$URL" ]; then
|
||||
echo "Error: --name, --host, and --url are required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Applying ConfigMap zero-scale-app-${NAME}..."
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: zero-scale-app-${NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
labels:
|
||||
zero-scale-app: "true"
|
||||
data:
|
||||
host: "${HOST}"
|
||||
url: "${URL}"
|
||||
type: "${TYPE}"
|
||||
$( [ "$DB" = "true" ] && echo " database: \"true\"" )
|
||||
$( [ -n "$MOUNTS" ] && echo " mounts: \"${MOUNTS}\"" )
|
||||
$( [ "$SCALE_TO_ZERO" = "false" ] && echo " scale-to-zero: \"false\"" )
|
||||
EOF
|
||||
echo "==> Application ${NAME} deployed successfully for host ${HOST}."
|
||||
;;
|
||||
|
||||
list-apps)
|
||||
echo "==> Configured Zero-Scale Applications in namespace '$NAMESPACE':"
|
||||
kubectl get configmaps -n "$NAMESPACE" --show-labels | grep "zero-scale-app" || echo "No zero-scale applications found."
|
||||
;;
|
||||
|
||||
delete-app)
|
||||
NAME="$2"
|
||||
if [ -z "$NAME" ]; then
|
||||
echo "Usage: $0 delete-app <name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> Deleting ConfigMap zero-scale-app-${NAME}..."
|
||||
kubectl delete configmap -n "$NAMESPACE" "zero-scale-app-${NAME}"
|
||||
;;
|
||||
|
||||
status)
|
||||
HOST="$2"
|
||||
if [ -z "$HOST" ]; then
|
||||
echo "Usage: $0 status <host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
POD=$(get_gateway_pod)
|
||||
UNIT="app-${HOST}"
|
||||
echo "==> Checking status of ${UNIT} in pod ${POD}..."
|
||||
kubectl exec -n "$NAMESPACE" "$POD" -- systemctl status "$UNIT" || true
|
||||
;;
|
||||
|
||||
logs)
|
||||
HOST="$2"
|
||||
if [ -z "$HOST" ]; then
|
||||
echo "Usage: $0 logs <host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
POD=$(get_gateway_pod)
|
||||
UNIT="app-${HOST}"
|
||||
echo "==> Fetching journal logs for ${UNIT} in pod ${POD}..."
|
||||
kubectl exec -n "$NAMESPACE" "$POD" -- journalctl -u "$UNIT" --no-pager -n 50
|
||||
;;
|
||||
|
||||
install-chart)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CHART_DIR="${SCRIPT_DIR}/zero-scale-chart"
|
||||
echo "==> Installing / upgrading zero-scale-platform Helm chart from ${CHART_DIR}..."
|
||||
helm upgrade --install zero-scale-platform "$CHART_DIR" -n "$NAMESPACE"
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
135
systemd-pod.yaml
135
systemd-pod.yaml
@@ -1,135 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: zero-scale-artifacts
|
||||
namespace: default
|
||||
data:
|
||||
artifacts.json: |
|
||||
{
|
||||
"artifacts": {
|
||||
"bdash2.kube.fairfaxmedia.net": {
|
||||
"url": "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/README.md",
|
||||
"type": "elf"
|
||||
},
|
||||
"hopscotch.kube.fairfaxmedia.net": {
|
||||
"url": "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/README.md",
|
||||
"type": "elf"
|
||||
}
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: zero-scale-binaries-pvc
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: longhorn-2-replicas
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
labels:
|
||||
app: zero-scale-gateway
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: didi-macbookpro14-3
|
||||
containers:
|
||||
- name: systemd-runner
|
||||
image: localhost:32000/systemd-runner:latest
|
||||
securityContext:
|
||||
privileged: true
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: run
|
||||
mountPath: /run
|
||||
- name: run-lock
|
||||
mountPath: /run/lock
|
||||
- name: apps-volume
|
||||
mountPath: /opt/apps
|
||||
- name: bdash-uploads
|
||||
mountPath: /app/uploads
|
||||
- name: config-volume
|
||||
mountPath: /etc/zero-scale
|
||||
volumes:
|
||||
- name: run
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
- name: run-lock
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
- name: apps-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: zero-scale-binaries-pvc
|
||||
- name: bdash-uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: builds-dash-uploads-longhorn-rwx
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: zero-scale-artifacts
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: zero-scale-gateway
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: zero-scale-gateway
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
spec:
|
||||
ingressClassName: public
|
||||
rules:
|
||||
- host: bdash2.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
- host: bdash2.kube.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
- host: hopscotch.kube.fairfaxmedia.net
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: zero-scale-gateway
|
||||
port:
|
||||
number: 80
|
||||
path: /
|
||||
pathType: ImplementationSpecific
|
||||
Reference in New Issue
Block a user