Deploying Omniverse Kit Apps#

This guide focuses on the integration and management of containerized Omniverse Kit Apps with an Omniverse Application Streaming API instance. It explains the use of Kubernetes Custom Resource Definitions (CRDs) and Helm charts to manage application lifecycles, including application versions and runtime profiles. It also provides instructions for configuring application manifests, deploying streaming applications, and optimizing performance, enabling efficient management and scaling of streaming services in cloud environments.

Upon completion, you should be able to integrate, configure, and manage containerized Omniverse Kit Apps using Kubernetes CRDs and Helm charts, manage application versions and optimize streaming performance.

Overview

For an Omniverse Kit App Streaming instance to be able to stream a Omniverse Kit App, it requires information about it. This information is broken into three key concepts:

  • Application - the overall Kit App that you want to make available for streaming.

  • ApplicationVersion - a specific release or version of an application.

  • Application Profile - the runtime configuration and deployment settings to use when instantiating a Kit App stream.

Application - Overview

This represents the overall Kit App that you want to make available for streaming; it is essentially the “product” that users interact with. Application Versions and Profiles are attached to Applications and define their specific configurations and deployment options.

An Application is defined in an application.yaml file that contains some basic information about it, consisting of a metadata and spec (specification) section.

Example application.yaml resource definition for the usd-viewer Kit application#
 1apiVersion: omniverse.nvidia.com/v1
 2kind: Application
 3metadata:
 4  name: usd-viewer
 5  labels:
 6    USD: "true"
 7    visualisation: "true"
 8spec:
 9  name: Omniverse USD Viewer
10  description: View Universal Scene Description files.

Application Version - Overview

This represents a specific release or version of an application. Each version might differ based on updates, bug fixes, or new features. Each application can have multiple versions. The version provides granular control over which iteration of the application is deployed and streamed. Profiles reference specific application versions for deployment.

An ApplicationVersion is defined in the application-version.yaml file that contains information about this specific application. In particular, the ApplicationVersion is where you specify the URL of the container image of the Kit App.

Example application-version.yaml resource definition for the usd-viewer Kit App#
 1apiVersion: omniverse.nvidia.com/v1
 2kind: ApplicationVersion
 3metadata:
 4  name: usd-viewer-0.2.0
 5  labels:
 6    app: usd-viewer
 7    applicationName: usd-viewer
 8    version: '0.2.0'
 9spec:
10  helm_chart: ngc-omniverse/kit-appstream-session
11  helm_chart_version: '1.4.0'
12  container: nvcr.io/nvidia/omniverse/usd-viewer
13  container_version: '0.2.0'

Application Profile - Overview

An ApplicationProfile defines how a specific version of a Kit Application should be deployed and managed within the cluster. The Application Profile includes key settings such as: supported applications and versions, resource allocation, network configuration, environment-specific settings and Helm chart mappings.

Application Profiles can have a many-to-many relationship with application versions, removing the need to create profiles for applications that have the same requirements, while also providing the ability to create profiles to differentiate between different types of execution workloads, such as those requiring additional memory, CPUs or GPUs.

An ApplicationProfile is defined in an application-profile.yaml file that provides methods for configuring or modifying many aspects of the Kit App deployment.

Example application-profile.yaml resource definition for the usd-viewer Kit App#
 1apiVersion: omniverse.nvidia.com/v1
 2kind: ApplicationProfile
 3metadata:
 4  name: default
 5spec:
 6  name: Default profile
 7  description: Updated memory and CPU settings.
 8  supportedApplications:
 9    - name: "usd-viewer"
10      versions: 
11        - "*"
12  chartMappings:
13    container: streamingKit.image.repository
14    container_version: streamingKit.image.tag
15    name: streamingKit.name
16  chartValues:
17    global:
18      imagePullSecrets:
19        - name: regcred
20    streamingKit:
21      image:
22        repository: nvcr.io/nvidia/omniverse/usd-viewer
23        pullPolicy: Always
24        tag: '0.2.0'
25      sessionId: session_id
26      name: kit-app
27      resources:
28        requests:
29          nvidia.com/gpu: "1"
30        limits:
31          cpu: "3"
32          memory: "20Gi"
33          nvidia.com/gpu: "1"
34      env:
35        - name: USD_PATH
36          value: "/app/data/Forklift_A/Forklift_A01_PR_V_NVD_01.usd"
Example application-profile.yaml resource definition for the usd-viewer Kit App, with AWS NLB settings#
 1apiVersion: omniverse.nvidia.com/v1
 2kind: ApplicationProfile
 3metadata:
 4  name: nlb
 5spec:
 6  name: NLB example profile
 7  description: Default profile - uses an AWS NLB per stream 
 8  supportedApplications:
 9    - name: "usd-viewer"
