Skip to content

About

Substrate customizations on GKE

Resources

Stars

49 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

72 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

substrate-gke

GKE packaging for Agent Substrate: an interactive terminal installer that provisions the required GCP resources and installs the Substrate control plane onto a GKE cluster.

Tip

New here? Run make dry-run first β€” it walks the entire wizard without touching GCP, so you can see every prompt before committing to anything.

Warning

This creates billable GCP resources. A completed install provisions a GKE cluster, a snapshot bucket, IAM bindings, monitoring dashboards, and (for some source builds) an Artifact Registry repository. See Tearing down to remove them β€” the wizard prints the exact cleanup command at the end of every install.

The installer's welcome screen

Table of contents

Quickstart

Prerequisites

Tool Notes
gcloud Authenticated, with application-default credentials
Go Version checked automatically β€” run make substrate-pin-check if unsure
git β€”
kubectl β€”
gke-gcloud-auth-plugin gcloud components install gke-gcloud-auth-plugin, or the google-cloud-cli-gke-gcloud-auth-plugin package when gcloud came from apt or dnf
Docker with buildx Only to build Substrate 0.2 or later from source, which builds its envoy-dataplane image with docker buildx. Docker also needs credentials for your registry: gcloud auth configure-docker <registry-host>
A GCP project with billing Checked on the project step, which can also enable the GKE API for you
# One-line install and launch:
curl -sSL https://raw.githubusercontent.com/ai-on-gke/substrate-gke/main/install.sh | bash

Or from a local clone:

git clone https://github.com/ai-on-gke/substrate-gke.git
cd substrate-gke
gcloud auth application-default login
make run          # launch the interactive installer

Useful variants

Command What it does
make doctor Preflight checks only β€” no GCP calls
make dry-run Walks the full wizard without touching GCP

What the installer does

A terminal wizard walks the ten steps below, running the real command it shows and streaming its output as it goes:

# Step What happens
1 βœ… Check your setup Probes gcloud, application-default credentials, Go, kubectl, gke-gcloud-auth-plugin, network reachability, git, and Docker with buildx and registry credentials β€” with copy-paste fixes for anything missing. Docker is only needed to build Substrate 0.2 or later from source, so those checks only warn here
2 πŸ–ΌοΈ Choose your images Pre-built images (the default), or build your own from a commit β€” see Where the images come from
3 πŸ—οΈ Choose your GCP project Validated live with gcloud projects describe, then checked for billing and the GKE API (container.googleapis.com), which the cluster step needs. A disabled GKE API can be enabled right there with [e]. Checks an explicit image registry when Docker is needed; the default regional registry is checked after cluster selection
4 πŸ”— Connect your cluster Lists your GKE clusters with install-state badges, or creates a new one (1.36 by default, overridable in advanced setup). Clusters already running Substrate are protected by a reinstall guard
5 βš™οΈ Provision GCP resources setup-gcp bootstrap β€” APIs, cluster (if new), per-cluster snapshot bucket, IAM grants, monitoring dashboards, and an optional image repository. Idempotent
6 πŸš€ Turn on Substrate ate-setup deploy ate-system β€” installs CRDs, the API server, controller, atenet, and atelet, plus the bundled Kubernetes Secrets credential provider for egress credential injection. Export ATE_CREDENTIAL_PROVIDER before starting the installer to choose another, e.g. {"enabled":false} to turn injection off
7 πŸ’Ύ Install Filestore CSI driver (optional) Deploys the GCP Filestore CSI Driver configured for Substrate
8 πŸ“ˆ Configure autoscaling (optional) Node-pool autoscaling via gcloud
9 πŸ“¦ Choose your sandbox runtime (optional) gVisor (installed with the control plane) or micro-VM β€” see Sandbox runtimes
10 🎬 Deploy a demo workload (optional) Upstream counter demo (counter on gVisor, or counter-microvm on micro-VM), plus live verification and next steps

Note

Exiting and re-running is safe; every step is idempotent. A cluster that already runs Substrate is blocked from reinstall by the wizard's guard (preventing broken, mixed-version states); the wizard offers an in-place teardown or points at Upgrading an installed cluster instead.

