Deploying Janus WebRTC Server on Kubernetes Like a Pro

Learn how to deploy Janus WebRTC server on Kubernetes for seamless scaling. This guide covers setup, configuration, and best practices for…

Deploying Janus WebRTC Server on Kubernetes Like a Pro

Deploying Janus on Kubernetes Like a Pro

Janus-webrtc server

Introduction

Imagine this: you’re tasked with deploying a Janus WebRTC server — one of the most powerful and flexible WebRTC solutions available — on Kubernetes. Naturally, you think, “There must be a guide for this, right?” But as you dig deeper, you realize no one — not even the original authors of Janus — has bothered to address this challenge. Frustrating, right?

I found myself in the same situation. I was searching everywhere for a step-by-step guide but came up empty-handed. That’s when I roped in a colleague who’s a Kubernetes guru (he might deny it, but he lives for YAML files). Together, we decided to tackle this challenge head-on. I brought my Janus expertise to the table, while he unraveled the mysteries of Kubernetes. After some serious trial and error, a few late nights, and more coffee than I’d care to admit, we finally cracked it.

This post results from our combined efforts — a complete guide to deploying Janus WebRTC on Kubernetes. Whether you’re a Kubernetes enthusiast or a Janus expert (or neither!), this guide will walk you through the process step by step. By the end, you’ll have your deployment running and understand how to scale it like a pro.

Ready to dive in? Let’s get started!

Prerequisites for Deploying Janus on Kubernetes

Before we begin, ensure we meet the following requirements:

Kubernetes Cluster:

We’ll use Minikube with the Docker driver to create a local Kubernetes cluster.

  • The cluster will have 3 nodes to replicate a real-world environment.
  • Make sure we have installed the helm

Envoy Gateway API:

We’ll use Envoy Gateway for traffic management to simplify routing and load balancing for Janus WebRTC.

  1. Start a Minikube Cluster with 3 Nodes
    This sets up a local Kubernetes cluster using the Docker driver with three nodes:
minikube start --nodes=3 --driver=docker

2. Install Envoy Gateway CRDs
Add the Envoy Gateway CRDs to your Kubernetes cluster to enable Gateway API for routing traffic to the right Janus pod

helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.2.4 -n envoy-gateway-system --create-namespace

These steps will prepare your local environment for deploying Janus on Kubernetes. Once done, you’ll have a multi-node cluster and Gateway API ready for further steps. Let’s move to the next step!

Action Time

Before we dive into the inevitable YAML madness, let me save you some time (and sanity). I’ve already set up a GitHub repo with all the YAML and configurations you need. So, if you’re a certified Kubernetes wizard who doesn’t need a step-by-step guide, feel free to skip the lecture and head straight to janus-on-kubernetes. The rest of us mortals will carry on here.

To keep things concise, I won’t be diving into the Janus configurations — you can find those in the GitHub repo. Instead, I’ll focus on the Kubernetes setup and a handy Python CLI tool we built to simplify generating service and UDPRoute.

What is Gateway API?

The Gateway API in Kubernetes is a more flexible and extensible way to manage networking for services. It builds on Ingress but provides additional features for more complex use cases. Here are its core components:

Gateway:
A Gateway is the entry point for network traffic into your cluster. It defines how traffic should be routed (e.g., which IPs or ports to listen on). Think of it as a load balancer or reverse proxy.

GatewayClass:
A GatewayClass defines the “type” of Gateways, specifying the controller (like NGINX, Envoy, etc.) that will manage the Gateway’s behavior. It’s like a blueprint for Gateways.

Listeners:
Listeners are part of a Gateway and define the protocols and ports the Gateway should listen on (e.g., HTTP on port 80).

HTTPRoute (or other Route types):
Routes define how specific types of traffic (like HTTP or TCP) should flow through the Gateway. For example, HTTPRoute maps incoming HTTP requests to specific Kubernetes services.