10      versions: 
11        - "*"
12  chartMappings:
13    container: streamingKit.image.repository
14    container_version: streamingKit.image.tag
15    name: streamingKit.name
16  chartValues:
17    global:
18      imagePullSecrets:
19        - name: regcred
20    streamingKit:
21      image:
22        repository: nvcr.io/nvidia/omniverse/usd-viewer
23        pullPolicy: Always
24        tag: '0.2.0'
25      sessionId: session_id
26      service:
27        signalingPort: 31000
28        mediaPort: 31001
29        healthPort: 31002
30        annotations:
31          # NLB configuration
32          service.beta.kubernetes.io/aws-load-balancer-type: "external"
33          service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
34          service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
35          service.beta.kubernetes.io/load-balancer-source-ranges: "<replace with allowed ranged>"
36          service.beta.kubernetes.io/aws-load-balancer-attributes: "load_balancing.cross_zone.enabled=true"
37          # Health check
38          service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "HTTP"
39          service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
40          service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/health"
41        type: LoadBalancer
42      name: kit-app
43      resources:
44        requests:
45          nvidia.com/gpu: "1"
46        limits:
47          cpu: "3"
48          memory: "20Gi"
49          nvidia.com/gpu: "1"
50      env:
51        - name: USD_PATH
52          value: "/app/data/Forklift_A/Forklift_A01_PR_V_NVD_01.usd"
Example application-profile.yaml resource definition for the usd-viewer Kit App, with AWS NLB settings#
 1--- /builds/omniverse/omni-docs/docs/app_ovas/reference_data/crds/generic/application-profile.yaml
 2+++ /builds/omniverse/omni-docs/docs/app_ovas/reference_data/crds/aws/application-profile-nlb.yaml
 3@@ -1,10 +1,10 @@
 4 apiVersion: omniverse.nvidia.com/v1
 5 kind: ApplicationProfile
 6 metadata:
 7-  name: default
 8+  name: nlb
 9 spec:
10-  name: Default profile
11-  description: Updated memory and CPU settings.
12+  name: NLB example profile
13+  description: Default profile - uses an AWS NLB per stream 
14   supportedApplications:
15     - name: "usd-viewer"
16       versions: 
17@@ -23,6 +23,22 @@
18         pullPolicy: Always
19         tag: '0.2.0'
20       sessionId: session_id
21+      service:
22+        signalingPort: 31000
23+        mediaPort: 31001
24+        healthPort: 31002
25+        annotations:
26+          # NLB configuration
27+          service.beta.kubernetes.io/aws-load-balancer-type: "external"
28+          service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
29+          service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
30+          service.beta.kubernetes.io/load-balancer-source-ranges: "<replace with allowed ranged>"
31+          service.beta.kubernetes.io/aws-load-balancer-attributes: "load_balancing.cross_zone.enabled=true"
32+          # Health check
33+          service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "HTTP"
34+          service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
35+          service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/health"
36+        type: LoadBalancer
37       name: kit-app
38       resources:
39         requests:
Example application-profile.yaml resource definition for the usd-viewer Kit App, with AWS NLB settings and TLS.#
 1apiVersion: omniverse.nvidia.com/v1
 2kind: ApplicationProfile
 3metadata:
 4  name: nlb-tls
 5spec:
 6  name: NLB example profile using TLS
 7  description: Default profile - uses an AWS NLB per stream 
 8  supportedApplications:
 9    - name: "usd-viewer"