Connect your cluster Blocked: already installed
Cluster list with install-state badges The reinstall guard
Provisioning Complete
Provisioning GCP resources The completion screen
Why the steps are ordered this way (click to expand)
  • Setup check runs first because the next step (images) is the first one to reach the network.

  • Images comes before the project step because the answer decides what that step needs β€” a pre-built install pushes nothing, so it's never asked for a registry.

  • Connecting an existing cluster probes it to confirm Substrate isn't already running there, guarding against mixed-version installs. It also badges whether the cluster meets Substrate's cluster requirements: GKE 1.36 or newer, serving the PodCertificate APIs. GKE 1.37 serves them as v1, which Substrate uses; below 1.37 they are beta APIs that GKE serves only for clusters that opted in. Node pools count as well as the control plane β€” the kubelet is what serves pod certificates, and GKE lets pools trail the control plane:

    Release Remedy
    1.37+, and every pool on 1.37+ Nothing to fix. Bootstrap still turns the beta APIs on, which costs a control-plane update of about ten minutes but changes nothing Substrate depends on.
    1.36, or any pool below 1.37, without the beta APIs Provision enables them in place (about ten minutes per control-plane update; bootstrap may make several). But the kubelet serves pod certificate projection only on nodes created after it, so those pools have to be replaced β€” and before Substrate is turned on, since its own control plane mounts pod certificates. Provision ends on a list of them, in place of the "turn on Substrate" prompt: add a new pool of the same shape, move workloads to it, delete the old one. On a later run the APIs are already on and the cluster badges ready, but provision still lists pools below 1.37 to check, since the installer can't tell whether they were replaced. Upgrading a pool to the version it already runs doesn't help; GKE skips it without replacing any node.
    below 1.36 Not fixable in place: 1.36 is the oldest release Substrate is supported on, and below 1.35 GKE rejects the enablement outright because PodCertificateRequest didn't reach v1beta1 until then. The control plane has to be upgraded first.

    New clusters are created at 1.36 by default, with the beta APIs on from the start, so no repair applies. 1.37 would need no beta APIs at all, but GKE carries it only in the Rapid channel, which the installer can't select yet.

  • Filestore CSI driver is optional and separate from autoscaling because configuring a Filestore VolumePool afterward is an additional step, not automatic.

  • Sandbox runtime comes right before the demo so steps 1–8 finish setting up the cluster, storage, and node pools first, and step 10 immediately deploys the matching demo (counter or counter-microvm).

Sandbox runtimes

Substrate runs each actor inside a sandbox, and step 9 chooses which kind.

gVisor Micro-VM
Isolation syscall interception in userspace hardware virtualization (kata + cloud-hypervisor)
Installed by the control-plane step, always step 9, on request
Node requirement none /dev/kvm on the node

gVisor is the default and needs nothing extra: ate-setup applies the cluster-wide gvisor-default SandboxConfig as part of turning Substrate on.

Micro-VM is opt-in, because its sandbox binaries β€” cloud-hypervisor, virtiofsd, the guest kernel, the guest rootfs and the base kata config β€” are fetched from the snapshot bucket at boot rather than baked into the worker image. Step 9 (Choose your sandbox runtime) reports whether the cluster you selected has a KVM-capable node pool, and on choosing Micro-VM it runs upstream's hack/install-microvm-deps.sh --install to assemble those five assets, upload them under kata-assets/ in the snapshot bucket, and apply a cluster-wide SandboxConfig named microvm that points at them.

Actor templates must then name it explicitly:

sandboxConfig:
  sandboxClass: SANDBOX_CLASS_MICROVM
  configName: microvm

Warning

The installer does not yet provision KVM-capable node pools. A micro-VM worker needs /dev/kvm; atelet advertises the ate.dev/kvm extended resource only on nodes that have the device, and the scheduler places micro-VM workers by that resource. On a cluster of ordinary nodes the install succeeds and micro-VM workers then stay Pending indefinitely.

Before installing, ask GKE what your node pools are, since nested virtualization can only be set when a pool is created:

gcloud container node-pools list --cluster CLUSTER --location LOCATION \
  --format='table(name,config.machineType,config.advancedMachineFeatures.enableNestedVirtualization)'

You need at least one pool with enableNestedVirtualization: True. Note that e2 machine types cannot do nested virtualization at all β€” use n1, n2, n2d, or a bare-metal (-metal) machine type.

If you taint that pool to reserve it, use ate.dev/sandboxClass=microvm:NoSchedule. That is the only toleration micro-VM worker pods carry, so a pool tainted with any other key repels them β€” and nothing above catches it: the pool has the hardware, atelet advertises ate.dev/kvm, the capacity check below reports the resource present, and the workers still sit Pending. To reserve a pool with a different key, add your own toleration to the WorkerPool's spec.template.tolerations, which is merged with the built-in one rather than replacing it.