Backend:
A Backend is where the traffic ends up — usually a Kubernetes Service or other resource handling the actual workload.

Together, these components make it easier to manage complex networking setups, offering more customization and scalability compared to traditional Ingress. but mostly because it supports udp routing

Now that we have a clear mental map of the Gateway API, let’s dive into writing the YAML files, starting with the deployment and service configurations.

Deployment & Service Yaml

Here, we’re creating a StatefulSet deployment along with a headless service. This setup ensures that each Janus instance gets a dedicated and stable DNS name, such as:

janus-0.janus-service.default.svc.cluster.local
janus-1.janus-service.default.svc.cluster.local
janus-2.janus-service.default.svc.cluster.local

Using the BackendTrafficPolicy (BTP) in conjunction with StatefulSets and a headless service, each DNS will consistently route traffic to a fixed Janus instance. This ensures reliable, stable, and predictable connections, which are crucial for stateful workloads like Janus where each instance needs a dedicated, stable network identity.

apiVersion: apps/v1 
kind: StatefulSet 
metadata: 
  name: janus 
  namespace: default # Omit or change if not using a namespace 
spec: 
  replicas: 2 
  selector: 
    matchLabels: 
      app: janus 
  serviceName: "janus-service" # Required for StatefulSet networking 
  template: 
    metadata: 
      labels: 
        app: janus 
    spec: 
      initContainers: 
        - name: init-elevated 
          image: busybox:latest 
          command: ["sh", "-c"] 
          args: 
            - | 
              chmod -R 777 /recordings/; 
          securityContext: 
            privileged: true 
          volumeMounts: 
            - name: janus-recordings 
              mountPath: /recordings/ 
      containers: 
        - name: janus 
        # you can build your own image and push it to a registry from https://github.com/flutterjanus/JanusDocker 
          image: shivanshtalwar0/janus-server:latest # for x86_64 => shivanshtalwar0/janus-server:linux-amd64 
          resources: 
            requests: 
              memory: "256Mi" # Minimum memory the container requires 
              cpu: "250m" # Minimum CPU the container requires 
            limits: 
              memory: "512Mi" # Maximum memory the container can use 
              cpu: "500m" # Maximum CPU the container can use 
          volumeMounts: 
            - name: janus-config 
              mountPath: /opt/janus/etc/janus 
            - name: janus-recordings 
              mountPath: /recordings 
 
      volumes: 
        - name: janus-config 
          configMap: 
            name: janus-config 
        - name: janus-recordings 
          hostPath: 
            path: /recordings 
          emptyDir: {} 
 
  volumeClaimTemplates: 
    - metadata: 
        name: janus-recordings 
      spec: 
        accessModes: ["ReadWriteMany"] 
        resources: 
          requests: 
            storage: 100Gi

We use a service_template.yaml along with a port-ranger.py to dynamically create the final service_generated.yaml. This allows us to specify a custom range of ports, which is crucial for Janus as it requires UDP ports for media traversal, specifically for handling RTP media stream traffic.

# service_template.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: janus-service 
  namespace: default 
spec: 
  selector: 
    app: janus 
  ports: 
    - name: janus-http 
      protocol: TCP 
      port: 8088 
      targetPort: 8088 
    - name: janus-ws 
      protocol: TCP 
      port: 8188 
      targetPort: 8188 
    - name: janus-a-http 
      protocol: TCP 
      port: 7088 
      targetPort: 7088 
    - name: janus-a-ws 
      protocol: TCP 
      port: 7188 
      targetPort: 7188 
  clusterIP: None # Makes the service headless

We run the command below to create service_generated.yaml

python3 port-ranger.py ./service_template.yaml janus 10000 11000 UDP ./service_generated.yaml

similarly, we will create one for UDPRoute from udpRoute_template.yaml which will come in handy later

# udpRoute_template.yaml 
apiVersion: gateway.networking.k8s.io/v1alpha2 
kind: UDPRoute 
metadata: 
  name: janus-udp-route 
  namespace: default 
