Skip to content

Commit ca5b37b

Browse files
committed
init: docs
0 parents  commit ca5b37b

2 files changed

Lines changed: 394 additions & 0 deletions

File tree

README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# marimo-operator
2+
3+
A Kubernetes operator for deploying [marimo](https://marimo.io) notebooks.
4+
5+
## Features
6+
7+
- **Git-native deployment**: Clone notebooks directly from repositories
8+
- **Extensible sidecars**: Add SSH, git-sync, or custom containers
9+
- **Persistent storage**: Browser edits persist across restarts
10+
- **Resource management**: Memory, CPU, and GPU allocation per notebook
11+
12+
## Quickstart
13+
14+
```bash
15+
# Install the operator
16+
kubectl apply -f https://raw.githubusercontent.com/marimo-team/marimo-operator/main/deploy/install.yaml
17+
18+
# Deploy a notebook project
19+
kubectl apply -f - <<EOF
20+
apiVersion: marimo.io/v1alpha1
21+
kind: MarimoNotebook
22+
metadata:
23+
name: my-project
24+
spec:
25+
source: https://github.com/marimo-team/examples.git
26+
storage:
27+
size: 1Gi
28+
EOF
29+
30+
# Check status
31+
kubectl get marimonotebooks
32+
```
33+
34+
_Or_ deploy individual notebooks with the kubectl plugin:
35+
36+
```bash
37+
# Install plugin
38+
pip install kubectl-marimo # or: kubectl krew install marimo
39+
40+
# Deploy a local notebook
41+
kubectl marimo apply notebook.py
42+
43+
# Sync changes back
44+
kubectl marimo sync notebook.py
45+
```
46+
47+
## Usage
48+
49+
### Deploy from Git
50+
51+
```yaml
52+
apiVersion: marimo.io/v1alpha1
53+
kind: MarimoNotebook
54+
metadata:
55+
name: my-project
56+
spec:
57+
source: https://github.com/org/notebooks.git
58+
storage:
59+
size: 1Gi
60+
```
61+
62+
The operator clones the repository into persistent storage and starts the marimo server.
63+
64+
### Add Sidecars
65+
66+
Sidecars run alongside marimo, sharing the same storage volume:
67+
68+
```yaml
69+
apiVersion: marimo.io/v1alpha1
70+
kind: MarimoNotebook
71+
metadata:
72+
name: dev-environment
73+
spec:
74+
source: https://github.com/org/notebooks.git
75+
storage:
76+
size: 5Gi
77+
sidecars:
78+
# SSH for remote access
79+
- name: ssh
80+
image: linuxserver/openssh-server:latest
81+
exposePort: 2222
82+
env:
83+
- name: PASSWORD_ACCESS
84+
value: "true"
85+
86+
# Continuous git synchronization
87+
- name: git-sync
88+
image: registry.k8s.io/git-sync/git-sync:v4.2.1
89+
env:
90+
- name: GITSYNC_REPO
91+
value: https://github.com/org/notebooks.git
92+
- name: GITSYNC_ROOT
93+
value: /data
94+
```
95+
96+
| Sidecar | Image | Use Case |
97+
|---------|-------|----------|
98+
| **SSH** | `linuxserver/openssh-server` | Remote shell, rsync, SSHFS mount |
99+
| **Git Sync** | `registry.k8s.io/git-sync` | Bidirectional repo synchronization |
100+
101+
The `exposePort` field adds the port to the Service for external access.
102+
103+
### GPU Support
104+
105+
```yaml
106+
spec:
107+
resources:
108+
requests:
109+
memory: 4Gi
110+
limits:
111+
memory: 16Gi
112+
nvidia.com/gpu: 1
113+
```
114+
115+
### Authentication
116+
117+
```yaml
118+
spec:
119+
auth:
120+
password:
121+
secretKeyRef:
122+
name: marimo-auth
123+
key: password
124+
```
125+
126+
## kubectl Plugin
127+
128+
For deploying individual notebooks from local files. See [docs/PLUGIN.md](docs/PLUGIN.md) for details.
129+
130+
```bash
131+
pip install kubectl-marimo
132+
kubectl marimo apply notebook.py
133+
kubectl marimo sync notebook.py
134+
kubectl marimo delete notebook.py # PVC preserved by default
135+
kubectl marimo delete notebook.py --delete-pvc # Also delete storage
136+
```
137+
138+
## Architecture
139+
140+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for design decisions.
141+
142+
## Installation
143+
144+
```bash
145+
kubectl apply -f https://raw.githubusercontent.com/marimo-team/marimo-operator/main/deploy/install.yaml
146+
```
147+
148+
## Development
149+
150+
```bash
151+
make test # Run tests
152+
make docker-build # Build operator image
153+
make deploy # Deploy to local Kind cluster
154+
```
155+
156+
## License
157+
158+
Apache 2.0

docs/ARCHITECTURE.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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

Comments
 (0)