Skip to content

Clusters

A cluster is a managed Kubernetes cluster owned by your organization. It hosts one or more projects and the plugins installed on it. Clusters are managed by Gardener on top of bare-metal infrastructure provisioned with metal-stack; see Infrastructure for the layers underneath.

Clusters → Add cluster starts a three-step wizard.

How many clusters your organization may have is a quota set by the platform operator; a new organization may have one.

Field Notes
Name Lowercase letters, digits, - and .; must start and end with a letter or digit. Maximum 253 characters. Must be unique within the organization.
Region Picked from the region catalog offered by the installation. The region determines which Kubernetes versions and machine types you can choose.
Kubernetes version The versions offered by the chosen region.

Define one or more node pools. A node pool is a group of identical machines; the machine types on offer come from the region you picked in step 1. Use separate pools when you need different machine sizes or want to isolate workloads onto their own hardware.

Review everything and confirm. Provisioning is asynchronous: the cluster appears in the cluster list and moves through its states while the platform builds it.

Regions group the physical locations an installation offers. Within a region, availability zones give failure isolation for workloads that need it. The platform’s position on availability zones is recorded in ADR 0009.

Node pools can be viewed and changed from the cluster’s Nodes tab after creation. A pool has a name, a machine type and an autoscaler range:

Field Notes
Name Unique within the cluster. Fixed after creation.
Machine type Must be offered by the cluster’s region. Fixed after creation.
Minimum nodes Lower bound of the autoscaler. May be 0.
Maximum nodes Upper bound of the autoscaler. Must be greater than or equal to the minimum.

Editing a pool means changing its minimum and maximum. Save changes applies the whole form at once: pools you added are created, pools you removed are deleted, and pools whose bounds you changed are updated. Because name and machine type are fixed, moving a workload to different hardware means adding a pool with the new machine type and removing the old one. The platform does not convert a pool in place.

The minimum and maximum are the bounds of the Kubernetes cluster autoscaler: nodes are added when pods cannot be scheduled and removed when they are no longer needed, within that range. The Nodes tab shows each pool’s current node count alongside its bounds. There is no manual “set the node count to N” operation; set the minimum instead.

A cluster runs only the node pools you give it. A new cluster starts once its first node pool is added; until then it waits, and its status and events say so (“Creating cluster: waiting for a node pool”). A cluster’s last node pool cannot be removed on its own: replace it in one step instead, which the console’s node pools sheet and the OpenTofu provider do by saving the new set of pools (the old pool goes and the new one comes in the same change, so it fits within your quota). At least one pool must have a maximum above 0, so a cluster always has somewhere to run its nodes.

Node pool changes are asynchronous, like cluster creation: the console saves them and the platform reconciles the cluster’s worker pools with Gardener in the background. Nodes are replaced rather than modified: adding, removing or shrinking a pool takes the nodes involved out of service, so drain-sensitive workloads should have a PodDisruptionBudget.

The platform operator sets, per organization, how many clusters it may have and how many nodes of each machine type it may have in a region. Both are enforced when something new is made, never against what already runs:

  • Clusters. Adding a cluster beyond the quota fails with a message saying so. A new organization may have one cluster. A deleted cluster counts until the platform has torn it down, so the room it took comes back a little after the delete.
  • Nodes. Per region and machine type, the maximum size of every node pool with that machine type counts, across all the organization’s clusters, whether or not the autoscaler ever reaches it. A new organization has no node quota at all, so its first node pool needs the operator first: without a quota the refusal says so. With one, creating a pool, or raising a pool’s maximum, that would take the sum over the quota is refused with a message naming the sum and the quota. Pools on a deleted cluster count until the cluster is torn down.

A quota lowered below what you already run changes nothing about it: your clusters and pools keep running, and a pool may still be shrunk. It only stops the next cluster, or the next new or larger pool, until there is room again.

Organization → Quotas shows every quota next to what counts against it: the clusters you have, and per region and machine type the sum of your pools’ maximum sizes. It is read-only. To request an increase in quotas, contact a Fundament operator; the funops page says what they run.

The Defaults block sets the resource requests and limits containers on this cluster get for each request or limit they leave unset. Every namespace Fundament manages on the cluster inherits them (one created straight on the cluster with kubectl gets nothing), and a project may narrow them but never widen them. See Defaults.

Each cluster’s Namespaces tab lists the namespaces on that cluster and which project owns them. See Namespaces.

Each cluster’s Plugins tab shows what is installed and lets cluster admins install more from the catalog. Plugins are per cluster: two clusters in the same organization can run a different set, at different versions.

Each cluster has its own private network and its own firewall. Services get their addresses automatically: one that only works inside the cluster by default, and a public one for a Service of type LoadBalancer or a Gateway. See Service IP addresses for where those addresses come from and how long you keep them.

Access to a cluster’s Kubernetes API goes through a kubeconfig you download per cluster. There is no separate kubeconfig per namespace: one kubeconfig covers the whole cluster, and what you may actually do in it is decided per request (see What the kubeconfig grants below).