10      versions: 
11        - "*"
12    - name: "usd-explorer"
13      versions: 
14        - "*"
15  chartValues:
16    global:
17      imagePullSecrets:
18        - name: regcred
19    streamingKit:
20      sessionId: session_id
21      service:
22        signalingPort: 443
23        mediaPort: 80
24        healthPort: 31002
25        annotations:
26          # NLB configuration
27          service.beta.kubernetes.io/aws-load-balancer-type: "external"
28          service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
29          service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
30          service.beta.kubernetes.io/load-balancer-source-ranges: "<replace with allowed ranged>"
31          # Cross zone
32          service.beta.kubernetes.io/aws-load-balancer-attributes: "load_balancing.cross_zone.enabled=true"
33          # Health check
34          service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "HTTP"
35          service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
36          service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/health"
37          # DNS Annotations
38          external-dns.alpha.kubernetes.io/hostname: "<not set, will be done dynamically>"
39          # NLB TLS Annotations
40          service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "<replace me with SSL cert ARN>"
41          service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
42          service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
43          service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"
44        type: LoadBalancer
45      name: kit-app
46      resources:
47        requests:
48          nvidia.com/gpu: "1"
49        limits:
50          cpu: "3"
51          memory: "20Gi"
52          nvidia.com/gpu: "1"
53      env:
54        - name: USD_PATH
55          value: "/app/data/Forklift_A/Forklift_A01_PR_V_NVD_01.usd"
Example application-profile.yaml resource definition for the usd-viewer Kit App with authentication.#
  1apiVersion: omniverse.nvidia.com/v1
  2kind: ApplicationProfile
  3metadata:
  4  name: default-auth
  5spec:
  6  name: Default profile with authorisation check
  7  description: Updated memory and CPU settings.
  8  supportedApplications:
  9    - name: "usd-viewer"
 10      versions: 
 11        - "*"
 12  chartMappings:
 13    container: streamingKit.image.repository
 14    container_version: streamingKit.image.tag
 15    name: streamingKit.name
 16  chartValues:
 17    global:
 18      imagePullSecrets:
 19        - name: regcred    # Refers to image pull secret for accessing private container registry.
 20    streamingKit:
 21      envoy:
 22        config:
 23          node:
 24            id: node0
 25            cluster: envoy-cluster    # Defines the cluster that Envoy is part of.
 26          static_resources:
 27            listeners:
 28            - name: webrtc_signaling_listener
 29              address:
 30                socket_address:
 31                  address: 0.0.0.0
 32                  port_value: 49200    # Listener for WebRTC signaling on port 49200.
 33              filter_chains:
 34              - filters:
 35                # Parent HTTP filter to allow websocket, token extraction, JWT and routing of the signaling channel
 36                - name: envoy.filters.network.http_connection_manager
 37                  typed_config:
 38                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
 39                    stat_prefix: signaling_http
 40                    codec_type: AUTO
 41                    upgrade_configs:
 42                    - upgrade_type: websocket    # Enables WebSocket upgrade for WebRTC signaling.
 43                    route_config:
 44                      name: local_route
 45                      virtual_hosts:
 46                      - name: local_service
 47                        domains: ["*"]
 48                        routes:
 49                        - match:
 50                            prefix: "/"
 51                          route:
 52                            cluster: service_cluster
 53                    # This Lua filter processes the Authorization header, extracting and validating the JWT token.
 54                    http_filters:
 55                    - name: envoy.filters.http.lua
 56                      typed_config:
 57                        "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
 58                        inline_code: |
 59                          function envoy_on_request(request_handle)
 60                            local headers = request_handle:headers()
 61                            local sec_websocket_protocol = headers:get("Sec-WebSocket-Protocol")
 62    
 63                            request_handle:logInfo("Lua filter: Checking Sec-WebSocket-Protocol header")
 64                            if sec_websocket_protocol == nil then
 65                              local checked = request_handle:streamInfo():dynamicMetadata():get("lua_checked")
 66                              if checked == nil then
 67                                request_handle:streamInfo():dynamicMetadata():set("lua_checked", "true")
 68                                request_handle:respond({[":status"] = "403"}, "Forbidden")
 69                                return
 70                              end
 71                            else
 72                              -- Correctly match and extract x-nv-sessionid and Authorization values
 73                              local sessionid, authorization = sec_websocket_protocol:match("x%-nv%-sessionid%.([%w%-]+)%-Authorization%.Bearer%-(.+)")
 74                              if sessionid and authorization then
 75                                headers:add("x-nv-sessionid", sessionid)
 76                                headers:add("Authorization", "Bearer " .. authorization)
 77                                request_handle:logInfo("Lua filter: Extracted x-nv-sessionid and Authorization headers")
 78                              else
 79                                request_handle:logInfo("Lua filter: Failed to extract x-nv-sessionid and Authorization headers")
 80                                request_handle:respond({[":status"] = "403"}, "Forbidden")
 81                                return
 82                              end
 83                            end
 84                          end
 85                    # JWT Authentication Filter - Validates the extracted JWT token.
 86                    - name: envoy.filters.http.jwt_authn
 87                      typed_config:
 88                        "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
 89                        providers:
 90                          keycloak:
 91                            issuer: "<replace with issuer>"    # Replace with your JWT issuer (e.g., Keycloak URL).
 92                            remote_jwks:
 93                              http_uri:
 94                                uri: "<replace with jwks URL>"    # Replace with JWKS URL to fetch public keys.
 95                                cluster: keycloak_cluster    # Use the Keycloak cluster for fetching keys.
 96                                timeout: 60s
 97                              cache_duration:
 98                                seconds: 1    # Caches the JWKS for 1 second.
 99                        rules:
