Skip to content

Deploy the Kubernetes cluster bootstrap template with OpenTofu

Deployment · Updated Jun 2026

Coming from another cloud?

▸AWS·Amazon EKS Cluster

Amazon EKS Clusterhigh

  • EKS control plane fully AWS-managed, single-tenant, across 3 AZs with auto scale/replace; on Quake AI you run the control plane on Nova instances, provisioned through Magnum or self-managed with OpenTofu, and you operate it.
  • EKS regional API endpoint with SLA; Quake AI exposes the kube API via Neutron LB with floating IP.
  • EKS charges a per-hour cluster platform fee on top of the underlying compute; Quake AI charges only for underlying Nova/Neutron/Cinder resources with no K8s platform fee.
  • EKS managed nodes auto AMI updates, Spot integration; Quake AI self-managed nodes require manual OS image selection and update management.
AWS docs ↗
▸Azure·AKS Cluster

AKS Clusterhigh

  • Azure automatically provisions and manages the control plane at no additional cost (Free tier) or fixed fee (Standard tier with SLA), offloading health monitoring and upgrades; on Quake AI you provision a cluster through Magnum (openstack coe cluster create) or self-managed Kubernetes on Nova instances (OpenTofu plus kubeadm, k3s, or RKE2), and you operate the cluster after creation.
  • No OpenStack integration; uses Azure Resource Manager for cluster lifecycle.
  • Pre-configured with Azure-specific defaults and add-ons like application routing.
  • Managed via Azure Virtual Machine Scale Sets (VMSS) with auto-scaling and upgrades; Quake AI uses Nova instances provisioned via OpenTofu with user-managed scaling and upgrades.
Azure docs ↗
▸DigitalOcean·Kubernetes

This Quake AI feature maps to DigitalOcean’s Kubernetes.

▸Google Cloud·GKE Cluster

GKE Clusterhigh

  • GKE provides Autopilot mode with fully managed node provisioning and scaling by Google; on Quake AI you provision clusters through Magnum or self-managed Kubernetes on Nova instances and manage node scaling yourself.
  • Control plane is fully managed with automatic upgrades through release channels; Quake AI requires user-provisioned and user-managed control plane nodes.
  • Cluster creation uses gcloud CLI vs OpenStack CLI (openstack coe cluster create).
  • Custom machine types, spot VMs, accelerators in node pools; Quake AI K8s nodes use standard Nova flavors.
Google Cloud docs ↗

Deploy the Kubernetes cluster bootstrap template with OpenTofu

Stand up a kubeadm cluster on Compute instances using the validated OpenTofu template k8s-cluster. Cloud-init runs kubeadm init and installs Flannel on the bootstrap control plane; you join workers after apply and run kubectl on the control plane over SSH because the API listens on the private subnet only.

This template bootstraps Kubernetes with kubeadm on Compute instances. It is separate from the managed Kubernetes service (Magnum). For a Magnum-based cluster, see Deploy your first app on Kubernetes.

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on a mix of shared and dedicated vCPU.

Starting template$238.80/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Control plane

m2a.xlarge · 4 dedicated vCPU, 16 GiB RAM, 1 Gbps

$132.00/mo

3× Worker node

s1a.medium · 4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$99.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

m2a.xlarge

4 dedicated vCPU, 16 GiB RAM, 1 Gbps

$132.00

s1a.medium

4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$33.00

s1a.medium

4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$33.00

s1a.medium

4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$33.00

Compute + RAM rate basis

16 vCPU + 28 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (160 GiB)

160 GiB at $0.08/GiB/mo

$12.80

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Kubernetes control plane

Magnum clusters run on Nova instances; there is no separate K8s platform fee in Quake AI pricing.

Managed Kubernetes on AWS, GCP, and Azure charges a control-plane fee on top of worker nodes.

Learn more
$0.00

Dev/test vs production

Start on shared CPU for dev/test, then promote to dedicated for production with a flavor resize. The network, storage, and template stay the same.

Dev/test on shared CPU

