Kubernetes Operator for Tangled Spindles
15
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 204 lines 6.8 kB view raw view rendered
1# Loom 2 3Loom is a Kubernetes operator that runs CI/CD pipeline workflows from [tangled.org](https://tangled.org). It creates ephemeral Jobs in response to events (pushes, pull requests) and streams logs back to the tangled.org platform. 4 5## Architecture 6 7``` 8┌─────────────────────────────────────────────────────────────┐ 9│ Loom Operator Pod │ 10│ │ 11│ ┌────────────────────────────────────────────────────────┐ │ 12│ │ Controller Manager │ │ 13│ │ - Watches SpindleSet CRD │ │ 14│ │ - Creates/monitors Kubernetes Jobs │ │ 15│ └────────────────────────────────────────────────────────┘ │ 16│ │ 17│ ┌────────────────────────────────────────────────────────┐ │ 18│ │ Embedded Spindle Server │ │ 19│ │ - WebSocket connection to tangled.org knots │ │ 20│ │ - Database, queue, secrets vault │ │ 21│ │ - KubernetesEngine (creates Jobs) │ │ 22│ └────────────────────────────────────────────────────────┘ │ 23└─────────────────────────────────────────────────────────────┘ 24 25 │ creates 26 27 ┌───────────────────────────────┐ 28 │ Kubernetes Job (per workflow) │ 29 │ │ 30 │ Init: setup-user, clone-repo │ 31 │ Main: runner binary + image │ 32 └───────────────────────────────┘ 33``` 34 35### Components 36 37**Controller (`cmd/controller`)** - The Kubernetes operator that: 38- Connects to tangled.org knots via WebSocket to receive pipeline events 39- Creates `SpindleSet` custom resources for each pipeline run 40- Reconciles SpindleSets into Kubernetes Jobs 41- Manages secrets injection and cleanup 42 43**Runner (`cmd/runner`)** - A lightweight binary injected into workflow pods that: 44- Executes workflow steps sequentially 45- Emits structured JSON log events for real-time status updates 46- Handles step-level environment variable injection 47 48## How It Works 49 501. A push or PR event triggers a pipeline on tangled.org 512. Loom receives the event via WebSocket and parses the workflow YAML 523. A `SpindleSet` CR is created with the pipeline specification 534. The controller creates a Kubernetes Job with: 54 - Init containers for user setup and repository cloning 55 - The runner binary injected via shared volume 56 - The user's workflow image as the main container 575. The runner executes steps and streams logs back to the controller 586. On completion, the SpindleSet and its resources are cleaned up 59 60## Features 61 62- **Multi-architecture support**: Schedule workflows on amd64 or arm64 nodes 63- **Rootless container builds**: Buildah support with user namespace configuration 64- **Secret management**: Repository secrets injected as environment variables with log masking 65- **Resource profiles**: Configure CPU/memory based on node labels 66- **Automatic cleanup**: TTL-based Job cleanup and orphan detection 67 68## Configuration 69 70### Loom ConfigMap 71 72Loom is configured via a ConfigMap mounted at `/etc/loom/config.yaml`: 73 74```yaml 75maxConcurrentJobs: 10 76template: 77 resourceProfiles: 78 - nodeSelector: 79 kubernetes.io/arch: amd64 80 resources: 81 requests: 82 cpu: "500m" 83 memory: "1Gi" 84 limits: 85 cpu: "2" 86 memory: "4Gi" 87 - nodeSelector: 88 kubernetes.io/arch: arm64 89 resources: 90 requests: 91 cpu: "500m" 92 memory: "1Gi" 93 limits: 94 cpu: "2" 95 memory: "4Gi" 96``` 97 98### Spindle Environment Variables 99 100The embedded spindle server is configured via environment variables: 101 102| Variable | Required | Description | 103|----------|----------|-------------| 104| `SPINDLE_SERVER_LISTEN_ADDR` | Yes | HTTP server address (e.g., `0.0.0.0:6555`) | 105| `SPINDLE_SERVER_DB_PATH` | Yes | SQLite database path | 106| `SPINDLE_SERVER_HOSTNAME` | Yes | Hostname for spindle DID | 107| `SPINDLE_SERVER_OWNER` | Yes | Owner DID (e.g., `did:web:example.com`) | 108| `SPINDLE_SERVER_JETSTREAM_ENDPOINT` | Yes | Bluesky jetstream WebSocket URL | 109| `SPINDLE_SERVER_MAX_JOB_COUNT` | No | Max concurrent workflows (default: 2) | 110| `SPINDLE_SERVER_SECRETS_PROVIDER` | No | `sqlite` or `openbao` (default: sqlite) | 111 112## Workflow Format 113 114Workflows are defined in `.tangled/workflows/*.yaml` in your repository: 115 116```yaml 117image: golang:1.24 118architecture: amd64 119 120steps: 121 - name: Build 122 command: go build ./... 123 124 - name: Test 125 command: go test ./... 126``` 127 128## Security 129 130### Job Pod Security 131 132Jobs run with hardened security contexts: 133- Non-root user (UID 1000) 134- Minimal capabilities (only SETUID/SETGID for buildah) 135- No service account token mounting 136- Unconfined seccomp (required for buildah user namespaces) 137 138### Secrets 139 140Repository secrets are: 141- Stored in the spindle vault (SQLite or OpenBao) 142- Injected as environment variables via Kubernetes Secrets 143- Masked in log output 144 145## Prerequisites 146 147- go version v1.24.0+ 148- docker version 17.03+ 149- kubectl version v1.11.3+ 150- Access to a Kubernetes v1.11.3+ cluster 151 152## Deployment 153 154Build and push the image: 155 156```sh 157make docker-build docker-push IMG=<registry>/loom:tag 158``` 159 160Install the CRDs: 161 162```sh 163make install 164``` 165 166Deploy the controller: 167 168```sh 169make deploy IMG=<registry>/loom:tag 170``` 171 172## Development 173 174Generate CRDs and code: 175 176```sh 177make manifests generate 178``` 179 180Run tests: 181 182```sh 183make test 184``` 185 186Run locally (for debugging): 187 188```sh 189make install run 190``` 191 192## Uninstall 193 194```sh 195kubectl delete -k config/samples/ 196make uninstall 197make undeploy 198``` 199 200## License 201 202Copyright 2025 Evan Jarrett. 203 204Licensed under the Apache License, Version 2.0.