Kubernetes
Sahabti runs managed Kubernetes clusters inside your workspace's private network. We run the control plane and the virtual machines your workers run on, watch them, and replace a worker that stops working; you get a kubeconfig and use kubectl, Helm or any tool you already know.
Every node is a virtual machine in your network, so your pods reach your databases and other resources privately, and nothing in the cluster is exposed until you expose it.
Managed for you today
- The control plane. We run it, check it every minute and are alerted when it stops answering.
- Workers. We watch every worker and replace one that stops working, with the exceptions listed under When a worker stops working. One that fails to start or to join while the cluster is created or resized is replaced too, and a worker you remove is drained first.
- The platform's controller in your cluster, which connects it to volumes, load balancers and access, is updated by us, and an update that fails is rolled back.
Coming next
- Kubernetes version upgrades, and new node images rolled onto existing workers. Until then, a cluster stays on the Kubernetes version it was created with, and a worker keeps the node image it started with.
What you get
- Kubernetes 1.36.
- A fully managed control plane: we run and monitor it, and it is not shown to you, neither among the cluster's nodes nor in the console's charts. With high availability it is replicated across separate hosts, so the API keeps answering through a host failure.
- Workers in node pools of the size and zone you choose, with your own labels and taints. Add or remove workers at any time.
- Services of type
LoadBalanceron your workspace's public address, and Gateway API for routing HTTP by hostname. - Persistent volumes from two storage classes, one that deletes data with its claim and one that keeps it. See Kubernetes volumes.
kubectl top, horizontal pod autoscaling and network policies, ready to use, and pod security that refuses privileged pods by default.- A dashboard in the console for what runs in the cluster: workloads and container logs, services and routes, worker usage and events. See Kubernetes dashboard.
Plans and limits
| Free plan | Paid plans | |
|---|---|---|
| Clusters per workspace | 1 | 5 |
| Workers per cluster, across its node pools | 1 | up to 20 |
| Worker sizes | Free | Small, Medium, Large |
| High availability | — | yes |
| Volumes per cluster | 2, up to 10 GB each | 50, up to 1,000 GB each |
| Worker size | vCPU | Memory | Disk |
|---|---|---|---|
| Free | 2 | 4 GB | 20 GB |
| Small | 2 | 2 GB | 20 GB |
| Medium | 2 | 4 GB | 40 GB |
| Large | 4 | 8 GB | 80 GB |
The Free worker's network is capped at 10 Mbit/s, like a free instance's, and its two vCPUs share one core's worth of CPU time. Every worker keeps part of its CPU and memory for itself: see what a worker keeps for itself.
Workers of a paid size (Small, Medium and Large) count against your plan's quota exactly like instances: each one takes its vCPU, memory and disk from the same pool your instances draw on, so new workers need room in it. The Free worker does not count, and the control plane comes with every cluster without counting either. Volumes count against the disk quota too, and each Service of type LoadBalancer uses one of your load balancers — the free plan includes one, so to serve several HTTP sites through it, use Gateway API.
The workspace owner's plan decides which column applies. When the quota is used up, delete instances or workers you no longer need, or request an increase from Usage.
Workers of a paid size, a highly available control plane and volumes also have pay-as-you-go prices, metered by the hour; the Free worker and the standard control plane cost nothing. See Kubernetes pricing and usage.
Create a cluster
Open Kubernetes in your workspace and choose New cluster. A short wizard takes you through every choice. Back returns to an earlier step without losing what you entered, and nothing is created before the last step.
- Basics: the cluster's name (lowercase letters, digits and dashes; it names the cluster's nodes and cannot change later), the network its nodes join (a ready private network in this workspace), the Kubernetes version, and the region and zone its nodes run in.
- Control plane: we run it for you either way. If your plan includes it, choose Highly available, so the cluster's API keeps answering through a host failure; otherwise the cluster gets the Standard control plane. Each option shows its price per month.
- Node pools: the cluster's first pool, named
defaultunless you rename it, with its worker size, its number of workers and, if you need them, its labels and taints. Choose Add another pool for workloads that need machines of their own. The workers of every pool count against your plan's workers per cluster, and at least one pool must have noNoScheduleorNoExecutetaint, because the cluster's own services run there. Workers of a paid size also use your plan's quota, and the step shows how much, with each size's price per month and what each pool's workers cost. See Kubernetes node pools. - Features: the managed gateway, on by default, which serves the cluster's HTTP routes over HTTPS. Turn it off to run your own gateway or ingress controller. This step appears when the managed gateway is available for your workspace.
- Review: every choice, each with Edit to go back to its step, and what the cluster takes from your plan: its workers, the vCPU, memory and disk they add up to, how much of your plan's quota its paid-size workers use, and the workspace's clusters, and its Pay-as-you-go estimate per month. Select Create cluster.
The API and the agent tools take the same choices, node pools included.
The cluster goes from Queued to Creating — reserving addresses, starting the control plane, joining workers — and is Ready in about five minutes. The cluster's Overview shows each step as it happens.
Connect with kubectl
Open the cluster, go to Access and choose Download kubeconfig. The file is called <cluster>.kubeconfig:
export KUBECONFIG=~/Downloads/<cluster>.kubeconfig
kubectl get nodes
That kubeconfig renews its own credentials, using sh and curl (macOS, Linux and WSL). On a machine without them, choose 12-hour kubeconfig instead. Cluster access explains both and what your role lets you do.
The cluster's API endpoint, shown on its page, is on your workspace's public address and stays open to the internet until you limit it: in the cluster's Access section, API access can limit it to the networks you connect from, or make the cluster private, reachable only from inside your workspace network, as Who can reach the API explains. To the addresses it admits, it answers health checks to anyone and everything else only to a valid credential.
Deploy a workload
kubectl create deployment web --image=nginx:1.27-alpine --replicas=2
kubectl get pods -o wide
Pods spread over your workers and can reach anything else in your private network by its private address. The cluster's Workloads section shows them too, with their logs.
Expose it
A load balancer for a Service
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
kubectl apply -f web-service.yaml
kubectl get service web
The Service gets a platform load balancer on your workspace's public address, which appears as its external IP, and traffic is spread over every worker. Your workspace shares that address between its load balancers, so each port of the Service answers on a public port of its own, between 40000 and 49999, which the platform records on the Service:
kubectl get service web -o jsonpath='{.metadata.annotations.service\.sahabti\.com/public-ports}'
# 80/TCP=40001
Connect to that port. Deleting the Service releases its load balancer. It is listed on the Load balancers page, marked Kubernetes and read-only: change it through the Service, which can also choose who reaches it, deny sources, and set its protection profile, idle timeout and client IP mode, as Configure a LoadBalancer Service explains. It also has a hostname of its own under lb.sahabti.app: connect to it with that port, for example prod-default-web-a1b2c3d4-k3x9mq.lb.sahabti.app:40001. A shared address has no port 443 to give, so for HTTPS without a port use the managed gateway below.
Route HTTP by hostname
The simplest way to serve HTTP sites over HTTPS is the cluster's managed gateway, which needs no load balancer and takes care of certificates for you: see Expose HTTP services.
To run a Gateway of your own instead, every cluster comes with Gateway API and a gateway class named sahabti, so one load balancer can serve many sites. Creating a Gateway needs the Admin role on the cluster; editors can create routes, but not Gateways:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
spec:
gatewayClassName: sahabti
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
spec:
parentRefs:
- name: public
hostnames:
- shop.example.com
rules:
- backendRefs:
- name: web
port: 80
kubectl get gateway public shows its address once it is programmed. Like every load balancer of a cluster, the Gateway's is on your workspace's shared address, where its port 80 answers on a public port of its own: the Load balancers page lists it, marked Kubernetes, with that port under Listeners, and the Service created for the Gateway carries it in its service.sahabti.com/public-ports annotation. Point the hostname's DNS at the address and connect on that port; requests for hostnames no route claims get 404. For HTTPS, add a listener with protocol: HTTPS and a certificate Secret you create in the cluster.
Add or remove workers
Workers belong to node pools. In the cluster's Nodes, edit a pool to change its number of workers, or add a node pool of another size. When you remove workers, the cluster keeps a worker in every zone that holds one of its volumes as long as the count you ask for allows it, removes workers that are not ready yet or have no volume attached before the others, and otherwise removes the newest first. Each is taken out of the load balancers, drained — your pod disruption budgets get up to 15 minutes — and its volumes move to the workers that remain in their zone. New workers go first to a zone that holds one of the cluster's volumes but has no worker. Kubernetes node pools explains pools and their labels and taints.
New workers of a paid size need room in your plan's quota; removing workers never does. The cluster shows Updating while it changes and keeps serving the whole time.
When a worker stops working
We watch every worker. One that has not been ready for 10 minutes, or whose virtual machine is gone, is replaced by a new virtual machine of the same size and name on the same host, so the volumes it held attach to it again. While that happens the cluster shows Updating and keeps serving from its other workers, and an event with the reason WorkerReplacing on the node says why: you see it in the cluster's Events and with kubectl get events. Pods that ran on the worker are scheduled again as usual.
- One worker per cluster is replaced at a time, and never while the cluster is changing or its control plane has a problem. The control plane is ours to repair, and a problem there alerts our team.
- When most of a cluster's workers stop being ready at the same time, the cause is rarely the workers themselves, so none is replaced and our team looks into it.
- A worker whose host is down is not replaced: it starts again when its host is back.
- A replacement that cannot finish within 30 minutes leaves the cluster Degraded with "A worker could not be replaced". The other workers keep running, and our team is alerted.
What we tell you
We tell the workspace's owners and admins by email when one of these starts, and again when it is over, once each time. While it lasts, the cluster's Overview shows it too, under the cluster's name:
- The cluster is degraded: a change did not finish, with the reason. Its workloads keep running on the workers that are ready.
- Workers not ready for more than 15 minutes, by name: the cases in which a worker is not replaced on its own, listed under When a worker stops working.
- A volume more than 85% full, with how full it is. It is over once the volume is below 80% full, so a volume that hovers around the line is not mailed about twice. Grow it from the cluster's Storage.
- Its Kubernetes version reaches the end of its support within 30 days, then again within 7 days: after that day it gets no more security fixes. The dates are in the Kubernetes reference.
The emails go to the addresses the owners and admins sign in with, in the language each of them chose. A workspace gets at most 10 such emails an hour; any more follow in the next hour.
Delete a cluster
Every cluster starts with deletion protection on: while it is on, nobody can delete the cluster, from the console, the API or an AI assistant. To delete one, a cluster admin, or a workspace owner or admin, first turns it off under Deletion protection in the cluster's Settings. Only they can, and only signed in to the console: API tokens and AI assistants can turn protection on, never off, whatever they are allowed to do otherwise. A cluster created through the API with deletionProtection: false, for one you mean to delete again, starts without it.
Then, in the cluster's Settings, choose Delete cluster and type its name. Every node and workload is destroyed, its load balancers are released, and volumes of the default class are deleted with their data. Volumes of the sahabti-retain class are kept in your workspace's Volumes page. Kubeconfigs for the cluster stop working.
What is yours to look after
We run and monitor the control plane, keep the cluster's API reachable, and run the workers and replace one that stops working; upgrades are still to come, as listed at the top of this page. Your workloads, their configuration and their data are yours: a persistent volume is not a backup, so keep your own backups of anything you cannot recreate, and be deliberate about who gets a kubeconfig or the Editor role on a cluster.
Names the platform uses in your cluster
The platform creates and keeps these in every cluster. Leave them in place, and give your own objects other names:
| Name | Kind | What it is |
|---|---|---|
sahabti-delete, sahabti-retain | StorageClass | The two storage classes; sahabti-delete is the default |
sahabti | GatewayClass | For Gateways of your own, each behind a load balancer |
sahabti-managed | GatewayClass | The managed gateway's own class; do not use it for your Gateways |
sahabti, in namespace sahabti-gateway | Gateway | The managed gateway: attach routes to it, and do not edit it |
sahabti | IngressClass | Ingresses the managed gateway serves |
sahabti:cluster-admins, sahabti:developers, sahabti:viewers | Group | The roles of the people with access |
service.sahabti.com/public-ports | Annotation on a Service | The public port each of its ports answers on |
lb.sahabti.com/… | Annotations on a Service | Its load balancer's settings |
service.sahabti.com/load-balancer | Finalizer on a Service | Holds a deleted Service until its load balancer is released |
node.sahabti.com/pool | Label on a worker | Its node pool |
topology.disk.sahabti.com/zone | Label on a worker | The zone it is in, which volumes are tied to |
The managed gateway runs in the sahabti-gateway namespace and one more; like kube-system, kube-public and kube-node-lease, they belong to the platform.