Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/on-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ jobs:
{ "example": "examples/celldstacks/standard.yaml" },
{ "example": "examples/celldstacks/azurite.yaml" },
{ "example": "examples/celldstacks/aws.yaml" },
{ "example": "examples/celldstacks/aws-local.yaml" }
{ "example": "examples/celldstacks/aws-local.yaml" },
{ "example": "examples/celldstacks/rustfs.yaml" },
{ "example": "examples/celldstacks/rustfs.yaml", "observed_resources": "examples/celldstacks/observed/rustfs-ready.yaml" }
]
api_path: apis/celldstacks
error_on_missing_schemas: true
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/on-push-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ jobs:
{ "example": "examples/celldstacks/standard.yaml" },
{ "example": "examples/celldstacks/azurite.yaml" },
{ "example": "examples/celldstacks/aws.yaml" },
{ "example": "examples/celldstacks/aws-local.yaml" }
{ "example": "examples/celldstacks/aws-local.yaml" },
{ "example": "examples/celldstacks/rustfs.yaml" },
{ "example": "examples/celldstacks/rustfs.yaml", "observed_resources": "examples/celldstacks/observed/rustfs-ready.yaml" }
]
api_path: apis/celldstacks
error_on_missing_schemas: true
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ EXAMPLES := \
examples/celldstacks/standard.yaml:: \
examples/celldstacks/azurite.yaml:: \
examples/celldstacks/aws.yaml:: \
examples/celldstacks/aws-local.yaml::
examples/celldstacks/aws-local.yaml:: \
examples/celldstacks/rustfs.yaml:: \
examples/celldstacks/rustfs.yaml::examples/celldstacks/observed/rustfs-ready.yaml

render\:all:
@tmpdir=$$(mktemp -d); \
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ spec:
clusterName: my-cluster
```

This installs one celld node in namespace `celld`. For local, enable Azurite. For AWS, enable `spec.aws` so the stack creates a bucket and binds the celld ServiceAccount with EKS Pod Identity.
This installs one celld node in namespace `celld`. For local, enable RustFS (S3-compatible, recommended) or Azurite. For AWS, enable `spec.aws` so the stack creates a bucket and binds the celld ServiceAccount with EKS Pod Identity.

```yaml
spec:
Expand All @@ -29,6 +29,38 @@ spec:
enabled: true
```

### Stage 1b: Local S3 with RustFS

```yaml
spec:
clusterName: my-cluster
rustfs:
enabled: true
buckets:
- game-services # extra application buckets; the fleet bucket is always created
credentialsSecretRef:
name: celld-rustfs-credentials
```

Create the root credentials yourself; the stack never generates or embeds keys:

```bash
kubectl -n default create secret generic celld-rustfs-credentials \
--from-literal=RUSTFS_ACCESS_KEY="$(openssl rand -hex 12)" \
--from-literal=RUSTFS_SECRET_KEY="$(openssl rand -hex 24)"
```

The stack copies that Secret into the release namespace as `<releaseName>-rustfs`
with `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION` and
`S3_ENDPOINT` added, installs the official RustFS chart (standalone, one PVC),
creates `celld` plus the extra buckets with a one-shot Job (which also deploys
a fetch-only placeholder Worker once, only into an empty fleet bucket, so pod
restarts never replace your application), and then installs
celld against `s3://celld` on `http://<releaseName>-rustfs-svc.<namespace>.svc.cluster.local:9000`.
Applications in the cluster can mount the same `<releaseName>-rustfs` Secret
(`envFrom`) to reach their buckets with any S3 client. RustFS takes precedence
over `azurite` and `aws`.

### Stage 2: Growing — AWS S3

