Skip to content

Architecture

Here you see what pieces kubelatch is made of, where its state lives, and how it connects to your clusters and to GitHub. It helps you understand what happens if a piece fails and why the deployment is so small.

One binary, three pieces

kubelatch is a single Go binary with the web interface embedded. Three pieces run inside the same process:

flowchart LR
    dev["People<br/>browser and kubectl"] -->|HTTPS| kl
    ci["CI<br/>GitHub Actions"] -->|HTTPS| kl
    subgraph kl["kubelatch (1-2 replicas)"]
        cp["Control plane<br/>+ SPA"]
        px["Impersonation<br/>proxy"]
        rc["RBAC<br/>reconciler"]
    end
    kl --> pg[("Postgres")]
    px -->|"SA kubelatch-proxy"| api["API servers<br/>EKS · GKE · AKS · k3s…"]
    rc -->|"SA kubelatch-reconciler"| api
    cp <-->|"login, members,<br/>webhook"| gh["GitHub"]
Piece What it does Entry point
Control plane The JSON API and the web interface (SPA in React). Accounts, clusters, permissions, credentials, trust rules and audit. /api/... and /
Impersonation proxy Receives requests from kubectl, k9s, Lens, Helm or CI, validates the token, logs the request and forwards it to the API server acting as whoever made it. /clusters/<id>/...
RBAC reconciler Maintains the ClusterRoles for each tier on every cluster, a binding for each tier-and-scope combination that has active permissions, and the exact list of users and groups the proxy is allowed to impersonate. Outbound: talks to each API server

There are also two routes outside /api with no session: the GitHub Actions token exchange (/v1/ci/github-actions/token) and the GitHub webhook (/v1/github/webhook). They're described in the API reference.

Alongside these pieces, background jobs run on every replica: the reconciler's periodic pass (every 10 minutes), the audit retention job (every hour) and, with GitHub configured, organization member synchronization (every hour).

Where the state lives

All state is in Postgres: subjects, clusters (with their encrypted ServiceAccount tokens), permissions, credentials (only their hash), trust rules and the two audit tables. kubelatch applies its migrations on startup.

Replicas don't keep anything of their own. That's why you can run one or two behind the same Service. With two, the reconciler, retention and member synchronization take turns using Postgres advisory locks. The only thing not shared is the in-memory per-IP rate limiters: each replica counts its own.

Without Postgres, kubelatch doesn't validate any token: the proxy responds with 503 and no one gets in. It's a deliberately closed failure.

How it fits with everything else

kubelatch talks to each cluster with two distinct ServiceAccounts, created by the bootstrap manifest in the kubelatch-system namespace:

  • kubelatch-proxy can only impersonate (impersonate) the users and groups the reconciler puts in its list (resourceNames). It's the only identity that travels in client requests.
  • kubelatch-reconciler writes RBAC, but only over the fixed names kubelatch manages. It's never in the path of a client request.

GitHub appears twice. For people, as the login provider (a GitHub App belonging to your organization, see Identity). For CI, as the issuer of the GitHub Actions OIDC tokens that kubelatch exchanges for short-lived credentials.

Why it works on any cluster

The proxy and the reconciler only use the standard Kubernetes and RBAC API. There's no need to touch API server flags, install anything inside the nodes, or run a per-cluster agent. That's why the same mechanism works on managed clusters (EKS, GKE, AKS) and self-managed ones (kubeadm, k3s, RKE2, Talos). The page about the proxy explains which alternatives were discarded.

The cluster still decides with its native RBAC. kubelatch doesn't authorize each verb: it tells the API server who the person is and which groups they belong to, and the API server applies the bindings the reconciler created.

What happens if kubelatch goes down

Your clusters' workloads don't depend on kubelatch. If it goes down, only people's and CI's access through it is lost. That's why kubelatch must never be the only path to a cluster: keep a native emergency access route (see Accounts and recovery).

A restart or a rollout cuts open exec, port-forward, watch and logs -f connections. kubelatch gives normal requests a 3 s grace period, cuts the streams, and finishes writing its audit rows before exiting. The deployment manifest uses terminationGracePeriodSeconds: 60 and a rollout with maxSurge: 1 and maxUnavailable: 0.

Reference deployment

The default deployment (deploy/k8s/base) is a single-replica Deployment behind a Service of type LoadBalancer L4, with TLS terminated in the binary itself. The ha overlay scales up to two replicas with a PodDisruptionBudget. The ingress overlay puts kubelatch behind an Ingress, with the settings that long-lived streams need. The steps are in Install.