Burstable s1a flavors; suited to prototyping and low or bursty load.

$139.80/mo

Production on the configured CPU

The headline estimate above; predictable steady-load performance.

$238.80/mo

Saves $99.00/mo while you build on shared CPU.

Shared flavors carry less RAM (m2a.xlarge (16 GiB RAM) -> s1a.medium (4 GiB RAM)). A resize reboots the instance; data on attached volumes persists. Size the dedicated flavor for the RAM your production workload needs.

Pricing data last validated: . For current rates, check quake.ai/pricing.

Quake AIFloating IPSSH :22Private network192.168.70.0/24Routerto PublicStaticControl planekubeadm initWorkerkubeadm join SSHjoin
Click to zoom
Kubernetes cluster topology: private subnet, control plane with floating IP for SSH, and worker nodes joined after apply

Prerequisites#

You need:

Step 1: Configure variables#

Copy terraform.tfvars.example to terraform.tfvars and set:

HCL
key_name     = "YOUR_KEY_NAME"
worker_count = 1

Defaults for flavors, Kubernetes version, and network CIDR are documented on the Kubernetes Cluster Bootstrap reference page.

Step 2: Apply the template#

From the template directory, run:

bash
tofu init
tofu plan
tofu apply

Type yes when prompted. Expect 10 to 15 minutes before the API responds while cloud-init runs kubeadm init and applies Flannel.

When the run finishes, note api_endpoint from the outputs.

Step 3: Wait for the control plane to finish bootstrapping#

SSH to the control plane through the floating IP and wait for cloud-init and kubeadm init:

bash
API_HOST=$(tofu output -raw api_endpoint | cut -d: -f1)
ssh -i YOUR_PRIVATE_KEY_PATH -o StrictHostKeyChecking=accept-new ubuntu@"${API_HOST}" \
  'cloud-init status --wait && test -f /etc/kubernetes/admin.conf && echo control-plane-ready'

Confirm the API listener responds locally on the control plane:

bash
ssh -i YOUR_PRIVATE_KEY_PATH ubuntu@"${API_HOST}" \
  'sudo kubectl --kubeconfig=/etc/kubernetes/admin.conf get nodes'

The control plane node should report Ready before you join a worker.

Step 4: Run kubectl on the control plane#

Define a shell helper that runs kubectl on the control plane over SSH:

bash
k8s() {
  ssh -i YOUR_PRIVATE_KEY_PATH ubuntu@"${API_HOST}" \
    "sudo kubectl --kubeconfig=/etc/kubernetes/admin.conf $*"
}

k8s get nodes

Step 5: Join the worker node#

Cloud-init on workers installs Kubernetes packages but does not run kubeadm join. SSH to the worker uses ProxyJump through the control plane because worker nodes have no public address:

bash
WORKER_IP=$(tofu output -json worker_ips | python3 -c 'import json,sys; print(json.load(sys.stdin)[0])')

ssh -i YOUR_PRIVATE_KEY_PATH \
  -o StrictHostKeyChecking=accept-new \
  -J ubuntu@"${API_HOST}" \
  ubuntu@"${WORKER_IP}" \
  'cloud-init status --wait && command -v kubeadm'

JOIN_CMD=$(ssh -i YOUR_PRIVATE_KEY_PATH ubuntu@"${API_HOST}" 'sudo kubeadm token create --print-join-command')

ssh -i YOUR_PRIVATE_KEY_PATH \
  -J ubuntu@"${API_HOST}" \
  ubuntu@"${WORKER_IP}" \
  "sudo ${JOIN_CMD}"

Run k8s get nodes -o wide. Both nodes should read Ready.

Step 6: Deploy a test workload#

bash
k8s create deployment hello-k8s --image=nginx:1.27-alpine
k8s rollout status deployment/hello-k8s --timeout=120s
k8s get pods -o wide
k8s delete deployment hello-k8s

Next steps#

Clean up#

Run tofu destroy from the project directory when finished. Type yes to confirm. Verify in the Console that the instances and floating IP are gone.

Was this page helpful?