Skip to content

Deploy a Kubernetes Cluster

This guide covers two paths: provisioning a new Gardener-managed cluster and registering an existing cluster via kubeconfig. Both end with a verified connection and a running workload.

Time to complete: ~20 minutes (Gardener), ~5 minutes (generic)
Prerequisites: An organization, a project, and a bearer token with garden:create permission.


Path A — Gardener-managed cluster

Step 1 — Discover available options

GET /k8s/cloudprofiles
Authorization: Bearer <token>

The response lists available Kubernetes versions, machine types, and regions. Note the values you want for version, cloud, and machine_type.

Step 2 — Create the cluster

POST /k8s/clusters?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "prod-cluster",
  "project_id": "<uuid>",
  "cloud": "openstack",
  "version": "1.29",
  "node_pools": [
    {
      "name": "workers",
      "machine_type": "m1.large",
      "min": 2,
      "max": 5
    }
  ]
}

Returns 201 Created. Note the cluster id.

Step 3 — Wait for the cluster to be ready

Poll until creating: false and status.lastOperation.state is "Succeeded":

GET /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

Provisioning typically takes 5–10 minutes. While it runs, creating: true and lastOperation tracks progress.

Step 4 — Download the kubeconfig

Once the cluster is ACTIVE, download the kubeconfig from the dashboard or through the Gardener API (available via the dashboard's Download kubeconfig button). The api_server field in the cluster show response gives you the Kubernetes API server URL.

Step 5 — Deploy a workload

export KUBECONFIG=~/Downloads/kubeconfig-prod-cluster.yaml

kubectl get nodes
# NAME           STATUS   ROLES    AGE   VERSION
# worker-abc123  Ready    <none>   3m    v1.29.4

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello
        image: nginx:alpine
        ports:
        - containerPort: 80
EOF

kubectl rollout status deployment/hello-world

Path B — Register an existing cluster (generic)

Use this path to bring an existing cluster (k3s, EKS, GKE, on-prem) under platform management.

Step 1 — Register the cluster

POST /k8s/clusters?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "kind": "generic",
  "name": "on-prem-k3s",
  "kubeconfig": "<raw-kubeconfig-yaml-or-base64>"
}

The kubeconfig field is write-only — it is never returned in responses. The response includes has_kubeconfig: true to confirm it was stored.

Step 2 — Verify connectivity

GET /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

The connection_status field reports reachable or unreachable based on an API server probe. If unreachable, check that the cluster's API server is publicly accessible or that you have provided a kubeconfig with the correct server URL.

Step 3 — Rotate the kubeconfig

When credentials expire or you rotate your cluster's service account:

PATCH /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{ "kubeconfig": "<new-kubeconfig-yaml>" }

Scale node pools (Gardener clusters only)

To scale an existing node pool:

PATCH /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>
Content-Type: application/json

{
  "node_pools": [
    { "name": "workers", "min": 3, "max": 10 }
  ]
}

Gardener applies the change asynchronously. Watch lastOperation to track rollout progress.


Delete a cluster

DELETE /k8s/clusters/<cluster-id>?project_id=<uuid>
Authorization: Bearer <token>

For Gardener clusters, this triggers deprovisioning of the underlying cloud resources. The cluster disappears from list results once teardown is complete.



Using the dashboard

The dashboard at app.bnerd.cloud provides a visual Kubernetes management interface:

  • Create a cluster: Services → Kubernetes → New Cluster
  • Download kubeconfig: Cluster detail → Download kubeconfig button
  • Scale node pools: Cluster detail → Node Pools tab → edit min/max
  • Delete a cluster: Cluster detail → Delete Cluster

Using the CLI

# List cloud profiles (available versions, machine types)
bnerd k8s cloud-profiles list

# List clusters
bnerd k8s clusters list

# Create a Gardener-managed cluster
bnerd k8s clusters create prod-cluster \
  --version 1.29 \
  --cloud openstack \
  --machine-type m1.large \
  --min 2 \
  --max 5

# Show cluster status
bnerd k8s clusters get <cluster-id>

# Delete a cluster
bnerd k8s clusters delete <cluster-id>

See cli.docs.bnerd.com for the full flag reference.


Terraform

Kubernetes cluster management via Terraform is available via data sources for reading existing clusters; full lifecycle management (create/delete) is not yet covered by the Terraform provider — use the API or CLI for cluster provisioning:

# Read an existing cluster
data "bnerd_k8s_cluster" "prod" {
  id         = var.cluster_id
  project_id = var.project_id
}

# List cloud profiles
data "bnerd_k8s_cloudprofiles" "all" {}

Troubleshooting

Symptom Likely cause Fix
Cluster stuck in creating: true > 15 min Gardener provisioning delay or resource limit Check lastOperation.description in the show response; contact support if stuck > 30 min
kubectl get nodes returns no nodes kubeconfig is stale or node pool has 0 replicas Re-download the kubeconfig; verify min ≥ 1 on the node pool
connection refused on kubeconfig download Cluster API server not yet ready Wait for lastOperation.state: "Succeeded" before attempting to download
403 on cluster create Missing garden:create permission Check your membership permissions in Dashboard → Settings → Members
generic cluster shows unreachable API server not publicly accessible Ensure the cluster API server URL in the kubeconfig is reachable from the platform