After installing, confirm atelet actually found the device:

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.ate\.dev/kvm}{"\n"}{end}'

This second check is only meaningful once Substrate is running: the resource is published by atelet, so on a cluster without it every node reports empty regardless of what hardware it has.

Until node provisioning lands, bring your own KVM-capable pool β€” a node type with nested virtualization enabled, or bare metal, where KVM is native.

Upgrading and teardown: The upgrade flow (--upgrade) updates the control plane and gVisor SandboxConfig, but does not re-stage kata-assets/; after upgrading a cluster that uses micro-VM across a Substrate revision bump, target the cluster's kube context and re-run NO_DEV_ENV=1 ARCH=amd64 BUCKET_NAME=<bucket> hack/install-microvm-deps.sh --install so the bucket assets and microvm SandboxConfig digests stay in sync. Full teardown (ate-setup delete ate-system) removes the SandboxConfig CRD and all SandboxConfig resources automatically; if you want to drop micro-VM without tearing anything else down, run NO_DEV_ENV=1 hack/install-microvm-deps.sh --delete against the cluster's kube context (the staged assets in kata-assets/ remain inert in the bucket).

Where the images come from

The images step chooses between two ways of getting the Substrate control-plane images. Both name a commit of agent-substrate/substrate β€” ate-setup reads deployment manifests from that source tree either way. The repository is fixed; the commit is yours to choose.

Pre-built images (default) Build from source
You provide Registry, tag, and commit β€” all pre-filled, all overridable A revision: branch, tag, or full commit SHA
Default value v0.4.0-gke.0 at us-docker.pkg.dev/gke-substrate-release/substrate, pinned to upstream's v0.4.0 tag Repo's current HEAD, resolved live via git ls-remote
Needs a registry of yours? No β€” pull-only Yes β€” built with ko and pushed there
Best for Just getting Substrate running A branch or commit with no published images

For source builds using the default registry, the installer creates ate-images at <region>-docker.pkg.dev/<project>/ate-images. The region follows the selected cluster. Advanced mode can override the repository name or use an existing image registry. Pre-built installs and custom image registries skip repository creation.

Important

If you point at a custom registry/tag, move the commit with it. Only the release registry is guaranteed to match its tags β€” images from anywhere else need the commit they were built from, or they'll run behind manifests from a different Substrate. The wizard warns you as soon as the registry or tag changes.

Other details worth knowing (click to expand)
  • ate-setup pins every image to the digest its tag resolves to β€” nothing is built or pushed for a pre-built install.
  • The tag doubles as the Substrate version (it names the atelet DaemonSet and sets the ate.dev/substrate-version node label), so it must be a valid Kubernetes label value. The wizard validates this at the prompt. A tag carrying its digest (v0.1.0-gke.1@sha256:…) is fine β€” the version is the tag alone.
  • For build-from-source, a checkout of your own (fork included) is installed with --substrate-root instead β€” see How Substrate itself is obtained.
  • Whatever you name is resolved to an exact commit before the install starts, and checked against the remote via a filtered shallow fetch β€” so a naming mismatch is caught at the prompt, not ten minutes into an install.

How Substrate itself is obtained

ate-setup needs a Substrate source tree on disk at install time regardless of which images you chose β€” a pre-built install skips the build, not the checkout. That's why both paths in the images step ask for a revision.

The installer fetches it with a shallow git fetch pinned to an exact commit, into:

<user cache dir>/substrate-gke/substrate-<short commit>

One directory per commit, so two revisions never collide. The default pin is public, so no credentials are needed for it.

Caution

This tree is scratch space for one install, not somewhere to work. It is deleted once the install succeeds, and re-running the installer fetches it again. If you want to develop against Substrate, use your own clone β€” see below.

# Point at your own checkout instead (handy for testing an unmerged change):
cd installer && go run . --substrate-root=/path/to/substrate

A checkout you supply this way is used as-is and never modified or deleted.

GCP provisioning is the exception: the setup-gcp bootstrap step runs this repository's tools/setup-gcp, not a copy from the Substrate tree, whatever revision you chose. The tool moved here from upstream (agent-substrate/substrate#2304). The installer finds it above its working directory or its executable; pass --setup-gcp=/path/to/tools/setup-gcp if you run the installer from somewhere else.

