[PR #54] [CLOSED] Wolf-Agent DRA: Implementing the Wolf DRA driver #55

Closed
opened 2026-10-15 12:47:02 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/fenrir/pull/54
Author: @axolotlite
Created: 8/1/2026
Status: ❌ Closed

Base: staging ← Head: feature/wolf-dra-driver


📝 Commits (10+)

  • 3559540 [go] update go from 1.24 to 1.26 for dra v1
  • d817ca0 [go] update code and references to newer GA Kind (gatewayapi v1)
  • 6b5bf3e [codegen] update generated code
  • 7cb8bfa [wolf-dra] initial implementation
  • 2bd8ca7 [lint] apply linter recommendations
  • 4407af0 [dev] add resources for testing the wolf-dra
  • ea5da51 [dev] add initial readmes for wolf dra manual resources
  • 7aaaff9 [wolf-dra] add pulse audio mount
  • 8667076 [wolf-dra] add wolf-dra standalone deployment and firefox app pod manifests
  • 4958bc0 [wolf-dra] add informers to watch for status updates, so it can join sessions to lobbies

📊 Changes

64 files changed (+4419 additions, -805 deletions)

View changed files

📝 .dockerignore (+0 -1)
📝 Dockerfile (+1 -1)
📝 Dockerfile.dev (+5 -3)
📝 cmd/operator/main.go (+2 -2)
📝 cmd/wolf-agent/main.go (+200 -177)
📝 crds/direwolf.games-on-whales.github.io_apps.yaml (+524 -83)
📝 crds/direwolf.games-on-whales.github.io_lobbies.yaml (+3 -0)
📝 crds/direwolf.games-on-whales.github.io_profiles.yaml (+257 -29)
📝 crds/direwolf.games-on-whales.github.io_sessions.yaml (+4 -0)
📝 go.mod (+57 -42)
📝 go.sum (+146 -90)
➕ hack/deploy/01-wolf-agent-standalone.yaml (+231 -0)
📝 hack/deploy/01-wolf-agent.yaml (+137 -17)
📝 hack/deploy/02-moonlight-proxy.yaml (+0 -61)
📝 hack/deploy/03-misc.yaml (+240 -1)
➕ hack/resources/device_classes/README.md (+6 -0)
➕ hack/resources/device_classes/default_device_class.yaml (+11 -0)
➕ hack/resources/device_classes/multi_gpu_class.yaml (+22 -0)
➕ hack/resources/dra_tests/README.md (+8 -0)
➕ hack/resources/dra_tests/deployment_resource_claim_template.yaml (+63 -0)

...and 44 more files

📄 Description

This PR aims to create the initial wolf dra driver for use with the direwolf-operator & moonlight proxy.
My aim while writing this was to seperate the DRA from the rest of the components, they're not supposed to directly communicate with the wolf-dra, instead create resources that the dra monitors / creates for the other operators
The following kubernetes resources are translated into wolf specific resources:

  • resourceClaims -> Lobby
  • deviceClasses -> Wolf Configurations
  • sessions -> I've not figured that out yet, will most likely figure it out during the operator rewrite

Wolf-Agent

The Wolf-Agent Pod consists of 2 Containers:

  • Wolf-DRA: Responsible for communications between kubelet and wolf, alongside CDI mounts and env vars
  • Wolf: Responsible for creating the Lobby(wayland sock and pulse audio sink) & Sessions (User Stream)

Wolf-DRA Container

The wolf-dra container was previous the wolf-agent container, which was a "per-app" container, as in; each application had 4 containers (wolf, wolf-agent, pulseaudio and the application itself).
Now, it's a daemonset whose purpose is to recieve resourceClaims from the kubelet and then call wolf to create a lobby, before mounting the resulting sockets, alongside their relevant env vars into the application pod created on the same node.
Each Wolf-DRA also watches the status of the resourceClaims it created, to check if someone or something (most likely the direwolf-operator) updated the session_ids of this resourceClaim:

  • on adding a session_id: it calls wolf's Join Lobby, which joins the user session into the application lobby
  • on removing a session_id: it calls wolf's Leave Lobby, which removes the user from the application lobby

The user is responsible for setting up the daemonsets on GPU enabled nodes, since both wolf and the application will require access to some form of GPU compute.

Wolf Container

The wolf container works the same way it does on docker, it gets a call from wolf-dra to create/stop a lobby or session, then another call to join or remove a session from a lobby.
It creates:

  • Lobbies: for application pods
  • Sessions: for user streaming

Lobby

When an application statefulset(pod) is created with a resourceClaim targetting driverName="wolf.dra.io", the kubernetes scheduler will pick a node with the wolf-agent installed on it, and once the pod is scheduled there, the kubelet will call the wolf-dra plugin to prepare the wayland socket, alongside the pulseaudio sink for this pod.
The wolf-dra will call LobbyCreateRequest to the wolf container
Once the resources (wayland-N, pulse.sink) are created by the wolf container, it takes their path on the node host(/var/run/wolf-sockets) and where they're supposed to be placed on the application pod (XDG_RUNTIME_DIR) alongside the env vars that configure the application to use them in the CDI it passes to the kubelet.
The application pod is scheduled and will be created after the claims(sockets) are ready to be mounted into the pod.

Session

I have not yet decided on session creation mechanism, mostly because it will depend on the how the user's moonlight client reaching the specific node where the application is and that will likely require it's own mechanism that involves the moonlight-proxy (next PR).

Development and Example Resources

Different Resources in the hack directory alongside their purpose and relevant to this PR.

Wolf-Agent

in the hack/deploy there are 2 different manifests for wolf-agent:

  • hack/deploy/01-wolf-agent-standalone.yaml: creates a deployment, I used it to test the wolf-dra's status monitoring and joining sessions with lobby through manually patching the resource claim status with my session's client id
  • hack/deploy/01-wolf-agent.yaml: creates a daemonset, I used it to test the lobby creation, on different nodes

DeviceClasses

The device class implementation is kinda weak? I've not worked with device classes before, so I'm not fully aware of what they're capable of, so I just assigned it the role of Wolf Configuration.
I will need to expose more relevant information about wolf on the node, the main example that comes to mind is Render Nodes, so that people can use selectors to have more control on which GPU each wolf-agent can use to stream stuff.
This change should affect hack/resources/device_classes/multi_gpu_class.yaml:

apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
  name: multi-gpu-node-wolf
spec:
  selectors:
  - cel:
      expression: "device.driver == 'wolf.dra.io' && device.attributes['renderNode'] == 'renderD129'"
  config:
    - opaque:
        driver: wolf.dra.io
        parameters:
          video_settings:
            wayland_render_node: "/dev/dri/renderD129" # removed from here
            runner_render_node: "/dev/dri/renderD129" # removed from here

also, the parameters in the DeviceClass work as an override for the parameters in the ResourceClaims.

ResourceClaims

The direwolf-operator will take an application CRD and create a statefulset, alongside a resourceClaim for the Wolf-Agent to create a lobby for.
The resourceClaim Parameters are a json object similar to wolf's lobbyCreationRequest.
and example of this is hack/resources/dra_tests/firefox.yaml:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: firefox-wayland-claim
  namespace: direwolf-dev
spec:
  devices:
    requests:
    - name: wayland
      exactly:
        deviceClassName: default-wolf
        capacity:
          requests:
            slots: "1" # otherwise, it'll take the entire device, preventing any further scheduling on this node
    config:
    - requests:
      - wayland
      opaque:
        driver: wolf.dra.io
        parameters:
          # The parameters here are supposed to be a json object similar
          # to the lobby creation request from wolf
          video_settings:
            width: 1920
            height: 1080
            refresh_rate: 30
            video_producer_buffer_caps: "video/x-raw(memory:DMABuf), drm-format={NV12,YV12,YU12,P012,YUYV,YU24,AB24,AR24,XB24,XR24}"
          audio_settings:
            channel_count: 2
          client_settings:
            h_scroll_acceleration: 1
            mouse_acceleration: 1
            run_gid: 1000
            run_uid: 1000
            v_scroll_acceleration: 1
---
apiVersion: v1
kind: Pod
metadata:
  name: firefox
  namespace: direwolf-dev
spec:
  containers:
  - name: app
    image: ghcr.io/games-on-whales/firefox:edge
    env:
    # We can setup application container specific env vars from here
    # or from the app
      - name: RUN_SWAY
        value: "1"
    resources:
      claims:
      - name: wayland
      limits:
        devic.es/gpu: "1"
        cpu: 2
        memory: 4G
      requests:
        devic.es/gpu: "1"
        cpu: 2
        memory: 4G
  resourceClaims:
  - name: wayland
    resourceClaimName: firefox-wayland-claim

Additional Notes:

  • I've named the device resource "wayland" however, I now believe it's more fitting to name it "lobby"
  • I need someone to tell me if the status update triggering lobby join and leave is a good or bad idea
  • I need some help with what to do with the DeviceClass, I'm kinda lost
  • ClientSettings RunPID and RunGID, do those affect anything other than the app folder? I assume they're not applicable to fenrir...

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/fenrir/pull/54 **Author:** [@axolotlite](https://github.com/axolotlite) **Created:** 8/1/2026 **Status:** ❌ Closed **Base:** `staging` ← **Head:** `feature/wolf-dra-driver` --- ### 📝 Commits (10+) - [`3559540`](https://github.com/games-on-whales/fenrir/commit/355954094e2143b32648f38e0cb792b20cfb4fe4) [go] update go from 1.24 to 1.26 for dra v1 - [`d817ca0`](https://github.com/games-on-whales/fenrir/commit/d817ca0aa652105fc2a4628a71db621aa5890879) [go] update code and references to newer GA Kind (gatewayapi v1) - [`6b5bf3e`](https://github.com/games-on-whales/fenrir/commit/6b5bf3ed4edf5d43bd6ea4b2f4db921987d8dc9e) [codegen] update generated code - [`7cb8bfa`](https://github.com/games-on-whales/fenrir/commit/7cb8bfaa3388d807586ef0b194e3fd436cb36365) [wolf-dra] initial implementation - [`2bd8ca7`](https://github.com/games-on-whales/fenrir/commit/2bd8ca7b7100a61df95f0e245d91de361177c3ef) [lint] apply linter recommendations - [`4407af0`](https://github.com/games-on-whales/fenrir/commit/4407af07fea89ed6bcccead96a187f073ed00a32) [dev] add resources for testing the wolf-dra - [`ea5da51`](https://github.com/games-on-whales/fenrir/commit/ea5da51d75074a8541bc4b446a672f56190b64a7) [dev] add initial readmes for wolf dra manual resources - [`7aaaff9`](https://github.com/games-on-whales/fenrir/commit/7aaaff9a484dcd430871793c628e1eb88d4dc233) [wolf-dra] add pulse audio mount - [`8667076`](https://github.com/games-on-whales/fenrir/commit/8667076656e7a0f5d79938edfb6800f31b52477e) [wolf-dra] add wolf-dra standalone deployment and firefox app pod manifests - [`4958bc0`](https://github.com/games-on-whales/fenrir/commit/4958bc08660b15537499e58074b55d43d95a3b9a) [wolf-dra] add informers to watch for status updates, so it can join sessions to lobbies ### 📊 Changes **64 files changed** (+4419 additions, -805 deletions) <details> <summary>View changed files</summary> 📝 `.dockerignore` (+0 -1) 📝 `Dockerfile` (+1 -1) 📝 `Dockerfile.dev` (+5 -3) 📝 `cmd/operator/main.go` (+2 -2) 📝 `cmd/wolf-agent/main.go` (+200 -177) 📝 `crds/direwolf.games-on-whales.github.io_apps.yaml` (+524 -83) 📝 `crds/direwolf.games-on-whales.github.io_lobbies.yaml` (+3 -0) 📝 `crds/direwolf.games-on-whales.github.io_profiles.yaml` (+257 -29) 📝 `crds/direwolf.games-on-whales.github.io_sessions.yaml` (+4 -0) 📝 `go.mod` (+57 -42) 📝 `go.sum` (+146 -90) ➕ `hack/deploy/01-wolf-agent-standalone.yaml` (+231 -0) 📝 `hack/deploy/01-wolf-agent.yaml` (+137 -17) 📝 `hack/deploy/02-moonlight-proxy.yaml` (+0 -61) 📝 `hack/deploy/03-misc.yaml` (+240 -1) ➕ `hack/resources/device_classes/README.md` (+6 -0) ➕ `hack/resources/device_classes/default_device_class.yaml` (+11 -0) ➕ `hack/resources/device_classes/multi_gpu_class.yaml` (+22 -0) ➕ `hack/resources/dra_tests/README.md` (+8 -0) ➕ `hack/resources/dra_tests/deployment_resource_claim_template.yaml` (+63 -0) _...and 44 more files_ </details> ### 📄 Description This PR aims to create the initial wolf dra driver for use with the direwolf-operator & moonlight proxy. My aim while writing this was to seperate the DRA from the rest of the components, they're not supposed to directly communicate with the wolf-dra, instead create resources that the dra monitors / creates for the other operators The following kubernetes resources are translated into wolf specific resources: * resourceClaims -> Lobby * deviceClasses -> Wolf Configurations * sessions -> I've not figured that out yet, will most likely figure it out during the operator rewrite ## Wolf-Agent The Wolf-Agent Pod consists of 2 Containers: * Wolf-DRA: Responsible for communications between kubelet and wolf, alongside CDI mounts and env vars * Wolf: Responsible for creating the Lobby(wayland sock and pulse audio sink) & Sessions (User Stream) ### Wolf-DRA Container The wolf-dra container was previous the wolf-agent container, which was a "per-app" container, as in; each application had 4 containers (wolf, wolf-agent, pulseaudio and the application itself). Now, it's a daemonset whose purpose is to recieve resourceClaims from the kubelet and then call wolf to create a lobby, before mounting the resulting sockets, alongside their relevant env vars into the application pod created on the same node. Each Wolf-DRA also watches the status of the resourceClaims it created, to check if someone or something (most likely the direwolf-operator) updated the session_ids of this resourceClaim: * on adding a session_id: it calls wolf's Join Lobby, which joins the user session into the application lobby * on removing a session_id: it calls wolf's Leave Lobby, which removes the user from the application lobby The user is responsible for setting up the daemonsets on GPU enabled nodes, since both wolf and the application will require access to some form of GPU compute. ### Wolf Container The wolf container works the same way it does on docker, it gets a call from wolf-dra to create/stop a lobby or session, then another call to join or remove a session from a lobby. It creates: * Lobbies: for application pods * Sessions: for user streaming #### Lobby When an application statefulset(pod) is created with a resourceClaim targetting driverName="wolf.dra.io", the kubernetes scheduler will pick a node with the wolf-agent installed on it, and once the pod is scheduled there, the kubelet will call the wolf-dra plugin to prepare the wayland socket, alongside the pulseaudio sink for this pod. The wolf-dra will call LobbyCreateRequest to the wolf container Once the resources (wayland-N, pulse.sink) are created by the wolf container, it takes their path on the node host(/var/run/wolf-sockets) and where they're supposed to be placed on the application pod (XDG_RUNTIME_DIR) alongside the env vars that configure the application to use them in the CDI it passes to the kubelet. The application pod is scheduled and will be created after the claims(sockets) are ready to be mounted into the pod. #### Session I have not yet decided on session creation mechanism, mostly because it will depend on the how the user's moonlight client reaching the specific node where the application is and that will likely require it's own mechanism that involves the moonlight-proxy (next PR). ### Development and Example Resources Different Resources in the hack directory alongside their purpose and relevant to this PR. #### Wolf-Agent in the `hack/deploy` there are 2 different manifests for wolf-agent: * hack/deploy/01-wolf-agent-standalone.yaml: creates a deployment, I used it to test the wolf-dra's status monitoring and joining sessions with lobby through manually patching the resource claim status with my session's client id * hack/deploy/01-wolf-agent.yaml: creates a daemonset, I used it to test the lobby creation, on different nodes #### DeviceClasses The device class implementation is kinda weak? I've not worked with device classes before, so I'm not fully aware of what they're capable of, so I just assigned it the role of Wolf Configuration. I will need to expose more relevant information about wolf on the node, the main example that comes to mind is Render Nodes, so that people can use selectors to have more control on which GPU each wolf-agent can use to stream stuff. This change should affect `hack/resources/device_classes/multi_gpu_class.yaml`: ```yaml apiVersion: resource.k8s.io/v1 kind: DeviceClass metadata: name: multi-gpu-node-wolf spec: selectors: - cel: expression: "device.driver == 'wolf.dra.io' && device.attributes['renderNode'] == 'renderD129'" config: - opaque: driver: wolf.dra.io parameters: video_settings: wayland_render_node: "/dev/dri/renderD129" # removed from here runner_render_node: "/dev/dri/renderD129" # removed from here ``` also, the parameters in the DeviceClass work as an override for the parameters in the ResourceClaims. #### ResourceClaims The direwolf-operator will take an application CRD and create a statefulset, alongside a resourceClaim for the Wolf-Agent to create a lobby for. The resourceClaim Parameters are a json object similar to wolf's lobbyCreationRequest. and example of this is `hack/resources/dra_tests/firefox.yaml`: ```yaml apiVersion: resource.k8s.io/v1 kind: ResourceClaim metadata: name: firefox-wayland-claim namespace: direwolf-dev spec: devices: requests: - name: wayland exactly: deviceClassName: default-wolf capacity: requests: slots: "1" # otherwise, it'll take the entire device, preventing any further scheduling on this node config: - requests: - wayland opaque: driver: wolf.dra.io parameters: # The parameters here are supposed to be a json object similar # to the lobby creation request from wolf video_settings: width: 1920 height: 1080 refresh_rate: 30 video_producer_buffer_caps: "video/x-raw(memory:DMABuf), drm-format={NV12,YV12,YU12,P012,YUYV,YU24,AB24,AR24,XB24,XR24}" audio_settings: channel_count: 2 client_settings: h_scroll_acceleration: 1 mouse_acceleration: 1 run_gid: 1000 run_uid: 1000 v_scroll_acceleration: 1 --- apiVersion: v1 kind: Pod metadata: name: firefox namespace: direwolf-dev spec: containers: - name: app image: ghcr.io/games-on-whales/firefox:edge env: # We can setup application container specific env vars from here # or from the app - name: RUN_SWAY value: "1" resources: claims: - name: wayland limits: devic.es/gpu: "1" cpu: 2 memory: 4G requests: devic.es/gpu: "1" cpu: 2 memory: 4G resourceClaims: - name: wayland resourceClaimName: firefox-wayland-claim ``` Additional Notes: * I've named the device resource "wayland" however, I now believe it's more fitting to name it "lobby" * I need someone to tell me if the status update triggering lobby join and leave is a good or bad idea * I need some help with what to do with the DeviceClass, I'm kinda lost * ClientSettings RunPID and RunGID, do those affect anything other than the app folder? I assume they're not applicable to fenrir... --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:47:02 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/fenrir#55
No description provided.