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.
- Quickstart
- What the installer does
- Sandbox runtimes
- Where the images come from
- How Substrate itself is obtained
- Logs
- Upgrading an installed cluster
- Tearing down
- Development
- Glossary
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 | bashOr 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 installerUseful variants
| Command | What it does |
|---|---|
make doctor |
Preflight checks only β no GCP calls |
make dry-run |
Walks the full wizard without touching GCP |
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 |
|---|---|
| Provisioning | Complete |
|---|---|
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
PodCertificateAPIs. GKE 1.37 serves them asv1, 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 PodCertificateRequestdidn't reachv1beta1until 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 (
counterorcounter-microvm).
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: microvmWarning
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).
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-setuppins 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-versionnode 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-rootinstead β 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.
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/substrateA 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.
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.
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.
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 summaryAPIs enabled by the install are left enabled β they cost nothing while unused.
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 wizardThis 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 bumpWarning
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.
| 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 |