```yaml
Expand Down Expand Up @@ -80,6 +112,13 @@ Not applicable. This stack installs a Helm release; it does not adopt cloud reso
| `credentials.existingSecret` | string | — | Secret with AWS_* keys (R2 / BYO). Unused when `aws.enabled` |
| `azurite.enabled` | boolean | `false` | Deploy in-cluster Azurite and point celld at `az://celld` (local/dev only) |
| `azurite.container` | string | `celld` | Blob container name |
| `rustfs.enabled` | boolean | `false` | Deploy in-cluster RustFS and point celld at it (wins over `azurite`/`aws`) |
| `rustfs.bucket` | string | `celld` | Fleet bucket name |
| `rustfs.buckets` | string[] | — | Extra buckets to create |
| `rustfs.credentialsSecretRef` | object | `<name>-rustfs-credentials` in the XR namespace | Secret with `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` |
| `rustfs.persistence.size` | string | `2Gi` | RustFS data volume |
| `rustfs.persistence.storageClassName` | string | cluster default | RustFS storage class |
| `rustfs.values` | object | — | RustFS chart values merged with stack defaults |
| `aws.enabled` | boolean | `false` | Create S3 bucket and point celld at `s3://` |
| `aws.bucketName` | string | `hops-celld-<name>` | Globally unique S3 bucket name |
| `aws.region` | string | `us-east-2` | Bucket region |
Expand All @@ -95,11 +134,13 @@ Not applicable. This stack installs a Helm release; it does not adopt cloud reso

| Field | Meaning |
|-------|---------|
| `ready` | Namespace, Helm release, Usage, and (when `aws.enabled`) bucket + Pod Identity are Ready |
| `ready` | Namespace, Helm release, Usage, and (when `aws.enabled`) bucket + Pod Identity, or (when `rustfs.enabled`) RustFS + buckets are Ready |
| `release.name` / `release.namespace` | Installed Helm release |
| `service.name` / `service.port` | Public Worker Service (port 8080) |
| `bucket.name` / `bucket.id` | Observed S3 bucket when `aws.enabled` |
| `podIdentity.roleArn` / `podIdentity.associationId` | Observed Pod Identity when `aws.enabled` |
| `rustfs.endpoint` / `rustfs.credentialsSecret` | In-cluster S3 endpoint and the Secret S3 clients mount (when `rustfs.enabled`) |
| `rustfs.buckets` / `rustfs.ready` | Buckets the bucket Job ensured; credentials, RustFS and buckets ready |

## Composed Resources

Expand All @@ -110,6 +151,10 @@ Not applicable. This stack installs a Helm release; it does not adopt cloud reso
| `<name>-s3` | `s3.aws.m.upbound.io/Bucket` (when `aws.enabled`) |
| `<name>-celld` | `aws.hops.ops.com.ai/PodIdentity` (when `aws.enabled`) |
| `<name>-delete-helm-celld-before-namespace` | `protection.crossplane.io/Usage` (after both are Ready) |
| `<name>-rustfs-creds` | `kubernetes.m.crossplane.io/Object` (Secret, when `rustfs.enabled`) |
| `<releaseName>-rustfs` | `helm.m.crossplane.io/Release` (RustFS, when `rustfs.enabled`) |
| `<name>-rustfs-buckets-<hash>` | `kubernetes.m.crossplane.io/Object` (bucket Job, when `rustfs.enabled`) |
| `<name>-delete-helm-celld-before-rustfs`, `-before-rustfs-creds`, `<name>-delete-helm-rustfs-before-namespace`, `-before-rustfs-creds` | `protection.crossplane.io/Usage` (RustFS teardown order) |
| `<name>-delete-helm-celld-before-pod-identity` | `protection.crossplane.io/Usage` (after Helm + Pod Identity are Ready) |

