|
| 1 | +# Architecture |
| 2 | + |
| 3 | +This document describes the design decisions and architecture of the marimo-operator. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The marimo-operator is a Kubernetes operator that manages `MarimoNotebook` custom resources. For each notebook, it creates: |
| 8 | + |
| 9 | +- **PVC**: Persistent storage for notebook files (preserved on CR deletion) |
| 10 | +- **Pod**: Runs marimo server with init container and optional sidecars |
| 11 | +- **Service**: Exposes marimo port (and sidecar ports) |
| 12 | + |
| 13 | +``` |
| 14 | +┌─────────────────────────────────────────────────────────────┐ |
| 15 | +│ MarimoNotebook CR │ |
| 16 | +└─────────────────────────────────────────────────────────────┘ |
| 17 | + │ |
| 18 | + ▼ |
| 19 | +┌─────────────────────────────────────────────────────────────┐ |
| 20 | +│ Operator Controller │ |
| 21 | +└─────────────────────────────────────────────────────────────┘ |
| 22 | + │ |
| 23 | + ┌─────────────────────┼─────────────────────┐ |
| 24 | + ▼ ▼ ▼ |
| 25 | +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ |
| 26 | +│ PVC │ │ Pod │ │ Service │ |
| 27 | +│ (preserved) │ │ + sidecars │ │ (ports) │ |
| 28 | +└─────────────┘ └─────────────┘ └─────────────┘ |
| 29 | +``` |
| 30 | + |
| 31 | +## CRD Schema |
| 32 | + |
| 33 | +### MarimoNotebookSpec |
| 34 | + |
| 35 | +| Field | Type | Required | Description | |
| 36 | +|-------|------|----------|-------------| |
| 37 | +| `image` | string | No | Container image (default: `ghcr.io/marimo-team/marimo:latest`) | |
| 38 | +| `port` | int32 | No | Server port (default: 2718) | |
| 39 | +| `source` | string | Yes | Git URL for notebook content | |
| 40 | +| `storage` | StorageSpec | No | PVC configuration | |
| 41 | +| `resources` | ResourcesSpec | No | CPU/memory/GPU requests and limits | |
| 42 | +| `auth` | AuthSpec | No | Authentication configuration | |
| 43 | +| `sidecars` | []SidecarSpec | No | Additional containers | |
| 44 | +| `podOverrides` | PodSpec | No | Strategic merge patch for Pod customization | |
| 45 | + |
| 46 | +### Source |
| 47 | + |
| 48 | +The `source` field specifies where to fetch notebook content: |
| 49 | + |
| 50 | +```yaml |
| 51 | +spec: |
| 52 | + source: https://github.com/org/notebooks.git |
| 53 | +``` |
| 54 | +
|
| 55 | +The operator uses an init container to clone the repository into the PVC. |
| 56 | +
|
| 57 | +### Storage |
| 58 | +
|
| 59 | +Storage configuration for the PVC: |
| 60 | +
|
| 61 | +```yaml |
| 62 | +spec: |
| 63 | + storage: |
| 64 | + size: 1Gi |
| 65 | + storageClassName: standard # optional |
| 66 | +``` |
| 67 | +
|
| 68 | +The init container clones `source` into the PVC, and marimo serves from there. |
| 69 | + |
| 70 | +**PVC Preservation**: PVCs are **not** deleted when the MarimoNotebook CR is deleted. This preserves user data by default. Explicit deletion requires `--delete-pvc` flag via the plugin or manual PVC deletion. |
| 71 | + |
| 72 | +### Sidecars |
| 73 | + |
| 74 | +Additional containers that run alongside marimo, sharing the PVC volume: |
| 75 | + |
| 76 | +**SSH access:** |
| 77 | +```yaml |
| 78 | +spec: |
| 79 | + sidecars: |
| 80 | + - name: sshd |
| 81 | + image: linuxserver/openssh-server:latest |
| 82 | + exposePort: 2222 |
| 83 | + env: |
| 84 | + - name: PASSWORD_ACCESS |
| 85 | + value: "true" |
| 86 | +``` |
| 87 | + |
| 88 | +**Git sync:** |
| 89 | +```yaml |
| 90 | +spec: |
| 91 | + sidecars: |
| 92 | + - name: git-sync |
| 93 | + image: registry.k8s.io/git-sync/git-sync:v4.2.1 |
| 94 | + env: |
| 95 | + - name: GITSYNC_REPO |
| 96 | + value: https://github.com/user/notebooks.git |
| 97 | +``` |
| 98 | + |
| 99 | +The `exposePort` field adds the port to the Service for external access. |
| 100 | + |
| 101 | +### MarimoNotebookStatus |
| 102 | + |
| 103 | +| Field | Type | Description | |
| 104 | +|-------|------|-------------| |
| 105 | +| `phase` | string | Current state: Pending, Running, Failed | |
| 106 | +| `url` | string | Internal service URL | |
| 107 | +| `sourceHash` | string | Hash of source URL + ref | |
| 108 | +| `podName` | string | Name of the created Pod | |
| 109 | +| `serviceName` | string | Name of the created Service | |
| 110 | +| `conditions` | []Condition | Standard Kubernetes conditions | |
| 111 | + |
| 112 | +## Reconciliation |
| 113 | + |
| 114 | +### Controller Flow |
| 115 | + |
| 116 | +1. **Validate Spec**: Ensure `source` is set |
| 117 | +2. **Ensure PVC**: Create if `storage` is specified (no owner reference) |
| 118 | +3. **Ensure Pod**: Create with init container (clones source), marimo container, sidecars |
| 119 | +4. **Ensure Service**: Expose marimo port and sidecar `exposePort`s |
| 120 | +5. **Update Status**: Set phase, URL, source hash, conditions |
| 121 | + |
| 122 | +### Pod Structure |
| 123 | + |
| 124 | +``` |
| 125 | +┌─────────────────────────────────────────────────────────────┐ |
| 126 | +│ Pod │ |
| 127 | +├─────────────────────────────────────────────────────────────┤ |
| 128 | +│ Init Container: git-clone │ |
| 129 | +│ - Clones source repo to /data (if empty) │ |
| 130 | +├─────────────────────────────────────────────────────────────┤ |
| 131 | +│ Container: marimo │ |
| 132 | +│ - Serves notebooks from /data │ |
| 133 | +│ - Port: 2718 (default) │ |
| 134 | +├─────────────────────────────────────────────────────────────┤ |
| 135 | +│ Sidecar: ssh (optional) │ |
| 136 | +│ - Shares /data volume │ |
| 137 | +│ - Port: 2222 │ |
| 138 | +├─────────────────────────────────────────────────────────────┤ |
| 139 | +│ Sidecar: git-sync (optional) │ |
| 140 | +│ - Watches /data for changes │ |
| 141 | +│ - Syncs to/from repo │ |
| 142 | +└─────────────────────────────────────────────────────────────┘ |
| 143 | + │ |
| 144 | + ▼ |
| 145 | + ┌─────────────┐ |
| 146 | + │ PVC │ |
| 147 | + │ /data │ |
| 148 | + └─────────────┘ |
| 149 | +``` |
| 150 | +
|
| 151 | +### Pod Update Strategy |
| 152 | +
|
| 153 | +The operator uses a **recreate** strategy for pod updates: |
| 154 | +
|
| 155 | +1. On spec change, delete the existing Pod |
| 156 | +2. Create a new Pod with updated spec |
| 157 | +3. Init container skips clone if PVC already has content |
| 158 | +
|
| 159 | +### Owner References |
| 160 | +
|
| 161 | +Pod and Service have owner references to the MarimoNotebook CR (garbage collected on delete). |
| 162 | +
|
| 163 | +PVC does **not** have an owner reference (preserved on delete). This is intentional - user data should not be accidentally deleted. |
| 164 | +
|
| 165 | +## Plugin Architecture |
| 166 | +
|
| 167 | +The `kubectl-marimo` plugin handles local notebook deployment: |
| 168 | +
|
| 169 | +1. Reads local notebook files (`.py`, `.md`) |
| 170 | +2. Uploads content to a ConfigMap |
| 171 | +3. Generates `MarimoNotebook` CR referencing the ConfigMap |
| 172 | +4. Tracks deployments in swap files (`.notebook.marimo`) |
| 173 | +
|
| 174 | +See [PLUGIN.md](PLUGIN.md) for distribution details. |
| 175 | +
|
| 176 | +### Swap Files |
| 177 | +
|
| 178 | +The plugin creates a swap file (e.g., `.notebook.py.marimo`) that tracks: |
| 179 | +
|
| 180 | +```json |
| 181 | +{ |
| 182 | + "name": "notebook", |
| 183 | + "namespace": "default", |
| 184 | + "appliedAt": "2025-01-01T00:00:00Z", |
| 185 | + "originalFile": "notebook.py", |
| 186 | + "fileHash": "sha256:abc123" |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +This enables: |
| 191 | +- `kubectl marimo sync` to find the deployed resource |
| 192 | +- `kubectl marimo delete` to clean up resources |
| 193 | +- Hash comparison for conflict detection |
| 194 | + |
| 195 | +## Design Decisions |
| 196 | + |
| 197 | +### Why source-based content? |
| 198 | + |
| 199 | +Real projects have multiple files. Git URLs are the natural unit of deployment: |
| 200 | + |
| 201 | +1. Clone via init container |
| 202 | +2. Sidecars like git-sync enable bidirectional sync |
| 203 | +3. Plugin handles local files via ConfigMap upload |
| 204 | + |
| 205 | +### Why preserve PVCs? |
| 206 | + |
| 207 | +User data is precious. Accidental deletion is worse than orphaned PVCs: |
| 208 | + |
| 209 | +1. PVCs have no owner reference (not garbage collected) |
| 210 | +2. Explicit `--delete-pvc` required for full cleanup |
| 211 | +3. PVC can be reattached to new CR with same name |
| 212 | + |
| 213 | +### Why recreate pods? |
| 214 | + |
| 215 | +Kubernetes Pods are largely immutable. Rather than complex in-place updates: |
| 216 | + |
| 217 | +1. Recreate is explicit and predictable |
| 218 | +2. Init container skips clone if data exists |
| 219 | +3. PVC preserves user changes across restarts |
| 220 | + |
| 221 | +### Why cluster-scoped? |
| 222 | + |
| 223 | +A single operator installation manages all namespaces: |
| 224 | + |
| 225 | +1. Simpler deployment (one `kubectl apply`) |
| 226 | +2. Consistent behavior across namespaces |
| 227 | +3. Centralized logging and metrics |
| 228 | + |
| 229 | +### Why Python plugin? |
| 230 | + |
| 231 | +Target users (marimo users) already have Python: |
| 232 | + |
| 233 | +1. Can import marimo's file parsers |
| 234 | +2. `uv`/`uvx` provides fast installation |
| 235 | +3. Single source of truth (no version drift) |
| 236 | +4. Krew compatibility via Go shim |
0 commit comments