# Cluster registration

> Register an existing Kubernetes cluster as a ZopDay deployment space.

Source: https://zop.dev/docs/zopday/cloud-setup/cluster-registration

---

A **deployment space** is a registered Kubernetes cluster ZopDay can deploy to. This page covers the BYOC path: registering a cluster you already have, so ZopDay can mint short-lived kubeconfigs and ship deploys to it.

If you want ZopDay to **create** the cluster for you, see [Provisioning → Clusters](https://zop.dev/docs/zopday/provisioning/clusters) instead; that flow registers the cluster as a space automatically when provisioning completes.

## Prerequisites

- A connected cloud account with at least the cluster-registration IAM scope. See [Cloud permissions](https://zop.dev/docs/zopday/cloud-setup/permissions).
- The cluster's identity in cloud terms; for AWS: cluster name + region; for GCP: cluster name + region + project; for Azure: cluster name + resource group + subscription.

## Two-step registration

ZopDay separates the registration into:

1. **Access grant**: the cluster is admitted to ZopDay's kubeauth registry. ZopDay can now mint kubeconfigs for it.
2. **Space promotion**: the cluster is registered as a `deployment_space` row so it shows up in space pickers (provisioning, environments, autoscaler targets).

Both happen back-to-back in the UI; they're separated in the API because the access grant is the security-sensitive operation.

**Pick the cluster from the connected account**

  Settings → Cloud accounts → click the connected account. The right panel lists every discovered cluster on that account with its current state (running, stopped, updating).

**Grant kubeauth access**

  Click **Register** on the cluster row. This calls:

  ```
  PUT /cloud-accounts/{accountID}/clusters/{region}/{clusterName}/access
  ```

  ZopDay records the cluster identity in its kubeauth registry. No kubeconfig is fetched yet.

**Promote to a deployment space**

  Confirm the next dialog and ZopDay calls:

  ```
  POST /spaces
  ```

  …with the cluster identifier. ZopDay creates a `deployment_space` row, registers the space with the discoverer (so resources flow into Reports) and the aggregator (so cost flows into showback), and the space appears on Settings → Spaces.

The cluster is now ready to receive deploys.

## Kubeauth; how ZopDay actually authenticates

ZopDay does not store a long-lived kubeconfig anywhere. Each deploy mints a fresh, short-lived kubeconfig at runtime from your cloud credentials. The deploy pod holds **zero standing credentials** on your cluster.

| Cloud | Token source | TTL |
|---|---|---|
| **AWS EKS** | STS-presigned URL token | ~13 minutes |
| **GCP GKE** | `container.Clusters.Get` + OAuth2 access token | ~45 minutes |
| **Azure AKS** | Stubbed (clean error at the kubeauth boundary) | In preview |

Each token expires before the deploy could plausibly still be running. If a deploy takes longer than the TTL, the runtime mints a fresh token mid-flight and continues.

## RBAC on the cluster

ZopDay's kubeauth identity is mapped into your cluster's Kubernetes RBAC. The recommended binding:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: zopday-deployer
subjects:
  - kind: User
    name: zopday-deployer   # match the kubeauth principal ZopDay shows in the wizard
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin       # narrow this down for least privilege
  apiGroup: rbac.authorization.k8s.io
```

For least privilege, replace `cluster-admin` with a custom ClusterRole that grants only the verbs ZopDay needs in the namespaces it deploys to. Contact support for a least-privilege ClusterRole template; most customers run `cluster-admin` for simplicity in the first few months and tighten it later.

## Reachability probe

After registration, ZopDay probes the cluster every **60 seconds** with a lightweight `GET /version` call. The probe result drives the space's status:

| Status | Meaning |
|---|---|
| **Active** | Last probe succeeded. Deploys / provisioning ops proceed. |
| **Inactive** | Last 3 probes failed. The space is gated cleanly. UI surfaces "Cluster not reachable", no cryptic SDK errors. |
| **Unknown** | Probe has not yet run since registration / restart. Resolves to Active or Inactive within 60s. |

If a cluster goes Inactive, queued deploys do **not** retry indefinitely; they stop after 3 attempts. Once the cluster recovers, re-enqueue from the Provisioning Jobs view.

## Spaces in the UI

Once registered, the space shows up:

- **Settings → Spaces**: full list with status, provider, region, namespace count
- **Environment picker**: when creating an environment inside a project, pick this space from the dropdown
- **Provisioning job target**: when provisioning a Helm component or new VM/datastore, pick this space as the destination
- **Architecture canvas**: clusters appear as top-level nodes with namespace clouds underneath

## Deregistering a space

Settings → Spaces → click the space → **Deregister**. This:

1. Removes the kubeauth grant; no more kubeconfig minting
2. Soft-deletes the `deployment_space` row
3. **Does not** touch anything on the cluster itself; workloads continue running, manifests remain
4. **Does not** delete dependent environments. Environments bound to this space gate cleanly with a "Space deregistered" notice; you can re-bind them to another space.

To remove cluster-side resources installed by ZopDay (Helm releases, secrets, ConfigMaps), `helm uninstall` them from outside ZopDay before deregistering; or trigger a full teardown per deployment from the UI for a clean removal of everything ZopDay added.