## Development
Expand Down
61 changes: 59 additions & 2 deletions apis/celldstacks/definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,48 @@ spec:
default: 2Gi
storageClassName:
type: string
rustfs:
description: Deploy in-cluster RustFS (S3-compatible) and point celld at it. Takes precedence over azurite and aws. Local/dev and single-node use.
type: object
properties:
enabled:
description: When true, install RustFS (standalone), create the buckets, and configure celld for s3://<bucket> on the RustFS endpoint.
type: boolean
default: false
bucket:
description: celld fleet bucket name. Defaults to "celld".
type: string
pattern: '^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$'
buckets:
description: Additional buckets to create for applications (for example game-services blobs). The fleet bucket is always created.
type: array
items:
type: string
pattern: '^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$'
credentialsSecretRef:
description: Secret holding the RustFS root credentials as RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY. Copied into the release namespace with AWS_* aliases and S3_ENDPOINT. Defaults to <name>-rustfs-credentials in the XR namespace. Must not be the copied Secret itself (<releaseName>-rustfs in the release namespace). No credentials are generated or stored in the manifest.
type: object
properties:
name:
type: string
namespace:
type: string
persistence:
description: Persistent volume for RustFS object data.
type: object
properties:
size:
type: string
default: 2Gi
storageClassName:
description: Storage class. Empty uses the cluster default.
type: string
values:
description: Helm values merged on top of the RustFS chart defaults set by this stack.
type: object
x-kubernetes-preserve-unknown-fields: true
aws:
description: Provision an S3 bucket and grant the celld pod access. On EKS, Pod Identity is the default. Locally, disable podIdentity and reuse hops `local aws` credentials. Ignored when azurite.enabled is true.
description: Provision an S3 bucket and grant the celld pod access. On EKS, Pod Identity is the default. Locally, disable podIdentity and reuse hops `local aws` credentials. Ignored when azurite.enabled or rustfs.enabled is true.
type: object
properties:
enabled:
Expand Down Expand Up @@ -237,7 +277,7 @@ spec:
type: object
properties:
ready:
description: Whether the namespace, Helm release, and deletion Usage are ready.
description: Whether the namespace, Helm releases, buckets, and deletion Usage are ready.
type: boolean
release:
description: Observed Helm release.
Expand Down Expand Up @@ -269,6 +309,23 @@ spec:
type: string
ready:
type: boolean
rustfs:
description: Observed in-cluster RustFS object store.
type: object
properties:
endpoint:
description: In-cluster S3 endpoint URL.
type: string
credentialsSecret:
description: Secret in the release namespace with AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION and S3_ENDPOINT for S3 clients.
type: string
buckets:
description: Buckets the bucket Job created or found.
type: array
items:
type: string
ready:
type: boolean
podIdentity:
description: Observed EKS Pod Identity binding the celld ServiceAccount to the fleet bucket.
type: object
Expand Down
70 changes: 70 additions & 0 deletions examples/celldstacks/observed/rustfs-ready.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Observed state for examples/celldstacks/rustfs.yaml once every step is up:
# credentials copied, RustFS installed, buckets created, celld installed.
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
metadata:
name: celld-namespace
namespace: default
annotations:
crossplane.io/composition-resource-name: namespace
status:
conditions:
- type: Ready
status: "True"
---
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
metadata:
name: celld-rustfs-creds
namespace: default
uid: 0b6d3f0e-0000-4000-8000-000000000001
annotations:
crossplane.io/composition-resource-name: rustfs-creds-secret
status:
conditions:
- type: Ready
status: "True"
---
apiVersion: helm.m.crossplane.io/v1beta1
kind: Release
metadata:
name: celld-rustfs
namespace: default
annotations:
crossplane.io/composition-resource-name: helm-release-rustfs
status:
atProvider:
revision: 1
conditions:
- type: Ready
status: "True"
---
apiVersion: kubernetes.m.crossplane.io/v1alpha1
kind: Object
metadata:
name: celld-rustfs-buckets-f68067af
namespace: default
annotations:
crossplane.io/composition-resource-name: rustfs-buckets-job
status:
atProvider:
manifest:
status:
succeeded: 1
conditions:
- type: Ready
status: "True"
---
apiVersion: helm.m.crossplane.io/v1beta1
kind: Release
metadata:
name: celld
namespace: default
annotations:
crossplane.io/composition-resource-name: helm-release-celld
status:
atProvider:
revision: 1
conditions:
- type: Ready
status: "True"
13 changes: 13 additions & 0 deletions examples/celldstacks/rustfs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: hops.ops.com.ai/v1alpha1
kind: CelldStack
metadata:
name: celld
namespace: default
spec:
clusterName: my-cluster
rustfs:
enabled: true
buckets:
- game-services
credentialsSecretRef:
name: celld-rustfs-credentials
51 changes: 46 additions & 5 deletions functions/render/000-state-init.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,16 @@
{{- $azuritePersistence := $azurite.persistence | default dict }}
{{- $azuritePersistenceEnabled := true }}
{{- if hasKey $azuritePersistence "enabled" }}{{- $azuritePersistenceEnabled = $azuritePersistence.enabled }}{{- end }}
{{- $rustfs := $spec.rustfs | default dict }}
{{- $rustfsEnabled := false }}
{{- if hasKey $rustfs "enabled" }}{{- $rustfsEnabled = $rustfs.enabled }}{{- end }}
{{- if $rustfsEnabled }}
{{- $azuriteEnabled = false }}
{{- end }}
{{- $aws := $spec.aws | default dict }}
{{- $awsEnabled := false }}
{{- if hasKey $aws "enabled" }}{{- $awsEnabled = $aws.enabled }}{{- end }}
{{- if $azuriteEnabled }}
{{- if or $azuriteEnabled $rustfsEnabled }}
{{- $awsEnabled = false }}
{{- end }}
{{- $awsForceDestroy := true }}
Expand All @@ -83,8 +89,27 @@
{{- if and $awsEnabled (ne $awsCredsName "") }}
{{- $awsCredsLocalName = printf "%s-aws" $releaseName }}
{{- end }}
{{- $rustfsName := printf "%s-rustfs" $releaseName }}
{{- $rustfsBucket := $rustfs.bucket | default "celld" }}
{{- $rustfsBuckets := list $rustfsBucket }}
{{- range ($rustfs.buckets | default list) }}
{{- if not (has . $rustfsBuckets) }}
{{- $rustfsBuckets = append $rustfsBuckets . }}
{{- end }}
{{- end }}
{{- $rustfsCredsRef := $rustfs.credentialsSecretRef | default dict }}
{{- $rustfsPersistence := $rustfs.persistence | default dict }}
{{- $rustfsEndpoint := printf "http://%s-svc.%s.svc.cluster.local:9000" $rustfsName $namespace }}
{{- $endpoint := $spec.endpoint | default "" }}
{{- $existingSecret := $credentials.existingSecret | default "" }}
{{- if $rustfsEnabled }}
{{- $endpoint = $rustfsEndpoint }}
{{- $existingSecret = $rustfsName }}
{{- end }}
{{- $bucket := $spec.bucket | default "" }}
{{- if $azuriteEnabled }}
{{- if $rustfsEnabled }}
{{- $bucket = printf "s3://%s" $rustfsBucket }}
{{- else if $azuriteEnabled }}
{{- $bucket = printf "az://%s" $azuriteContainer }}
{{- else if $awsEnabled }}
{{- $bucket = printf "s3://%s" $awsBucketName }}
Expand Down Expand Up @@ -114,11 +139,11 @@
"awsProviderConfigRef" $awsProviderConfigRef
"serviceName" $serviceName
"bucket" $bucket
"endpoint" ($spec.endpoint | default "")
"region" $awsRegion
"endpoint" $endpoint
"region" (ternary "us-east-1" $awsRegion $rustfsEnabled)
"xrNamespace" $xrNamespace
"credentials" (dict
"existingSecret" ($credentials.existingSecret | default "")
"existingSecret" $existingSecret
"sharedCredentialsSecret" $awsCredsLocalName
"sharedCredentialsKey" "credentials"
)
Expand Down Expand Up @@ -155,6 +180,22 @@
"storageClassName" ($azuritePersistence.storageClassName | default "")
)
)
"rustfs" (dict
"enabled" $rustfsEnabled
"name" $rustfsName
"endpoint" $rustfsEndpoint
"bucket" $rustfsBucket
"buckets" $rustfsBuckets
"credentialsSecretRef" (dict
"name" ($rustfsCredsRef.name | default (printf "%s-rustfs-credentials" $name))
"namespace" ($rustfsCredsRef.namespace | default $xrNamespace)
)
"persistence" (dict
"size" ($rustfsPersistence.size | default "2Gi")
"storageClassName" ($rustfsPersistence.storageClassName | default "")
)
"values" ($rustfs.values | default dict)
)
"image" (dict
"repository" ($image.repository | default "ghcr.io/denoland/celld")
"tag" $imageTag
Expand Down
Loading
Loading