spec: 
  parentRefs: 
    - name: envoy-gateway  
      namespace: default 
  rules: 
    - backendRefs: 
        - name: janus-service  
          namespace: default 
          port: 10000 
          kind: Service
# Note: UDP routes cannot exceed 16 due to Kubernetes' limitation on UDP routes 
python3 port-ranger.py ./udpRoutes_template.yaml janus 10000 11000 UDP ./udp_routes_generated.yaml --chunk-size 16

Now that we’ve completed the generated YAML step, we can define the critical Gateway components. Here, we’ll outline the YAML files for the Gateway, GatewayClass, and HTTPRoute. Once we define these, we’ll be done with the core setup. Below are the YAML files for all three components:

Gateway.yaml

apiVersion: gateway.networking.k8s.io/v1 
kind: Gateway 
metadata: 
  name: envoy-gateway 
spec: 
  gatewayClassName: gateway-class 
  listeners: 
    - name: janus-http-api 
      port: 80 
      hostname: "*.example.local" 
      protocol: HTTP

GatewayClass.yaml

apiVersion: gateway.networking.k8s.io/v1 
kind: GatewayClass 
metadata: 
  name: gateway-class 
spec: 
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

httpRoute.yaml

apiVersion: gateway.networking.k8s.io/v1 
kind: HTTPRoute 
metadata: 
  name: backend 
spec: 
  parentRefs: [ {name: envoy-gateway, port: 80, sectionName: janus-http-api } ] 
  hostnames: ["*.example.local"] 
  rules: 
    - matches: 
        - path: {type: PathPrefix, value: /rest} 
      backendRefs: 
        - {group: "", kind: Service, name: janus-service, port: 8088} 
    - matches: 
        - path: {type: PathPrefix, value: /admin-rest} 
      backendRefs: 
        - {group: "", kind: Service, name: janus-service, port: 7088} 
    - matches: 
        - path: {type: PathPrefix, value: /admin-ws} 
      backendRefs: 
        - {group: "", kind: Service, name: janus-service, port: 7188}                 
    - matches: 
        - path: {type: PathPrefix, value: /ws} 
      backendRefs: 
        - {group: "", kind: Service, name: janus-service, port: 8188}

Initialize Janus Configuration

Run the following script to initialize the Janus configuration in the ConfigMap:

./update_conf.sh

Apply Kubernetes YAML Configurations

Apply the configurations in the following order to finalize the setup:

kubectl apply -f gatewayClass.yaml 
kubectl apply -f gateway.yaml 
kubectl apply -f btp.yaml 
kubectl apply -f httpRoutes.yaml 
kubectl apply -f udp_routes_generated.yaml 
kubectl apply -f deployment.yaml 
kubectl apply -f service_generated.yaml

Testing the Deployment

Update Local DNS

Update your /etc/hosts file with the following entries:

# /etc/hosts 
127.0.0.1 janus-0.example.local 
127.0.0.1 janus-1.example.local 
127.0.0.1 janus-2.example.local

Verify Access

Once everything is correctly configured, you can verify the Janus server’s accessibility via the following endpoints:

REST API

curl http://janus-0.example.local/rest/janus/info

WebSocket

http://janus-0.example.local/ws

Admin WebSocket

http://janus-0.example.local/admin-ws

Notes:

  • Ensure the port range used in your configuration is consistent.
  • For large-scale deployments, adjust the port range and user calculations as needed.

Following these steps will ensure you have a fully deployed and functional Janus setup on Kubernetes!

Conclusion

In this guide, we’ve walked through the process of deploying Janus on Kubernetes, from setting up the configurations to testing the deployment. We’ve covered essential components like StatefulSets, headless services, Gateway API, and port management to ensure a seamless deployment for Janus.

If you found this guide helpful, give it a clap! Your support helps keep this content going and encourages more technical deep dives. Happy scaling with Janus on Kubernetes!