Files
kuber/README.md
T

301 lines
8.5 KiB
Markdown

# kuber
`kuber` is Astral's internal Docker Compose to Kubernetes translation layer.
It reads a local Compose file, renders Kubernetes resources, applies them to the cluster, and can build images remotely from your working tree without requiring Docker on your machine.
## What It Does
- Reads `compose.yml` or `docker-compose.yml`
- Converts supported Compose services into Kubernetes resources
- Applies those resources into a namespace named after the current directory
- Turns `env_file` entries into Kubernetes Secrets and mounts them through `envFrom`
- Translates file mounts into ConfigMaps and directory/volume mounts into PVC-backed volumes
- Builds images on the remote builder by syncing:
- committed git state
- tracked local diffs
- untracked files
- ignored `.env*` files
- Supports host-based `ports` syntax that renders Kubernetes `Ingress` rules
- Supports managed Postgres claims through special pseudo-volumes such as `postgresql:app`
- Supports managed S3 buckets and credentials through pseudo-volumes such as `s3:app`
## Environment Assumptions
This tool is built for Astral's cluster and workstation setup. It is not intended to work unchanged outside that environment.
Expected local setup:
- Tailscale access to the remote builder and Kubernetes network
- a working `~/.kube/config`
- `ssh` available locally
Not required locally:
- Docker
- `kubectl`
Docker is not required because builds happen on the remote builder after `kuber` syncs your repo state there. `kubectl` is not required because cluster access is handled through the bundled Kubernetes client.
## Running
During development:
```bash
bun run index.ts up
```
Run the dedicated unit suite and type checks:
```bash
bun run test
bun run typecheck
```
Other useful commands:
```bash
bun run index.ts ps
bun run index.ts logs
bun run index.ts logs -f
bun run index.ts exec app sh
bun run index.ts start
bun run index.ts stop
bun run index.ts restart
bun run index.ts db ls
```
### Shell Completion
Generate and load completions for your shell:
```bash
source <(kuber complete zsh)
source <(kuber complete bash)
```
For a permanent setup, write the generated script to a file and source it from your shell configuration. Fish and PowerShell are also supported through `kuber complete fish` and `kuber complete powershell`.
## Commands
- `up`: build images if needed, render manifests, apply them, restart deployments whose image content changed, and wait for rollout
- `start`: same as `up` but skips image builds
- `stop`: scale managed deployments to zero
- `restart`: roll out a restart across managed deployments
- `down`: delete managed resources while keeping ingress, PVCs, managed databases, and managed S3 storage
- `down -f`: also delete ingress, PVCs, managed database and S3 resources, and the namespace
- `ps`: list deployments in the current project namespace
- `logs [deployment]`: print logs for one deployment or all managed deployments
- `logs -f [deployment]`: follow logs continuously
- `exec <deployment> <command...>`: execute a command inside a running deployment pod
- `db ls`: list managed Postgres claims declared in the current Compose file
- `db creds <service>`: print the generated connection details for a managed Postgres claim
## Compose Conventions
`kuber` supports a few project-specific Compose conventions on top of normal service translation.
### Host-Based Ports
If a `ports` entry uses a hostname instead of a numeric published port, `kuber` treats it as an ingress host and routes traffic to the target container port.
Example:
```yml
services:
app:
ports:
- somedomain.astrxl.dev:3000
```
That produces a Kubernetes `Ingress` rule for `somedomain.astrxl.dev` pointing at the service port for container port `3000`.
Single-level wildcard subdomains are supported. Quote wildcard entries so YAML does not treat the leading `*` as an alias:
```yml
services:
app:
ports:
- "*.astrxl.dev:3000"
- "*.secure.astrxl.dev:3001:protected"
```
Protected routes use the kuber dialect and render Traefik `IngressRoute` resources instead of plain Kubernetes `Ingress`:
```yml
services:
app:
ports:
- db.astrxl.dev:4984:protected
- status.astrxl.dev:3001:protected(/dashboard,/socket.io)
```
Translation rules:
- `host:port` -> Kubernetes `Ingress`
- `host:port:protected` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and host-wide matching
- `host:port:protected(path1,path2,...)` -> Traefik `IngressRoute` with middleware `routing/cf-auth` and explicit `PathPrefix(...)` matches only
### Managed Postgres
You can declare a managed Postgres database with a pseudo-volume:
```yml
services:
app:
volumes:
- postgresql:app
```
Or with an explicit username and database name:
```yml
services:
app:
volumes:
- postgresql:user/database
```
This creates or reuses the managed CNPG role secret, reconciles the database resource, and injects `DATABASE_URL` into the generated app secret in Kubernetes.
### Managed S3
Declare a Garage S3 bucket and access key with a pseudo-volume:
```yml
services:
app:
volumes:
- s3:app
```
This creates `GarageBucket/app` and `GarageKey/app` in `garage-system`. To use different key and bucket names:
```yml
services:
app:
volumes:
- s3:app-key/shared-assets
```
The Garage operator generates the credentials. `kuber` reads its generated Secret and injects these values into the service's `<service>-env` Secret:
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_ENDPOINT_URL_S3`
- `AWS_REGION`
- `S3_BUCKET`
One service can declare both `postgresql:...` and `s3:...`; all generated values are merged into the same service Secret. Managed Garage buckets and keys are retained by normal `down` and deleted by `down -f`.
### Environment Files
`env_file` entries are read locally and turned into a Kubernetes `Secret` named `<service>-env`. Deployments then consume that secret through `envFrom`.
This is also where generated values such as `DATABASE_URL` and the managed S3 environment are injected.
### Volumes
`kuber` treats different volume shapes differently:
- file bind mounts become ConfigMaps
- directory bind mounts become PVC-backed mounts
- named volumes become PVC-backed mounts
- `tmpfs` becomes `emptyDir` with memory backing
- `postgresql:...` is treated as a managed database claim, not as a filesystem mount
- `s3:...` is treated as a managed object-storage claim, not as a filesystem mount
Named volumes also support kuber-specific storage hints.
Default behavior:
```yml
# compose
services:
app:
volumes:
- myvolume:/data
# effective kuber interpretation
services:
app:
volumes:
- myvolume(1Gi on 2 fast):/data
```
Short syntax:
```yml
services:
app:
volumes:
- data(20Gi):/data
- archive(200Gi on archive):/archive
- cache(10Gi on 1 fast):/cache
```
Meaning:
- `name(20Gi):/path` -> PVC size `20Gi`
- `name(20Gi on archive):/path` -> PVC size `20Gi`, `diskTag: ["archive"]`, `dataLocality: "none"`
- `name(20Gi on 1 archive):/path` -> PVC size `20Gi`, `replicaCount: 1`, `diskTag: ["archive"]`, `dataLocality: "none"`
Explicit extensions are also supported.
Top-level named volume:
```yml
volumes:
data:
x-size: 20Gi
x-diskTag: [archive]
x-replicaCount: 1
x-dataLocality: none
```
Long-form service mount:
```yml
services:
app:
volumes:
- type: volume
source: data
target: /data
volume:
x-size: 20Gi
x-diskTag: [archive]
x-replicaCount: 1
x-dataLocality: none
```
Precedence:
- short syntax like `data(20Gi on 1 archive):/data`
- long-form `volume.x-*`
- top-level `volumes.<name>.x-*`
- fallback default `1Gi on 2 fast`
## Building
Image builds and image-digest checks run through the selected SSH builder. If a pushed image has the same content fingerprint as the existing registry image, `up` does not restart that deployment.
To build distributable binaries:
```bash
bun run build.ts
```
If you only want a plain JavaScript bundle for quick local use in another workspace, build `index.ts` directly:
```bash
bun build index.ts --target bun --minify --sourcemap --outdir dist
```
## Notes
- Resource names and namespaces are derived from the current working directory.
- The remote build flow is optimized for local iteration, not for producing a perfectly clean export of the repository.
- Managed database support is Kubernetes-only. It injects `DATABASE_URL` into the generated app secret and does not rewrite local `.env` files.
- `kuber` operates on managed resources in the namespace matching the current directory name.