100                        - match:
101                            prefix: "/"
102                          requires:
103                            provider_name: "keycloak"    # Use the Keycloak JWT provider for authentication.
104                    - name: envoy.filters.http.router
105                    access_log:
106                      - name: envoy.access_loggers.stream
107                        typed_config:
108                          "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
109                          log_format:
110                            text_format: |
111                              [START_TIME: %START_TIME%]
112                              REQUEST_METHOD: %REQ(:METHOD)%
113                              PATH: %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%
114                              PROTOCOL: %PROTOCOL%
115                              RESPONSE_CODE: %RESPONSE_CODE%
116                              RESPONSE_FLAGS: %RESPONSE_FLAGS%
117                              BYTES_RECEIVED: %BYTES_RECEIVED%
118                              BYTES_SENT: %BYTES_SENT%
119                              DURATION: %DURATION%
120                              UPSTREAM_HOST: %UPSTREAM_HOST%
121                              DOWNSTREAM_REMOTE_ADDRESS: %DOWNSTREAM_REMOTE_ADDRESS%
122            - name: health_listener
123              address:
124                socket_address:
125                  address: 0.0.0.0
126                  port_value: 8080
127              filter_chains:
128              - filters:
129                - name: envoy.filters.network.http_connection_manager
130                  typed_config:
131                    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
132                    stat_prefix: health_check
133                    codec_type: AUTO
134                    route_config:
135                      name: local_route
136                      virtual_hosts:
137                      - name: local_service
138                        domains: ["*"]
139                        routes:
140                        - match:
141                            prefix: "/health"
142                          direct_response:
143                            status: 200
144                            body:
145                              inline_string: "OK"
146                    http_filters:
147                    - name: envoy.filters.http.router
148            clusters:
149            - name: service_cluster
150              connect_timeout: 0.25s
151              type: STATIC
152              lb_policy: ROUND_ROBIN
153              load_assignment:
154                cluster_name: service_cluster
155                endpoints:
156                - lb_endpoints:
157                  - endpoint:
158                      address:
159                        socket_address:
160                          address: 127.0.0.1
161                          port_value: 49100  # Forwarding to the stream
162            # Keycloak cluster - Used to securely validate JWT tokens.
163            - name: keycloak_cluster
164              connect_timeout: 0.25s
165              type: STRICT_DNS
166              lb_policy: ROUND_ROBIN
167              load_assignment:
168                cluster_name: keycloak_cluster
169                endpoints:
170                - lb_endpoints:
171                  - endpoint:
172                      address:
173                        socket_address:
174                          address: <replace with FQDN of keycloak server>    # Replace with the Keycloak server address
175                          port_value: 443    # Port for HTTPS.
176              transport_socket:
177                name: envoy.transport_sockets.tls    # Use TLS for secure communication.
178                typed_config:
179                  "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
180                  # TLS context ensures encrypted communication with the Keycloak server.
181      image:
182        repository: nvcr.io/nvidia/omniverse/usd-viewer
183        pullPolicy: Always
184        tag: '0.2.0'
185      sessionId: session_id
186      name: kit-app
187      resources:
188        requests:
189          nvidia.com/gpu: "1"
190        limits:
191          cpu: "3"
192          memory: "20Gi"
193          nvidia.com/gpu: "1"
194      env:
195        - name: USD_PATH
196          value: "/app/data/Forklift_A/Forklift_A01_PR_V_NVD_01.usd"

These examples highlights a few of the many ways that an application profile can be used to tailor the execution of a Kit App to fit a broad-array of deployment needs.

  • Dynamically modifying the container image and version to be deployed, ensuring the latest or specified version is used consistently.

  • Configuring resource limits for CPU, memory, and GPU usage, guaranteeing optimal performance and preventing resource exhaustion.

  • Setting up an external Network Load Balancer (NLB) to manage traffic routing and ensure secure and reliable access to the application.

  • Defining custom health check protocols and paths, ensuring that only healthy instances of the application receive traffic.

  • Injecting environment variables into the container, allowing dynamic configuration of critical runtime paths and settings.

  • Managing secure image pulls with imagePullSecrets, ensuring access to private Docker registries for authenticated image retrieval.

  • Customizing network access controls by specifying acceptable IP ranges, enhancing security by limiting traffic to trusted sources.