Failure and retry behavior (click to expand)
  • Nothing is deleted while an install could still be retried β€” a failed run leaves the tree in place, so retrying costs no re-download.
  • The tree is staged under a temporary name and moved into place only once complete, so an interrupted fetch costs you the download and nothing else.

Logs

Every command's full output is written to a timestamped log under <user cache dir>/substrate-gke/logs/. Press v in the wizard for a scrollable log viewer; failures show the extracted cause and the log path.

Upgrading an installed cluster

Caution

Do not re-run the install track against a cluster that already runs Substrate (the wizard blocks it). Run the installer and choose Upgrade an installed cluster instead.

That flow names the cluster, reads what it currently runs, takes the new version from the images step, fetches both the installed and new source trees into <user cache dir>/substrate-gke/upgrades/, and prints the hand-over in runbook order for upstream's rolling upgrade runbook: the runbook itself, the variables its commands use, which tree to check out, the environment for ate-setup, and what a rollback changes. Nothing on the cluster changes until you follow the runbook.

When a source upgrade needs the default Artifact Registry repository, the hand-over includes its setup commands.

The installed tree is fetched at the commit the running API server reports it was built from (Go stamps every binary; ateapi --version prints it). If the cluster can't be read, or the binary carries no commit, you'll be prompted to enter the commit, version, and image registry. For source builds, enter the registry the images were pushed to.

Tearing down

An install creates billable resources: the GKE cluster, the snapshot bucket, IAM bindings, monitoring dashboards, and any image repository provisioned for a source build.

./tools/cleanup-gcp --project <project> --cluster <cluster> --location <zone> --bucket <bucket>
# or:
make teardown PROJECT_ID=<project> CLUSTER_NAME=<cluster> CLUSTER_LOCATION=<zone> BUCKET_NAME=<bucket>

Tip

The exit summary from your install prints this exact invocation pre-filled β€” copy it from there rather than retyping values.

The script asks for confirmation, then runs this repository's tools/setup-gcp/teardown.sh. It needs only gcloud. cleanup-gcp sets NO_DEV_ENV=1, so teardown.sh ignores any .ate-dev-env.sh in the working directory; run directly, teardown.sh sources that file. Already-deleted resources are skipped. Failed deletion steps are listed at the end, and the script exits non-zero.

Repositories are kept by default; the installer adds --delete-repository when it provisioned one, and --repository if the name is not ate-images. The region is derived from the cluster location. Replace --delete-repository with --keep-repository if the images are shared. With make teardown, use DELETE_REPOSITORY=true and, for a custom name, pass ARTIFACT_REGISTRY_REPOSITORY=<name> on the command line.

To remove only the Substrate control plane and keep the cluster:

ate-setup delete ate-system   # printed in the exit summary

APIs enabled by the install are left enabled β€” they cost nothing while unused.

Development

make test         # unit tests for installer/ and tools/setup-gcp/, including a scripted dry-run walk of the wizard
make verify       # gofmt + go vet for both modules
make screenshots  # regenerate the README screenshots from the dry-run wizard

Bumping the pinned commit

This changes what fresh installs get. Edit Commit in installer/internal/snapshot/snapshot.go, and update MinGoVersion next to it to match the go directive in that revision's go.mod.

make substrate-pin        # print the current pinned values
make substrate-pin-check  # verify MinGoVersion against upstream's go.mod β€” run after every bump

Warning

Skipping make substrate-pin-check after a bump lets the preflight doctor pass while the install fails mid-bootstrap.

Bump ReleaseVersion and Commit together when a newer release is published β€” Commit has to be the manifest revision those images were actually built from. All three (Commit, ReleaseRepo, ReleaseVersion) are defaults, not limits: the wizard accepts any registry, tag, or revision, and the build-from-source track never uses Commit at all.

Glossary

Term Meaning
ate-setup CLI that installs/upgrades/deletes the Substrate control plane on a cluster
atenet, atelet Substrate control-plane components installed alongside the API server and controller
PodCertificate APIs The certificates.k8s.io APIs Substrate requires: clustertrustbundles and podcertificaterequests. Substrate uses v1 where it's served (GKE 1.37+) and falls back to v1beta1. Below 1.37 the v1beta1 APIs are beta, and GKE serves them only for clusters that enabled them β€” at creation, or later with gcloud container clusters update --enable-kubernetes-unstable-apis
Pinned commit The exact commit of agent-substrate/substrate this installer's manifests and default images are built from

About

Substrate customizations on GKE

Resources

Stars

49 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages