# RoboCo Open Core + Kubernetes Migration Plan ## Overview **Goal:** Restructure RoboCo for commercialization with an Open Core model, then deploy to Kubernetes with ArgoCD on the UGREEN NAS. **Order:** Separation first, then K8s migration. --- ## What's Open vs Closed | Component | Status | Reason | |-----------|--------|--------| | Backend API (`roboco/api/`, `roboco/services/`) | **CLOSED** | Core product value | | Orchestrator (`roboco/runtime/`) | **CLOSED** | Competitive advantage | | MCP Servers (`roboco/mcp/`) | **CLOSED** | Business logic | | Frontend (future dashboard) | **OPEN** | Drives adoption | | Python/JS SDK | **OPEN** | Ecosystem growth | | CLI Tool | **OPEN** | Developer experience | | Agent Blueprints | **OPEN** | Community templates | --- ## Phase 1: API Separation (7-10 days) ### Why This First - SDK/CLI can be developed independently of infra - Validates API surface before K8s migration - Creates public-facing documentation - Allows community to start building agents ### Step 1.1: Create OSS Repo Structure (1 day) Create public `roboco-agents` repo on GitHub: ``` roboco-agents/ ├── sdk/ │ └── python/ │ ├── roboco_sdk/ │ │ ├── __init__.py │ │ ├── client.py # Main RobocoClient class │ │ ├── tasks.py # TasksAPI wrapper │ │ ├── messages.py # MessagesAPI wrapper │ │ ├── journals.py # JournalsAPI wrapper │ │ ├── notifications.py # NotificationsAPI wrapper │ │ └── models.py # Pydantic models (Task, Message, etc.) │ ├── pyproject.toml │ ├── README.md │ └── tests/ ├── cli/ │ └── roboco_cli/ │ ├── __init__.py │ ├── main.py # Typer app entry │ ├── commands/ │ │ ├── task.py # roboco task │ │ ├── agent.py # roboco agent │ │ ├── message.py # roboco message │ │ └── config.py # roboco config │ └── config.py # Config file handling ├── blueprints/ │ ├── templates/ │ │ ├── developer.md # Generic dev blueprint │ │ ├── qa.md # Generic QA blueprint │ │ ├── pm.md # Generic PM blueprint │ │ └── documenter.md # Generic documenter blueprint │ └── examples/ │ └── simple-team/ # Example 3-agent team ├── docker/ │ ├── agent-base.Dockerfile # Base image for agents │ └── claude-code.Dockerfile # Claude Code agent image ├── examples/ │ ├── hello-world/ # Minimal agent example │ ├── task-worker/ # Task processing example │ └── multi-agent/ # Team coordination example ├── docs/ │ ├── getting-started.md │ ├── sdk-reference.md │ ├── cli-reference.md │ └── blueprint-guide.md ├── LICENSE # MIT or Apache 2.0 ├── README.md └── pyproject.toml # Workspace/monorepo config ``` **Actions:** 1. `gh repo create roboco-agents --public` 2. Initialize with pyproject.toml workspace 3. Setup GitHub Actions for CI/CD 4. Configure PyPI publishing workflow --- ### Step 1.2: Extract SDK from MCP Utils (2-3 days) **Source files to extract from:** - `roboco/mcp/utils.py` - `ApiClient` class - `roboco/api/schemas/*.py` - Response models - `roboco/models/*.py` - Core domain models **SDK Structure:** ```python # roboco_sdk/client.py import httpx from typing import Optional class RobocoClient: """Main client for RoboCo API.""" def __init__( self, base_url: str = "http://localhost:8000/api/v1", api_key: Optional[str] = None, agent_id: Optional[str] = None, agent_role: Optional[str] = None, ): self.base_url = base_url self.headers = {} if api_key: self.headers["Authorization"] = f"Bearer {api_key}" if agent_id: self.headers["X-Agent-ID"] = agent_id if agent_role: self.headers["X-Agent-Role"] = agent_role self._http = httpx.AsyncClient( base_url=base_url, headers=self.headers, timeout=30.0 ) @property def tasks(self) -> "TasksAPI": return TasksAPI(self._http) @property def messages(self) -> "MessagesAPI": return MessagesAPI(self._http) @property def journals(self) -> "JournalsAPI": return JournalsAPI(self._http) @property def notifications(self) -> "NotificationsAPI": return NotificationsAPI(self._http) async def close(self): await self._http.aclose() async def __aenter__(self): return self async def __aexit__(self, *args): await self.close() ``` ```python # roboco_sdk/tasks.py from typing import Optional, List from .models import Task, TaskCreate, TaskUpdate class TasksAPI: def __init__(self, http: httpx.AsyncClient): self._http = http async def list( self, status: Optional[str] = None, team: Optional[str] = None, assigned_to: Optional[str] = None, limit: int = 50, ) -> List[Task]: params = {"limit": limit} if status: params["status"] = status if team: params["team"] = team if assigned_to: params["assigned_to"] = assigned_to resp = await self._http.get("/tasks", params=params) resp.raise_for_status() return [Task(**t) for t in resp.json()["items"]] async def get(self, task_id: str) -> Task: resp = await self._http.get(f"/tasks/{task_id}") resp.raise_for_status() return Task(**resp.json()) async def create(self, data: TaskCreate) -> Task: resp = await self._http.post("/tasks", json=data.model_dump()) resp.raise_for_status() return Task(**resp.json()) async def claim(self, task_id: str) -> Task: resp = await self._http.post(f"/tasks/{task_id}/claim") resp.raise_for_status() return Task(**resp.json()) async def start(self, task_id: str) -> Task: resp = await self._http.post(f"/tasks/{task_id}/start") resp.raise_for_status() return Task(**resp.json()) async def progress( self, task_id: str, message: str, percentage: int ) -> Task: resp = await self._http.post( f"/tasks/{task_id}/progress", json={"message": message, "percentage": percentage} ) resp.raise_for_status() return Task(**resp.json()) async def submit_for_qa(self, task_id: str) -> Task: resp = await self._http.post(f"/tasks/{task_id}/submit-qa") resp.raise_for_status() return Task(**resp.json()) # ... more methods ``` ```python # roboco_sdk/models.py from pydantic import BaseModel from typing import Optional, List from datetime import datetime from enum import Enum class TaskStatus(str, Enum): BACKLOG = "backlog" PENDING = "pending" CLAIMED = "claimed" IN_PROGRESS = "in_progress" VERIFYING = "verifying" AWAITING_QA = "awaiting_qa" AWAITING_DOCUMENTATION = "awaiting_documentation" AWAITING_PM_REVIEW = "awaiting_pm_review" COMPLETED = "completed" BLOCKED = "blocked" CANCELLED = "cancelled" class Task(BaseModel): id: str title: str description: Optional[str] = None status: TaskStatus priority: int = 3 team: Optional[str] = None assigned_to: Optional[str] = None parent_task_id: Optional[str] = None created_at: datetime updated_at: datetime # ... more fields class TaskCreate(BaseModel): title: str description: Optional[str] = None priority: int = 3 team: Optional[str] = None parent_task_id: Optional[str] = None acceptance_criteria: Optional[List[str]] = None ``` **Testing:** ```python # tests/test_tasks.py import pytest from roboco_sdk import RobocoClient @pytest.mark.asyncio async def test_list_tasks(): async with RobocoClient() as client: tasks = await client.tasks.list(status="pending") assert isinstance(tasks, list) ``` --- ### Step 1.3: Create CLI Tool (2-3 days) **CLI using Typer:** ```python # roboco_cli/main.py import typer from roboco_cli.commands import task, agent, message, config app = typer.Typer( name="roboco", help="RoboCo CLI - Manage AI agent workflows" ) app.add_typer(task.app, name="task") app.add_typer(agent.app, name="agent") app.add_typer(message.app, name="message") app.add_typer(config.app, name="config") if __name__ == "__main__": app() ``` ```python # roboco_cli/commands/task.py import typer from rich.console import Console from rich.table import Table from roboco_sdk import RobocoClient import asyncio app = typer.Typer(help="Task management commands") console = Console() @app.command("list") def list_tasks( status: str = typer.Option(None, "--status", "-s"), team: str = typer.Option(None, "--team", "-t"), limit: int = typer.Option(20, "--limit", "-n"), ): """List tasks with optional filters.""" async def _list(): async with RobocoClient() as client: return await client.tasks.list( status=status, team=team, limit=limit ) tasks = asyncio.run(_list()) table = Table(title="Tasks") table.add_column("ID", style="cyan") table.add_column("Title") table.add_column("Status", style="green") table.add_column("Team") table.add_column("Assigned") for t in tasks: table.add_row( t.id[:8], t.title[:40], t.status.value, t.team or "-", t.assigned_to[:8] if t.assigned_to else "-" ) console.print(table) @app.command("get") def get_task(task_id: str): """Get task details.""" async def _get(): async with RobocoClient() as client: return await client.tasks.get(task_id) task = asyncio.run(_get()) console.print(task) @app.command("claim") def claim_task(task_id: str): """Claim a task.""" async def _claim(): async with RobocoClient() as client: return await client.tasks.claim(task_id) task = asyncio.run(_claim()) console.print(f"✓ Claimed task: {task.title}") # ... more commands ``` **Config Management:** ```python # roboco_cli/config.py from pathlib import Path import toml CONFIG_PATH = Path.home() / ".roboco" / "config.toml" def get_config() -> dict: if not CONFIG_PATH.exists(): return {"api_url": "http://localhost:8000/api/v1"} return toml.load(CONFIG_PATH) def set_config(key: str, value: str): config = get_config() config[key] = value CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) with open(CONFIG_PATH, "w") as f: toml.dump(config, f) ``` **CLI Commands:** ```bash # Task management roboco task list --status pending --team backend roboco task get abc123 roboco task claim abc123 roboco task start abc123 roboco task progress abc123 "50% done" --percent 50 # Agent info roboco agent list roboco agent status be-dev-1 # Messages roboco message send --channel backend-cell --content "Hello team" roboco message history backend-cell # Config roboco config set api-url https://api.roboco.io roboco config set api-key sk-xxx roboco config show ``` --- ### Step 1.4: Package Blueprints (1 day) **Genericize blueprints for open source:** Current (RoboCo-specific): ```markdown # Backend Developer 1 (be-dev-1) You are Backend Developer 1 in the RoboCo organization... Channel: #backend-cell ``` Generic (OSS template): ```markdown # Developer Agent Blueprint ## Role You are a Developer agent responsible for implementing features and fixing bugs. ## Capabilities - Claim and work on assigned tasks - Write and test code - Submit work for QA review - Communicate progress via channels ## Workflow 1. SCAN - Check for pending/assigned tasks 2. CLAIM - Lock and take ownership 3. PLAN - Break down into subtasks 4. EXECUTE - Write code, commit frequently 5. VERIFY - Self-check against acceptance criteria 6. SUBMIT - Send for QA review ## Required Tools - roboco_task_scan - roboco_task_claim - roboco_task_start - roboco_task_progress - roboco_task_submit_qa - roboco_message_send - roboco_journal_entry ## Configuration ```yaml agent: role: developer team: ${TEAM} channel: ${TEAM}-cell ``` ``` **Files to move:** - `agents/blueprints/backend/be-dev.md` → `blueprints/templates/developer.md` - `agents/blueprints/backend/be-qa.md` → `blueprints/templates/qa.md` - `agents/blueprints/backend/be-pm.md` → `blueprints/templates/pm.md` - `agents/blueprints/backend/be-documenter.md` → `blueprints/templates/documenter.md` --- ### Step 1.5: Restructure Private Repo (1-2 days) **After extraction, private repo becomes:** ``` roboco/ # PRIVATE - Commercial product ├── roboco/ │ ├── api/ # FastAPI backend (unchanged) │ ├── services/ # Business logic (unchanged) │ ├── runtime/ # Orchestrator (unchanged) │ ├── mcp/ # MCP servers (unchanged) │ ├── models/ # Domain models (unchanged) │ ├── db/ # Database layer (unchanged) │ └── config.py ├── agents/ │ └── blueprints/ # RoboCo-specific blueprints (keep) │ ├── backend/ │ ├── frontend/ │ ├── ux_ui/ │ └── board/ ├── deploy/ # K8s manifests (Phase 2) │ ├── base/ │ └── overlays/ ├── docker/ │ ├── api.Dockerfile │ ├── orchestrator.Dockerfile │ └── docker-compose.yml ├── tests/ ├── pyproject.toml └── README.md ``` **Changes needed:** 1. Remove SDK extraction from `mcp/utils.py` (keep minimal internal client) 2. Update imports if needed 3. Document private repo setup --- ## Phase 2: Kubernetes + ArgoCD (12-16 days) ### Why K8s Over Docker Compose - **Auto-healing**: Pods restart automatically on failure - **Scaling**: Easy horizontal scaling of API/orchestrator - **GitOps**: ArgoCD enables push-to-deploy workflow - **Resource limits**: Proper CPU/memory management for agents - **Secrets management**: Sealed Secrets for git-safe secrets - **Observability**: Built-in metrics, logs aggregation --- ### Step 2.1: Setup K8s on NAS (1-2 days) **Prerequisites on UGREEN NAS:** ```bash # Disable swap (required for K8s) sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab # Enable required kernel modules cat < str: """Spawn an agent as a K8s Job.""" job_name = f"agent-{agent_id}-{task_id[:8]}" # Get agent config (blueprint, role, etc.) agent_config = self._get_agent_config(agent_id) job = client.V1Job( api_version="batch/v1", kind="Job", metadata=client.V1ObjectMeta( name=job_name, namespace=self.namespace, labels={ "app": "roboco-agent", "agent-id": agent_id, "task-id": task_id[:8], }, ), spec=client.V1JobSpec( ttl_seconds_after_finished=3600, # Cleanup after 1 hour backoff_limit=0, # No retries template=client.V1PodTemplateSpec( metadata=client.V1ObjectMeta( labels={ "app": "roboco-agent", "agent-id": agent_id, }, ), spec=client.V1PodSpec( restart_policy="Never", service_account_name="roboco-agent", containers=[ client.V1Container( name="agent", image=f"ghcr.io/renzof/roboco-agent:{agent_id}", env=[ client.V1EnvVar( name="AGENT_ID", value=agent_id, ), client.V1EnvVar( name="TASK_ID", value=task_id, ), client.V1EnvVar( name="INITIAL_PROMPT", value=initial_prompt, ), client.V1EnvVar( name="API_URL", value=settings.internal_api_url, ), client.V1EnvVar( name="ANTHROPIC_API_KEY", value_from=client.V1EnvVarSource( secret_key_ref=client.V1SecretKeySelector( name="roboco-secrets", key="anthropic-api-key", ), ), ), ], resources=client.V1ResourceRequirements( requests={"memory": "256Mi", "cpu": "100m"}, limits={"memory": "1Gi", "cpu": "500m"}, ), volume_mounts=[ client.V1VolumeMount( name="blueprints", mount_path="/app/agents/blueprints", read_only=True, ), ], ), ], volumes=[ client.V1Volume( name="blueprints", config_map=client.V1ConfigMapVolumeSource( name="roboco-blueprints", ), ), ], ), ), ), ) try: self.batch_v1.create_namespaced_job( namespace=self.namespace, body=job, ) logger.info( "Spawned agent job", job_name=job_name, agent_id=agent_id, task_id=task_id, ) return job_name except ApiException as e: logger.error( "Failed to spawn agent job", error=str(e), agent_id=agent_id, ) raise async def stop_agent(self, agent_id: str) -> None: """Stop an agent by deleting its Job.""" # Find jobs for this agent jobs = self.batch_v1.list_namespaced_job( namespace=self.namespace, label_selector=f"agent-id={agent_id}", ) for job in jobs.items: self.batch_v1.delete_namespaced_job( name=job.metadata.name, namespace=self.namespace, propagation_policy="Background", ) logger.info("Deleted agent job", job_name=job.metadata.name) async def get_agent_status(self, agent_id: str) -> AgentState: """Get agent status from Job status.""" jobs = self.batch_v1.list_namespaced_job( namespace=self.namespace, label_selector=f"agent-id={agent_id}", ) if not jobs.items: return AgentState.STOPPED job = jobs.items[-1] # Most recent if job.status.succeeded: return AgentState.COMPLETED elif job.status.failed: return AgentState.FAILED elif job.status.active: return AgentState.ACTIVE else: return AgentState.PENDING ``` **Config changes:** ```python # roboco/config.py class Settings(BaseSettings): # ... existing settings ... # K8s settings k8s_namespace: str = "roboco" k8s_agent_image: str = "ghcr.io/renzof/roboco-agent" k8s_agent_memory_request: str = "256Mi" k8s_agent_memory_limit: str = "1Gi" k8s_job_ttl: int = 3600 # seconds ``` --- ### Step 2.7: Secrets with Sealed Secrets (1 day) **Install kubeseal CLI:** ```bash # macOS brew install kubeseal # Linux wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.5/ku beseal-0.24.5-linux-amd64.tar.gz tar -xvf kubeseal-*.tar.gz sudo mv kubeseal /usr/local/bin/ ``` **Install controller:** ```bash kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/downloa d/v0.24.5/controller.yaml ``` **Create secrets:** ```yaml # secrets.yaml (NOT committed to git) apiVersion: v1 kind: Secret metadata: name: roboco-secrets namespace: roboco type: Opaque stringData: postgres-user: roboco postgres-password: your-secure-password database-url: postgresql://roboco:your-secure-password@postgres:5432/roboco anthropic-api-key: sk-ant-xxx redis-url: redis://redis:6379 ``` **Seal the secrets:** ```bash kubeseal --format yaml < secrets.yaml > deploy/overlays/production/sealed-secrets.yaml rm secrets.yaml # Don't keep plaintext! ``` --- ### Step 2.8: ArgoCD Applications (1 day) ```yaml # deploy/argocd/apps/roboco.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: roboco namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: git@github.com:renzof/roboco.git targetRevision: main path: deploy/overlays/production destination: server: https://kubernetes.default.svc namespace: roboco syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - CreateNamespace=true - PrunePropagationPolicy=foreground retry: limit: 5 backoff: duration: 5s factor: 2 maxDuration: 3m # Health checks ignoreDifferences: - group: apps kind: Deployment jsonPointers: - /spec/replicas # Allow HPA to manage ``` **Apply:** ```bash kubectl apply -f deploy/argocd/apps/roboco.yaml ``` --- ### Step 2.9: Testing & Validation (2-3 days) **Validation checklist:** ```bash # 1. Verify all pods running kubectl get pods -n roboco # Expected: postgres-0, redis-xxx, qdrant-0, roboco-api-xxx (x2), roboco-orchestrator-xxx # 2. Check PostgreSQL kubectl exec -it postgres-0 -n roboco -- psql -U roboco -c "SELECT 1" # 3. Check Redis kubectl exec -it deploy/redis -n roboco -- redis-cli ping # 4. Check API health kubectl port-forward svc/roboco-api -n roboco 8000:8000 & curl http://localhost:8000/health # 5. Check API endpoints curl http://localhost:8000/api/v1/agents curl http://localhost:8000/api/v1/tasks # 6. Test agent spawning # Trigger a task that spawns an agent curl -X POST http://localhost:8000/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{"title": "Test task", "team": "backend"}' # Watch for agent job kubectl get jobs -n roboco -w # 7. Check ArgoCD sync argocd app get roboco argocd app sync roboco # 8. Test GitOps flow git commit --allow-empty -m "test: trigger deploy" git push # Watch ArgoCD sync ``` **Monitoring setup:** ```bash # Install metrics-server kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/late st/download/components.yaml # View resource usage kubectl top pods -n roboco kubectl top nodes ``` --- ## Critical Files to Modify ### Phase 1 Files: | File | Change | |------|--------| | `roboco/mcp/utils.py` | Extract `ApiClient` → SDK | | `roboco/api/schemas/*.py` | Copy models to SDK | | `agents/blueprints/` | Genericize and move to OSS | ### Phase 2 Files: | File | Change | |------|--------| | `roboco/runtime/orchestrator.py` | Docker CLI → K8s API | | `roboco/config.py` | Add K8s settings | | `docker/orchestrator.Dockerfile` | Add `kubernetes` package | | NEW: `deploy/` | All K8s manifests | --- ## Git Strategy **Monorepo approach:** ``` roboco/ # Private repo ├── roboco/ # Python source ├── deploy/ # K8s manifests (ArgoCD watches this) │ ├── base/ │ └── overlays/production/ └── docker/ roboco-agents/ # Public repo (separate) ├── sdk/ ├── cli/ ├── blueprints/ └── docs/ ``` ArgoCD flow: `git push` → ArgoCD detects → auto-sync → pods updated --- ## Timeline Summary | Phase | Task | Days | |-------|------|------| | **1.1** | Create OSS repo | 1 | | **1.2** | Extract SDK | 2-3 | | **1.3** | Create CLI | 2-3 | | **1.4** | Package blueprints | 1 | | **1.5** | Restructure private repo | 1-2 | | | **Phase 1 Total** | **7-10** | | **2.1** | Setup K8s on NAS | 1-2 | | **2.2** | Install ArgoCD | 1 | | **2.3** | NFS storage | 1 | | **2.4** | K8s manifests | 2-3 | | **2.5** | RBAC setup | (included) | | **2.6** | Orchestrator K8s migration | 2-3 | | **2.7** | Sealed Secrets | 1 | | **2.8** | ArgoCD apps | 1 | | **2.9** | Testing & validation | 2-3 | | | **Phase 2 Total** | **12-16** | | | **Grand Total** | **19-26 days** | --- ## Dependencies & Prerequisites ### Before Phase 1: - [ ] GitHub account for `roboco-agents` repo - [ ] PyPI account for publishing SDK - [ ] Decide on license (MIT vs Apache 2.0) ### Before Phase 2: - [ ] UGREEN NAS accessible via SSH - [ ] NAS has Docker removed or disabled - [ ] SSH key for GitHub access from NAS - [ ] Anthropic API key for production --- ## Risk Mitigation | Risk | Mitigation | |------|------------| | K8s learning curve | Start with minimal manifests, iterate | | Data loss during migration | Keep Docker Compose as fallback | | Network issues between pods | Use ClusterIP services, test connectivity | | Secret leakage | Use Sealed Secrets from day 1 | --- ## Success Criteria ### Phase 1 Complete When: - [ ] `pip install roboco-sdk` works from PyPI - [ ] `roboco task list` CLI works - [ ] Community can build agents with templates - [ ] Private repo has no SDK dependencies ### Phase 2 Complete When: - [ ] `kubectl get pods -n roboco` shows all healthy - [ ] Agent jobs spawn and complete successfully - [ ] `git push` triggers ArgoCD deploy - [ ] API accessible from outside cluster - [ ] Zero downtime during rolling updates --- ## Phase 3: Production Hardening ### Step 3.1: Ingress & External Access ```yaml # deploy/base/ingress/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: roboco-ingress namespace: roboco annotations: nginx.ingress.kubernetes.io/proxy-body-size: "50m" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: ingressClassName: nginx tls: - hosts: - api.roboco.local secretName: roboco-tls rules: - host: api.roboco.local http: paths: - path: / pathType: Prefix backend: service: name: roboco-api port: number: 8000 ``` **Install nginx ingress:** ```bash helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace \ --set controller.service.type=NodePort \ --set controller.service.nodePorts.http=30080 \ --set controller.service.nodePorts.https=30443 ``` --- ### Step 3.2: Monitoring Stack (Prometheus + Grafana) ```bash # Install kube-prometheus-stack helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install monitoring prometheus-community/kube-prometheus-stack \ --namespace monitoring --create-namespace \ --set grafana.adminPassword=your-password \ --set prometheus.prometheusSpec.retention=30d \ --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageCl assName=nfs-roboco \ --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources .requests.storage=20Gi ``` **ServiceMonitor for RoboCo API:** ```yaml # deploy/base/monitoring/servicemonitor.yaml apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: roboco-api namespace: roboco spec: selector: matchLabels: app: roboco-api endpoints: - port: http path: /metrics interval: 30s ``` **Grafana dashboards to create:** - API request latency & throughput - Agent job success/failure rates - Task lifecycle metrics - Database connection pool - Memory/CPU per component --- ### Step 3.3: Log Aggregation (Loki) ```bash helm repo add grafana https://grafana.github.io/helm-charts helm install loki grafana/loki-stack \ --namespace monitoring \ --set promtail.enabled=true \ --set loki.persistence.enabled=true \ --set loki.persistence.storageClassName=nfs-roboco \ --set loki.persistence.size=20Gi ``` **Add to Grafana datasources** for querying logs like: ``` {namespace="roboco", app="roboco-api"} |= "error" {namespace="roboco"} | json | level="ERROR" ``` --- ### Step 3.4: CI/CD Pipeline (GitHub Actions) ```yaml # .github/workflows/build-and-push.yaml name: Build and Push Images on: push: branches: [main] paths: - 'roboco/**' - 'docker/**' env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-api: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push API image uses: docker/build-push-action@v5 with: context: . file: docker/api.Dockerfile push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-api:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-api:latest - name: Update Kustomize image tag run: | cd deploy/overlays/production kustomize edit set image ghcr.io/renzof/roboco-api:${{ github.sha }} git config user.name "GitHub Actions" git config user.email "actions@github.com" git add . git commit -m "chore: update image to ${{ github.sha }}" git push build-orchestrator: runs-on: ubuntu-latest # Similar to above... build-agent: runs-on: ubuntu-latest # Build base agent image... ``` --- ### Step 3.5: Backup & Restore **PostgreSQL backup CronJob:** ```yaml # deploy/base/postgres/backup-cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata: name: postgres-backup namespace: roboco spec: schedule: "0 2 * * *" # Daily at 2 AM jobTemplate: spec: template: spec: containers: - name: backup image: postgres:16 command: - /bin/sh - -c - | pg_dump -h postgres -U roboco roboco | gzip > /backups/roboco-$(date +%Y%m%d).sql.gz # Keep last 7 days find /backups -name "*.sql.gz" -mtime +7 -delete env: - name: PGPASSWORD valueFrom: secretKeyRef: name: roboco-secrets key: postgres-password volumeMounts: - name: backups mountPath: /backups volumes: - name: backups persistentVolumeClaim: claimName: postgres-backups restartPolicy: OnFailure ``` **Restore procedure:** ```bash # 1. Scale down API and orchestrator kubectl scale deployment roboco-api --replicas=0 -n roboco kubectl scale deployment roboco-orchestrator --replicas=0 -n roboco # 2. Restore from backup kubectl exec -it postgres-0 -n roboco -- bash -c \ "gunzip -c /backups/roboco-20241225.sql.gz | psql -U roboco roboco" # 3. Scale back up kubectl scale deployment roboco-api --replicas=2 -n roboco kubectl scale deployment roboco-orchestrator --replicas=1 -n roboco ``` --- ### Step 3.6: Rollback Procedures **ArgoCD rollback:** ```bash # View history argocd app history roboco # Rollback to previous version argocd app rollback roboco # Or via kubectl kubectl rollout undo deployment/roboco-api -n roboco kubectl rollout undo deployment/roboco-orchestrator -n roboco ``` **Emergency rollback script:** ```bash #!/bin/bash # scripts/emergency-rollback.sh set -e REVISION=${1:-1} # Default: rollback 1 revision echo "Rolling back roboco-api..." kubectl rollout undo deployment/roboco-api -n roboco --to-revision=$REVISION echo "Rolling back roboco-orchestrator..." kubectl rollout undo deployment/roboco-orchestrator -n roboco --to-revision=$REVISION echo "Waiting for rollout..." kubectl rollout status deployment/roboco-api -n roboco kubectl rollout status deployment/roboco-orchestrator -n roboco echo "Rollback complete!" ``` --- ## Transition Strategy (Docker → K8s) ### Week 1: Parallel Running 1. Deploy K8s stack alongside existing Docker Compose 2. Point K8s to SAME database (careful!) 3. Test API endpoints, verify responses match 4. Run shadow traffic (duplicate requests to both) ### Week 2: Gradual Cutover 1. Update DNS/ingress to point 10% traffic to K8s 2. Monitor error rates and latency 3. Increase to 50%, then 100% 4. Keep Docker Compose as fallback ### Week 3: Cleanup 1. Verify all agents running on K8s 2. Shut down Docker Compose services 3. Migrate remaining data if any 4. Remove Docker Compose files (keep in git history) --- ## Cost Estimation (Self-Hosted) | Resource | Usage | Notes | |----------|-------|-------| | UGREEN NAS | Already owned | 36TB storage | | Electricity | ~50W avg | ~$5/month | | Anthropic API | Variable | Based on agent usage | | Domain (optional) | $12/year | For external access | | **Total** | **~$5-10/month** | Excluding API costs | --- ## What's NOT Included (Future) - [ ] Multi-node K8s cluster (HA) - [ ] External database (RDS/Cloud SQL) - [ ] CDN for static assets - [ ] Rate limiting / API gateway - [ ] Multi-region deployment - [ ] Disaster recovery site