Kubernetes for TRE people¶
You already know what a TRE has to do: keep projects apart, stop data leaving, give researchers compute and storage, and prove all of this to an auditor. This page maps those ideas onto the Kubernetes objects you will meet in the labs. It is not a complete Kubernetes course.
The cluster¶
A cluster is a set of machines (nodes) run by a control plane. The control plane's
API server is the only front door: kubectl, JupyterHub, Argo CD and Kyverno all make
requests to it. Every object in the cluster is a record stored behind that API.
KID runs a single-node cluster using k3s, a lightweight Kubernetes
distribution, inside a Docker container called k3s-server.
kubectl get nodes -o wide
kubectl api-resources | head -30 # every kind of object this cluster understands
Declarative objects and controllers¶
You never tell Kubernetes "start a container". You tell it "this is how things should be" (a YAML object) and controllers keep making reality match. Delete a pod that belongs to a Deployment and a new one appears. That idea runs through everything in a Kubernetes TRE: network rules, quotas and policies are all objects, and something keeps enforcing them.
apiVersion: v1 # which API group/version
kind: ConfigMap # what sort of object
metadata:
name: example # its name...
namespace: portal # ...inside this namespace
labels: # key/value tags that other objects select on
app: portal
data:
greeting: hello
The objects you'll meet¶
| Object | What it is | TRE analogy |
|---|---|---|
| Pod | One or more containers scheduled together, with one IP address | A running workspace VM |
| Namespace | A named partition of the cluster; most objects live in one | A project / tenant boundary |
| Label | A key/value tag, e.g. k8tre.io/project=alpha |
Asset tagging, used by policy |
| Deployment | Keeps N copies of a pod running | A managed service |
| Service | A stable name and virtual IP in front of pods | Internal DNS / load balancer |
| Ingress | HTTP routing from outside into Services | Reverse proxy / gateway |
| PersistentVolumeClaim (PVC) | A request for storage ("1 GiB, this class") | A project or home drive |
| PersistentVolume (PV) | The actual storage that satisfies a claim | The disk/share itself |
| StorageClass | A kind of storage and how to create it | Storage tier / policy |
| ResourceQuota | Totals a namespace may use (CPU, memory, storage, pods) | Project allocation |
| LimitRange | Default and maximum per container in a namespace | VM size policy |
| ServiceAccount | An identity for software running in the cluster | Service principal |
| Role / ClusterRole | A list of allowed API actions | Permission set |
| RoleBinding | "This identity gets this role in this namespace" | Group membership |
| CiliumNetworkPolicy | Allowed network flows for selected pods | Firewall rules |
| CustomResourceDefinition (CRD) | Teaches the API a new kind of object | Plug-in schema |
Namespaces are the project boundary¶
In K8TRE, and in KID, each research project is a namespace called project-<name>. Almost every
control is scoped to it:
flowchart TB
subgraph NS["namespace: project-alpha"]
direction TB
P1[Pod: jupyter-researcher1]
P2[Pod: jupyter-researcher3]
V1[(PVC: home-researcher1)]
V2[(PVC: home-researcher3)]
VS[(PVC: project-shared)]
Q[ResourceQuota]
L[LimitRange]
N[CiliumNetworkPolicy x2]
RB[RoleBinding: hub may spawn here]
end
P1 --- V1 & VS
P2 --- V2 & VS
A namespace on its own does not isolate network traffic. By default any pod can reach any other pod in the cluster. Network isolation comes from network policy (lab 3).
Labels and selectors¶
Much of Kubernetes is wired together by selecting on labels rather than naming things. A Service finds its pods by label, and a network policy picks the pods it applies to and the peers it allows by label. The same goes for Kyverno, which picks namespaces by label.
kubectl get pods -A -l component=singleuser-server # every workspace
kubectl get ns -l k8tre.io/type=project # every project
So labels are security-relevant. That is why a Kyverno policy in KID checks that workspace pods carry the right project label.
Requests, limits and quotas¶
Each container can declare:
- requests: what the scheduler reserves for it on a node (guaranteed).
- limits: the hard ceiling. CPU above the limit is throttled; memory above the limit gets the process OOM-killed.
A LimitRange fills in defaults and enforces a per-container maximum. A ResourceQuota caps the namespace total. Lab 6 shows all three.
RBAC: who may do what¶
Every API request is made by an identity (a user or a ServiceAccount) and checked against
Roles bound to it. In KID, JupyterHub's ServiceAccount hub can create pods only in
namespaces where a project has bound the tre-workspace-spawner ClusterRole to it. Cluster-wide
it can only read namespaces.
kubectl auth can-i create pods -n project-alpha --as system:serviceaccount:jupyterhub:hub # yes
kubectl auth can-i create pods -n keycloak --as system:serviceaccount:jupyterhub:hub # no
Admission control¶
Before an object is stored, the API server passes it through admission controllers. They can change it (mutating) or reject it (validating). KID uses three:
- LimitRanger (built in): adds default requests/limits.
- Pod Security Admission (built in): enforces the
baselineprofile on project namespaces. - Kyverno (a webhook): your own policies, written as YAML.
Operators, CRDs and Helm¶
- A CRD adds a new object type (Cilium adds
CiliumNetworkPolicy, Argo CD addsApplication, Kyverno addsClusterPolicy). - An operator/controller watches those objects and acts on them.
- Helm packages many YAML files into a chart with parameters (values). KID uses the JupyterHub and Kyverno charts, plus two small charts of its own.
- Kustomize patches and combines YAML without templates. Argo CD supports both.
GitOps¶
Instead of running kubectl apply by hand, Argo CD watches a Git repository and applies what
it finds, continuously. Git becomes the audit log and the change-control process for the
platform. See Argo CD.