Home Deploying a GameCube Emulator (Dolphin) on Kubernetes with GitOps
Post
Cancel

Deploying a GameCube Emulator (Dolphin) on Kubernetes with GitOps

Introduction

This post details a comprehensive setup for deploying the Dolphin GameCube emulator on a Kubernetes cluster, making it accessible via a web browser. The entire infrastructure is managed using GitOps principles with ArgoCD, ensuring a declarative, version-controlled, and easily reproducible environment for retro gaming.

Project Overview

The gamecube project provides a robust and performant environment for running the Dolphin emulator, featuring:

  • Dolphin Emulator: Running inside a linuxserver/dolphin Docker container.
  • Web-based Access: The emulator’s GUI is exposed via HTTP and accessed through a secure web gateway.
  • Kubernetes: Orchestrates the deployment, storage, and networking of the application.
  • ArgoCD: Implements GitOps by continuously synchronizing the desired state defined in a Git repository with the live state of the Kubernetes cluster.
  • Persistent Storage: Two separate persistent volumes are used to manage the emulator’s configuration and the game ROMs.
  • Hardware Acceleration: GPU passthrough is configured for improved graphical performance.
  • Audio Optimization: PulseAudio is fine-tuned to prevent stuttering.

Architecture and Components

The deployment consists of several key Kubernetes resources, all defined as YAML manifests:

1. Namespace

A dedicated gamecube namespace is created to logically isolate the application’s resources.

1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
  name: gamecube

2. Storage: StatefulSet with Persistent Volumes

A StatefulSet is used to manage the Dolphin pod, ensuring stable identity and storage. It defines two volumeClaimTemplates to create PersistentVolumeClaims (PVCs) for:

  • /config: Stores the emulator’s configuration files.
  • /roms: A larger volume intended for storing GameCube game ROMs.

Using ReadWriteMany allows these volumes to be potentially accessed by multiple pods or for easier data management.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  volumeClaimTemplates:
  - metadata:
      name: config
    spec:
      accessModes:
        - ReadWriteMany 
      storageClassName: kubenfs 
      resources:
        requests:
          storage: 10Gi
  - metadata:
      name: roms
    spec:
      accessModes:
        - ReadWriteMany
      storageClassName: kubenfs
      resources:
        requests:
          storage: 50Gi

3. Service and External Access (Gateway API)

A ClusterIP service exposes the Dolphin container’s web GUI on port 3000.

To make this accessible from outside the cluster, an HTTPRoute is used. This is part of the Kubernetes Gateway API, a modern and flexible way to manage ingress traffic. The HTTPRoute attaches to a pre-existing Gateway resource (nginx-gateway) and routes traffic from a public hostname (e.g., gamecube.<your-domain>.com) to the internal gamecube-service.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: gamecube-httproute
  namespace: gamecube
spec:
  parentRefs:
    - name: nginx-gateway
      namespace: zinfra
      sectionName: https
  hostnames:
    - "gamecube.<your-domain>.com"
  rules:
    - matches:
      - path:
          type: PathPrefix
          value: /
      backendRefs:
        - name: gamecube-service
          port: 3000

4. GPU Passthrough and Privileged Mode

To achieve hardware-accelerated graphics, the GPU device from the host node (/dev/dri) is mounted directly into the container using a hostPath volume. This requires the container to run in privileged mode, which grants it extended permissions to access host devices. The StatefulSet is also pinned to a specific node (kube-worker3) that has the GPU.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    spec:
      securityContext:
        fsGroup: 1000
      containers:
      - name: gamecube
        securityContext:
          privileged: true
        #...
        volumeMounts:
        #...
        - name: gpu-device
          mountPath: /dev/dri
      volumes:
      #...
      - name: gpu-device
        hostPath:
          path: /dev/dri
      nodeName: <your-gpu-node>

5. Audio Optimization with PulseAudio

To prevent common audio stuttering issues in emulators, a ConfigMap is used to override the default PulseAudio configuration. It forces a higher latency buffer and locks the sample rate, resulting in smoother audio playback. This ConfigMap is mounted as a file into /etc/pulse/daemon.conf inside the container.

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: ConfigMap
metadata:
  name: pulse-audio-config
  namespace: gamecube
data:
  daemon.conf: |
    default-fragments = 10
    default-fragment-size-msec = 20
    default-sample-rate = 48000
    alternate-sample-rate = 48000
    high-priority = yes
    # ... more optimizations

6. Secrets and GitOps

A SealedSecret is used to securely manage the password for the web GUI. This encrypted secret is safe to commit to a Git repository. ArgoCD, configured via the Application manifest, then deploys all these resources from the Git repository, completing the GitOps workflow.

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gamecube
  namespace: argocd
spec:
  project: default
  source:
    repoURL: git@github.com:<your-username>/gamecube.git
    targetRevision: HEAD
    path: gitops/basic
  # ...

Conclusion

This setup provides a powerful, web-accessible GameCube emulation station on Kubernetes. By leveraging a StatefulSet for persistent data, the Gateway API for secure access, and host-device passthrough for GPU acceleration, it creates a high-performance and easily manageable gaming environment, all maintained through a declarative GitOps workflow.

This post is licensed under CC BY 4.0 by the author.