From the console: open the cluster and use Download kubeconfig in the header of the cluster page. From the command line:

Terminal window
functl cluster kubeconfig <CLUSTER_ID> > ~/.kube/fundament.yaml

Both require view access on the cluster, which every member of the owning organization has: organization admins and members alike. The kubeconfig is available as soon as the cluster is created. While the cluster is still being provisioned, kubectl answers with an explanation instead of a result, for example:

Error from server (ServiceUnavailable): getting your credentials for this cluster failed: Gardener has no shoot for this cluster yet

Access works shortly after the cluster’s API server is up, which is before the cluster is ready: the worker nodes may still be joining. In the seconds between, while your access is being set up, kubectl says so and asks you to try again.

The kubeconfig contains no long-lived credential. It points at the platform’s Kubernetes API proxy and delegates authentication to an exec credential plugin:

users:
- name: <organization>-<cluster>
user:
exec:
apiVersion: client.authentication.k8s.io/v1
command: functl
args: [cluster, token, <cluster-id>]

The cluster, context and user entries are named after the organization and the cluster (for example acme-corp--production), which is the name kubectl, k9s, Lens and Headlamp show. Cluster names are reduced to lowercase letters, digits and dashes; a name that had to be changed gets a short suffix to stay unique. Rename the context locally if you prefer another name (kubectl config rename-context).

So functl must be installed, on your PATH and logged in (functl auth login) for the kubeconfig to work, including on machines that only ever run kubectl. kubectl calls functl cluster token whenever it needs a fresh token; you never handle the token yourself.

Every request goes to the API proxy rather than straight to the cluster. The proxy checks that you may view the cluster, then swaps your platform token for your personal ServiceAccount on that cluster (fundament-<user-id> in the fundament-system namespace) and forwards the request. What that ServiceAccount may do depends on your role:

Your role Cluster-side result
Organization admin The ServiceAccount is bound to the cluster-admin role: full access to the cluster.
Project admin In every namespace of the project, the ServiceAccount is bound to the built-in admin role: manage workloads, services, config and access within those namespaces.
Project viewer In every namespace of the project, the ServiceAccount is bound to the built-in view role: read-only access, without Secrets.
Neither No ServiceAccount is created, and requests are refused.

The bindings are named fundament:project:<user-id> and follow project membership: adding, promoting or removing a member, or creating a namespace, updates them in the background.

Listing namespaces (kubectl get namespaces) is answered by the proxy itself, because Kubernetes can only grant that cluster-wide. Organization admins see every namespace; everyone else sees the namespaces of the projects they are a member of, and nothing else. kubectl auth can-i list namespaces answers yes accordingly.

Consequences worth knowing:

  • The proxy forwards only the /api, /apis, /openapi and /version paths and the /healthz, /livez and /readyz probes. Other endpoints return 404 regardless of your permissions.
  • ServiceAccounts and bindings are synced in the background after a membership change, so immediately after being granted access a request can return 503 with “your access to this cluster is still being set up”, or 403. Retry shortly. After losing your last role on the cluster, requests can still succeed for a few seconds, and return that 503 for up to half a minute, before they are refused.
  • Listing namespaced resources across all namespaces (kubectl get pods --all-namespaces) is refused for project members. Work per namespace instead.

Tools that talk to the Kubernetes API, such as k9s, Headlamp and Freelens, work with the same kubeconfig: their namespace view lists the namespaces you can see, and from there you can open pods, follow logs and describe resources within your role. Things to keep in mind:

  • The kubeconfig runs functl cluster token, so functl must be on the PATH of the tool, not just of your shell. Desktop apps started from a launcher may not inherit your shell’s PATH.
  • The kubeconfig sets no default namespace. Pick one of your namespaces in the tool (in k9s: :namespace, then select), or pass it on the command line (k9s -n <namespace>).
  • “All namespaces” views depend on the tool. Headlamp falls back to loading your namespaces one by one. k9s shows an access error; pick a namespace instead. Freelens (and Lens) show an empty list with a warning until you enter your namespaces as the cluster’s accessible namespaces in its cluster settings; after that its “All namespaces” view loads them one by one.

Cluster lifecycle management (creation, upgrades, and deletion) follows the model described in ADR 0006. In line with the platform’s soft-delete policy, deleting a cluster in the console removes it from your view rather than erasing its record.

A cluster can disappear from the underlying infrastructure without anyone deleting it in the platform, for instance after an operator error or the loss of the machines that ran its control plane. Its workloads and data are gone with it. If the platform had already seen the cluster in the infrastructure, it then shows the cluster as Lost, records a “Cluster lost” event, and offers no kubeconfig. A cluster that disappears before the platform first saw it stays Provisioning, without an event to show for it.

A lost cluster is not rebuilt by itself. Any change to the cluster, such as resizing a node pool, creates it again under the same name. The new cluster is empty: restore its workloads and data from your own sources. If you no longer need the cluster, delete it instead.