# Starter manifest for running forkd-controller on Kubernetes.
#
# What this deploys
# -----------------
# A single Pod (Deployment with replicas=1) that hosts the controller
# daemon. ALL sandbox children fork inside this Pod — K8s only sees
# one Pod, not N pods-per-sandbox. The Pod's container memory.max and
# cpu_quota govern the whole forkd workload; per-child memory limits
# live in forkd's own cgroup v2 leaf under the Pod.
#
# Why a single Pod
# ----------------
# Each fork is a Firecracker process inside the controller's process
# tree. Children inherit the Pod's namespaces (PID, IPC) but get
# their own network namespace (forkd-controller manages that
# internally). K8s scheduling cost is O(1) regardless of fan-out.
#
# What you MUST customise
# -----------------------
# 1. image:  swap ghcr.io/deeplethe/forkd-controller:latest for the
#            tag you actually want.
# 2. KVM access: either
#       a) keep `privileged: true` (simplest, but broad)
#       b) replace with a KVM device plugin (e.g. kubevirt/kvm-device-plugin)
#          and remove `privileged`.
# 3. nodeSelector: scoped to nodes that actually have /dev/kvm.
# 4. PV: this manifest uses emptyDir for /var/lib/forkd. Snapshots
#        survive container restarts but NOT pod restarts. For
#        production, swap in a PersistentVolumeClaim.
# 5. NET_ADMIN is required for the controller to set up TAP devices.
#    `privileged: true` already grants it; otherwise add it explicitly.

apiVersion: v1
kind: Namespace
metadata:
  name: forkd
---
apiVersion: v1
kind: Secret
metadata:
  name: forkd-token
  namespace: forkd
type: Opaque
stringData:
  # Replace with `head -c 32 /dev/urandom | base64`. The controller
  # reads /etc/forkd/token; clients pass it as `Authorization: Bearer`.
  # The daemon REFUSES to start if this value is left at the literal
  # placeholder (any string beginning with "REPLACE_ME" or "CHANGE_ME"
  # is rejected at startup) — so forgetting this step is a noisy fail,
  # not a silent compromise.
  token: REPLACE_ME_WITH_32_BYTES_BASE64
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: forkd-controller
  namespace: forkd
  labels:
    app.kubernetes.io/name: forkd-controller
spec:
  replicas: 1
  strategy:
    type: Recreate          # forkd holds VM state, can't roll
  selector:
    matchLabels:
      app.kubernetes.io/name: forkd-controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: forkd-controller
    spec:
      # Schedule onto nodes that have KVM. Label nodes with:
      #   kubectl label node <n> feature.node.kubernetes.io/cpu-cpuid.VMX=true
      # or use a custom label your platform team prefers.
      nodeSelector:
        kubernetes.io/arch: amd64
      containers:
        - name: forkd-controller
          image: ghcr.io/deeplethe/forkd-controller:latest
          imagePullPolicy: IfNotPresent
          # Dockerfile ENTRYPOINT already supplies `forkd-controller serve`,
          # so args here are appended after `serve`.
          args:
            - --bind=0.0.0.0:8889
            - --state=/var/lib/forkd/state.json
            - --snapshot-root=/var/lib/forkd/snapshots
            - --audit-log=/var/lib/forkd/audit.log
            - --token-file=/etc/forkd/token
          ports:
            - name: api
              containerPort: 8889
              protocol: TCP
          securityContext:
            # Simplest path: privileged + root. For tighter security,
            # drop privileged, use a KVM device plugin, set fsGroup to
            # the non-root forkd GID baked into the image, and use
            # explicit caps. The Dockerfile creates a non-root `forkd`
            # user; running as root here lets us read the mode-0400
            # bearer-token secret without configuring fsGroup, and is
            # consistent with the privileged + CAP_NET_ADMIN model.
            privileged: true
            runAsUser: 0
            capabilities:
              add:
                - NET_ADMIN
                - SYS_ADMIN
          resources:
            requests:
              cpu: "4"
              memory: 8Gi
            limits:
              cpu: "16"
              memory: 32Gi
          readinessProbe:
            httpGet:
              path: /healthz
              port: api
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: api
            initialDelaySeconds: 30
            periodSeconds: 10
          volumeMounts:
            - name: kvm
              mountPath: /dev/kvm
            - name: state
              mountPath: /var/lib/forkd
            - name: token
              mountPath: /etc/forkd
              readOnly: true
            # cgroup v2 unified hierarchy access for per-child
            # memory.max. forkd uses /sys/fs/cgroup/forkd/.
            - name: cgroup
              mountPath: /sys/fs/cgroup
      volumes:
        - name: kvm
          hostPath:
            path: /dev/kvm
            type: CharDevice
        - name: cgroup
          hostPath:
            path: /sys/fs/cgroup
            type: Directory
        - name: token
          secret:
            secretName: forkd-token
            items:
              - key: token
                path: token
                mode: 0o400
        - name: state
          # Replace with a PersistentVolumeClaim for snapshot
          # persistence across pod restarts.
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: forkd-controller
  namespace: forkd
spec:
  selector:
    app.kubernetes.io/name: forkd-controller
  ports:
    - name: api
      port: 8889
      targetPort: api
  type: ClusterIP
