diff options
| author | soryu <soryu@soryu.co> | 2026-02-21 23:51:11 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-21 23:51:11 +0000 |
| commit | 0523765af84492640928d571f481e17b26008b13 (patch) | |
| tree | 644e0bac90c1945120df27dea36d18c81f4470e9 /k8s/daemon | |
| parent | d670dcb72984cfa483063d161bb468704038895c (diff) | |
| download | soryu-0523765af84492640928d571f481e17b26008b13.tar.gz soryu-0523765af84492640928d571f481e17b26008b13.zip | |
feat: Add daemon health monitoring page, downloads & K8s support (#76)
* feat: soryu-co/soryu - makima: Add server-side daemon binary download endpoint
* feat: soryu-co/soryu - makima: Create Kubernetes daemon manifests and Dockerfile
* feat: soryu-co/soryu - makima: Create dedicated Daemons page with health monitoring UI
* WIP: heartbeat checkpoint
* feat: soryu-co/soryu - makima: Integrate daemon platform availability into frontend downloads
Diffstat (limited to 'k8s/daemon')
| -rw-r--r-- | k8s/daemon/Dockerfile | 78 | ||||
| -rw-r--r-- | k8s/daemon/README.md | 182 | ||||
| -rw-r--r-- | k8s/daemon/configmap.yaml | 14 | ||||
| -rw-r--r-- | k8s/daemon/deployment.yaml | 106 | ||||
| -rw-r--r-- | k8s/daemon/hpa.yaml | 42 | ||||
| -rw-r--r-- | k8s/daemon/kustomization.yaml | 14 | ||||
| -rw-r--r-- | k8s/daemon/secret.yaml | 31 |
7 files changed, 467 insertions, 0 deletions
diff --git a/k8s/daemon/Dockerfile b/k8s/daemon/Dockerfile new file mode 100644 index 0000000..1c1ccd1 --- /dev/null +++ b/k8s/daemon/Dockerfile @@ -0,0 +1,78 @@ +# ============================================================================== +# Makima Daemon - Lightweight Container Image +# ============================================================================== +# This Dockerfile builds a minimal image for running `makima daemon` in +# Kubernetes. Unlike the full server image (which includes ML models), this +# image contains only the makima binary and the tools it needs to execute +# tasks: git, gh CLI, curl, and SSH client. +# ============================================================================== + +# ---------- Builder stage ---------- +FROM rust:1.91-bookworm AS builder + +WORKDIR /app + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy workspace files +COPY Cargo.toml Cargo.lock ./ +COPY makima ./makima +COPY vendor ./vendor +COPY tools/stt-client ./tools/stt-client + +# Build release binary +RUN cargo build --release --package makima --bin makima + +# ---------- Runtime stage ---------- +FROM debian:bookworm-slim + +# Install runtime dependencies: +# - ca-certificates: TLS certificate verification +# - libssl3: OpenSSL runtime for TLS connections +# - git: Git operations (clone, worktree, push, etc.) +# - curl: Health checks and HTTP requests +# - openssh-client: SSH key-based git authentication +# - jq: JSON processing in scripts +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + git \ + curl \ + openssh-client \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (gh) +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends gh \ + && rm -rf /var/lib/apt/lists/* + +# Copy the built binary from the builder stage +COPY --from=builder /app/target/release/makima /usr/local/bin/makima + +# Create application directories +# - /app/workdir: Working directory for git worktrees +# - /app/data: Local database and state +RUN mkdir -p /app/workdir /app/data /root/.makima + +# Set environment defaults +ENV RUST_LOG=makima=info +ENV MAKIMA_DAEMON_WORKTREE_BASEDIR=/app/workdir +ENV MAKIMA_DAEMON_WORKTREE_REPOSDIR=/app/workdir/repos +ENV MAKIMA_DAEMON_LOCALDB_PATH=/app/data/daemon.db +ENV MAKIMA_DAEMON_REPOS_HOMEDIR=/app/workdir/home +ENV HOME=/root + +WORKDIR /app + +ENTRYPOINT ["makima"] +CMD ["daemon"] diff --git a/k8s/daemon/README.md b/k8s/daemon/README.md new file mode 100644 index 0000000..79c8f96 --- /dev/null +++ b/k8s/daemon/README.md @@ -0,0 +1,182 @@ +# Makima Daemon — Kubernetes Deployment + +Run makima daemon workers in Kubernetes. Each daemon pod connects to the Makima server via WebSocket, authenticates with an API key, and executes tasks that involve git operations and Claude Code subprocesses. + +## Prerequisites + +- Kubernetes 1.25+ +- `kubectl` configured for your cluster +- A Makima server accessible from inside the cluster +- A Makima API key (generate one from the Makima dashboard) +- *(Optional)* A GitHub personal access token for `gh` CLI operations +- *(Optional)* SSH keys for git-over-SSH authentication + +## Quick Start + +### 1. Build the daemon image + +From the repository root: + +```bash +docker build -f k8s/daemon/Dockerfile -t ghcr.io/soryu-co/makima-daemon:latest . +docker push ghcr.io/soryu-co/makima-daemon:latest +``` + +### 2. Configure secrets + +Edit `secret.yaml` with your actual credentials, or create the secret directly: + +```bash +kubectl create secret generic makima-daemon-secrets \ + --from-literal=api-key=YOUR_API_KEY \ + --from-literal=github-token=YOUR_GITHUB_TOKEN +``` + +### 3. Configure the server URL + +Edit `configmap.yaml` to point to your Makima server: + +```yaml +data: + server-url: "wss://api.makima.jp" # or your self-hosted server + log-level: "makima=info" +``` + +### 4. Deploy with Kustomize + +```bash +kubectl apply -k k8s/daemon/ +``` + +Or apply individual manifests: + +```bash +kubectl apply -f k8s/daemon/configmap.yaml +kubectl apply -f k8s/daemon/secret.yaml +kubectl apply -f k8s/daemon/deployment.yaml +kubectl apply -f k8s/daemon/hpa.yaml +``` + +### 5. Verify + +```bash +kubectl get pods -l app=makima-daemon +kubectl logs -l app=makima-daemon -f +``` + +## Scaling + +### Horizontal Pod Autoscaler + +The included `hpa.yaml` scales the daemon deployment between 1 and 10 replicas based on: + +| Metric | Target | Description | +|--------|--------|-------------| +| CPU | 70% utilization | Scales up when tasks are CPU-bound | +| Memory | 80% utilization | Scales up when worktrees consume memory | + +Scale-up adds up to 2 pods per minute; scale-down removes 1 pod every 2 minutes with a 5-minute stabilization window to avoid flapping. + +### Manual scaling + +```bash +# Scale to 5 replicas +kubectl scale deployment makima-daemon --replicas=5 + +# Or patch the HPA limits +kubectl patch hpa makima-daemon -p '{"spec":{"maxReplicas":20}}' +``` + +## SSH Keys + +To use SSH-based git authentication, create a secret with your SSH key: + +```bash +kubectl create secret generic makima-daemon-ssh \ + --from-file=id_ed25519=$HOME/.ssh/id_ed25519 \ + --from-file=known_hosts=$HOME/.ssh/known_hosts +``` + +The deployment mounts this at `/root/.ssh` (read-only, mode 0600). + +> **Tip:** For GitHub, you can use a deploy key or a personal SSH key. Make sure +> `github.com` is in your `known_hosts` file. + +## Environment Variables + +All daemon configuration can be controlled via environment variables. The deployment +sources these from the ConfigMap and Secret, but you can add more in the deployment spec. + +| Variable | Source | Description | +|----------|--------|-------------| +| `MAKIMA_API_KEY` | Secret | **(Required)** API key for server authentication | +| `MAKIMA_DAEMON_SERVER_URL` | ConfigMap | WebSocket URL of the Makima server | +| `RUST_LOG` | ConfigMap | Log level filter (e.g., `makima=info`, `makima=debug`) | +| `GITHUB_TOKEN` | Secret | GitHub PAT for `gh` CLI and HTTPS git auth | +| `GH_TOKEN` | Secret | Alias for `GITHUB_TOKEN` (used by `gh` CLI) | +| `MAKIMA_DAEMON_WORKTREE_BASEDIR` | Dockerfile | Base dir for worktrees (default: `/app/workdir`) | +| `MAKIMA_DAEMON_WORKTREE_REPOSDIR` | Dockerfile | Cached repo clones (default: `/app/workdir/repos`) | +| `MAKIMA_DAEMON_LOCALDB_PATH` | Dockerfile | SQLite database path (default: `/app/data/daemon.db`) | +| `MAKIMA_DAEMON_PROCESS_MAXCONCURRENTTASKS` | — | Max concurrent tasks per daemon (default: 10) | +| `MAKIMA_DAEMON_PROCESS_CLAUDECOMMAND` | — | Path to Claude Code CLI (default: `claude`) | +| `MAKIMA_DAEMON_SERVER_HEARTBEATINTERVALSECS` | — | WebSocket heartbeat interval (default: 30) | +| `MAKIMA_DAEMON_SERVER_RECONNECTINTERVALSECS` | — | Reconnect delay on disconnect (default: 5) | + +## Resource Tuning + +Default resource requests/limits: + +```yaml +resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "2Gi" + cpu: "2000m" +``` + +**When to increase resources:** +- Tasks involve large repositories — increase `memory` limits and `workdir` volume size +- Many concurrent tasks per pod — increase `cpu` limits and `MAKIMA_DAEMON_PROCESS_MAXCONCURRENTTASKS` +- Large diffs or many worktrees — increase the `emptyDir.sizeLimit` on the `workdir` volume + +## Troubleshooting + +### Pod is CrashLoopBackOff + +```bash +kubectl logs <pod-name> --previous +``` + +Common causes: +- **"API key is required"** — the `makima-daemon-secrets` secret is missing or `api-key` is empty +- **"Authentication failed"** — the API key is invalid or the server URL is wrong +- **DNS resolution failure** — the server URL hostname is not resolvable from inside the cluster + +### Daemon connects but no tasks execute + +- Verify the daemon appears in the Makima dashboard under connected daemons +- Check that `GITHUB_TOKEN` is set if tasks involve GitHub repositories +- Ensure the `claude` CLI is available inside the container (it should be, but custom images may differ) + +### Worktree disk pressure + +The `workdir` volume is an `emptyDir` with a 10Gi limit. If tasks create many large worktrees: + +```bash +# Check disk usage inside a pod +kubectl exec <pod-name> -- du -sh /app/workdir/* +``` + +Increase the limit in `deployment.yaml` or switch to a PersistentVolumeClaim for larger workloads. + +### Viewing daemon logs + +```bash +# Follow logs from all daemon pods +kubectl logs -l app=makima-daemon -f --tail=100 + +# Debug-level logging +kubectl set env deployment/makima-daemon RUST_LOG=makima=debug +``` diff --git a/k8s/daemon/configmap.yaml b/k8s/daemon/configmap.yaml new file mode 100644 index 0000000..b75ca88 --- /dev/null +++ b/k8s/daemon/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: makima-daemon-config + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima +data: + # WebSocket URL of the Makima server (wss:// for TLS, ws:// for plain) + server-url: "wss://api.makima.jp" + # Log level (trace, debug, info, warn, error) + log-level: "makima=info" diff --git a/k8s/daemon/deployment.yaml b/k8s/daemon/deployment.yaml new file mode 100644 index 0000000..b0f5fb3 --- /dev/null +++ b/k8s/daemon/deployment.yaml @@ -0,0 +1,106 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: makima-daemon + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima +spec: + replicas: 1 + selector: + matchLabels: + app: makima-daemon + template: + metadata: + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima + spec: + terminationGracePeriodSeconds: 30 + containers: + - name: makima-daemon + image: ghcr.io/soryu-co/makima-daemon:latest + imagePullPolicy: Always + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "2Gi" + cpu: "2000m" + env: + # --- Secrets --- + - name: MAKIMA_API_KEY + valueFrom: + secretKeyRef: + name: makima-daemon-secrets + key: api-key + - name: GITHUB_TOKEN + valueFrom: + secretKeyRef: + name: makima-daemon-secrets + key: github-token + optional: true + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: makima-daemon-secrets + key: github-token + optional: true + # --- ConfigMap --- + - name: MAKIMA_DAEMON_SERVER_URL + valueFrom: + configMapKeyRef: + name: makima-daemon-config + key: server-url + - name: RUST_LOG + valueFrom: + configMapKeyRef: + name: makima-daemon-config + key: log-level + volumeMounts: + - name: workdir + mountPath: /app/workdir + - name: data + mountPath: /app/data + - name: ssh-keys + mountPath: /root/.ssh + readOnly: true + # Liveness probe: check that the makima daemon process is running + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pgrep -f "makima daemon" > /dev/null + initialDelaySeconds: 10 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + # Readiness probe: same check — daemon is ready when the process is alive + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pgrep -f "makima daemon" > /dev/null + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: workdir + emptyDir: + sizeLimit: 10Gi + - name: data + emptyDir: + sizeLimit: 1Gi + - name: ssh-keys + secret: + secretName: makima-daemon-ssh + optional: true + defaultMode: 0600 diff --git a/k8s/daemon/hpa.yaml b/k8s/daemon/hpa.yaml new file mode 100644 index 0000000..e529d75 --- /dev/null +++ b/k8s/daemon/hpa.yaml @@ -0,0 +1,42 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: makima-daemon + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: makima-daemon + minReplicas: 1 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + behavior: + scaleUp: + stabilizationWindowSeconds: 60 + policies: + - type: Pods + value: 2 + periodSeconds: 60 + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Pods + value: 1 + periodSeconds: 120 diff --git a/k8s/daemon/kustomization.yaml b/k8s/daemon/kustomization.yaml new file mode 100644 index 0000000..a4372cc --- /dev/null +++ b/k8s/daemon/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + name: makima-daemon + +commonLabels: + app.kubernetes.io/part-of: makima + +resources: + - deployment.yaml + - configmap.yaml + - secret.yaml + - hpa.yaml diff --git a/k8s/daemon/secret.yaml b/k8s/daemon/secret.yaml new file mode 100644 index 0000000..74b0e13 --- /dev/null +++ b/k8s/daemon/secret.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Secret +metadata: + name: makima-daemon-secrets + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima +type: Opaque +stringData: + # REQUIRED: Your Makima API key for daemon authentication + api-key: "REPLACE_WITH_YOUR_API_KEY" + # OPTIONAL: GitHub personal access token for gh CLI and git operations + github-token: "REPLACE_WITH_YOUR_GITHUB_TOKEN" +--- +# Optional: SSH keys for git authentication over SSH +# Create with: kubectl create secret generic makima-daemon-ssh \ +# --from-file=id_ed25519=/path/to/key \ +# --from-file=known_hosts=/path/to/known_hosts +apiVersion: v1 +kind: Secret +metadata: + name: makima-daemon-ssh + labels: + app: makima-daemon + app.kubernetes.io/name: makima-daemon + app.kubernetes.io/component: daemon + app.kubernetes.io/part-of: makima +type: Opaque +data: {} |
