Install the operator
This guide installs display-operator on a
liken cluster. At the end, every monitor
output on the cluster is a device a workload can claim.
You need:
- A
likencluster. The operator claims the graphics card through Dynamic Resource Allocation (DRA), from the devicesliken’s own driver publishes. Devices describes those. - A machine in that cluster with a graphics card, with a monitor on a connector.
kubectlwith cluster-admin access, because the install touches cluster scope: theDeviceClassesyou create in step 2 and aClusterRole.
1. Check that the card publishes
The machine with the monitors must publish its graphics card as a
device. Look for a displayNode attribute in that node’s
liken.sh ResourceSlice:
kubectl get resourceslice <node>-liken.sh -o yaml
If no device carries displayNode, the operator’s own claim will
park and its pod will stay Pending. The
hardware operators
page describes this layering: liken publishes the card, and this
operator refines it into outputs.
2. The device classes
A DeviceClass is cluster-scoped policy: you name and curate the
classes, the same convention as a StorageClass. The classes split
by owner:
-
display-gpu,display-render, anddisplay-i2care wiring, and the base ships them, served atdeviceclasses.yaml. The operator’s own pod claims the graphics card’s card node, its render node, and its monitor-control wires through them, from the deviceslikenpublishes, and theResourceClaimTemplateinoperator.yamlnames them literally, so the operator cannot start without them. Do not delete them. The classes select on thedisplayNodeandrenderNodeattributes and on the i2c companion’ssubsystem, rather than on a vendor and a product id, so they stay correct across a fleet of different machines. -
The class your workloads claim through is yours to create, because it is your cluster’s vocabulary, and the base ships no policy.
display-outputis the one to start with:apiVersion: resource.k8s.io/v1 kind: DeviceClass metadata: name: display-output spec: selectors: - cel: expression: | device.driver == "display.liken.sh" && has(device.attributes["display.liken.sh"].appId)The
appIdguard is what keeps the class on outputs. The driver also publishes each panel’s control device, which carries noappId, and a class that matched the whole driver would allocate either.
Generic or specific
A class is the cluster’s vocabulary for a kind of device, and you
choose its grain. display-output above is generic: it matches
every monitor output, keeps the class list short, and leaves the
choice of screen to each claim’s selector, written in
Common Expression Language (CEL).
A specific class holds the selector itself. A claim then names the
class and writes no CEL, and you make the choice once, in cluster
policy you control:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: lobby-screen
spec:
selectors:
- cel:
expression: |
device.driver == "display.liken.sh" &&
has(device.attributes["display.liken.sh"].appId) &&
device.attributes["display.liken.sh"].connector == "HDMI-A-1"
Start generic. When several workloads repeat the same selector, or when you want the choice of screen in cluster policy rather than in each workload’s manifest, create a specific class.
The example selects by connector, an attribute every output
always publishes; the appId guard is there because the panel’s
control device publishes connector too. A specific class that selects by a monitor
attribute, such as model, must guard the read with has(), the
way Put a window on a screen shows. Those
attributes are absent on a dark connector, and a selector that
reads a missing attribute fails the whole allocation.
3. Apply the manifests
This site serves the repository’s deploy/
directory as raw YAML, so the install needs no clone. Three files
are the rest of the install:
kubectl apply -n liken-system \
-f https://display.liken.sh/deploy/deviceclasses.yaml \
-f https://display.liken.sh/deploy/rbac.yaml \
-f https://display.liken.sh/deploy/operator.yaml
The -n flag places the ServiceAccount and the DaemonSet in
liken-system, the namespace every liken cluster has. The
ClusterRoleBinding’s subject names that namespace, so the binding
only works there. DeviceClass is cluster-scoped, so the flag
leaves it alone.
For GitOps, point a Kustomization at your specific classes and the
same URLs. kustomize takes a raw YAML URL as a resource:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: liken-system
resources:
- classes.yaml
- https://display.liken.sh/deploy/deviceclasses.yaml
- https://display.liken.sh/deploy/rbac.yaml
- https://display.liken.sh/deploy/operator.yaml
A clone works too: kubectl apply -k deploy/ from the repository
applies the same base through
deploy/kustomization.yaml.
4. Watch the operator find the screens
The operator runs as a DaemonSet, so a pod lands on every node and
no manifest names the machine with the monitors. Each pod claims the
card on its own node. On a node with no graphics card, the claim
finds no device and the pod parks Pending, which costs nothing.
kubectl -n liken-system get pods -o wide
On the machine with the card, the pod’s log names each monitor it found:
kubectl -n liken-system logs ds/display-operator
display.liken.sh: operating the monitors on kitchen
display.liken.sh: HDMI-A-1 carries gsm-7716-lg-hdr-wqhd, app-id hdmi-a-1
5. See the devices
The operator publishes one device for each connector on the card,
into a ResourceSlice named <node>-display.liken.sh:
kubectl get resourceslice <node>-display.liken.sh -o yaml
A connector with a monitor carries the monitor’s attributes. An
empty connector publishes too, with a disconnected taint, so a
claim on it parks until a monitor arrives.
Devices describes every attribute.
Remove the operator
Delete the manifests. Then delete the slice on each node that published one:
kubectl delete -n liken-system \
-f https://display.liken.sh/deploy/rbac.yaml \
-f https://display.liken.sh/deploy/operator.yaml
kubectl delete resourceslice <node>-display.liken.sh
The slice step is yours because the operator never deletes its slice. A device that leaves the inventory while a claim still names it strands the kubelet’s prepare call. So the operator taints devices instead of removing them, and the slice outlives every pod. The device classes are yours too. When nothing else claims through them, delete them.