Files
volt/docs/architecture.md
Karl Clinger 0ebe75b2ca Volt CLI: source-available under AGPSL v5.0
Complete infrastructure platform CLI:
- Container runtime (systemd-nspawn)
- VoltVisor VMs (Neutron Stardust / QEMU)
- Stellarium CAS (content-addressed storage)
- ORAS Registry
- GitOps integration
- Landlock LSM security
- Compose orchestration
- Mesh networking

Copyright (c) Armored Gates LLC. All rights reserved.
Licensed under AGPSL v5.0
2026-03-21 02:08:15 -05:00

602 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Volt Architecture
Volt is a unified platform management CLI built on three engines:
- **Voltainer** — Container engine (`systemd-nspawn`)
- **Voltvisor** — Virtual machine engine (KVM/QEMU)
- **Stellarium** — Content-addressed storage (CAS)
This document describes how they work internally and how they integrate with the host system.
## Design Philosophy
### systemd-Native
Volt works **with** systemd, not against it. Every workload is a systemd unit:
- Containers are `systemd-nspawn` machines managed via `volt-container@<name>.service`
- VMs are QEMU processes managed via `volt-vm@<name>.service`
- Tasks are `systemd timer` + `service` pairs
- All logging flows through the systemd journal
This gives Volt free cgroup integration, dependency management, process tracking, and socket activation.
### One Binary
The `volt` binary at `/usr/local/bin/volt` handles everything. It communicates with the volt daemon (`voltd`) over a Unix socket at `/var/run/volt/volt.sock`. For read-only operations like `volt ps`, `volt top`, and `volt service list`, the CLI can query systemd directly without the daemon.
### Human-Readable Everything
Every workload has a human-assigned name. `volt ps` shows names, not hex IDs. Status columns use natural language (`running`, `stopped`, `failed`), not codes.
## Voltainer — Container Engine
### How Containers Work
Voltainer containers are `systemd-nspawn` machines. When you create a container:
1. **Image resolution**: Volt locates the rootfs directory under `/var/lib/volt/images/`
2. **Rootfs copy**: The image rootfs is copied (or overlaid) to `/var/lib/volt/containers/<name>/rootfs/`
3. **Unit generation**: A systemd unit file is generated at `/var/lib/volt/units/volt-container@<name>.service`
4. **Network setup**: A veth pair is created, one end in the container namespace, the other attached to the specified bridge (default: `volt0`)
5. **Start**: `systemctl start volt-container@<name>.service` launches `systemd-nspawn` with the appropriate flags
### Container Lifecycle
```
create → stopped → start → running → stop → stopped → delete
↑ |
└── restart ───────┘
```
State transitions are all mediated through systemd. `volt container stop` is `systemctl stop`. `volt container start` is `systemctl start`. This means systemd handles process cleanup, cgroup teardown, and signal delivery.
### Container Isolation
Each container gets:
- **Mount namespace**: Own rootfs, bind mounts for volumes
- **PID namespace**: PID 1 is the container init
- **Network namespace**: Own network stack, connected via veth to bridge
- **UTS namespace**: Own hostname
- **IPC namespace**: Isolated IPC
- **cgroup v2**: Resource limits (CPU, memory, I/O) enforced via cgroup controllers
Containers share the host kernel. They are not VMs — there is no hypervisor overhead.
### Container Storage
```
/var/lib/volt/containers/<name>/
├── rootfs/ # Container filesystem
├── config.json # Container configuration (image, resources, network, etc.)
└── state.json # Runtime state (PID, IP, start time, etc.)
```
Volumes are bind-mounted into the container rootfs at start time.
### Resource Limits
Resource limits map directly to cgroup v2 controllers:
| Volt Flag | cgroup v2 Controller | File |
|-----------|---------------------|------|
| `--memory 1G` | `memory.max` | Memory limit |
| `--cpu 200` | `cpu.max` | CPU quota (percentage × 100) |
Limits can be updated on a running container via `volt container update`, which writes directly to the cgroup filesystem.
## Voltvisor — VM Engine
### How VMs Work
Voltvisor manages KVM/QEMU virtual machines. When you create a VM:
1. **Image resolution**: The base image is located or pulled
2. **Disk creation**: A qcow2 disk is created at `/var/lib/volt/vms/<name>/disk.qcow2`
3. **Kernel selection**: The appropriate kernel is selected from `/var/lib/volt/kernels/` based on the `--kernel` profile
4. **Unit generation**: A systemd unit is generated at `/var/lib/volt/units/volt-vm@<name>.service`
5. **Start**: `systemctl start volt-vm@<name>.service` launches QEMU with appropriate flags
### Kernel Profiles
Voltvisor supports multiple kernel profiles:
| Profile | Description |
|---------|-------------|
| `server` | Default. Optimized for server workloads. |
| `desktop` | Includes graphics drivers, input support for VDI. |
| `rt` | Real-time kernel for latency-sensitive workloads. |
| `minimal` | Stripped-down kernel for maximum density. |
| `dev` | Debug-enabled kernel with extra tracing. |
### VM Storage
```
/var/lib/volt/vms/<name>/
├── disk.qcow2 # Primary disk image
├── config.json # VM configuration
├── state.json # Runtime state
└── snapshots/ # VM snapshots
└── <snap-name>.qcow2
```
### VM Networking
VMs connect to volt bridges via TAP interfaces. The TAP device is created when the VM starts and attached to the specified bridge. From the network's perspective, a VM on `volt0` and a container on `volt0` are peers — they communicate at L2.
### VM Performance Tuning
Voltvisor supports hardware-level tuning:
- **CPU pinning**: Pin vCPUs to physical CPUs via `volt tune cpu pin`
- **Hugepages**: Use 2M or 1G hugepages via `volt tune memory hugepages`
- **I/O scheduling**: Set per-device I/O scheduler via `volt tune io scheduler`
- **NUMA awareness**: Pin to specific NUMA nodes
## Stellarium — Content-Addressed Storage
### How CAS Works
Stellarium is the storage backend shared by Voltainer and Voltvisor. Files are stored by their content hash (BLAKE3), enabling:
- **Deduplication**: Identical files across images are stored once
- **Integrity verification**: Every object can be verified against its hash
- **Efficient transfer**: Only missing objects need to be pulled
### CAS Layout
```
/var/lib/volt/cas/
├── objects/ # Content-addressed objects (hash → data)
│ ├── ab/ # First two chars of hash for fanout
│ │ ├── ab1234...
│ │ └── ab5678...
│ └── cd/
│ └── cd9012...
├── refs/ # Named references to object trees
│ ├── images/
│ └── manifests/
└── tmp/ # Temporary staging area
```
### CAS Operations
```bash
# Check store health
volt cas status
# Verify all objects
volt cas verify
# Garbage collect unreferenced objects
volt cas gc --dry-run
volt cas gc
# Build CAS objects from a directory
volt cas build /path/to/rootfs
# Deduplication analysis
volt cas dedup
```
### Image to CAS Flow
When an image is pulled:
1. The rootfs is downloaded/built (e.g., via debootstrap)
2. Each file is hashed and stored as a CAS object
3. A manifest is created mapping paths to hashes
4. The manifest is stored as a ref under `/var/lib/volt/cas/refs/`
When a container is created from that image, files are assembled from CAS objects into the container rootfs.
## Filesystem Layout
### Configuration
```
/etc/volt/
├── config.yaml # Main configuration file
├── compose/ # System-level Constellation definitions
└── profiles/ # Custom tuning profiles
```
### Persistent Data
```
/var/lib/volt/
├── containers/ # Container rootfs and metadata
├── vms/ # VM disks and state
├── kernels/ # VM kernels
├── images/ # Downloaded/built images
├── volumes/ # Named persistent volumes
├── cas/ # Stellarium CAS object store
├── networks/ # Network configuration
├── units/ # Generated systemd unit files
└── backups/ # System backups
```
### Runtime State
```
/var/run/volt/
├── volt.sock # Daemon Unix socket
├── volt.pid # Daemon PID file
└── locks/ # Lock files for concurrent operations
```
### Cache (Safe to Delete)
```
/var/cache/volt/
├── cas/ # CAS object cache
├── images/ # Image layer cache
└── dns/ # DNS resolution cache
```
### Logs
```
/var/log/volt/
├── daemon.log # Daemon operational log
└── audit.log # Audit trail of all state-changing operations
```
## systemd Integration
### Unit Templates
Volt uses systemd template units to manage workloads:
| Unit | Description |
|------|-------------|
| `volt.service` | Main volt daemon |
| `volt.socket` | Socket activation for daemon |
| `volt-network.service` | Network bridge management |
| `volt-dns.service` | Internal DNS resolver |
| `volt-container@<name>.service` | Per-container unit |
| `volt-vm@<name>.service` | Per-VM unit |
| `volt-task-<name>.timer` | Per-task timer |
| `volt-task-<name>.service` | Per-task service |
### Journal Integration
All workload logs flow through the systemd journal. `volt logs` queries the journal with appropriate filters:
- Container logs: `_SYSTEMD_UNIT=volt-container@<name>.service`
- VM logs: `_SYSTEMD_UNIT=volt-vm@<name>.service`
- Service logs: `_SYSTEMD_UNIT=<name>.service`
- Task logs: `_SYSTEMD_UNIT=volt-task-<name>.service`
### cgroup v2
Volt relies on cgroup v2 for resource accounting and limits. The cgroup hierarchy:
```
/sys/fs/cgroup/
└── system.slice/
├── volt-container@web.service/ # Container cgroup
├── volt-vm@db-primary.service/ # VM cgroup
└── nginx.service/ # Service cgroup
```
This is where `volt top` reads CPU, memory, and I/O metrics from.
## ORAS Registry
Volt includes a built-in OCI Distribution Spec compliant container registry. The registry is backed entirely by Stellarium CAS — there is no separate storage engine.
### CAS Mapping
The key insight: **an OCI blob digest IS a CAS address**. When a client pushes a blob with digest `sha256:abc123...`, that blob is stored directly as a CAS object at `/var/lib/volt/cas/objects/ab/abc123...`. No translation, no indirection.
```
OCI Client Volt Registry Stellarium CAS
───────── ───────────── ──────────────
PUT /v2/myapp/blobs/uploads/... ─→ Receive blob ─→ Store as CAS object
Content: <binary data> Compute sha256 digest objects/ab/abc123...
←──────────────────────────────────────────────────────────────
201 Created Index digest→repo
Location: sha256:abc123... in refs/registry/
```
Manifests are stored as CAS objects too, with an additional index mapping `repository:tag → digest` under `/var/lib/volt/cas/refs/registry/`.
### Deduplication
Because all storage is CAS-backed, deduplication is automatic and cross-system:
- Two repositories sharing the same layer → stored once
- A registry blob matching a local container image layer → stored once
- A snapshot and a registry artifact sharing files → stored once
### Architecture
```
┌────────────────────┐
│ OCI Client │ (oras, helm, podman, skopeo, etc.)
│ (push / pull) │
└────────┬───────────┘
│ HTTP/HTTPS (OCI Distribution Spec)
┌────────┴───────────┐
│ Registry Server │ volt registry serve --port 5000
│ (Go net/http) │
│ │
│ ┌──────────────┐ │
│ │ Tag Index │ │ refs/registry/<repo>/<tag> → digest
│ │ Manifest DB │ │ refs/registry/<repo>/manifests/<digest>
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ Auth Layer │ │ HMAC-SHA256 bearer tokens
│ │ │ │ Anonymous pull (configurable)
│ └──────────────┘ │
└────────┬───────────┘
│ Direct read/write
┌────────┴───────────┐
│ Stellarium CAS │ objects/ (content-addressed by sha256)
│ /var/lib/volt/cas │
└────────────────────┘
```
See [Registry](registry.md) for usage documentation.
---
## GitOps Pipeline
Volt's built-in GitOps system links Git repositories to workloads for automated deployment.
### Pipeline Architecture
```
┌──────────────┐ ┌──────────────────────────┐ ┌──────────────┐
│ Git Provider │ │ Volt GitOps Server │ │ Workloads │
│ │ │ │ │ │
│ GitHub ─────┼──────┼→ POST /hooks/github │ │ │
│ GitLab ─────┼──────┼→ POST /hooks/gitlab │ │ │
│ Bitbucket ──┼──────┼→ POST /hooks/bitbucket │ │ │
│ │ │ │ │ │
│ SVN ────────┼──────┼→ Polling (configurable) │ │ │
└──────────────┘ │ │ │ │
│ ┌─────────────────────┐ │ │ │
│ │ Pipeline Manager │ │ │ │
│ │ │ │ │ │
│ │ 1. Validate webhook │ │ │ │
│ │ 2. Clone/pull repo │─┼──┐ │ │
│ │ 3. Detect Voltfile │ │ │ │ │
│ │ 4. Deploy workload │─┼──┼──→│ container │
│ │ 5. Log result │ │ │ │ vm │
│ └─────────────────────┘ │ │ │ service │
│ │ │ └──────────────┘
│ ┌─────────────────────┐ │ │
│ │ Deploy History │ │ │
│ │ (JSON log) │ │ │ ┌──────────────┐
│ └─────────────────────┘ │ └──→│ Git Cache │
└──────────────────────────┘ │ /var/lib/ │
│ volt/gitops/ │
└──────────────┘
```
### Webhook Flow
1. Git provider sends a push event to the webhook endpoint
2. The GitOps server validates the HMAC signature against the pipeline's configured secret
3. The event is matched to a pipeline by repository URL and branch
4. The repository is cloned (or pulled if cached) to `/var/lib/volt/gitops/<pipeline>/`
5. Volt scans the repo root for `volt-manifest.yaml`, `Voltfile`, or `volt-compose.yaml`
6. The workload is created or updated according to the manifest
7. The result is logged to the pipeline's deploy history
### SVN Polling
For SVN repositories, a polling goroutine checks for revision changes at the configured interval (default: 60s). When a new revision is detected, the same clone→detect→deploy flow is triggered.
See [GitOps](gitops.md) for usage documentation.
---
## Ingress Proxy
Volt includes a built-in reverse proxy for routing external HTTP/HTTPS traffic to workloads.
### Architecture
```
┌─────────────────┐
│ Internet │
│ (HTTP/HTTPS) │
└────────┬────────┘
┌────────┴────────┐
│ Ingress Proxy │ volt ingress serve
│ │ Ports: 80 (HTTP), 443 (HTTPS)
│ ┌───────────┐ │
│ │ Router │ │ Hostname + path prefix matching
│ │ │ │ Route: app.example.com → web:8080
│ │ │ │ Route: api.example.com/v1 → api:3000
│ └─────┬─────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ TLS │ │ Auto: ACME (Let's Encrypt)
│ │ Terminator│ │ Manual: user-provided certs
│ │ │ │ Passthrough: forward TLS to backend
│ └───────────┘ │
│ │
│ ┌───────────┐ │
│ │ Health │ │ Backend health checks
│ │ Checker │ │ Automatic failover
│ └───────────┘ │
└────────┬────────┘
│ Reverse proxy to backends
┌────────┴────────┐
│ Workloads │
│ web:8080 │
│ api:3000 │
│ static:80 │
└─────────────────┘
```
### Route Resolution
Routes are matched in order of specificity:
1. Exact hostname + longest path prefix
2. Exact hostname (no path)
3. Wildcard hostname + longest path prefix
### TLS Modes
| Mode | Description |
|------|-------------|
| `auto` | Automatic certificate provisioning via ACME (Let's Encrypt). Volt handles certificate issuance, renewal, and storage. |
| `manual` | User-provided certificate and key files. |
| `passthrough` | TLS is forwarded to the backend without termination. |
### Hot Reload
Routes can be updated without proxy restart:
```bash
volt ingress reload
```
The reload is zero-downtime — existing connections are drained while new connections use the updated routes.
See [Networking — Ingress Proxy](networking.md#ingress-proxy) for usage documentation.
---
## License Tier Feature Matrix
| Feature | Free | Pro |
|---------|------|-----|
| Containers (Voltainer) | ✓ | ✓ |
| VMs (Voltvisor) | ✓ | ✓ |
| Services & Tasks | ✓ | ✓ |
| Networking & Firewall | ✓ | ✓ |
| Stellarium CAS | ✓ | ✓ |
| Compose / Constellations | ✓ | ✓ |
| Snapshots | ✓ | ✓ |
| Bundles | ✓ | ✓ |
| ORAS Registry (pull) | ✓ | ✓ |
| Ingress Proxy | ✓ | ✓ |
| GitOps Pipelines | ✓ | ✓ |
| ORAS Registry (push) | — | ✓ |
| CDN Integration | — | ✓ |
| Deploy (rolling/canary) | — | ✓ |
| RBAC | — | ✓ |
| Cluster Multi-Node | — | ✓ |
| Audit Log Signing | — | ✓ |
| Priority Support | — | ✓ |
---
## Networking Architecture
### Bridge Topology
```
┌─────────────────────────────┐
│ Host Network │
│ (eth0, wlan0, etc.) │
└─────────────┬───────────────┘
│ NAT / routing
┌─────────────┴───────────────┐
│ volt0 (bridge) │
│ 10.0.0.1/24 │
├──────┬──────┬──────┬─────────┤
│ veth │ veth │ tap │ veth │
│ ↓ │ ↓ │ ↓ │ ↓ │
│ web │ api │ db │ cache │
│(con) │(con) │(vm) │(con) │
└──────┴──────┴──────┴─────────┘
```
- Containers connect via **veth pairs** — one end in the container namespace, one on the bridge
- VMs connect via **TAP interfaces** — the TAP device is on the bridge, passed to QEMU
- Both are L2 peers on the same bridge, so they communicate directly
### DNS Resolution
Volt runs an internal DNS resolver (`volt-dns.service`) that provides name resolution for all workloads. When container `api` needs to reach VM `db`, it resolves `db` to its bridge IP via the internal DNS.
### Firewall
Firewall rules are implemented via `nftables`. Volt manages a dedicated nftables table (`volt`) with chains for:
- Input filtering (host-bound traffic)
- Forward filtering (inter-workload traffic)
- NAT (port forwarding, SNAT for outbound)
See [networking.md](networking.md) for full details.
## Security Model
### Privilege Levels
| Operation | Required | Method |
|-----------|----------|--------|
| Container lifecycle | root or `volt` group | polkit |
| VM lifecycle | root or `volt` + `kvm` groups | polkit |
| Service creation | root | sudo |
| Network/firewall | root | polkit |
| `volt ps`, `volt top`, `volt logs` | any user | read-only |
| `volt config show` | any user | read-only |
### Audit Trail
All state-changing operations are logged to `/var/log/volt/audit.log` in JSON format:
```json
{
"timestamp": "2025-07-12T14:23:01.123Z",
"user": "karl",
"uid": 1000,
"action": "container.create",
"resource": "web",
"result": "success"
}
```
## Exit Codes
| Code | Name | Description |
|------|------|-------------|
| 0 | `OK` | Success |
| 1 | `ERR_GENERAL` | Unspecified error |
| 2 | `ERR_USAGE` | Invalid arguments |
| 3 | `ERR_NOT_FOUND` | Resource not found |
| 4 | `ERR_ALREADY_EXISTS` | Resource already exists |
| 5 | `ERR_PERMISSION` | Permission denied |
| 6 | `ERR_DAEMON` | Daemon unreachable |
| 7 | `ERR_TIMEOUT` | Operation timed out |
| 8 | `ERR_NETWORK` | Network error |
| 9 | `ERR_CONFLICT` | Conflicting state |
| 10 | `ERR_DEPENDENCY` | Missing dependency |
| 11 | `ERR_RESOURCE` | Insufficient resources |
| 12 | `ERR_INVALID_CONFIG` | Invalid configuration |
| 13 | `ERR_INTERRUPTED` | Interrupted by signal |
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `VOLT_CONFIG` | Config file path | `/etc/volt/config.yaml` |
| `VOLT_COLOR` | Color mode: `auto`, `always`, `never` | `auto` |
| `VOLT_OUTPUT` | Default output format | `table` |
| `VOLT_DEBUG` | Enable debug output | `false` |
| `VOLT_HOST` | Daemon socket path | `/var/run/volt/volt.sock` |
| `VOLT_CONTEXT` | Named context (multi-cluster) | `default` |
| `VOLT_COMPOSE_FILE` | Default Constellation file path | `volt-compose.yaml` |
| `EDITOR` | Editor for `volt service edit`, `volt config edit` | `vi` |
## Signal Handling
| Signal | Behavior |
|--------|----------|
| `SIGTERM` | Graceful shutdown — drain, save state, stop workloads in order |
| `SIGINT` | Same as SIGTERM |
| `SIGHUP` | Reload configuration |
| `SIGUSR1` | Dump goroutine stacks to log |
| `SIGUSR2` | Trigger log rotation |