Overview
A state snapshot is a complete copy of a Genesis instance: its metadata database, its bot storage (uploads, threads, wiki, context graph, scheduler, apps, datasets) and its git repositories. It is written to an S3 or Google Cloud Storage (GCS) bucket in the layout of the Genesis Helm chart’s volume, so it can be restored onto any Kubernetes install of the chart. The same mechanism serves three purposes:
Taking a snapshot pauses the instance: the server stops, the snapshot is copied while nothing writes to the state, and the server starts again on its own. Repeat snapshots to the same location upload only files that changed and copy the rest inside the bucket.
What a snapshot contains
Each snapshot is a folder of ordinary objects under the location you choose:Before you start
- A bucket. Private. On S3, with KMS encryption; to restore in a different AWS account, use a customer-managed KMS key (
kms_key_id) and grant the target account use of it, because the AWS-managedaws/s3key cannot be shared across accounts. On GCS, with uniform bucket-level access (the default for new buckets) and public access prevention;kms_key_iddoes not apply. - Credentials for the source, GCS. The exporter lists, reads, writes and deletes objects (it finds the previous snapshot, copies unchanged files from it and removes a failed attempt), so grant
roles/storage.objectAdminon the bucket;roles/storage.objectCreatoralone makes snapshots fail. On GKE, use the pod’s Workload Identity: annotate the chart’s service account withiam.gke.io/gcp-service-accountfor a Google service account holding that role (or bind the Kubernetes service account directly). On Snowflake SPCS, create a GCP connection in Genesis with that service account’s key, and name it in the request asgcp_connection. A restore only reads, soroles/storage.objectVieweris enough for the restoring cluster. - Credentials for the source, S3. These are AWS credentials. On EKS, the pod’s identity (IRSA) with write access to the prefix. On other clusters (GKE, AKS), GKE Workload Identity and Azure identities grant nothing on AWS: either trust the cluster’s OIDC issuer in AWS IAM and give the pod
AWS_ROLE_ARNand a projected token atAWS_WEB_IDENTITY_TOKEN_FILE, or name a Genesis AWS connection in the request. On Snowflake SPCS there is no pod identity: create an AWS connection in Genesis with an access key that can write to the prefix, and name it in the request. - Network egress from SPCS. The Native App’s external access integration must allow the bucket’s endpoints: for S3,
<bucket>.s3.<region>.amazonaws.com; for GCS,storage.googleapis.comandoauth2.googleapis.com. - The master key. The target must use the same master key as the source, or it cannot decrypt any stored credential. Note the source’s key before migrating; on SPCS it is the Snowflake secret behind
CORE.GET_ENCRYPTION_KEY().
Take a snapshot
Requires an administrator: config panel access, viewing agents, users and connections, and managing secrets, all connections and the scheduler, because the snapshot carries every stored secret and stops the server. Listing jobs needs only the viewing permissions. Theconfirm field must equal the instance’s name, because the call pauses the instance. The destination must be an s3:// or gs:// prefix; local paths are accepted only by the export-state command inside the container. Name a Genesis connection for the destination’s cloud: aws_connection for s3://, gcp_connection for gs://; a connection for the other cloud is refused.
202 with a job id, then the instance stops. On a Helm chart install the container then ends and Kubernetes restarts it, so the startup probe, not the liveness probe, covers Genesis’s boot; the pod’s restart count goes up by one per snapshot. The chart’s liveness probe allows at least 180 seconds of failures, enough for the server to stop and the exporter to start answering it.
While the snapshot runs, the instance answers only its health checks and a progress endpoint:
succeeded, failed or aborted), the snapshot id, its size, and any error.
If the server has not finished shutting down two minutes after the request, it is stopped anyway and the snapshot proceeds. A container restart during a snapshot aborts that job; the instance comes back without it, and a new request starts over.
Restore onto Kubernetes
Setrestore.from on a new install of the Genesis Helm chart. An init container fills the empty volume from the snapshot before Genesis first starts:
The image must be a release after v1.19.93; earlier releases do not ship the restore. The chart refuses to render restore.from with a release tag (vX.Y.Z) at or below v1.19.93. A tag it cannot order, such as a build id, is not checked, and an image without the restore then fails in the init container.
gs:// snapshot, the restore reads the bucket through the pod’s Workload Identity: annotate the service account with iam.gke.io/gcp-service-account: <gsa>@<project>.iam.gserviceaccount.com for a Google service account that can read it, and let that account be impersonated by serviceAccount:<project>.svc.id.goog[<namespace>/<ksa>].
For an s3:// snapshot the restore reads the bucket with the pod’s AWS credentials, which the chart’s init container takes from the standard AWS chain. The service account annotation above is the EKS way. On GKE or AKS the cluster identity grants nothing on AWS: trust the cluster’s OIDC issuer in AWS IAM, mount a projected service-account token with that audience through extraVolumes and extraVolumeMounts, and set AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE in config.extraEnv. The init container receives the same environment and extra mounts as Genesis.
The signed manifest identifies the master key used to take the snapshot. Genesis
accepts a signature made with the current key or its bound previous key; the
published development key is not trusted as a previous key. If
requires_previous_key_fingerprint is present, secrets in the snapshot may
still be sealed with the source’s previous key, so that matching key must also
be bound here as the current or previous key. Restoring a snapshot signed under
the previous key restarts secret re-sealing; keep any previous key needed by
the restored secrets bound until those secrets have been re-sealed.
The restore refuses, and Genesis does not start, if:
- the manifest signature does not verify under the current or bound previous key, or a key named by
requires_previous_key_fingerprintis not bound here; - the snapshot comes from a newer Genesis release than the image (a development build, which has no release number, accepts a snapshot from any release);
- the manifest lists a path outside the volume’s trees, or the same path twice;
- any file is missing, unexpected or does not match its checksum, or the database fails its integrity check.
--runtime-root /app; without it, at the restored directories themselves). Only whole paths are rewritten: a directory that merely shares a prefix is left alone. The paths it rewrote, and any it could not, are recorded in db/.snapshot-restored-from.json, the one file the restore adds beside the snapshot’s; later snapshots leave it out.
Bots, the Slack app configuration and tunnel tokens belong to a runner, and a server serves only its own. The restore moves every runner-keyed row to the runner of the server that will serve the volume (RUNNER_ID, which the chart’s init container shares with Genesis; jl-local-runner when unset). Without this, a snapshot from Snowflake SPCS, whose bots belong to snowflake-1, would restore bots the Kubernetes server never starts. The rows it moved are recorded in db/.snapshot-restored-from.json under runner.
Migrating from Snowflake SPCS
- Upgrade the Native App to a release with state snapshot support, and allow the bucket’s endpoints in its external access integration.
- In Genesis on SPCS, create a connection for the snapshot bucket: a GCP connection whose service account holds
roles/storage.objectAdminon a GCS bucket (the path to GKE), or an AWS connection that can list, read, write and delete under an S3 prefix. - Record the SPCS master key and store it as a Kubernetes Secret in the target cluster.
- Take a snapshot with
gcp_connectionoraws_connectionset. The exporter converts the Snowflake metadata tables into the Kubernetes SQLite database, reading every table as of the same instant through Time Travel. A table with no history at that instant (created after it, or kept without Time Travel retention) is read as it is at copy time instead.reports/tables.json, written beside the trees, lists every table, its row count, whether it was read at the shared instant (point_in_time), duplicate keys it collapsed (Snowflake does not enforce keys), and any column that exists on only one side. A row SQLite would reject for any other reason fails the snapshot rather than being dropped. The report is for the operator: it is not in the signed manifest, and a restore neither downloads nor checks it. - Install the Helm chart with
restore.frompointing at the snapshot and the SPCS master key configured. - Configure authentication for the Kubernetes install (Auth0 or another OIDC provider). Snowflake sign-in does not carry over; users sign in again and roles are granted to their new identities.
- Replace connections that relied on the SPCS service’s own Snowflake session with regular Snowflake connections (key pair or OAuth).
reports/tables.json and review it. A table with skipped_duplicates above zero kept one row per key: where those rows differed in Snowflake, the other versions are not in the new database, so compare them in Snowflake and re-enter any that matter. A table with point_in_time: false was read later than the rest; check it if it changes often.
Once the Kubernetes install is verified, suspend the SPCS service so both instances do not run against the same external systems.
