From 48d4aea03e40327cc6c37c3e35c753625b2c2597 Mon Sep 17 00:00:00 2001 From: Stephanie Baum Date: Wed, 22 Jul 2026 21:10:50 -0700 Subject: [PATCH] docs(self-managed): publish 0.6.1-rc snapshot Signed-off-by: Stephanie Baum --- docs/v0.6.1-rc/api.md | 205 +++ docs/v0.6.1-rc/autoscaling/architecture.md | 64 + docs/v0.6.1-rc/autoscaling/index.md | 37 + docs/v0.6.1-rc/autoscaling/observability.md | 67 + docs/v0.6.1-rc/autoscaling/operations.md | 65 + docs/v0.6.1-rc/caches.md | 47 + docs/v0.6.1-rc/cli.md | 1078 ++++++++++++++ .../cluster-management/configuration.md | 1073 +++++++++++++ .../cluster-management/container-cache.md | 473 ++++++ docs/v0.6.1-rc/cluster-management/gxcache.md | 438 ++++++ docs/v0.6.1-rc/cluster-management/index.md | 45 + .../cluster-management/kai-scheduler.md | 77 + .../cluster-management/monitoring.md | 122 ++ .../v0.6.1-rc/cluster-management/reference.md | 248 +++ .../cluster-management/self-managed.md | 736 +++++++++ docs/v0.6.1-rc/configure-autoscaling.md | 154 ++ docs/v0.6.1-rc/container-functions.md | 325 ++++ docs/v0.6.1-rc/container-tasks.md | 135 ++ docs/v0.6.1-rc/control-plane-installation.md | 18 + docs/v0.6.1-rc/control-plane-operations.md | 293 ++++ .../csp-end-to-end-example-installation.md | 639 ++++++++ docs/v0.6.1-rc/example-dashboards.md | 356 +++++ docs/v0.6.1-rc/fake-gpu-operator.md | 222 +++ docs/v0.6.1-rc/function-creation.md | 87 ++ docs/v0.6.1-rc/gateway-routing.md | 809 ++++++++++ .../generic-http-function-invocation.md | 266 ++++ docs/v0.6.1-rc/grpc-function-invocation.md | 164 ++ docs/v0.6.1-rc/grpc-invocation-enablement.md | 97 ++ docs/v0.6.1-rc/grpc-load-test-sli-guide.md | 228 +++ docs/v0.6.1-rc/grpc-load-testing.md | 291 ++++ docs/v0.6.1-rc/helm-functions.md | 169 +++ docs/v0.6.1-rc/helm-tasks.md | 78 + docs/v0.6.1-rc/helmfile-installation.md | 1326 +++++++++++++++++ docs/v0.6.1-rc/http-load-test-sli-guide.md | 223 +++ docs/v0.6.1-rc/http-load-testing.md | 257 ++++ docs/v0.6.1-rc/http-soak-testing.md | 389 +++++ docs/v0.6.1-rc/image-mirroring.md | 567 +++++++ docs/v0.6.1-rc/images/apipersonalkey.png | Bin 0 -> 181344 bytes docs/v0.6.1-rc/images/grpc-reconnect-flow.png | Bin 0 -> 172632 bytes docs/v0.6.1-rc/images/grpc-single-client.png | Bin 0 -> 97920 bytes .../lls-self-hosted-arch-streaming-proxy.png | Bin 0 -> 123545 bytes .../images/low-latency-streaming.png | Bin 0 -> 206499 bytes docs/v0.6.1-rc/images/nvcf-banner.svg | 66 + .../images/nvcf-gpu-cluster-targeting.svg | 1 + .../images/nvcf-grpc-invocation-path.svg | 98 ++ .../nvcf-grpc-multicluster-invocation.svg | 136 ++ .../images/nvcf-high-level-stack.svg | 1 + .../images/nvcf-http-invocation-path.svg | 98 ++ .../nvcf-http-multicluster-invocation.svg | 132 ++ .../images/nvcf-llm-invocation-path.svg | 113 ++ .../nvcf-llm-multicluster-invocation.svg | 149 ++ docs/v0.6.1-rc/images/nvcf-lls-session.png | Bin 0 -> 79347 bytes .../nvcf-multi-region-multi-cluster.svg | 1 + .../images/self-hosted-min-topology.png | Bin 0 -> 97396 bytes docs/v0.6.1-rc/index.md | 16 + docs/v0.6.1-rc/infrastructure-sizing.md | 197 +++ docs/v0.6.1-rc/installation.md | 113 ++ docs/v0.6.1-rc/llm-function-enablement.md | 209 +++ docs/v0.6.1-rc/llm-gateway.md | 203 +++ docs/v0.6.1-rc/lls-installation.md | 676 +++++++++ docs/v0.6.1-rc/local-development.md | 103 ++ .../local-development/multi-cluster-cli.md | 331 ++++ .../multi-cluster-helmfile.md | 272 ++++ .../local-development/single-cluster-cli.md | 306 ++++ .../single-cluster-helmfile.md | 239 +++ docs/v0.6.1-rc/manifest.md | 170 +++ docs/v0.6.1-rc/metrics/cassandra/metrics.md | 11 + docs/v0.6.1-rc/metrics/ess/metrics.md | 6 + docs/v0.6.1-rc/metrics/grpc-proxy/metrics.md | 37 + .../metrics/init-container/metrics.md | 7 + .../metrics/invocation-service/metrics.md | 12 + .../metrics/llm-api-gateway/metrics.md | 42 + .../metrics/llm-function-invocation-path.md | 97 ++ .../metrics/llm-request-router/metrics.md | 35 + docs/v0.6.1-rc/metrics/metrics-index.md | 34 + docs/v0.6.1-rc/metrics/nvcf-api/metrics.md | 6 + docs/v0.6.1-rc/metrics/sis-spot/metrics.md | 5 + .../metrics/state-metrics/metrics.md | 13 + .../metrics/utils-container/metrics.md | 8 + .../metrics/vault-openbao/metrics.md | 5 + docs/v0.6.1-rc/observability.md | 414 +++++ docs/v0.6.1-rc/optional-enhancements-gpu.md | 3 + .../optional-enhancements-standalone.md | 3 + docs/v0.6.1-rc/optional-enhancements.md | 45 + docs/v0.6.1-rc/quickstart.md | 352 +++++ docs/v0.6.1-rc/registry-allowlist.md | 109 ++ .../release-notes/0.5.0-to-0.6.0-upgrade.md | 473 ++++++ docs/v0.6.1-rc/release-notes/0.6.0.md | 115 ++ docs/v0.6.1-rc/release-notes/index.md | 6 + docs/v0.6.1-rc/runbooks/caches.md | 54 + .../control-plane-key-rotation-mek.md | 215 +++ docs/v0.6.1-rc/runbooks/index.md | 10 + docs/v0.6.1-rc/runbooks/overview.md | 8 + .../samples/configs/cp-env-eks-example.yaml | 123 ++ .../samples/configs/cp-example-secrets.yaml | 46 + .../samples/configs/local-dev-env.yaml | 85 ++ .../scripts/create-nvcr-pull-secrets.sh | 20 + .../samples/scripts/force-cleanup-nvcf.sh | 238 +++ docs/v0.6.1-rc/streaming-functions.md | 448 ++++++ docs/v0.6.1-rc/task-creation.md | 44 + docs/v0.6.1-rc/third-party-registries.md | 505 +++++++ docs/v0.6.1-rc/troubleshooting.md | 1082 ++++++++++++++ fern/docs.yml | 3 + fern/versions/v0.6.1-rc.yml | 297 ++++ 104 files changed, 20514 insertions(+) create mode 100644 docs/v0.6.1-rc/api.md create mode 100644 docs/v0.6.1-rc/autoscaling/architecture.md create mode 100644 docs/v0.6.1-rc/autoscaling/index.md create mode 100644 docs/v0.6.1-rc/autoscaling/observability.md create mode 100644 docs/v0.6.1-rc/autoscaling/operations.md create mode 100644 docs/v0.6.1-rc/caches.md create mode 100644 docs/v0.6.1-rc/cli.md create mode 100644 docs/v0.6.1-rc/cluster-management/configuration.md create mode 100644 docs/v0.6.1-rc/cluster-management/container-cache.md create mode 100644 docs/v0.6.1-rc/cluster-management/gxcache.md create mode 100644 docs/v0.6.1-rc/cluster-management/index.md create mode 100644 docs/v0.6.1-rc/cluster-management/kai-scheduler.md create mode 100644 docs/v0.6.1-rc/cluster-management/monitoring.md create mode 100644 docs/v0.6.1-rc/cluster-management/reference.md create mode 100644 docs/v0.6.1-rc/cluster-management/self-managed.md create mode 100644 docs/v0.6.1-rc/configure-autoscaling.md create mode 100644 docs/v0.6.1-rc/container-functions.md create mode 100644 docs/v0.6.1-rc/container-tasks.md create mode 100644 docs/v0.6.1-rc/control-plane-installation.md create mode 100644 docs/v0.6.1-rc/control-plane-operations.md create mode 100644 docs/v0.6.1-rc/csp-end-to-end-example-installation.md create mode 100644 docs/v0.6.1-rc/example-dashboards.md create mode 100644 docs/v0.6.1-rc/fake-gpu-operator.md create mode 100644 docs/v0.6.1-rc/function-creation.md create mode 100644 docs/v0.6.1-rc/gateway-routing.md create mode 100644 docs/v0.6.1-rc/generic-http-function-invocation.md create mode 100644 docs/v0.6.1-rc/grpc-function-invocation.md create mode 100644 docs/v0.6.1-rc/grpc-invocation-enablement.md create mode 100644 docs/v0.6.1-rc/grpc-load-test-sli-guide.md create mode 100644 docs/v0.6.1-rc/grpc-load-testing.md create mode 100644 docs/v0.6.1-rc/helm-functions.md create mode 100644 docs/v0.6.1-rc/helm-tasks.md create mode 100644 docs/v0.6.1-rc/helmfile-installation.md create mode 100644 docs/v0.6.1-rc/http-load-test-sli-guide.md create mode 100644 docs/v0.6.1-rc/http-load-testing.md create mode 100644 docs/v0.6.1-rc/http-soak-testing.md create mode 100644 docs/v0.6.1-rc/image-mirroring.md create mode 100644 docs/v0.6.1-rc/images/apipersonalkey.png create mode 100644 docs/v0.6.1-rc/images/grpc-reconnect-flow.png create mode 100644 docs/v0.6.1-rc/images/grpc-single-client.png create mode 100644 docs/v0.6.1-rc/images/lls-self-hosted-arch-streaming-proxy.png create mode 100644 docs/v0.6.1-rc/images/low-latency-streaming.png create mode 100644 docs/v0.6.1-rc/images/nvcf-banner.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-gpu-cluster-targeting.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-grpc-invocation-path.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-grpc-multicluster-invocation.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-high-level-stack.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-http-invocation-path.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-http-multicluster-invocation.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-llm-invocation-path.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-llm-multicluster-invocation.svg create mode 100644 docs/v0.6.1-rc/images/nvcf-lls-session.png create mode 100644 docs/v0.6.1-rc/images/nvcf-multi-region-multi-cluster.svg create mode 100644 docs/v0.6.1-rc/images/self-hosted-min-topology.png create mode 100644 docs/v0.6.1-rc/index.md create mode 100644 docs/v0.6.1-rc/infrastructure-sizing.md create mode 100644 docs/v0.6.1-rc/installation.md create mode 100644 docs/v0.6.1-rc/llm-function-enablement.md create mode 100644 docs/v0.6.1-rc/llm-gateway.md create mode 100644 docs/v0.6.1-rc/lls-installation.md create mode 100644 docs/v0.6.1-rc/local-development.md create mode 100644 docs/v0.6.1-rc/local-development/multi-cluster-cli.md create mode 100644 docs/v0.6.1-rc/local-development/multi-cluster-helmfile.md create mode 100644 docs/v0.6.1-rc/local-development/single-cluster-cli.md create mode 100644 docs/v0.6.1-rc/local-development/single-cluster-helmfile.md create mode 100644 docs/v0.6.1-rc/manifest.md create mode 100644 docs/v0.6.1-rc/metrics/cassandra/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/ess/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/grpc-proxy/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/init-container/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/invocation-service/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/llm-api-gateway/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/llm-function-invocation-path.md create mode 100644 docs/v0.6.1-rc/metrics/llm-request-router/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/metrics-index.md create mode 100644 docs/v0.6.1-rc/metrics/nvcf-api/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/sis-spot/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/state-metrics/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/utils-container/metrics.md create mode 100644 docs/v0.6.1-rc/metrics/vault-openbao/metrics.md create mode 100644 docs/v0.6.1-rc/observability.md create mode 100644 docs/v0.6.1-rc/optional-enhancements-gpu.md create mode 100644 docs/v0.6.1-rc/optional-enhancements-standalone.md create mode 100644 docs/v0.6.1-rc/optional-enhancements.md create mode 100644 docs/v0.6.1-rc/quickstart.md create mode 100644 docs/v0.6.1-rc/registry-allowlist.md create mode 100644 docs/v0.6.1-rc/release-notes/0.5.0-to-0.6.0-upgrade.md create mode 100644 docs/v0.6.1-rc/release-notes/0.6.0.md create mode 100644 docs/v0.6.1-rc/release-notes/index.md create mode 100644 docs/v0.6.1-rc/runbooks/caches.md create mode 100644 docs/v0.6.1-rc/runbooks/control-plane-key-rotation-mek.md create mode 100644 docs/v0.6.1-rc/runbooks/index.md create mode 100644 docs/v0.6.1-rc/runbooks/overview.md create mode 100644 docs/v0.6.1-rc/samples/configs/cp-env-eks-example.yaml create mode 100644 docs/v0.6.1-rc/samples/configs/cp-example-secrets.yaml create mode 100644 docs/v0.6.1-rc/samples/configs/local-dev-env.yaml create mode 100644 docs/v0.6.1-rc/samples/scripts/create-nvcr-pull-secrets.sh create mode 100644 docs/v0.6.1-rc/samples/scripts/force-cleanup-nvcf.sh create mode 100644 docs/v0.6.1-rc/streaming-functions.md create mode 100644 docs/v0.6.1-rc/task-creation.md create mode 100644 docs/v0.6.1-rc/third-party-registries.md create mode 100644 docs/v0.6.1-rc/troubleshooting.md create mode 100644 fern/versions/v0.6.1-rc.yml diff --git a/docs/v0.6.1-rc/api.md b/docs/v0.6.1-rc/api.md new file mode 100644 index 000000000..b45a8a464 --- /dev/null +++ b/docs/v0.6.1-rc/api.md @@ -0,0 +1,205 @@ +# API + +This page provides a brief overview of the NVCF API. All API endpoints are served through your gateway. See [gateway-routing](./gateway-routing.md) for details on configuring your gateway domain and DNS. + +## OpenAPI Specification + +This page does not cover all endpoints. + +Please refer to the [OpenAPI Spec](https://api.nvcf.nvidia.com/v3/openapi) for the latest API information. + + +The OpenAPI spec linked above documents the full NVCF API surface. Replace the hosted domain with your own gateway domain when making requests. See [gateway-routing](./gateway-routing.md) for your deployment's base URL. + + + +The NVCF API is divided into the following sets of APIs: + +| APIs | Usage | +| --------------------- | ------------------------------------------------------------------------------------- | +| Function Invocation | Execution of a function that runs on a worker node. Usually an inference call. | +| Cluster Groups & GPUs | Defines endpoints to list Cluster Groups and GPUs as targets for function deployment. | +| Function Management | The creation, modification and deletion of functions | +| Function Deployment | Endpoints for creating and managing function deployments. | +| Task Management | GPU-backed batch jobs via NVIDIA Cloud Tasks (NVCT). | + +**API Versioning** + +All API endpoints include versioning in the path prefix. + +```bash +/v2/nvcf +``` + +## Authorization + +Self-hosted NVCF uses three bearer credential types: + +| Credential | CLI source | Typical use | +| --- | --- | --- | +| `NVCF_TOKEN` | `nvcf-cli init` | Default CLI credential for management operations and self-hosted cluster management | +| `NVCF_API_KEY` | `nvcf-cli api-key generate` | Default CLI credential for function invocation, function discovery, and queue status | +| `NVCF_NVCT_API_KEY` | `nvcf-cli api-key generate` | Credential for task commands (`task create`, `task list`, etc.) | + +All three credential types are sent as bearer credentials: + +```bash +Authorization: Bearer +``` + +Use the CLI for normal credential management: + +```bash +nvcf-cli init +nvcf-cli api-key generate +``` + +`nvcf-cli init` calls the API Keys admin endpoint and mints the JWT used as +`NVCF_TOKEN`. The CLI saves the token in `~/.nvcf-cli.state` for the default +configuration and reads it automatically on later commands. Export it only when +you are making direct API calls or using tooling that reads environment +variables: + +```bash +export NVCF_TOKEN=$(jq -r .token ~/.nvcf-cli.state) +``` + +The default self-hosted admin issuer role gives this token the following +scopes: + +- `register_function` +- `list_functions` +- `list_functions_details` +- `deploy_function` +- `update_function` +- `update_secrets` +- `delete_function` +- `manage_telemetries` +- `manage_registry_credentials` +- `cluster-management` + +The function API key (`NVCF_API_KEY`) is created with these default scopes: + +- `invoke_function` +- `list_functions` +- `queue_details` +- `list_functions_details` + +The task API key (`NVCF_NVCT_API_KEY`) is created with these default scopes: + +- `launch_task` +- `list_tasks` +- `task_details` +- `cancel_task` +- `delete_task` +- `list_events` +- `list_results` +- `update_secrets` + +NVCF API authorization is scope-based. For the NVCF API endpoints below, either +`NVCF_TOKEN` or `NVCF_API_KEY` can be used if that bearer includes the required +scope. The CLI prefers `NVCF_API_KEY` for read, invoke, and queue commands. It +prefers `NVCF_TOKEN` for management commands when both credentials are +configured. + +Pass `--scopes` when you need to narrow a key or create a key with management +scopes for testing. + +### Scope reference + +The OpenAPI spec describes the permission checked for each endpoint. "Accepted +bearer" means which saved CLI credential type can be sent as +`Authorization: Bearer ` when it includes the listed scope. + +| Category | Method and endpoint | Accepted bearer | Scope | +| --- | --- | --- | --- | +| Function invocation | `POST /v2/nvcf/pexec/functions/{functionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `invoke_function` | +| Function invocation | `POST /v2/nvcf/pexec/functions/{functionId}/versions/{versionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `invoke_function` | +| Function invocation | `GET /v2/nvcf/pexec/status/{requestId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `invoke_function` | +| Queue details | `GET /v2/nvcf/queues/{requestId}/position` | `NVCF_TOKEN` or `NVCF_API_KEY` | `queue_details` | +| Queue details | `GET /v2/nvcf/queues/functions/{functionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `queue_details` | +| Queue details | `GET /v2/nvcf/queues/functions/{functionId}/versions/{versionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `queue_details` | +| Function management | `GET /v2/nvcf/functions` | `NVCF_TOKEN` or `NVCF_API_KEY` | `list_functions` or `list_functions_details` | +| Function management | `GET /v2/nvcf/functions/ids` | `NVCF_TOKEN` or `NVCF_API_KEY` | `list_functions` or `list_functions_details` | +| Function management | `POST /v2/nvcf/functions` | `NVCF_TOKEN` or `NVCF_API_KEY` | `register_function` | +| Function management | `GET /v2/nvcf/functions/{functionId}/versions` | `NVCF_TOKEN` or `NVCF_API_KEY` | `list_functions` or `list_functions_details` | +| Function management | `POST /v2/nvcf/functions/{functionId}/versions` | `NVCF_TOKEN` or `NVCF_API_KEY` | `register_function` | +| Function management | `GET /v2/nvcf/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `list_functions` or `list_functions_details` | +| Function management | `PUT /v2/nvcf/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `update_function` | +| Function management | `DELETE /v2/nvcf/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `delete_function` | +| Function management | `PUT /v2/nvcf/metadata/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `update_function` | +| Function deployment | `GET /v2/nvcf/deployments/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Function deployment | `POST /v2/nvcf/deployments/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Function deployment | `PUT /v2/nvcf/deployments/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Function deployment | `DELETE /v2/nvcf/deployments/functions/{functionId}/versions/{functionVersionId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Function deployment | `GET /v2/nvcf/deployments/{deploymentId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Function deployment | `PATCH /v2/nvcf/deployments/{deploymentId}/gpu-specifications/{gpuSpecId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | +| Registry credential management | `GET /v2/nvcf/registry-credentials` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | +| Registry credential management | `POST /v2/nvcf/registry-credentials` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | +| Registry credential management | `GET /v2/nvcf/registry-credentials/{registryCredentialId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | +| Registry credential management | `PATCH /v2/nvcf/registry-credentials/{registryCredentialId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | +| Registry credential management | `DELETE /v2/nvcf/registry-credentials/{registryCredentialId}` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | +| Registry credential management | `GET /v2/nvcf/recognized-registries` | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | + +Self-hosted cluster management uses SIS endpoints from the Spot API. In +self-hosted CLI workflows, use `NVCF_TOKEN` with the `cluster-management` scope +for these endpoints. + +| Method and endpoint | Accepted bearer | Scope | +| --- | --- | --- | +| `GET /v1/accounts/{ncaId}/clusters` | `NVCF_TOKEN` | `cluster-management` | +| `POST /v1/accounts/{ncaId}/clusters` | `NVCF_TOKEN` | `cluster-management` | +| `GET /v1/accounts/{ncaId}/clusters/{clusterId}` | `NVCF_TOKEN` | `cluster-management` | +| `PUT /v1/accounts/{ncaId}/clusters/{clusterId}` | `NVCF_TOKEN` | `cluster-management` | +| `DELETE /v1/accounts/{ncaId}/clusters/{clusterId}` | `NVCF_TOKEN` | `cluster-management` | +| `GET /v1/accounts/{ncaId}/clusterVersions` | `NVCF_TOKEN` | `cluster-management` | + +Task management uses NVCT endpoints served through your gateway. All task +endpoints require `NVCF_NVCT_API_KEY` with the listed scope. + +| Method and endpoint | Accepted bearer | Scope | +| --- | --- | --- | +| `POST /v1/nvct/tasks` | `NVCF_NVCT_API_KEY` | `launch_task` | +| `GET /v1/nvct/tasks` | `NVCF_NVCT_API_KEY` | `list_tasks` | +| `POST /v1/nvct/tasks/bulk` | `NVCF_NVCT_API_KEY` | `list_tasks` | +| `GET /v1/nvct/tasks/{taskId}` | `NVCF_NVCT_API_KEY` | `task_details` | +| `DELETE /v1/nvct/tasks/{taskId}` | `NVCF_NVCT_API_KEY` | `delete_task` | +| `POST /v1/nvct/tasks/{taskId}/cancel` | `NVCF_NVCT_API_KEY` | `cancel_task` | +| `GET /v1/nvct/tasks/{taskId}/events` | `NVCF_NVCT_API_KEY` | `list_events` | +| `GET /v1/nvct/tasks/{taskId}/results` | `NVCF_NVCT_API_KEY` | `list_results` | +| `PUT /v1/nvct/secrets/tasks/{taskId}` | `NVCF_NVCT_API_KEY` | `update_secrets` | + +### Direct API key creation + +The CLI calls the API Keys service for you. If you need to call it directly, +set `expires_at` and the `scopes` array explicitly. + +```bash +GATEWAY_ADDR= +EXPIRES_AT=$(date -u -v+1d '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -d '+1 day' '+%Y-%m-%dT%H:%M:%SZ') +SERVICE_ID="nvidia-cloud-functions-ncp-service-id-aketm" + +curl -s -X POST "http://${GATEWAY_ADDR}/v1/keys" \ + -H "Host: api-keys.${GATEWAY_ADDR}" \ + -H "Content-Type: application/json" \ + -H "Key-Issuer-Service: nvcf-api" \ + -H "Key-Issuer-Id: ${SERVICE_ID}" \ + -H "Key-Owner-Id: test@nvcf-api.local" \ + -d '{ + "description": "invocation key", + "expires_at": "'"${EXPIRES_AT}"'", + "authorizations": { + "policies": [{ + "aud": "'"${SERVICE_ID}"'", + "auds": ["'"${SERVICE_ID}"'"], + "product": "nv-cloud-functions", + "resources": [ + {"id": "*", "type": "account-functions"}, + {"id": "*", "type": "authorized-functions"} + ], + "scopes": ["invoke_function", "list_functions", "queue_details", "list_functions_details"] + }] + }, + "audience_service_ids": ["'"${SERVICE_ID}"'"] + }' | jq . +``` diff --git a/docs/v0.6.1-rc/autoscaling/architecture.md b/docs/v0.6.1-rc/autoscaling/architecture.md new file mode 100644 index 000000000..532aa36ba --- /dev/null +++ b/docs/v0.6.1-rc/autoscaling/architecture.md @@ -0,0 +1,64 @@ +# Function Autoscaler Architecture + +The function autoscaler is a Rust service deployed as a horizontally scaled Kubernetes Deployment. It reads utilization and request metrics from a Prometheus-compatible timeseries database, stores discovered functions and coordination state in Cassandra, and writes desired instance counts to the NVCF API. Cassandra lightweight transactions handle leader election and short-lived per-function locks. + +The work is split into two loops. A leader-elected discovery loop scans the timeseries database for active function versions and upserts them into Cassandra. A scaling loop runs on every replica, but each replica only handles the functions whose IDs hash into its assigned buckets, so the active set is sharded across replicas. + +## Sequence Diagram + +```mermaid +sequenceDiagram + participant Workers as Workers / Invocation Services + participant TSDB as Time Series DB + participant Autoscaler as Function Autoscaler + participant Cassandra as Cassandra + participant NVCF as NVCF Service + + Workers->>TSDB: Emit utilization and instance metrics + + Note over Autoscaler,Cassandra: Discovery loop (~15s, leader-elected) + Autoscaler->>TSDB: Query active functions + TSDB-->>Autoscaler: Function set + Autoscaler->>Cassandra: Upsert newly discovered functions + + Note over Autoscaler,NVCF: Scaling loop (~30s, per-bucket) + Autoscaler->>Cassandra: Read active functions for this node's buckets + Autoscaler->>TSDB: Query current instances and utilization history + TSDB-->>Autoscaler: Metrics + Autoscaler->>TSDB: Check recent invocations (scale-to-zero guard, PromQL) + Note over Autoscaler: Compute desired instance count + Autoscaler->>NVCF: PUT requiredNumberOfInstances + NVCF-->>Autoscaler: OK or error + Autoscaler->>Cassandra: Write predicted count, refresh function TTL +``` + +The discovery loop runs on one leader-elected replica. The scaling loop runs on every replica, but each replica only processes the function buckets assigned to it. + +## Timeseries Database + +The function autoscaler is a read-only client of a Prometheus-compatible timeseries store. It calls the `/api/v1/query_range` HTTP endpoint and uses PromQL for every metric query, so any backend that implements that interface works: upstream Prometheus, Thanos, Grafana Mimir, or VictoriaMetrics. The reference NVCF deployments point at VictoriaMetrics via the `timeseries_db_url` setting. + +The function autoscaler does not run a scrape config of its own and does not write samples. Before it can do anything useful, the rest of the data plane has to be feeding the same store: + +- Worker pods export utilization and instance count metrics (`nvcf_worker_service_worker_thread_busy_seconds_total`, `nvcf_worker_service_worker_thread_count_total`, instance gauges). +- Invocation services and the gRPC proxy export request counters (`function_request`, `function_request_total`) labeled by `function_id`, `function_version_id`, and `nca_id`. These labels are how the discovery loop finds active function versions. + +For a self-hosted control plane, you need three things in place before bringing the function autoscaler online: + +1. A Prometheus-compatible store reachable from the function autoscaler pod. +2. A scrape configuration (or remote-write feed) covering the worker pods and the invocation-plane services. +3. The resulting query endpoint passed in as `timeseries_db_url`. The function autoscaler reports `not ready` on its readiness probe until that endpoint responds. + +## Coordination and Self-Healing + +Coordination relies on Cassandra TTLs to recover from failures without operator intervention: + +- The discovery lock self-expires if the leader replica crashes or is partitioned, so a new leader takes over on the next loop iteration. +- Bucket ownership is recomputed when replicas join or leave. During a reshuffle a function may be skipped for a single scaling cycle or briefly picked up by a different replica, and a short-lived per-function lock prevents two replicas from racing on the same function in that window. +- Each active function row carries a TTL refreshed by every scaling cycle, so functions that stop emitting metrics age out of the active set automatically. + +## See Also + +- [Configure Autoscaling](../configure-autoscaling.md) for setting per-function scaling bounds, factors, thresholds, and stickiness via the NVCF API. +- [Function Autoscaler Operations](./operations.md) for health endpoints and common issues. +- [Function Autoscaler Observability](./observability.md) for emitted metrics, traces, and logs. diff --git a/docs/v0.6.1-rc/autoscaling/index.md b/docs/v0.6.1-rc/autoscaling/index.md new file mode 100644 index 000000000..525d7bf7b --- /dev/null +++ b/docs/v0.6.1-rc/autoscaling/index.md @@ -0,0 +1,37 @@ +# Function Autoscaling + +The NVCF Function Autoscaler is a distributed Rust service that monitors function utilization and uses it to determine the ideal instance count per function on the NVCF control plane. It runs as a horizontally scaled deployment on the same Kubernetes cluster as the rest of the control plane. + +On an interval, the function autoscaler reads metrics from the timeseries database, decides how many instances each function should have, and calls the NVCF API to apply that decision. + +The function autoscaler depends on a Prometheus-compatible timeseries database fed by the worker pods and invocation-plane services. Without it, the service reports `not ready` and makes no scaling decisions. See [Timeseries database](./architecture.md#timeseries-database) for the required metrics and endpoints. + +## Function Autoscaler vs Horizontal Pod Autoscaler + +Function autoscaling is distinct from Kubernetes horizontal pod autoscaling (HPA). HPA scales pods within a single cluster, so it cannot reach NVCF worker pods that are spread across multiple clusters. Function autoscaling orchestrates scaling across clusters using global load patterns. + +## Key Functionality + +- Discovers active functions from invocation and worker metrics in the timeseries database and persists the active set in Cassandra. +- Periodically computes a desired instance count per function from recent utilization and the function's scaling policy. +- Applies the desired count by calling the NVCF API's predictions endpoint. +- Coordinates work across replicas using hash-based bucket assignment and Cassandra Lightweight Transaction (LWT) distributed locks. + +## Architecture Overview + +```mermaid +flowchart LR + Workers[Workers / Invocation Services] --> TSDB[(Time Series DB)] + TSDB --> Autoscaler[Function Autoscaler] + Autoscaler <--> Cassandra[(Cassandra)] + Autoscaler --> NVCF[NVCF API] +``` + +See [Architecture](./architecture.md#sequence-diagram) for the end-to-end sequence diagram and the bucket model. + +## See Also + +- [Architecture](./architecture.md) for components, data flow, and the Cassandra LWT lock behavior that elects the discovery leader. +- [Configure Autoscaling](../configure-autoscaling.md) for setting per-function scaling bounds, factors, thresholds, and stickiness via the NVCF API. +- [Function Autoscaler Operations](./operations.md) for health endpoints and operational guidance. +- [Function Autoscaler Observability](./observability.md) for the metrics, traces, and logs emitted by the service. diff --git a/docs/v0.6.1-rc/autoscaling/observability.md b/docs/v0.6.1-rc/autoscaling/observability.md new file mode 100644 index 000000000..131a30129 --- /dev/null +++ b/docs/v0.6.1-rc/autoscaling/observability.md @@ -0,0 +1,67 @@ +# Function Autoscaler Observability + +The function autoscaler emits structured logs, Prometheus metrics that explain dependency health statuses and scaling decisions, and OpenTelemetry spans for outbound calls to its dependencies. The Prometheus exporter serves metrics on the address configured in `server.metrics.exporters`. The local settings file at `crates/server/resources/settings-local.yaml` uses `0.0.0.0:41338`. + +Job and namespace labels follow the standard NVCF naming convention for the cluster that runs the function autoscaler. + +## Metric reference + +| Metric name | Metric type | Description | +|-------------|-------------|-------------| +| `nvcf_autoscaler.autoscaling.status` | Gauge | Scaling status per function, encoded as a reason code. | +| `nvcf_autoscaler.scaling.current_instances` | Gauge | Current instance count per function as read from the timeseries database. | +| `nvcf_autoscaler.scaling.desired_instances` | Gauge | Desired instance count computed by the scaling decision. | +| `nvcf_autoscaler.scaling.utilization` | Gauge | Utilization percentage per function used in the scaling decision. | +| `nvcf_autoscaler.requests.queued_total` | Counter | Scaling requests queued for processing. | +| `nvcf_autoscaler.requests.processed_total` | Counter | Scaling requests processed. | +| `nvcf_autoscaler.requests.rejected_total` | Counter | Scaling requests rejected by the policy or guard rails. | +| `nvcf_autoscaler.requests.rate_limited_total` | Counter | Scaling requests rate-limited downstream. | +| `nvcf_autoscaler.queue.size` | Gauge | Current depth of the scaling work queue. | +| `nvcf_autoscaler.queue.capacity` | Gauge | Configured capacity of the scaling work queue. | +| `nvcf_autoscaler.function_table_state` | Gauge | State of the active function table entry per function. | +| `nvcf_autoscaler.function_discovery_duration_seconds` | Histogram | Duration of each discovery loop run. | +| `nvcf_autoscaler.timeseries_db.requests_total` | Counter | Timeseries database requests, labeled by status. | +| `nvcf_autoscaler.timeseries_db.request_duration_milliseconds` | Histogram | Timeseries database request latency. | +| `nvcf_autoscaler.timeseries_db.auth_failure_total` | Counter | Timeseries database authentication failures. | +| `nvcf_autoscaler.timeseries_db.server_side_failure_total` | Counter | Timeseries database server-side query failures. | +| `nvcf_autoscaler.nvcf_api.request_duration_milliseconds` | Histogram | NVCF API request latency. | +| `nvcf_autoscaler.oauth2_api.request_duration_milliseconds` | Histogram | OAuth2 token endpoint request latency. | +| `nvcf_autoscaler.oauth2_client.token_refresh_failure_total` | Counter | OAuth2 client token refresh failures. | +| `nvcf_autoscaler.cassandra.health_status` | Gauge | Cassandra client health. 1 indicates healthy, 0 indicates unhealthy. | +| `nvcf_autoscaler.health.overall_status` | Gauge | Overall service health status. | +| `nvcf_autoscaler.health.component_status` | Gauge | Per-component health status. | +| `nvcf_autoscaler.distributed_lock` | Gauge | State of the discovery distributed lock for this replica. | +| `nvcf_autoscaler.distributed_lock.acquisition_failures_total` | Counter | Discovery lock acquisition failures. | +| `nvcf_autoscaler.processing.utilization_data_age_milliseconds` | Histogram | Age of the utilization data used in each scaling decision. | + +## Tracing + +The function autoscaler emits OpenTelemetry spans for outbound calls to the timeseries database and the NVCF API, with the OTLP endpoint and span filter configurable under `server.tracing`. + +## Logging + +The function autoscaler writes structured logs to stdout. Set log filter directives in the `server.envfilter_directive` configuration field. The format follows the `tracing_subscriber` env filter syntax (Rust ecosystem standard): + +```yaml +server: + envfilter_directive: "server=info,rs_autoscaler=debug,rs_autoscaler::cassandra=warn,info" +``` + +The same syntax applies to `server.tracing.logging_envfilter_directive` if you separate logging and tracing filters. + +Useful target prefixes: + +| Target | Covers | +|--------|--------| +| `server` | Binary entry point: startup, server lifecycle. | +| `rs_autoscaler` | Top-level function autoscaler library crate. | +| `rs_autoscaler::work` | Scaling loop, discovery loop, bucket reshuffles. | +| `rs_autoscaler::cassandra` | Cassandra client, LWT lock operations. | +| `rs_autoscaler::nvcf_api` | OAuth2, NVCF API calls. | +| `rs_autoscaler::timeseries_db` | Timeseries database query traces. | + +## See also + +- [Function Autoscaler Operations](./operations.md) for common symptoms tied to these metrics and log lines. +- [Architecture](./architecture.md) for the components that emit each signal. +- [Configure Autoscaling](../configure-autoscaling.md) for setting per-function scaling bounds and policy via the NVCF API. diff --git a/docs/v0.6.1-rc/autoscaling/operations.md b/docs/v0.6.1-rc/autoscaling/operations.md new file mode 100644 index 000000000..08e74d5b5 --- /dev/null +++ b/docs/v0.6.1-rc/autoscaling/operations.md @@ -0,0 +1,65 @@ +# Function Autoscaler Operations + +This page covers operating the function autoscaler after deployment, including health probes, common operational issues, and pointers to the Helm chart values. For log filter syntax, metrics, and traces, see [Function Autoscaler Observability](./observability.md). + +## Health endpoints + +The function autoscaler exposes three HTTP health endpoints. Their exact paths differ from the rest of the NVCF control plane: liveness and readiness are namespaced under `/admin/health/`. + +| Endpoint | Purpose | Use as | +|----------|---------|--------| +| `GET /admin/health/liveness` | Always returns 200. Indicates the process is alive. | Kubernetes liveness probe. | +| `GET /admin/health/readiness` | Returns 200 when all components are healthy, 503 otherwise. | Kubernetes readiness probe. | +| `GET /health` | Returns per-component health for `cassandra_client` and `timeseries_db_client`. | Operator-facing detail and dashboards. | + +The liveness probe deliberately does not check Cassandra or the timeseries database. Restarting the pod when those are unreachable does not help, so the function autoscaler stays running and lets readiness flip instead. + +## Common operational issues + +### Cassandra connection failures + +Symptoms: readiness flips to 503, `/health` reports the `cassandra_client` component as unhealthy, log lines from `rs_autoscaler::cassandra` show connection errors. + +Checks: + +- SSL certificates are mounted at the path expected by `cassandra.ssl`. The function autoscaler container expects the cert directory to exist; create `/etc/app/config` if it is missing. +- Credentials in the secrets file are valid for the configured keyspace. +- The contact points resolve from the pod's network namespace. + +### Timeseries database query failures + +Symptoms: `nvcf_autoscaler.timeseries_db.requests_total` shows a rising error count, `auth_failure_total` or `server_side_failure_total` is non-zero, log lines from `rs_autoscaler::timeseries_db` show 4xx or 5xx responses. + +Checks: + +- `timeseries_db.timeseries_db_url` is reachable from the pod. +- The bearer token in the secrets file is current. Token rotation is the most common cause of `auth_failure_total` spikes. +- Query time ranges fit the retention window of the backing store. + +### NVCF API errors + +Symptoms: `nvcf_autoscaler.nvcf_api.request_duration_milliseconds` shows a sustained rise in 4xx or 5xx, scaling decisions stop applying. + +Checks: + +- The OAuth2 token endpoint is reachable and the client credentials in the secrets file are valid. +- The functions being scaled are still in a deployable status. Functions in unexpected states are skipped, not retried. +- `nvcf_api.disable_auth` is set as intended for the deployment. Leave it `false` whenever the NVCF API enforces authentication. + +### Discovery is stalled + +Symptoms: the active function set in Cassandra stops growing despite traffic to new functions, `nvcf_autoscaler.distributed_lock.acquisition_failures_total` is rising across all replicas. + +Checks: + +- Inspect the `locks` table for the discovery lock row and its TTL. If the row never expires, the previous leader may have stopped refreshing without releasing it. +- Confirm at least one replica's `nvcf_autoscaler.distributed_lock` gauge reports the leader state. +- Restart the holding replica if the cluster is otherwise healthy. The lock expires within `discovery_lock_duration_seconds`. + +See [Architecture](./architecture.md#cassandra-lightweight-transactions-lwts) for the lock state machine. + +## See also + +- [Function Autoscaler Observability](./observability.md) for the metrics and traces referenced in the symptoms above. +- [Configure Autoscaling](../configure-autoscaling.md) for setting per-function scaling bounds and policy via the NVCF API. +- [Architecture](./architecture.md) for the component layout these symptoms map to. diff --git a/docs/v0.6.1-rc/caches.md b/docs/v0.6.1-rc/caches.md new file mode 100644 index 000000000..487327733 --- /dev/null +++ b/docs/v0.6.1-rc/caches.md @@ -0,0 +1,47 @@ +# Simulation Cluster Caches + +This section covers cache components for self-hosted NVCF deployments. Caches improve performance by storing frequently accessed content locally, reducing network bandwidth usage and accelerating scene loading. + +## Overview + +Self-hosted NVCF supports several cache components: + +- **Derived Data Cache Service (DDCS)** - Caches derived content to reduce scene load time and improve rendering performance +- **USD Content Cache (UCC)** - Caches USD content from object storage to accelerate scene loading + +## When to Use Caches + +See the individual cache component guides for detailed information on when to use each cache: + +- [Derived Data Cache Service](https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/) - Derived Data Cache Service +- [USD Content Cache](https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/) - USD Content Cache + +## Documentation + +Each cache component has comprehensive documentation covering configuration, deployment, and advanced features: + +## Configuration + +Cache components are configured using Helm values files. Each cache guide includes: + +- Base configuration examples +- Configuration options and parameters +- Performance tuning recommendations +- Best practices + +## Monitoring + +Cache components include Prometheus metrics for monitoring: + +- Cache hit ratios +- Storage utilization +- Request throughput +- Performance metrics + +## Next Steps + +1. **Review cache guides** - Read the detailed guides for caches you want to deploy +2. **Plan your deployment** - Determine which caches fit your use case +3. **Configure caches** - Set up Helm values files based on the examples +4. **Deploy caches** - Install caches using Helm or Helmfile +5. **Monitor performance** - Set up monitoring dashboards and alerts diff --git a/docs/v0.6.1-rc/cli.md b/docs/v0.6.1-rc/cli.md new file mode 100644 index 000000000..a84be034c --- /dev/null +++ b/docs/v0.6.1-rc/cli.md @@ -0,0 +1,1078 @@ +# Self-hosted CLI + +This page provides documentation for the NVCF Self-hosted CLI, a command-line interface for managing NVIDIA Cloud Functions in self-hosted deployments. + +## Overview + +The NVCF Self-hosted CLI provides: + +- Automatic Token Generation: Generate `NVCF_TOKEN` and API keys via direct API calls +- Smart State Management: Persistent workflow context eliminates manual ID copying +- Multi-Environment Support: Separate configurations for dev/staging/production +- gRPC Invocation: Native support for gRPC function invocation +- Shell Completion: Autocompletion for bash, zsh, fish, and PowerShell + +## Prerequisites + +- Network access to NVCF API endpoints +- A source checkout with Bazel/Bazelisk when you build from the repository +- [NGC CLI installed](https://org.ngc.nvidia.com/setup/installers/cli) when you download the CLI release from NGC + +## Installation + +You can build `nvcf-cli` from this repository or download a packaged CLI release +from NGC. Use the source build when you are validating local changes or running +the local k3d quickstart from a repository checkout. + +### Build from the repository + +Run the build from the repository root: + +```bash +bazel build //src/clis/nvcf-cli:nvcf-cli +``` + +The binary is written to: + +```text +bazel-bin/src/clis/nvcf-cli/nvcf-cli_/nvcf-cli +``` + +Install it on your `PATH`: + +```bash +install -m 0755 \ + bazel-bin/src/clis/nvcf-cli/nvcf-cli_/nvcf-cli \ + /usr/local/bin/nvcf-cli +``` + +If your environment cannot reach the configured Bazel remote cache, disable the +remote cache for this build: + +```bash +bazel build --remote_cache= //src/clis/nvcf-cli:nvcf-cli +``` + +### Download from NGC + +The CLI is available as a resource from NGC. See +[download-nvcf-cli](./image-mirroring.md) for detailed download and extraction +instructions. + +The downloaded package includes: + +- `nvcf-cli` - The CLI binary +- `.nvcf-cli.yaml.template` - Configuration template +- `examples/` - Sample configuration files +- `USAGE-GUIDE.md` - Detailed usage documentation + +## Configuration + +The CLI uses YAML configuration files. If you downloaded the packaged CLI, copy +the included template: + +```bash +cp .nvcf-cli.yaml.template .nvcf-cli.yaml +``` + +If you built the CLI from source, create `.nvcf-cli.yaml` from the examples +below or from `src/clis/nvcf-cli/examples/config-dev.yaml`. + +Configuration files are searched in this order: + +1. Explicit path via `--config` flag (highest priority) +2. Current directory: `./.nvcf-cli.yaml` +3. Home directory: `~/.nvcf-cli.yaml` + + +Place your `.nvcf-cli.yaml` in the directory where you run the CLI for project-specific configuration, or in your home directory for global configuration. + + + +### Self-Hosted Configuration + +For self-hosted deployments, the CLI must be configured to communicate with +your gateway. The gateway uses hostname-based routing for HTTP services. + + +For Gateway routing details, including architecture diagrams, verification commands, and production DNS/HTTPS setup, see [gateway-routing](./gateway-routing.md). + + + +#### Prepare Gateway API ingress + +For remote Helmfile deployments, set up Gateway API ingress before you configure +the CLI. The CLI calls the configured API, API Keys, +invocation, and gRPC endpoints during token minting, cluster registration, +health checks, and function operations. + +Complete [Gateway quickstart](./gateway-routing.md#gateway-quickstart) before you +configure the CLI. That procedure installs the Gateway API CRDs, creates and +labels the required namespaces, installs Envoy Gateway, creates the GatewayClass +and Gateway, waits for the Gateway to be programmed, and exports: + +```bash +echo "$HTTP_GATEWAY_NAMESPACE/$HTTP_GATEWAY_NAME" +echo "$GRPC_GATEWAY_NAMESPACE/$GRPC_GATEWAY_NAME" +echo "$GATEWAY_ADDR" +echo "$GRPC_GATEWAY_ADDR" +``` + +These are the same Gateway setup steps used by the Helmfile and standalone +install paths. Keep the exported values in your shell, then configure the CLI. +The local k3d quickstart uses local route hostnames instead. + +For test environments without production DNS, use the Gateway load balancer +address as the stack domain: + +```bash +export STACK_DOMAIN="$GATEWAY_ADDR" +``` + +For production environments, set `STACK_DOMAIN` to the DNS name that your +HTTPRoute hostnames use. + +#### Configuring the CLI + +Create your configuration file: + +```bash +# Copy the template +cp .nvcf-cli.yaml.template .nvcf-cli.yaml +``` + +Complete self-hosted configuration: + +```yaml +# ============================================================================== +# API Endpoints - Point to your gateway load balancer +# ============================================================================== + +# Main API endpoint (use http:// for non-TLS setups) +base_http_url: "http://" + +# Invocation endpoint (same as base_http_url for self-hosted) +invoke_url: "http://" + +# gRPC endpoint - uses dedicated TCP port (no Host header needed) +base_grpc_url: ":10081" + +# API Keys service endpoint +api_keys_service_url: "http://" + +# ============================================================================== +# Host Header Overrides (Required for Hostname-Based Routing) +# ============================================================================== +# +# Because the gateway routes HTTP requests based on the Host header, +# you must specify the correct Host header for each service. +# These values must match the HTTPRoute hostnames rendered from STACK_DOMAIN. +# +# Without these, the gateway returns 404 because it can't match the route. + +# Host header for API Keys service +api_keys_host: "api-keys." + +# Host header for NVCF API (function management) +api_host: "api." + +# Host header for Invocation service +invoke_host: "invocation." + +# ============================================================================== +# API Keys Service Configuration +# ============================================================================== + +api_keys_service_id: "nvidia-cloud-functions-ncp-service-id-aketm" +api_keys_issuer_service: "nvcf-api" +api_keys_owner_id: "svc@nvcf-api.local" + +# ============================================================================== +# Account Configuration +# ============================================================================== + +client_id: "nvcf-default" + +# ============================================================================== +# Debugging (optional - set to true for verbose output) +# ============================================================================== + +debug: false +``` + +For test environments without production DNS, the URL fields and host fields +can use the Gateway load balancer address: + +```yaml +# Gateway load balancer address +base_http_url: "http://a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" +invoke_url: "http://a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" +base_grpc_url: "a1b2c3d4e5f6.us-west-2.elb.amazonaws.com:10081" +api_keys_service_url: "http://a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" + +# Host headers matching HTTPRoute configuration +api_keys_host: "api-keys.a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" +api_host: "api.a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" +invoke_host: "invocation.a1b2c3d4e5f6.us-west-2.elb.amazonaws.com" + +# API Keys service +api_keys_service_id: "nvidia-cloud-functions-ncp-service-id-aketm" +api_keys_issuer_service: "nvcf-api" +api_keys_owner_id: "svc@nvcf-api.local" + +client_id: "nvcf-default" +debug: true +``` + +#### Verifying Your Configuration + +After configuring the CLI, verify connectivity: + +```bash +# Test token generation (uses api_keys_host) +./nvcf-cli init + +# Expected output: +# [INFO] Starting fresh session... +# [INFO] Generating admin token from API Keys service... +# [DEBUG] Using Host header override: api-keys. +# [SUCCESS] Admin token generated and saved +``` + +If you see a 404 error, verify: + +1. The `api_keys_host` value matches your HTTPRoute hostname +2. The gateway load balancer is accessible +3. The API Keys service is running: `kubectl get pods -n api-keys` + + +Why Host headers are needed: the Envoy Gateway uses hostname-based routing to +direct traffic to different backend services through a single load balancer. +Without the correct `Host` header, the gateway cannot match the request to a +route and returns 404. + + + + +gRPC does not need Host headers because it uses a dedicated TCP listener on +port 10081. The gateway routes all traffic on that port directly to the gRPC +service without hostname matching. + + + +#### Production Setup: DNS and HTTPS + +The Host header configuration above is designed for testing and development. For production deployments, configure proper DNS and TLS to eliminate the need for Host header overrides. + +With proper DNS and HTTPS configured: + +- DNS records resolve service hostnames directly to your Gateway's load balancer +- TLS certificates secure all traffic +- The CLI uses simple URLs without Host header overrides +- Browsers and other clients can access services directly + +```yaml +# Simple URLs using your domain - no Host header overrides needed! +base_http_url: "https://api.nvcf.example.com" +invoke_url: "https://invocation.nvcf.example.com" +base_grpc_url: "grpc.nvcf.example.com:443" +api_keys_service_url: "https://api-keys.nvcf.example.com" + +# No host header overrides required - DNS handles routing +# api_keys_host: "" # Not needed +``` + + +For complete instructions on setting up DNS records and TLS certificates, see [production-dns-https](./gateway-routing.md) in the Gateway Routing guide. + + + +#### Multi-Environment Setup + +Use the `--config` flag to manage multiple environments with separate configuration files: + +```bash +# Development +./nvcf-cli --config dev.yaml init +./nvcf-cli --config dev.yaml function create --input-file function.json + +# Production +./nvcf-cli --config prod.yaml init +./nvcf-cli --config prod.yaml function list +``` + +Each configuration maintains separate state files (e.g., `~/.nvcf-cli.dev.state` for `dev.yaml`). + +#### Debug Mode + +Enable debug mode for detailed logging by adding to your configuration file: + +```yaml +debug: true +``` + +Or use the `--debug` flag or `NVCF_DEBUG=true` environment variable per-command. + +## Quick Start + +```bash +# 1. Initialize - generate admin token +./nvcf-cli init + +# 2. Generate API key for invocations +./nvcf-cli api-key generate + +# 3. Create a function, update example file with your image +./nvcf-cli function create --input-file examples/create-function.json + +# 4. Deploy the function (uses saved context automatically) +./nvcf-cli function deploy create + +# 5. Invoke the function +./nvcf-cli function invoke --request-body '{"message": "hello world"}' + +# 6. Clean up +./nvcf-cli function deploy remove +./nvcf-cli function delete +``` + + +For immediate testing, you can use `load_tester_supreme` from `nvcf-onprem` (see [self-hosted-artifact-manifest](./manifest.md)), which supports the `{"message": "hello world"}` request body above. For more function samples, see the [NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples) repository and [function-creation](./function-creation.md) for function creation documentation. + + + +## Authentication + +The CLI stores three bearer credential types: + +- `NVCF_TOKEN`: Generated by `nvcf-cli init`. The default CLI credential for + management operations and self-hosted cluster management. +- `NVCF_API_KEY`: Generated by `nvcf-cli api-key generate`. The default CLI + credential for function invocation, function discovery, and queue status. +- `NVCF_NVCT_API_KEY`: Generated by `nvcf-cli api-key generate`. Used + automatically for all `task` subcommands. + +For NVCF API endpoints, either bearer type can be used when it includes the +required scope. The CLI prefers `NVCF_API_KEY` for read, invoke, and queue +commands. It prefers `NVCF_TOKEN` for management commands when both credentials +are configured. Self-hosted SIS cluster management uses `NVCF_TOKEN`. Task +commands always use `NVCF_NVCT_API_KEY`. + +See [Scope reference](./api.md#scope-reference) for the self-hosted scope +matrix used by CLI commands and API endpoints. + +### Generate NVCF_TOKEN + +```bash +# Generate fresh NVCF_TOKEN (clears existing state) +./nvcf-cli init + +# With debug output +./nvcf-cli init --debug + +# Example output: +# [INFO] Starting fresh session... +# [INFO] Generating admin token from API Keys service... +# [SUCCESS] Admin token generated and saved +# Token: +# Expires: 2025-11-19 06:08:15 +``` + +### Refresh NVCF_TOKEN + +Refresh your token while preserving function context: + +```bash +# Refresh NVCF_TOKEN (keeps current function state) +./nvcf-cli refresh + +# Example output: +# [SUCCESS] Admin token refreshed +# Function ID: func-abc123 (preserved) +``` + +### Generate API Keys + +`api-key generate` mints both a function key (`NVCF_API_KEY`) and a task key +(`NVCF_NVCT_API_KEY`) in a single command. Use `--for` to generate only one. + +```bash +# Generate both keys with defaults (24h expiration) +./nvcf-cli api-key generate + +# Generate only the function key +./nvcf-cli api-key generate --for function + +# Generate only the task key +./nvcf-cli api-key generate --for task + +# Custom expiration and description +./nvcf-cli api-key generate --expires-in 48h --description "Production key" + +# Generate with custom scopes (requires --for) +./nvcf-cli api-key generate --for function --scopes invoke_function,list_functions + +# Generate and validate +./nvcf-cli api-key generate --validate +``` + +Default function key scopes: + +| Scope | Description | +| --- | --- | +| `invoke_function` | Execute deployed functions | +| `list_functions` | View available functions | +| `list_functions_details` | View detailed function metadata | +| `queue_details` | Monitor function execution queues | + +Default task key scopes: + +| Scope | Description | +| --- | --- | +| `launch_task` | Submit new tasks | +| `list_tasks` | List tasks | +| `task_details` | Get task status and details | +| `cancel_task` | Cancel a running task | +| `delete_task` | Delete a task | +| `list_events` | List task events | +| `list_results` | Retrieve task results | +| `update_secrets` | Update secrets for a task | + +## Command Reference + +### Self-hosted Deployment Commands + +Use these commands to install and inspect self-hosted NVCF deployments. For the local k3d installation flow, see [Quickstart](./quickstart.md). + +| Command | Description | +| --- | --- | +| `self-hosted check --pre` | Check local tools and Kubernetes access before installation. | +| `self-hosted check --all` | Run all currently available self-hosted checks. Use this with pod, route, and function smoke validation. | +| `self-hosted up --cluster-name --nca-id --region ` | Run the local k3d fresh-install flow. | +| `self-hosted status` | Show a deployment health summary. | +| `self-hosted install --control-plane` | Run the control-plane installation primitive. | +| `self-hosted install --compute-plane --cluster-name ` | Run the compute-plane installation primitive for a registered GPU cluster. | +| `self-hosted uninstall --compute-plane --cluster-name ` | Remove compute-plane components for the GPU cluster. | +| `self-hosted uninstall --control-plane` | Remove control-plane components. | + +Bundle source overrides: + +- `--control-plane-stack` selects the control-plane stack bundle. +- `--compute-plane-stack` selects the compute-plane stack bundle. +- Both flags accept local paths, git URLs, and `oci://` references. + +`self-hosted up` supports only a single local k3d cluster. It requires +`--env local`, a current `k3d-*` kube context, and no split-context flags. For +separate control-plane and GPU clusters, use the explicit control-plane and +compute-plane install primitives with [Self-Managed Clusters](./cluster-management/self-managed.md). + +### Cluster Registration + +Self-managed GPU clusters must be registered with the control plane before the NVCA +operator can start an agent. Registration records the GPU cluster's OIDC issuer and public +JWKS with the control plane (ICMS) so the agent's projected service account tokens (PSAT) +validate at runtime. The `cluster register` command performs this registration and prints +the Helm values the operator install needs. + + +`init` does double duty: it mints the admin token and discovers the control-plane issuer. +Run `init` before `cluster register`. The one-click `self-hosted up` flow runs both +internally. + + + +```bash +# 1. Mint the admin token and discover the control-plane issuer +./nvcf-cli init + +# 2. Register the GPU cluster (prints a summary and a Helm values block) +./nvcf-cli cluster register \ + --name \ + --nca-id \ + --region \ + --icms-url "http://" \ + --ignore-existing +``` + +`cluster register` flags: + +| Flag | Description | +| --- | --- | +| `--name` | Cluster name (required) | +| `--nca-id` | NCA/tenant ID (required) | +| `--region` | Cluster region (default: `us-west-1`) | +| `--icms-url` | SIS/ICMS endpoint URL the agent uses to reach the control plane | +| `--nats-url` | NATS endpoint URL for the agent (optional) | +| `--kubeconfig` | Path to the target GPU cluster kubeconfig (defaults to the current context) | +| `--oidc-issuer-url` | OIDC issuer URL. Overrides auto-detection and skips SPIRE and Kubernetes discovery | +| `--ignore-existing` | Return existing IDs instead of failing if the cluster is already registered | + +Issuer and JWKS discovery: `cluster register` detects the GPU cluster's OIDC issuer and +fetches its public JWKS, then sends them to ICMS. Detection precedence: + +1. `--oidc-issuer-url` if provided (manual override). +2. A SPIRE OIDC discovery service in the cluster, if present. +3. The Kubernetes API server OIDC endpoint (default). + +The detected source is recorded as `identitySource` in the output: `psat` for the +Kubernetes API server, `spire` for SPIRE, or `custom` for a manual issuer. + +Output: the command prints a summary (cluster group ID, cluster ID, OIDC issuer, region) +followed by a `--- Helm values for nvca-operator ---` block. Copy that YAML block into a +`-register-values.yaml` file and pass it to the operator install. The values +schema: + +```yaml +clusterID: +clusterGroupID: +ncaID: +region: +selfManaged: + identitySource: psat + icmsServiceURL: "http://" + revalServiceURL: "http://" + natsURL: "nats://:4222" +``` + +For load-balancer-fronted gateways that route by hostname, add the matching host-header +overrides (`selfManaged.icmsServiceHostHeaderOverride`, +`selfManaged.revalServiceHostHeaderOverride`, `selfManaged.natsHostOverride`) to these +values. See [self-managed-clusters](./cluster-management/self-managed.md) for how the +register values feed the operator install and when host-header overrides are required. + +### General Commands + +| Command | Description | +| --- | --- | +| `init` | Generate admin token and start fresh session | +| `refresh` | Refresh admin token while preserving function context | +| `status` | Display CLI state and configuration (use `--show-tokens` for full token output) | +| `version` | Show CLI version information | +| `completion` | Generate shell autocompletion scripts (supports `bash`, `zsh`, `fish`, `powershell`) | + +### API Key Commands + +| Command | Description | +| --- | --- | +| `api-key generate` | Generate function and task API keys (both by default; use `--for function` or `--for task` for one) | +| `api-key list` | List all API keys | +| `api-key show` | Show the current saved API key | +| `api-key delete` | Delete a specific API key (supports `--force`) | +| `api-key revoke` | Revoke an API key (same as delete, supports `--force`) | +| `api-key clear` | Clear saved API key from state (supports `--force`) | +| `api-key clear-all` | Delete all API keys for an owner (supports `--force`) | + +### Task Commands + +Task commands manage NVCT (NVIDIA Cloud Tasks) workloads. They require a task +API key, which `api-key generate` mints automatically alongside the function key. + +| Command | Description | +| --- | --- | +| `task create` | Submit a new task (saves task ID to state) | +| `task list` | List tasks, optionally filtered by status | +| `task get` | Get details for a task by ID | +| `task cancel` | Cancel a running task | +| `task delete` | Delete a task | +| `task events` | List events for a task | +| `task results` | Retrieve results for a completed task | +| `task update-secrets` | Update secrets for a task | +| `task bulk` | Retrieve details for multiple tasks by ID | + +```bash +# Generate both keys (required before task commands) +./nvcf-cli api-key generate + +# Submit a container task +./nvcf-cli task create \ + --name my-training-job \ + --gpu H100 \ + --instance-type GPU.H100_1x \ + --image my-registry/training:latest + +# Check task details +./nvcf-cli task get + +# Stream lifecycle events +./nvcf-cli task events + +# Cancel a running task +./nvcf-cli task cancel + +# List recent tasks +./nvcf-cli task list +``` + +### Function Management Commands + +#### Create Function + +```bash +# Create from JSON file +./nvcf-cli function create --input-file examples/create-function.json + +# Create with CLI flags +./nvcf-cli function create \ + --name "my-function" \ + --image "nvcr.io/your-org/your-image:tag" \ + --inference-url "/predict" \ + --inference-port 8000 \ + --health-uri "/health" \ + --health-port 8000 + +# Create with additional options +./nvcf-cli function create \ + --name "my-function" \ + --image "nvcr.io/your-org/your-image:tag" \ + --inference-url "/predict" \ + --inference-port 8000 \ + --function-type STREAMING \ + --container-env "KEY1=value1" \ + --secrets "API_KEY=secret123" \ + --tags "production,v2" \ + --rate-limit "100-S" + +# Create an LLM function with model routing metadata +./nvcf-cli function create \ + --name "my-llm-function" \ + --image "nvcr.io/example/openai-compatible:latest" \ + --inference-url "/" \ + --inference-port 8000 \ + --function-type LLM \ + --llm-model "name=dummy-model,uris=/v1/chat/completions|/v1/responses|/v1/embeddings,routingMethod=round_robin,tokenRateLimit=1000-S" +``` + +All `function create` flags: + +| Flag | Description | +| --- | --- | +| `--name` | Function name (required) | +| `--image` | Container image (required) | +| `--inference-url` | Inference endpoint URL (required) | +| `--inference-port` | Inference endpoint port (required) | +| `--input-file` | JSON file with function configuration | +| `--description` | Function description | +| `--function-type` | `DEFAULT`, `STREAMING`, or `LLM` (default: `DEFAULT`) | +| `--api-body-format` | API body format (default: `CUSTOM`) | +| `--health-uri` | Health check endpoint URI | +| `--health-port` | Health check endpoint port | +| `--health-protocol` | Health protocol (`HTTP` or `gRPC`) | +| `--health-timeout` | Health check timeout (ISO 8601 duration, e.g., `PT30S`) | +| `--health-expected-status` | Expected health check status code (default: 200) | +| `--container-args` | Arguments for container launch | +| `--container-env` | Environment variables in `key=value` format (repeatable) | +| `--secrets` | Secrets in `name=value` format (repeatable) | +| `--tags` | Comma-separated tags | +| `--models` | Model artifacts in `name:version:uri` format (repeatable) | +| `--llm-model` | LLM model config in `name=MODEL,uris=URI\|URI,routingMethod=round_robin\|power_of_two\|groq_multiregion\|pulsar\|random,tokenRateLimit=LIMIT` format (repeatable). Token limits use `-` with `S`, `M`, `H`, `D`, or `W`, for example `1000-S`. Use JSON input for combined token limits because inline model specs use commas as field separators. | +| `--resources` | Resource artifacts in `name:version:uri` format (repeatable) | +| `--helm-chart` | Helm chart specification | +| `--helm-chart-service` | Helm chart service name | +| `--rate-limit` | Rate limit pattern (e.g., `100-S`, `50-M`, `10-H`, `5-D`) | +| `--rate-limit-exempted` | NCA IDs exempted from rate limiting (repeatable) | +| `--rate-limit-sync` | Enable synchronous rate limit checking | + +Example function JSON: + +```json +{ + "name": "my-inference-function", + "containerImage": "nvcr.io/your-org/your-image:tag", + "inferenceUrl": "/predict", + "inferencePort": 8000, + "health": { + "protocol": "HTTP", + "uri": "/health", + "port": 8000, + "timeout": "PT30S", + "expectedStatusCode": 200 + } +} +``` + +LLM functions use `functionType: "LLM"` and define model routing metadata under `models[].llmConfig`: + +```json +{ + "name": "sample-llm-function", + "containerImage": "nvcr.io/example/openai-compatible:latest", + "inferenceUrl": "/", + "inferencePort": 8000, + "functionType": "LLM", + "models": [ + { + "name": "dummy-model", + "llmConfig": { + "uris": ["/v1/chat/completions", "/v1/responses", "/v1/embeddings"], + "routingMethod": "round_robin", + "tokenRateLimit": "1000-S" + } + } + ] +} +``` + +For LLM models, `llmConfig.routingMethod` accepts `round_robin`, `power_of_two`, `groq_multiregion`, `pulsar`, or `random`. +Supported LLM paths are `/v1/chat/completions`, `/v1/responses`, and `/v1/embeddings`. +`llmConfig.tokenRateLimit` accepts one or more comma-separated positive integer token limits in `-` format. Supported units are `S` (seconds), `M` (minutes), `H` (hours), `D` (days), and `W` (weeks). Use `1000-S` for a single limit, or `1000-S,5000-M,100000-H,500000-D,1000000-W` for a combined limit with distinct units. Use JSON input for combined limits because inline CLI model specs use commas as field separators. + +#### Deploy Function + +The `function deploy` command group manages deployments with the following subcommands: + +| Command | Description | +| --- | --- | +| `function deploy create` | Create a new deployment for a function | +| `function deploy update` | Update an existing deployment | +| `function deploy get` | Get deployment details (supports `--json` for raw output) | +| `function deploy remove` | Remove a function deployment | + +```bash +# Deploy using saved context (from create) +./nvcf-cli function deploy create + +# Deploy with explicit IDs and configuration +./nvcf-cli function deploy create \ + --function-id \ + --version-id \ + --instance-type "NCP.GPU.A10G_1x" \ + --gpu "A10G" \ + --min-instances 1 \ + --max-instances 1 + +# Deploy from JSON file +./nvcf-cli function deploy create --input-file examples/deploy-function.json + +# View deployment details +./nvcf-cli function deploy get \ + --function-id \ + --version-id + +# Update an existing deployment +./nvcf-cli function deploy update \ + --function-id \ + --version-id \ + --gpu "A10G" \ + --instance-type "NCP.GPU.A10G_1x" \ + --min-instances 2 \ + --max-instances 4 + +# Remove a deployment +./nvcf-cli function deploy remove \ + --function-id \ + --version-id +``` + +Key `function deploy create` flags: + +| Flag | Description | +| --- | --- | +| `--function-id` | Function ID (uses state if not specified) | +| `--version-id` | Version ID (uses state if not specified) | +| `--gpu` | GPU name (default: `H100`) | +| `--instance-type` | Instance type (default: `NCP.GPU.H100_1x`) | +| `--min-instances` | Minimum instances (default: 1) | +| `--max-instances` | Maximum instances (default: 1) | +| `--max-request-concurrency` | Max request concurrency (1-1024) | +| `--input-file` | JSON file with deployment configuration | +| `--backend` | Backend/CSP for the GPU instance | +| `--regions` | Allowed deployment regions (repeatable) | +| `--clusters` | Specific clusters (repeatable) | +| `--availability-zones` | Availability zones (repeatable) | +| `--timeout` | Deployment timeout in seconds (default: 900) | +| `--storage` | Available storage (e.g., `80G`) | +| `--system-memory` | Amount of RAM | +| `--gpu-memory` | Amount of GPU memory | + +Example deployment JSON: + +```json +{ + "deploymentSpecifications": [ + { + "gpu": "A10G", + "instanceType": "NCP.GPU.A10G_1x", + "minInstances": 1, + "maxInstances": 1 + } + ] +} +``` + +#### List and Get Functions + +```bash +# List all functions +./nvcf-cli function list + +# List function IDs only +./nvcf-cli function list-ids + +# List versions of a specific function +./nvcf-cli function list-versions + +# Get details of a specific function version +./nvcf-cli function get \ + --function-id \ + --version-id + +# Get details as raw JSON +./nvcf-cli function get \ + --function-id \ + --version-id \ + --json +``` + +#### Update Function + +```bash +# Update function tags +./nvcf-cli function update \ + --function-id \ + --version-id \ + --tags "production,v2" + +# Update LLM model routing config +./nvcf-cli function update \ + --function-id \ + --version-id \ + --llm-model-update "name=dummy-model,routingMethod=round_robin,tokenRateLimit=1000-S" + +# Update from JSON file +./nvcf-cli function update \ + --function-id \ + --version-id \ + --input-file metadata-update.json +``` + +LLM model updates can also be provided in the input file: + +```json +{ + "functionId": "", + "versionId": "", + "modelUpdates": [ + { + "modelName": "dummy-model", + "llmConfig": { + "routingMethod": "round_robin", + "tokenRateLimit": "1000-S,5000-M,100000-H,500000-D,1000000-W" + } + } + ] +} +``` + +#### Invoke Function + +```bash +# Invoke using saved context +./nvcf-cli function invoke --request-body '{"input": "Hello, World!"}' + +# gRPC invocation +./nvcf-cli function invoke --grpc --request-body '{"input": "test"}' + +# gRPC with custom service and method +./nvcf-cli function invoke --grpc \ + --grpc-service "MyService" \ + --grpc-method "Predict" \ + --request-body '{"input": "test"}' + +# Invoke with explicit IDs and timeout +./nvcf-cli function invoke \ + --function-id \ + --version-id \ + --request-body '{"input": "Hello!"}' \ + --timeout 120 + +# Invoke an LLM function with the chat completions path +./nvcf-cli function invoke \ + --function-id \ + --version-id \ + --model-name dummy-model \ + --inference-url /v1/chat/completions \ + --request-body '{"messages":[{"role":"user","content":"Hello"}],"stream":true}' + +# Invoke another OpenAI-compatible LLM path +./nvcf-cli function invoke \ + --function-id \ + --version-id \ + --model-name dummy-model \ + --inference-url /v1/embeddings \ + --request-body '{"input":"NVCF embeddings check"}' +``` + +Note: The CLI `function invoke` command detects LLM functions automatically. +For LLM functions, `--model-name` and `--inference-url` are required. The CLI uses the LLM invocation route and sets the OpenAI `model` value to `/`. + +For LLM Gateway endpoint behavior, routing, and session stickiness details, see [LLM Gateway](./llm-gateway.md). + +For raw HTTP invocation, HTTP streaming, gRPC metadata, and invocation error +behavior, see [Generic HTTP Function Invocation](./generic-http-function-invocation.md) +and [gRPC Function Invocation](./grpc-function-invocation.md). + +Additional `function invoke` flags: + +| Flag | Description | +| --- | --- | +| `--grpc` | Use gRPC invocation | +| `--grpc-service` | gRPC service name | +| `--grpc-method` | gRPC method name | +| `--grpc-plaintext` | Use plaintext (insecure) gRPC | +| `--inference-url` | Function path, or OpenAI-compatible path for LLM functions (required for LLM) | +| `--model-name` | OpenAI model name for LLM functions | +| `--timeout` | Request timeout in seconds (default: 60) | +| `--poll-duration` | Invocation hold-open duration in seconds (default: 5) | +| `--input-file` | JSON file with invocation configuration | + +#### Queue Management + +```bash +# Get queue status for a function +./nvcf-cli function queue status + +# Get position for a specific request +./nvcf-cli function queue position +``` + +#### Delete Function + +```bash +# Delete current function from state +./nvcf-cli function delete + +# Delete specific function +./nvcf-cli function delete --function-id --version-id + +# Delete deployment only (keep function definition) +./nvcf-cli function delete --deployment-only --graceful +``` + +### Registry Credentials Commands + +Manage container registry credentials for function images and Helm charts. For comprehensive setup instructions including IAM configuration for AWS ECR, see [third-party-registries-self-hosted](./third-party-registries.md). + +| Command | Description | +| --- | --- | +| `registry-credential add` | Add a new registry credential | +| `registry-credential list` | List all registry credentials | +| `registry-credential get` | Get details of a specific credential | +| `registry-credential update` | Update an existing credential | +| `registry-credential delete` | Delete a registry credential | +| `registry-credential list-recognized` | List all recognized registries | + +```bash +# Add registry credentials using base64 secret +./nvcf-cli registry-credential add \ + --hostname "nvcr.io" \ + --secret "" \ + --artifact-type CONTAINER \ + --description "NGC Container Registry" + +# Add registry credentials using username/password +./nvcf-cli registry-credential add \ + --hostname "nvcr.io" \ + --username "" \ + --password "" \ + --artifact-type CONTAINER + +# List registry credentials (with optional filters) +./nvcf-cli registry-credential list +./nvcf-cli registry-credential list --artifact-type CONTAINER +./nvcf-cli registry-credential list --provisioned-by USER + +# Get details for a specific credential +./nvcf-cli registry-credential get + +# Update a credential +./nvcf-cli registry-credential update \ + --username "" \ + --password "" + +# Delete registry credentials +./nvcf-cli registry-credential delete +./nvcf-cli registry-credential delete --force + +# List recognized registries +./nvcf-cli registry-credential list-recognized +``` + + +Registry credential changes take up to about 5 minutes to take effect for task creation. `nvcf-cli registry-credential list` and `get` show the new value immediately, but task processing caches account credentials for about 5 minutes (`nvct.nvcf.cache-ttl`), so a task can keep using the previous value until the cache refreshes. After rotating or deleting a credential, allow up to about 5 minutes, or restart the task service to apply it immediately. See [Credential Propagation Delay](./third-party-registries.md). + + +## Troubleshooting + +### Authentication Errors + +401 Unauthorized on function creation: + +```bash +# Regenerate admin token +./nvcf-cli init --debug + +# Verify token is being used +./nvcf-cli function create --debug --input-file function.json +# Look for: "Using FUNCTION TOKEN for POST" +``` + +403 Forbidden on invocation: + +```bash +# Regenerate API key +./nvcf-cli api-key generate --validate + +# Verify API key is being used +./nvcf-cli function invoke --debug --request-body '{"input": "test"}' +# Look for: "Using API KEY for POST" +``` + +### Token Expiration + +```bash +# Check token status +./nvcf-cli status + +# Refresh admin token +./nvcf-cli refresh + +# Regenerate API key +./nvcf-cli api-key generate +``` + +### Connection Issues + +```bash +# Enable debug mode to see request details +./nvcf-cli function list --debug + +# Verify CLI state and configuration +./nvcf-cli status +``` + +### Token Usage Summary + +| Operation | Accepted bearer | Scope | CLI preference | +| --- | --- | --- | --- | +| `function create` | `NVCF_TOKEN` or `NVCF_API_KEY` | `register_function` | `NVCF_TOKEN` | +| `function deploy create` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | `NVCF_TOKEN` | +| `function deploy get` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | `NVCF_TOKEN` | +| `function deploy update` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | `NVCF_TOKEN` | +| `function deploy remove` | `NVCF_TOKEN` or `NVCF_API_KEY` | `deploy_function` | `NVCF_TOKEN` | +| `function delete` | `NVCF_TOKEN` or `NVCF_API_KEY` | `delete_function` | `NVCF_TOKEN` | +| `function update` | `NVCF_TOKEN` or `NVCF_API_KEY` | `update_function` | `NVCF_TOKEN` | +| `function invoke` | `NVCF_TOKEN` or `NVCF_API_KEY` | `invoke_function` | `NVCF_API_KEY` | +| `function list`, `function list-ids`, `function list-versions`, `function get` | `NVCF_TOKEN` or `NVCF_API_KEY` | `list_functions` or `list_functions_details` | `NVCF_API_KEY` | +| `function queue status`, `function queue position`, `function queue details` | `NVCF_TOKEN` or `NVCF_API_KEY` | `queue_details` | `NVCF_API_KEY` | +| `registry-credential` commands | `NVCF_TOKEN` or `NVCF_API_KEY` | `manage_registry_credentials` | `NVCF_TOKEN` | +| Self-hosted cluster register, list, rotate, delete | `NVCF_TOKEN` | `cluster-management` | `NVCF_TOKEN` | +| `task create` | `NVCF_NVCT_API_KEY` | `launch_task` | `NVCF_NVCT_API_KEY` | +| `task list` | `NVCF_NVCT_API_KEY` | `list_tasks` | `NVCF_NVCT_API_KEY` | +| `task get` | `NVCF_NVCT_API_KEY` | `task_details` | `NVCF_NVCT_API_KEY` | +| `task cancel` | `NVCF_NVCT_API_KEY` | `cancel_task` | `NVCF_NVCT_API_KEY` | +| `task delete` | `NVCF_NVCT_API_KEY` | `delete_task` | `NVCF_NVCT_API_KEY` | +| `task events` | `NVCF_NVCT_API_KEY` | `list_events` | `NVCF_NVCT_API_KEY` | +| `task results` | `NVCF_NVCT_API_KEY` | `list_results` | `NVCF_NVCT_API_KEY` | +| `task update-secrets` | `NVCF_NVCT_API_KEY` | `update_secrets` | `NVCF_NVCT_API_KEY` | + +For additional troubleshooting, see [self-hosted-troubleshooting](./troubleshooting.md). diff --git a/docs/v0.6.1-rc/cluster-management/configuration.md b/docs/v0.6.1-rc/cluster-management/configuration.md new file mode 100644 index 000000000..1a3c78214 --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/configuration.md @@ -0,0 +1,1073 @@ +# NVCA Configuration + +This page documents NVCA configuration options. For cluster registration and lifecycle +operations, see [Self-Managed Clusters](./self-managed.md). + +## Advanced Settings + + +Some of the options below are supported only on Cluster Agent Versions `2.50.0` or higher. + + + +See below for descriptions of all available configuration options. + +| Configuration | Description | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Cluster Agent Version | Version of the cluster agent to be installed on the cluster. Defaults to the latest available. Recommended to use the latest version available at the time of registration unless there are business reasons to pick another version. | +| Node Selector Key and Node Selector Value | This Key-Value pair is the label selector key to control the placement of the cluster agent and the cluster agent operator pods to specic nodes on the cluster. Not providing a value will allow these infrastructure components to be placed anywhere on the cluster. Ensure there are matching nodes in the cluster using `kubectl get node -l key=value` before registration as incorrect value will cause operational issues. For additional details: [Labels & Selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) | +| Priority Class | Set appropriate kubernetes priority class name for cluster agent and the operator pod. Additional details: [Priority Class](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#priorityclass) | +| Model Cache Volume Mount Options | Configure the model cache volume mount options based on the CSI Driver capabilities on the cluster. Refer to the CSI Driver documentation. Defaults to `Enabled` and `ro,norecovery,nouuid` on an upgrade. Requires cluster reconfiguration after upgrade to prevent disruption.Additional details: [Mount options](https://man7.org/linux/man-pages/man8/mount.8.html) | +| Network CIDR Range | Quoted & comma separated list of CIDR range for outbound network access for the infrastructure components & workloads on the cluster. | +| Worker Degradation Period | Stabilization time (in minutes) before cluster agent fails to consider a worker as healthy and initiates a purge. | + +## Cluster Features + +Cluster Features allow enabling specific features on the cluster. Dynamic GPU Discovery is enabled by default. + +See below for descriptions of all cluster features. + +| Capability | Description | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Dynamic GPU Discovery | Enables automatic detection and management of allocatable GPU capacity within the cluster via the NVIDIA GPU Operator. This capability is **strongly recommended** and would only be disabled in cases where [Manual Instance Configuration](./configuration.md) is required. | +| Caching Support | Enhances application performance by storing frequently accessed data (models, resources and containers) in a cache. See [cluster-caching](./configuration.md). | +| Optimized AI Workload Scheduling | Enable support for optimized AI workload scheduling using [KAI Scheduler](https://github.com/kai-scheduler/KAI-Scheduler). Additional setup details: [KAI Scheduler](./kai-scheduler.md) | +| Shared Cluster mode | Partitions the Kubernetes cluster's nodes into NVCF and non-NVCF pools. Set the label `nvca.nvcf.nvidia.io/schedule=true` on all nodes that can receive NVCF workload Pods. **Note**: this is an advanced use case and should not be used unless absolutely necessary | + + +Removing the Dynamic GPU Discovery will require manual instance configuration. See [Manual Instance Configuration](./configuration.md). + + + +### Caching Support + +Enabling caching for models, resources and containers is recommended for optimal performance. You must create `StorageClass` configurations for caching within your cluster to fully enable "Caching Support" with the Cluster Agent. See examples below. + + +Caching is not supported for Multi-Node Helm functions. If you attempt to deploy a Multi-Node Helm function with Caching Support enabled, the deployment will fail. + +Caching is also currently not supported for AWS EKS. + + + +**StorageClass Configurations in GCP** + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-sc +provisioner: pd.csi.storage.gke.io +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + type: pd-ssd + csi.storage.k8s.io/fstype: xfs +``` + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-cc-sc +provisioner: pd.csi.storage.gke.io +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + type: pd-ssd + csi.storage.k8s.io/fstype: xfs +``` + + +GCP currently allows only [10 VM's](https://cloud.google.com/compute/docs/disks#:~:text=You%20can%20attach%20a%20balanced,VMs%20in%20read%2Donly%20mode) to mount a Persistent Volume in Read-Only mode. + + + +**StorageClass Configurations in Azure** + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-sc +provisioner: file.csi.azure.com +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + skuName: Standard_LRS + csi.storage.k8s.io/fstype: xfs +``` + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-cc-sc +provisioner: file.csi.azure.com +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + skuName: Standard_LRS + csi.storage.k8s.io/fstype: xfs +``` + +**StorageClass Configurations in Oracle Cloud** + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-sc +provisioner: blockvolume.csi.oraclecloud.com +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + csi.storage.k8s.io/fstype: xfs +``` + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: nvcf-cc-sc +provisioner: blockvolume.csi.oraclecloud.com +allowVolumeExpansion: true +volumeBindingMode: Immediate +reclaimPolicy: Retain +parameters: + csi.storage.k8s.io/fstype: xfs +``` + +**Apply the StorageClass Configurations** + +Save the StorageClass template to files `nvcf-sc.yaml` and `nvcf-cc-sc.yaml` and apply them as: + +```bash +kubectl create -f nvcf-sc.yaml +kubectl create -f nvcf-cc-sc.yaml +``` + +**Override the Default Mount Options for Cache Volumes** + + +Supported in Cluster Agent Versions 2.45.21 or higher + + + + +Please note this is a Post NVCA Install Operation and needs careful consideration to ensure there are no volume corruptions. Use with caution. + + + +Cluster Agent with caching support by default will enable linux mount-options with `ro,norecovery,nouuid`. + +If the CSI Driver in the cluster doesn't support mount options then you may apply the following command on the cluster to disable the mount options + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" \ + --type='merge' \ + -p '{"spec":{"overrides":{"agentConfig":{"cacheMountOptionsEnabled":false}}}}' +``` + +If you want to update the mount-options to a different value for example: `ro,norecovery`. You may use the following command. Replace these options with desired value as dictated by CSI Driver Volume Mount Options. + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" \ + --type='merge' \ + -p '{"spec":{"overrides":{"agentConfig":{"cacheMountOptionsEnabled":true,"cacheMountOptions":"ro,norecovery"}}}}' +``` + +### Cluster Maintenance Modes + +The Cluster Agent supports two maintenance modes that control how workloads are handled during cluster configuration changes. Configure maintenance mode via the feature flags `CordonMaintenance` or `CordonAndDrainMaintenance` respectively. + +**Cordon Maintenance** + +In this mode, existing workloads continue to run uninterrupted on the cluster. New workloads will not be scheduled until maintenance mode is cleared. + +**Cordon and Drain Maintenance** + +In this mode, all existing workloads in the cluster are terminated. No updates to the state of workloads will be effective while in this mode. + + +Once maintenance mode is configured, it can take up to 10 minutes for the agent reconfiguration to take effect. + + + +### Account-Isolated Clusters + + +Supported in Cluster Agent Versions 2.49.0 or higher + + + +Clusters with the `AccountIsolation` attribute have enhanced isolation between workloads, ensuring that function and task instances run on nodes isolated by NCAId. This is particularly important for customers with strict security requirements or those who want to ensure complete separation of workloads at the account level. + + +In Account Isolated mode, the cluster might be inefficient in GPU utilization if workloads are not designed to utilize the full capacity of the isolated nodes. +While toggling this attribute, the cluster workloads also have to be drained using [CordonAndDrainMaintenance](./configuration.md) mode to effectively re-balance the workloads as the attribute will not be applied retroactively. + + + +### NVLink-optimized Clusters + +Clusters with [MNNVL](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-cds.html#dra-docs-compute-domains) GPUs like [GB200](https://www.nvidia.com/en-us/data-center/gb200-nvl72/) can run multi-node workloads that require inter-GPU data transfer with [large performance improvements](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-cds.html#usage-example-a-multi-node-nvbandwidth-test) when properly configured. +The Cluster Agent can be directed to configure multi-node workloads with their own [ComputeDomains](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-cds.html#computedomains-multi-node-nvlink-simplified) automatically to optimize inter-GPU connections. + +Additional prerequisites: +\- The [NVIDIA GPU DRA driver](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-intro-install.html) must be installed. +\- The `NVLinkOptimized` cluster attribute must be added during cluster registration. + + +In NVLink-optimized mode, the NVIDIA GPU DRA driver currently limits one GPU-enabled Pod to a node. To optimally utilize these clusters, GPU-enabled Pods _should_ request a full node's worth of GPUs. For example, nodes in GB200 clusters have 4 GPUs each so all containers and all GPU-enabled Pods in a workload must request `nvidia.com/gpu` values that sum to 4. + + + +### Kata Container-Isolated Workloads + +Clusters that have this attribute run all function/task Pods in [Kata Containers](https://katacontainers.io/) without exception. + +Additional cluster restrictions to be aware of: + +- Pod containers _must_ at least have resource `limits` defined for `cpu` and `memory`. If unset, runtime behavior is undefined. + +- Object count limits are configured for resource fairness in these clusters: + + - ConfigMaps: 20 + - Secrets: 20 + - Services: 20 + - Pods: 100 + - Jobs: 10 + - CronJobs: 10 + - Deployments: 10 + - ReplicaSets: 10 + - StatefulSets: 10 + +## Network Configuration + + +The network policies described in this section are only enforced if your cluster's Container Network Interface (CNI) supports Kubernetes Network Policies. Common CNIs that support network policies include: + +- Calico +- Cilium +- Weave Net +- Antrea + +If your cluster uses a CNI that doesn't support network policies, the security controls described below will not be enforced, and pods will be able to communicate with each other without restrictions. This could lead to security vulnerabilities. + + + +The NVCA operator requires outbound network connectivity to pull images, charts, and report logs and metrics. During installation, the operator pre-configures the `nvca-namespace-networkpolicies` configmap with the following network policies: + +| Policy Name | Description | +| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| allow-egress-gxcache | Allows egress traffic to the GX Cache namespace for caching operations (only relevant for NVIDIA managed clusters) | +| allow-egress-internet-no- internal-no-api | Allows egress traffic to the public internet (0.0.0.0/0) but blocks traffic to common private IP ranges. Also allows DNS resolution via kube-dns. | +| allow-egress-intra-namespace | Controls pod-to-pod communication within the same namespace. This policy is only applied to function namespaces and not to shared pod instance namespaces. | +| allow-egress-nvcf-cache | Allows egress traffic to NVCF cache services (only relevant for NVIDIA managed clusters) | +| allow-egress-prometheus- nvcf-byoo | Allows egress traffic to Prometheus monitoring endpoints (only relevant for NVIDIA managed clusters) | +| allow-ingress-monitoring | Allows ingress traffic for monitoring services | +| allow-ingress-monitoring-dcgm | Allows ingress traffic for DCGM monitoring | +| allow-ingress-monitoring- gxcache | Allows ingress traffic for GX Cache monitoring (only relevant for NVIDIA managed clusters) | + +## Key Network Requirements + +1. **Kubernetes API Access** + + - NVCA requires access to the Kubernetes API + - Consult your cloud provider's documentation (e.g., Azure, AWS, GCP) for the Kubernetes API endpoint + +2. **Container Registry and NVCF Control Plane Access** + + - NVCA requires access to your container registry to pull images and Helm charts. + - NVCA requires network access to NVCF control plane services (SIS, NATS, ESS) running in your cluster. The specific endpoints depend on your gateway configuration. See [gateway-routing](../gateway-routing.md) for details. + +3. **Monitoring and Logging** + + - If your environment requires advanced monitoring or logging (e.g., sending logs to external endpoints), ensure your cluster's NetworkPolicy or firewall rules allow egress to the required monitoring/logging domains + +## Network Policy Customization via ConfigMap + +The NVCA operator pre-configures the `nvca-namespace-networkpolicies` configmap during installation. If you need to customize these policies for your cluster, you can use a configmap to override the default policies. + +To customize a network policy: + +1. Create a configmap with your custom network policy, for example: + + ```yaml + + apiVersion: v1 + kind: ConfigMap + metadata: + name: demopatch-configmap + namespace: nvca-operator + labels: + nvca.nvcf.nvidia.io/operator-kustomization: enabled + data: + patches: | + - target: + group: "" + version: v1 + kind: ConfigMap + name: nvca-namespace-networkpolicies + patch: |- + - op: replace + path: /data/allow-egress-internet-no-internal-no-api + value: | + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-egress-internet-no-internal-no-api + labels: + app.kubernetes.io/name: nvca + app.kubernetes.io/instance: nvca + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: nvca-operator + spec: + podSelector: {} + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: gxcache + ports: + - port: 8888 + protocol: TCP + - port: 8889 + protocol: TCP +``` + +2. Apply the configmap: + + ```bash + + kubectl apply -f patchcm.yaml +``` + +3. Verify the changes: + + ```bash + + kubectl logs -n nvca-operator -l app.kubernetes.io/name=nvca-operator +``` + + You should see a message indicating successful patching: + `configmap patched successfully` + +The changes will be applied to the `nvcf-backend` namespace and will be used for all new namespaces' network policies. The network policies will also be updated across all helm chart namespaces. + +## Network Policy Customization via clusterNetworkCIDRs Flag + +You can customize the `allow-egress-internet-no-internal-no-api` policy with helm, by adding on the `networkPolicy.clusterNetworkCIDRs` flag. For example: + +```bash +helm upgrade nvca-operator -n nvca-operator --create-namespace -i --reuse-values --wait \ + oci://${REGISTRY}/${REPOSITORY}/nvca-operator --version \ + --set networkPolicy.clusterNetworkCIDRs="{10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/12}" +``` + +This command will override the default k8s networking CIDRs specified in the `allow-egress-internet-no-internal-no-api` with your input. + +## Advanced: Additional Configuration Options + +## CSI Volume Mount Options + +The NVIDIA Cluster Agent supports customizing CSI volume mount options for caching. This allows you to configure specific mount options for the CSI volumes used in your cluster. + + +CSI volume mount options configuration is an **experimental feature** and may be subject to change in future releases. + + + +To configure CSI volume mount options: + +1. Get the NVCF cluster name: + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +``` + +2. View current mount options configuration: + +```bash +kubectl get nvcfbackend -n nvca-operator "$nvcf_cluster_name" -o yaml | grep -A 5 "MountOptions" +``` + +1. Set mount options (example): + +```bash +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" \ + --type='merge' \ + -p '{"spec":{"overrides":{"agentConfig":{"cacheMountOptionsEnabled":true,"cacheMountOptions":"ro,norecovery,nouuid"}}}}' +``` + +4. Verify the changes: + +```bash +kubectl get nvcfbackend -n nvca-operator "$nvcf_cluster_name" -o yaml | grep -A 5 "MountOptions" +``` + +The default mount options are: +\- `ro`: Read-only mount +\- `norecovery`: Skip journal recovery +\- `nouuid`: Ignore filesystem UUID + +You can modify these options based on your specific requirements. The configuration will be applied to all CSI volumes created by the NVIDIA Cluster Agent for caching purposes. + +## Node Selection for Cloud Functions + +By default, the cluster agent uses all nodes discovered with GPU resources to schedule Cloud Functions and there are no additional configuration required. + +In order to limit the nodes that can run Cloud Functions, you may use `nvca.nvcf.nvidia.io/schedule=true` label on the specific nodes. + +If there are no nodes in the cluster with the `nvca.nvcf.nvidia.io/schedule=true` label set, the cluster agent will switch to the default behavior of using all nodes with GPUs. + +For example, to mark specific nodes as schedulable in a cluster: + +```bash +kubectl label node nvca.nvcf.nvidia.io/schedule=true +``` + +To mark a single node from the above set as unschedulable for nvcf workloads, you can unlabel using: + +```bash +kubectl label node nvca.nvcf.nvidia.io/schedule- +``` + +## GPU Product Name Override + +The NVIDIA Cluster Agent supports GPU product name override via node label. This is useful for customers who want to use a custom product name or override the default GPU product name. + +For example, to set the GPU product name for a node, use the following command: + +```bash +kubectl label node nvca.nvcf.nvidia.io/gpu.product= +``` + + +The GPU Product Name Override via node labeling only takes effect when there are no pre-existing active instances in the cluster. If active instances already exist with the original GPU instance types, the override will not be applied. + + +## Single MIG Mode Support + +The NVIDIA Cluster Agent supports GPUs configured in single +[Multi-Instance GPU (MIG)](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) +mode. When the NVIDIA GPU Operator is configured with the `single` MIG strategy, +each GPU is partitioned into multiple MIG instances of the same profile. NVCA +automatically detects MIG profiles from the `nvidia.com/gpu.product` node label and +generates a unique instance type for each profile. + +### How It Works + +In single MIG mode, the GPU Operator appends the MIG profile to the GPU product name +in the node label. For example: + +```text +nvidia.com/gpu.product=NVIDIA-H200-MIG-7g.141gb +nvidia.com/gpu.product=NVIDIA-RTX-PRO-6000-Blackwell-Server-Edition-MIG-2g.48gb +nvidia.com/gpu.product=NVIDIA-RTX-PRO-6000-Blackwell-Server-Edition-MIG-1g.24gb +``` + +NVCA parses the MIG suffix from the product name and produces a unique instance type +for each profile. The MIG profile in the instance type name uses hyphens and lowercase +(for example, `MIG-2g-48gb`). + +| GPU Product Label | Instance Type Name | +| --- | --- | +| `NVIDIA-H200-MIG-7g.141gb` | `NCP.GPU.H200-MIG-7g-141gb` | +| `NVIDIA-RTX-PRO-6000-...-MIG-2g.48gb` | `ON-PREM.GPU.RTXPRO6000-MIG-2g-48gb` | +| `NVIDIA-RTX-PRO-6000-...-MIG-1g.24gb` | `ON-PREM.GPU.RTXPRO6000-MIG-1g-24gb` | + +The cluster provider prefix, for example `NCP`, `ON-PREM`, or `AWS`, is determined +by the cluster registration configuration. + +### Prerequisites + +- **GPU Operator** configured with MIG strategy `single`. See the + [GPU Operator MIG documentation](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html) + for setup instructions. +- **Dynamic GPU Discovery** enabled, the default for customer-managed clusters. +- NVCA **v2.53.0** or later. + + +Changing MIG mode or MIG profiles on a node is a disruptive operation. Nodes may +reboot or terminate GPU clients while applying the new MIG geometry. Before +reconfiguring MIG: + +1. **Cordon** the target nodes to prevent new workload scheduling. +2. **Drain** all active GPU workloads from the nodes. +3. Apply the MIG configuration change. +4. Wait for the GPU Operator MIG Manager to finish, see verification below. +5. **Uncordon** the nodes once verification passes. + +See the [GPU Operator MIG reconfiguration guide](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html#configuring-mig-profiles) +for details on the reconfiguration process. + + + +### Verification + +After configuring MIG on your cluster, first confirm the GPU Operator has finished +applying the MIG profile before checking NVCA labels. + +**Step 1: Verify MIG Manager has converged** + +Check that MIG-configured nodes report `nvidia.com/mig.config.state=success`: + +```bash +# Check MIG configuration state on MIG nodes +kubectl get nodes -l nvidia.com/mig.strategy=single \ + -o custom-columns='NODE:.metadata.name,MIG_CONFIG:.metadata.labels.nvidia\.com/mig\.config,STATE:.metadata.labels.nvidia\.com/mig\.config\.state' +``` + +All target nodes should show `success` in the STATE column. If any node shows +`pending`, `rebooting`, or a failure state, wait for the GPU Operator to finish +or investigate the MIG Manager logs before proceeding. + +**Step 2: Verify NVCA instance type labels** + +Once MIG Manager has converged, verify that NVCA has detected the MIG profiles and +applied the correct instance type labels: + +```bash +# Check the GPU product label on MIG nodes +kubectl get nodes -l nvidia.com/mig.strategy=single \ + -o custom-columns='NODE:.metadata.name,GPU_PRODUCT:.metadata.labels.nvidia\.com/gpu\.product' + +# Verify the instance type label assigned by NVCA +kubectl get nodes -l nvidia.com/mig.strategy=single \ + -o custom-columns='NODE:.metadata.name,INSTANCE_TYPE:.metadata.labels.nvca\.nvcf\.nvidia\.io/instance-type' +``` + +Each MIG profile should have a distinct instance type label. For example, a node with +`MIG-2g.48gb` should show `ON-PREM.GPU.RTXPRO6000-MIG-2g-48gb` while a node with +`MIG-1g.24gb` should show `ON-PREM.GPU.RTXPRO6000-MIG-1g-24gb`. + + +Only the `single` MIG strategy is supported. In single mode, all GPUs on a node +are partitioned with the same MIG profile. The `mixed` MIG strategy, with different +profiles on the same node, is not currently supported by NVCA. + + + +## Managing Feature Flags + +The NVIDIA Cluster Agent supports various feature flags that can be enabled or disabled to customize its behavior. The following are some commonly used feature flags: + +| Feature Flag | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DynamicGPUDiscovery | Dynamically discover GPUs and instance types on this cluster. This is enabled by default for customer-managed clusters. | +| HelmSharedStorage | Configure Helm functions and tasks with shared read-only storage for ESS secrets. This is required for enabling Helm-based tasks in your cluster. Please note turning on this feature flag requires additional configuration, see [Helm Shared Storage section below](./configuration.md). | +| LogPosting | Post instance logs to SIS directly. This is enabled by default for NVIDIA managed clusters. | +| MultiNodeWorkloads | Instruct NVCA to report multi-node instance types to SIS during registration. | +| SelfHosted | Enables local vault-based authentication for self-hosted deployments. Required when `ngcConfig.clusterSource` is `self-managed`. | +| HelmAllowCPUNodes | Allow CPU-only pods (e.g. etcd, redis, envoy) from Helm-based functions to be scheduled on non-GPU nodes; GPU pods keep required instance-type affinity. Reduces cost and improves GPU utilization. Mutually exclusive with HelmResourceConstraints. See [helm-allow-cpu-nodes](./configuration.md). | + +### Setting Feature Flags at Install Time + +Feature flags can be set during the initial NVCA Operator installation through Helm values. + +**Standalone Helm** + +Set `selfManaged.featureGateValues` in your values file. The chart default is +`["DynamicGPUDiscovery"]`. + +**In the values file:** + +```yaml +selfManaged: + featureGateValues: ["DynamicGPUDiscovery", "SelfHosted", "LogPosting"] +``` + +**Or via** `--set` **during install:** + +```bash +helm upgrade --install nvca-operator \ + oci://${REGISTRY}/${REPOSITORY}/nvca-operator \ + --version 1.2.7 \ + --namespace nvca-operator --create-namespace \ + -f nvca-operator-values.yaml \ + --set 'selfManaged.featureGateValues={DynamicGPUDiscovery,SelfHosted,LogPosting}' +``` + + +The `--set` flag **replaces** the entire list. You must include all desired flags, +not just the new one. + + + +To update flags on an existing installation, run `helm upgrade` with the updated values +file or `--set`: + +```bash +helm upgrade nvca-operator \ + oci://${REGISTRY}/${REPOSITORY}/nvca-operator \ + --version 1.2.7 \ + --namespace nvca-operator \ + -f nvca-operator-values.yaml \ + --set 'selfManaged.featureGateValues={DynamicGPUDiscovery,SelfHosted,LogPosting,MultiNodeWorkloads}' +``` + +**Helmfile** + +The Helmfile deployment uses the same `selfManaged.featureGateValues` chart value. By +default, the helmfile does not set this field, so the chart default +`["DynamicGPUDiscovery"]` applies. + +To override, add `featureGateValues` to the worker release values in +`helmfile.d/03-worker.yaml.gotmpl`: + +```yaml +- selfManaged: + featureGateValues: ["DynamicGPUDiscovery", "SelfHosted", "LogPosting"] + imageCredHelper: + imageRepository: {{ .Values.global.image.registry }}/{{ .Values.global.image.repository }}/nvcf-image-credential-helper +``` + +Alternatively, set it in an environment-specific values file (e.g., +`environments/.yaml`) under the same key path, which avoids editing the shared +helmfile template. + +After changing, run `helmfile --selector release-group=workers sync` to apply. + +### Verifying Feature Flags + +After installing or upgrading, verify the active feature flags: + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +kubectl get nvcfbackends -n nvca-operator "$nvcf_cluster_name" -o jsonpath='{.spec.featureGate.values}' && echo "" +``` + +The NVCA agent pod command-line args also reflect the active flags: + +```bash +kubectl get pods -n nvca-system -o yaml | grep -i feature +``` + +### Modifying Feature Flags at Runtime + +Feature flags can also be modified at runtime by patching the NVCFBackend resource directly. +This is useful for quick changes without running a `helm upgrade`. + + +Prefer `helm upgrade` with updated values to change feature flags. Direct patches to the +NVCFBackend will be overwritten on the next Helm upgrade. + + + +1. Get the NVCF cluster name: + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +``` + +2. View current feature flags: + +```bash +kubectl get nvcfbackends -n nvca-operator -o yaml | grep -A 5 "featureGate:" +``` + +3. Patch the feature flags. Note that this will override all feature flags. + + +When modifying feature flags, you must preserve any existing feature flags you want to keep. The patch command will override all feature flags, so you need to include all desired feature flags in the value array. + + + +```bash +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" --type=merge -p '{"spec":{"overrides":{"featureGate":{"values":["LogPosting","CachingSupport"]}}}}' +``` + +As an alternative to the patch command, you can also modify the feature flags using the edit command: + +```bash +kubectl edit nvcfbackend -n nvca-operator +... +spec: + featureGate: + values: + - LogPosting # Existing feature flag + overrides: + featureGate: + values: + - LogPosting # Existing feature flag copied over + - -CachingSupport # Caching support disabled + ... +``` + +4. Verify the changes: + +```bash +kubectl get pods -n nvca-system -o yaml | grep -i feature +``` + +## CPU-only pod scheduling (HelmAllowCPUNodes) + + +Supported in Cluster Agent 2.50.4 or higher. + + + +When `HelmAllowCPUNodes` is enabled, NVCA schedules CPU-only pods from Helm-based functions on +non-GPU nodes while keeping GPU pods on GPU nodes with their required instance-type affinity. +This reduces infrastructure cost and improves GPU utilization. + +**Scheduling behavior** + +| Pod type | Scheduling behavior | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| GPU pods | Required instance-type node affinity (unchanged). | +| CPU-only pods | Preferred anti-affinity (weight 100) for nodes with the instance-type label; schedules on CPU-only nodes when available, can fall back to GPU nodes if needed. | + + +`HelmAllowCPUNodes` cannot be enabled when `HelmResourceConstraints` is enabled. +`HelmResourceConstraints` is enabled by default. You must disable it first by adding +`-HelmResourceConstraints` to the feature gate values, then add `HelmAllowCPUNodes`. + + + +**How to enable** + +1. Follow the steps in [Modifying Feature Flags at Runtime](./configuration.md). + +2. In `spec.overrides.featureGate.values`, include all existing flags you want to keep, add + `-HelmResourceConstraints`, then add `HelmAllowCPUNodes`. + + Example (preserve your existing flags and add the two changes): + + ```yaml + spec: + overrides: + featureGate: + values: + - # copy existing flags here + - -HelmResourceConstraints + - HelmAllowCPUNodes + ``` + +**How to disable** + +Remove `HelmAllowCPUNodes` from the values list, or explicitly disable it: + +```yaml +spec: + overrides: + featureGate: + values: + - -HelmAllowCPUNodes +``` + +## Enable Helm Shared Storage + +The NVIDIA Cluster Agent supports shared storage for Helm charts through the [SMB CSI driver](https://github.com/kubernetes-csi/csi-driver-smb/tree/master/charts#install-csi-driver-with-helm-3). This feature is required for enabling Helm-based tasks in your cluster. + + +The Helm shared storage feature must be enabled before you can use Helm-based tasks in your cluster. This feature provides the necessary storage infrastructure for Helm chart operations. + + + + +When enabling the Helm shared storage feature flag, you must preserve any existing feature flags. The patch command will override all feature flags, so you need to include all desired feature flags in the value array. If you already have other feature flags enabled, you should include them along with "HelmSharedStorage" in the value array. + + + +1. First, install the SMB CSI driver using Helm: + +```bash +helm repo add csi-driver-smb https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/master/charts +helm install csi-driver-smb csi-driver-smb/csi-driver-smb --namespace kube-system --version v1.16.0 +``` + +2. Get the NVCF cluster name: + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +``` + +3. Enable the Helm shared storage feature flag: + +```bash +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" --type=merge -p '{"spec":{"overrides":{"featureGate":{"values":["LogPosting","HelmSharedStorage", "CachingSupport"]}}}}' +``` + +4. Verify that the feature flag is enabled: + +```bash +kubectl get pods -n nvca-system -o yaml | grep HelmSharedStorage +``` + +## Agent Config Merging + +The NVIDIA Cluster Agent supports merging custom configuration into the generated NVCA config +via the `agentConfig.mergeConfig` Helm value. This allows you to override or extend NVCA +runtime settings without modifying the operator's config generation logic. + +When `agentConfig.mergeConfig` is set, the Helm chart creates a ConfigMap called +`agent-config-merge` containing the provided YAML. This ConfigMap is mounted into the NVCA +pod and merged with the generated config at runtime. + +Example values: + +```yaml +agentConfig: + mergeConfig: | + agent: + logLevel: debug +``` + +Local LLM worker transport example: + +```yaml +agentConfig: + mergeConfig: | + workload: + stargateQUICInsecure: true +``` + +`workload.stargateQUICInsecure: true` makes generated LLM workers pass +`--quic-insecure` to the `pylon` sidecar. Use it only for local or +isolated test clusters that run the LLM request router tunnel without TLS. For +the full LLM addon setup, see +[LLM Function Enablement](../llm-function-enablement.md). + +Apply via Helm: + +```bash +helm upgrade nvca-operator -n nvca-operator --create-namespace -i \ + \ + --set-file agentConfig.mergeConfig=my-nvca-config.yaml +``` + +Or include it in a values file passed to `helm upgrade -f values.yaml`. + +## Manual Instance Configuration + + +It is **highly recommended** to rely on Dynamic GPU Discovery (and therefore the NVIDIA GPU Operator), as manual instance configuration is error-prone. + +This type of configuration is only necessary when the cluster cloud provider does not support the NVIDIA GPU Operator. + + + +Manual instance configuration allows you to disable Dynamic GPU Discovery and instead provide a static list of instance types that NVCA will register with the NVCF control plane. This is useful when: + +- You have a known, fixed set of GPU configurations +- Dynamic GPU discovery isn't working correctly for your environment +- You want to control exactly which instance types are available + +By default, NVCA uses Dynamic GPU Discovery to automatically detect GPUs on cluster nodes and register appropriate instance types. When this is disabled, NVCA instead reads a static GPU configuration from a ConfigMap. + +### Prerequisites + +- A working NVCF cluster with `nvca-operator` installed + +- Access to modify Helm values for `nvca-operator` + +- Since you are not using the GPU Operator, you must ensure each GPU node has the instance-type label that matches the "value" field in your manual configuration: + + ```bash + kubectl label nodes nvca.nvcf.nvidia.io/instance-type= + ``` + + For example, if your configuration specifies `"value": "OCI.GPU.A10"`, you would label the node with: + + ```bash + kubectl label nodes gpu-node-1 nvca.nvcf.nvidia.io/instance-type=OCI.GPU.A10 + ``` + +### Step 1: Create the GPU Configuration JSON + +Create a JSON file defining your GPU types and instance configurations. The configuration is an array of GPU types, each containing an array of instance types. + +**Example Configuration (gpu-config.json):** + +```json +[ + { + "name": "H100", + "capacity": 8, + "instanceTypes": [ + { + "name": "ON-PREM.GPU.H100_1x", + "value": "ON-PREM.GPU.H100", + "description": "One Nvidia Hopper GPU", + "default": true, + "cpuCores": 16, + "systemMemory": "128G", + "gpuMemory": "80G", + "gpuCount": 1, + "os": "linux", + "driverVersion": "535.135.05", + "cpuArch": "amd64", + "storage": "1Ti" + }, + { + "name": "ON-PREM.GPU.H100_8x", + "value": "ON-PREM.GPU.H100", + "description": "Eight Nvidia Hopper GPUs (Full Node)", + "default": false, + "cpuCores": 128, + "systemMemory": "1Ti", + "gpuMemory": "640G", + "gpuCount": 8, + "os": "linux", + "driverVersion": "535.135.05", + "cpuArch": "amd64", + "storage": "8Ti" + } + ] + } +] +``` + +### Step 2: Base64 Encode the Configuration + +The GPU configuration must be Base64-encoded for the Helm values. Use the following command: + +```bash +# Base64 encode the configuration (without line wrapping) +GPU_CONFIG_B64=$(cat gpu-config.json | base64 -w 0) +echo $GPU_CONFIG_B64 +``` + + +On macOS, use `base64` without the `-w 0` flag: + +```bash +GPU_CONFIG_B64=$(cat gpu-config.json | base64) +``` + + + +### Step 3: Configure Helm Values + +Update your `nvca-operator` Helm values to disable Dynamic GPU Discovery and provide the manual configuration. + +**Example values.yaml:** + +```yaml +selfManaged: + featureGateValues: [] + gpuManualInstanceConfigB64: "" +``` + + +By default `selfManaged.featureGateValues` is `["DynamicGPUDiscovery"]`. Set it to an empty list (`[]`) to disable dynamic discovery and use your manual configuration instead. + + + +### Step 4: Install or Upgrade the Operator + +Apply the configuration using Helm: + +```bash +helm upgrade nvca-operator -n nvca-operator --create-namespace -i \ + \ + -f values.yaml +``` + +If you are using the NVCF self-hosted Helmfile, add your values file as an entry in the nvca-operator release `values:` list and run `helmfile sync` or `helmfile apply` instead. + +### Configuration Fields Reference + +**GPU Type Fields:** + +| Field | Required | Description | +| --- | --- | --- | +| `name` | Yes | GPU type name (e.g., "A100", "L40", "H100"). Must match the GPU product name reported by `nvidia-smi`. | +| `capacity` | No | Total GPU capacity for this type. Used for resource accounting and quota management. | +| `instanceTypes` | Yes | Array of instance type configurations for this GPU type. | + +**Instance Type Fields:** + +| Field | Required | Description | +| --- | --- | --- | +| `name` | Yes | Unique instance type identifier (e.g., "ON-PREM.GPU.H100_1x"). This is the name users select when deploying functions. | +| `value` | Yes | Instance type value used for internal matching (e.g., "ON-PREM.GPU.H100"). Must match the `nvca.nvcf.nvidia.io/instance-type` node label. | +| `description` | No | Human-readable description displayed in the UI. | +| `default` | No | Whether this is the default instance type for this GPU. Only one instance type per GPU should be marked as default. | +| `cpuCores` | Yes | Number of CPU cores allocated to workloads using this instance type. | +| `systemMemory` | Yes | System RAM allocation (e.g., "28G", "128G", "1Ti"). Uses Kubernetes quantity format. | +| `gpuMemory` | Yes | Total GPU memory for this instance type. For multi-GPU instances, this is the total across all GPUs. | +| `gpuCount` | Yes | Number of GPUs in this instance type. | +| `os` | No | Operating system (e.g., "linux"). | +| `driverVersion` | No | NVIDIA driver version (e.g., "535.135.05"). | +| `cpuArch` | No | CPU architecture (e.g., "amd64", "arm64"). | +| `storage` | No | Storage allocation per instance (e.g., "512Gi", "1Ti"). Uses Kubernetes quantity format. | + +### Verification + +After applying the configuration, verify that NVCA is using the static configuration: + +1. **Check the NVCFBackend resource:** + + ```bash + kubectl get nvcfbackend -n nvca-operator -o yaml + ``` + + Look for `-DynamicGPUDiscovery` in the feature gates and verify the GPU configuration is present. + +2. **Check the nvca-config ConfigMap:** + + ```bash + kubectl get configmap nvca-config -n nvca-system -o yaml + ``` + + The `gpus` key should contain your JSON configuration. + +3. **Check NVCA logs for registration:** + + ```bash + kubectl logs -n nvca-system -l app=nvca | grep -i "registration\|instance" + ``` + +### Troubleshooting + +**Configuration Not Applied:** + +1. Verify Dynamic GPU Discovery is disabled in the feature gate values +2. Ensure the Base64 encoding is correct and doesn't contain line breaks +3. Check that the JSON is valid before encoding + +**Invalid JSON Format:** + +1. Validate your JSON using a JSON validator before encoding +2. Ensure all required fields are present +3. Check that numeric values (`cpuCores`, `gpuCount`) are not quoted as strings + +**Memory/Storage Format Errors:** + +Memory and storage values must use valid Kubernetes quantity format: + +- Valid: `"28G"`, `"128Gi"`, `"1Ti"`, `"512Mi"` +- Invalid: `"28GB"`, `"128 Gi"`, `"1TB"` + + +Use `G` or `Gi` for gigabytes, `T` or `Ti` for terabytes. The `i` suffix indicates binary units (1024-based). + + + +## Cloud Provider-Specific Notes + +## Oracle Cloud Infrastructure (OCI) + +When using Oracle Container Engine for Kubernetes (OKE), ensure that: + +- Your compute nodes and GPU nodes are in the same availability domain +- This is required for proper network connectivity between the NVIDIA Cluster Agent and GPU nodes +- Flannel CNI is the current recommended and validated CNI vs OCI native CNI for OKE cluster networking. diff --git a/docs/v0.6.1-rc/cluster-management/container-cache.md b/docs/v0.6.1-rc/cluster-management/container-cache.md new file mode 100644 index 000000000..d3543629d --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/container-cache.md @@ -0,0 +1,473 @@ +# Container Cache + +This guide provides detailed information on the Container Cache component and its use in NVCF GPU clusters. + +Container Cache provides a container caching solution specifically optimized for NGC. It is designed to enhance the efficiency of Docker image pulls from NGC by caching the images locally, reducing network bandwidth and improving pull times for frequently accessed images. + +The Container Cache acts as a proxy between your Kubernetes cluster and the NGC registry, caching frequently accessed container images locally to reduce network bandwidth usage and improve deployment times. + +## Installation + +### Prerequisites + +- A running Kubernetes cluster with `kubectl` access +- **Helm** >= 3.12 +- Credentials for the registry where your NVCF charts and images are stored + +### Step 1. Authenticate Helm to your chart registry + +Authenticate Helm to your OCI registry where the NVCF charts are stored: + +```bash +echo "${REGISTRY_PASSWORD}" | helm registry login ${REGISTRY} \ + --username '${REGISTRY_USERNAME}' --password-stdin +``` + +### Step 2. Create the namespace and image pull secret + +```bash +kubectl create namespace container-caching +``` + +Create an image pull secret so that pods can pull container images from your registry. + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace=container-caching +``` + + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server=.dkr.ecr..amazonaws.com \ + --docker-username=AWS \ + --docker-password="$(aws ecr get-login-password --region )" \ + --namespace=container-caching +``` + + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server= \ + --docker-username= \ + --docker-password= \ + --namespace=container-caching +``` + + + + + + +The secret name `nvcr-creds` is referenced in the values file under `images.secrets`. +If you use a different secret name, update the values file to match. + + + +### Step 3. Create a values file + +Create a `values.yaml` using the complete example in [Base Configuration] below. + +- **BYOC users** pulling directly from NGC can use the `nvcr.io/nvidia/nvcf-byoc/` image + paths without mirroring. +- **Self-hosted users** should replace the `/` placeholders with + their mirrored registry path (see the Artifact Manifest in the self-hosted installation guide). + +Adjust `storageClassName` for your environment (e.g., `gp3` for AWS EKS). + +### Step 4. Install the chart + + + + +```bash +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf --force-update +helm repo update + +helm upgrade --install container-cache \ + nvcf/nvcf-container-cache \ + --version 0.25.22 \ + --namespace container-caching \ + --values values.yaml +``` + + + + + +Replace `/` with your mirrored registry path. + +```bash +helm upgrade --install container-cache \ + oci:////nvcf-container-cache \ + --version 0.25.22 \ + --namespace container-caching \ + --values values.yaml +``` + + + + + +### Step 5. Verify the installation + +Container Cache deploys two workloads: + +- A **StatefulSet** (`container-cache`) with the number of replicas set by `replicaCount`. + Each replica runs two containers (nginx proxy + prometheus exporter) and provisions two PVCs + (`cache` and `proxy-cache`). +- A **DaemonSet** (`container-cache-cc`) that runs on every node and configures the container + runtime (containerd) to route image pulls through the cache. + +```bash +# StatefulSet replicas and DaemonSet pods should all be Running +kubectl get pods -n container-caching + +# Verify the StatefulSet is fully ready (READY should match replicaCount) +kubectl get statefulset -n container-caching + +# Verify the DaemonSet is running on all nodes +kubectl get daemonset -n container-caching + +# Check services are created +kubectl get svc -n container-caching + +# Check persistent volume claims are Bound +kubectl get pvc -n container-caching +``` + +## Base Configuration + +The following is a complete example `values.yaml` for deploying Container Cache. +Copy this file and adjust values for your environment. Each section is explained in detail +below. + +- **BYOC users** can use the `nvcr.io/nvidia/nvcf-byoc/` paths directly. +- **Self-hosted users** should replace `/` with their mirrored + registry path (see the Artifact Manifest in the self-hosted installation guide for source image paths). + +```yaml +replicaCount: 3 + +targetHost: nvcr.io,docker.io + +images: + server: //nvcf-container-cache:v1.1.31 + exporter: nginx/nginx-prometheus-exporter:1.0 + certificates: //nvcf-proxy-tls-certs:1.2.0 + secrets: + - nvcr-creds + +cache: + keyStorageSize: 50m + maxSize: 180g + inactive: 1d + valid: 1h + +persistentVolumeClaim: + sizeGB: 100 + storageClassName: gp3 # Use gp3 for AWS EKS, adjust for other platforms + sizeProxyGB: 100 + +service: + type: ClusterIP + port: 30345 + +metrics: + cacheMetricsStorageSize: 300m + throughputHistogramBuckets: 25000000, 30000000, 35000000, 40000000, 50000000, 60000000, 80000000, 100000000 + +resources: + requests: + memory: 2Gi + cpu: "1" + limits: + memory: 4Gi + cpu: "2" + +traces: + enabled: false + +nucleus: + enabled: false + +vault: + enabled: false + +monitoring: + enabled: false +``` + +## Configuration Sections + +### Replicas + +The number of Container Cache pods is controlled through the `replicaCount` value. Container Cache replicas operate independently and distribute requests from worker nodes. + +```yaml +# values.yaml + +# Min Value: 1 +# Recommended Value: 3 +replicaCount: 3 +``` + + +Container Cache is designed to scale horizontally to handle increased load. + + + +### Node Selection + +Container Cache pods are scheduled on nodes with appropriate labels to ensure they run on compute nodes. Adjust the node selector based on your cluster's node labeling scheme. + +```yaml +# values.yaml + +nodeSelector: + nvcf.nvidia.com/workload: gpu # Adjust based on your node labels, or remove if not using node labels +``` + +### Target Hosts + +The Container Cache can proxy requests to multiple container registries. The default configuration includes both NGC and Docker Hub. + +```yaml +# values.yaml + +# Domain of target Host where the proxy passes the request to +targetHost: nvcr.io,docker.io +``` + +### Image Configuration + +Container Cache uses specific images for the server, exporter, and certificates: + +```yaml +# values.yaml + +images: + # Container Cache Nginx Proxy + server: //nvcf-container-cache:v1.1.31 + + # Nginx Prometheus Exporter (public image, no mirroring required) + exporter: nginx/nginx-prometheus-exporter:1.0 + + # TLS Certificates + certificates: //nvcf-proxy-tls-certs:1.2.0 + + # Image pull secret created in Step 2 + secrets: + - nvcr-creds +``` + +Replace `/` with your registry path. BYOC users pulling +directly from NGC can use `nvcr.io/nvidia/nvcf-byoc` as the registry/repo path. + +### Cache Configuration + +The cache behavior is controlled through several parameters: + +```yaml +# values.yaml + +cache: + # Size for storing cache keys + keyStorageSize: 50m + + # Maximum size of the cache + maxSize: 180g + + # Period a resource can remain in cache without being accessed + inactive: 1d + + # Period a cache is valid if resource doesn't become inactive first + valid: 1h +``` + +### Storage Configuration + +Container Cache requires persistent storage for caching container images: + +```yaml +# values.yaml + +persistentVolumeClaim: + # Size of persistent volume + sizeGB: 100 + + # Storage class for persistent volume claim + storageClassName: gp3 # Use gp3 for Amazon EKS, adjust for other platforms + + # Size of persistent volume for proxy cache + sizeProxyGB: 100 +``` + +### Service Configuration + +The service type and port can be configured based on your access requirements: + +```yaml +# values.yaml + +service: + # Service type: ClusterIP, NodePort, or LoadBalancer + type: ClusterIP + + # Port for the Container Cache service + port: 30345 +``` + +### Metrics Configuration + +Container Cache includes Prometheus metrics for monitoring cache performance: + +```yaml +# values.yaml + +metrics: + # Size for storing cache metrics + cacheMetricsStorageSize: 300m + + # Bucket configuration for throughput histogram + throughputHistogramBuckets: 25000000, 30000000, 35000000, 40000000, 50000000, 60000000, 80000000, 100000000 +``` + +### Resource Requests and Limits + +Resource requests and limits for the Container Cache StatefulSet pods are **required**. The +chart will fail to install without them. + +```yaml +# values.yaml + +resources: + requests: + memory: 2Gi + cpu: "1" + limits: + memory: 4Gi + cpu: "2" +``` + + +Adjust these values based on your cluster size and expected cache throughput. Larger +deployments with high pull rates may need more memory and CPU. + + + +## Architecture + +### Container Cache Architecture + +Container Cache consists of several components: + +1. **Nginx Proxy Server**: Handles incoming requests and serves cached content +2. **Prometheus Exporter**: Provides metrics for monitoring +3. **Persistent Storage**: Stores cached container images +4. **DaemonSet**: Configures containerd on worker nodes to use the cache + +### Data Flow + +1. **Initial Request**: Worker node requests a container image +2. **Cache Check**: Container Cache checks if image is cached locally +3. **Cache Hit**: If cached, serve image directly from local storage +4. **Cache Miss**: If not cached, fetch from upstream registry and cache locally +5. **Response**: Return image to requesting worker node + +## Performance Considerations + +### Cache Size + +The cache size should be sized based on your workload requirements: + +- **Small deployments**: 50-100GB +- **Medium deployments**: 100-500GB +- **Large deployments**: 500GB+ + +### Storage Performance + +For optimal performance, use high-performance storage: + +- **AWS**: Use gp3 or io1/io2 EBS volumes +- **Azure**: Use Premium SSD storage +- **GCP**: Use SSD persistent disks + +### Network Configuration + +Container Cache should be deployed close to worker nodes to minimize network latency: + +- Deploy in the same availability zone as worker nodes +- Use high-bandwidth network connections +- Consider using dedicated network interfaces for cache traffic + +## Monitoring and Observability + +### Metrics + +Container Cache provides several key metrics: + +- **Cache hit ratio**: Percentage of requests served from cache +- **Cache size**: Current size of cached data +- **Request throughput**: Number of requests per second +- **Response times**: Time to serve cached vs. uncached content + +### Logging + +Container Cache logs include: + +- Cache hit/miss events +- Upstream registry communication +- Error conditions and troubleshooting information +- Performance metrics + +## Troubleshooting + +### Common Issues + +**Cache Not Working** +: - Verify containerd configuration on worker nodes + - Check network connectivity to Container Cache service + - Ensure proper DNS resolution + +**Low Cache Hit Ratio** +: - Review cache size configuration + - Check cache eviction policies + - Monitor storage performance + +**Storage Issues** +: - Verify storage class availability + - Check persistent volume claims + - Monitor disk space usage + +## Best Practices + +### Deployment + +- Deploy Container Cache before deploying workloads +- Use multiple replicas for high availability +- Monitor cache performance and adjust configuration as needed + +### Configuration + +- Size cache appropriately for your workload +- Use high-performance storage for better performance +- Configure appropriate cache eviction policies + +### Maintenance + +- Monitor cache hit ratios and adjust cache size as needed +- Regularly review and clean up unused cached images +- Update Container Cache images regularly for security patches diff --git a/docs/v0.6.1-rc/cluster-management/gxcache.md b/docs/v0.6.1-rc/cluster-management/gxcache.md new file mode 100644 index 000000000..cca7ef80a --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/gxcache.md @@ -0,0 +1,438 @@ +# Distributed Shader Cache (GXCache) + +This guide provides detailed information on the Distributed Shader Cache (GXCache) component and its use in NVCF GPU clusters. + +GXCache is a cloud-native shader cache for NVIDIA that provides distributed shader caching capabilities. It consists of the GXCache server and mutating webhook to optimize shader compilation and caching across your cluster. + +GXCache improves rendering performance by caching compiled shaders, reducing compilation time for frequently used shaders and enabling faster scene loading and rendering. + +## Installation + +### Prerequisites + +- A running Kubernetes cluster with `kubectl` access +- **Helm** >= 3.12 +- Credentials for the registry where your NVCF charts and images are stored + +### Step 1. Authenticate Helm to your chart registry + +Authenticate Helm to your OCI registry where the NVCF charts are stored: + +```bash +echo "${REGISTRY_PASSWORD}" | helm registry login ${REGISTRY} \ + --username '${REGISTRY_USERNAME}' --password-stdin +``` + +### Step 2. Create the namespace and image pull secret + +```bash +kubectl create namespace gxcache +``` + +Create an image pull secret so that pods can pull container images from your registry. + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace=gxcache +``` + + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server=.dkr.ecr..amazonaws.com \ + --docker-username=AWS \ + --docker-password="$(aws ecr get-login-password --region )" \ + --namespace=gxcache +``` + + + + + +```bash +kubectl create secret docker-registry nvcr-creds \ + --docker-server= \ + --docker-username= \ + --docker-password= \ + --namespace=gxcache +``` + + + + + + +The secret name `nvcr-creds` is referenced in the values file under +`webhook.deployment.secret` and `service.deployment.imageSecret`. +If you use a different secret name, update the values file to match. + + + +### Step 3. Create a values file + +Create a `values.yaml` using the complete example in [Base Configuration] below. + +- **BYOC users** pulling directly from NGC can use the `nvcr.io/nvidia/nvcf-byoc/` image + paths without mirroring. +- **Self-hosted users** should replace the `/` placeholders with + their mirrored registry path (see the Artifact Manifest in the self-hosted installation guide). + +At minimum, configure the storage class for your environment (e.g., `gp3` for AWS EKS) +and the node selector for your GPU nodes. + +### Step 4. Install the chart + + + + +```bash +helm upgrade --install gxcache \ + nvcf-byoc/gxcache \ + --namespace gxcache \ + --values values.yaml +``` + + + + + +Replace `/` with your mirrored registry path. + +```bash +helm upgrade --install gxcache \ + oci:////gxcache \ + --version 0.8.2 \ + --namespace gxcache \ + --values values.yaml +``` + + + + + +### Step 5. Verify the installation + +```bash +# All pods should reach Running status +kubectl get pods -n gxcache + +# Check the service and webhook are available +kubectl get svc -n gxcache + +# Check persistent volume claims are bound +kubectl get pvc -n gxcache +``` + +## Base Configuration + +The following is a complete example `values.yaml` for deploying GXCache. Copy this file and +adjust values for your environment. Each section is explained in detail below. + +- **BYOC users** can use the `nvcr.io/nvidia/nvcf-byoc/` paths directly. +- **Self-hosted users** should replace `/` with their mirrored + registry path (see the Artifact Manifest in the self-hosted installation guide for source image paths). + +```yaml +webhook: + nodeSelector: + node-type: compute # Adjust based on your node labels, or set to null + deployment: + image: //gxcache-webhook + version: 59bd8ec5 + secret: nvcr-creds # Image pull secret created in Step 2 + client: + version: + image: //gxcache-init + tag: 1e47f722 + config: + tls: + enabled: false + metrics: + enabled: false # Requires monitoring.coreos.com/v1 CRD + +service: + nodeSelector: + node-type: compute # Adjust based on your node labels, or set to null + deployment: + image: //gxcache-service:b206ce39 # Tag is part of the image field + imageSecret: nvcr-creds # Image pull secret created in Step 2 + vault: + enabled: false + kns: + enabled: true + keyset: + api: /.well-known/jwks.json + endpoint: http://notary.nvcf.svc.cluster.local:8080 + persistence: + enabled: true + storageClass: gp3 # Use gp3 for AWS EKS, adjust for other platforms + accessMode: ReadWriteOnce + size: 20Gi + metrics: + enabled: false # Requires monitoring.coreos.com/v1 CRD +``` + +Replace `/` with your registry path. BYOC users pulling +directly from NGC can use `nvcr.io/nvidia/nvcf-byoc` as the registry/repo path. + + +If your cluster does not use node labels, set `nodeSelector` to `null`. + + + +## Configuration Sections + +### Webhook Configuration + +The GXCache webhook is responsible for intercepting and modifying pod specifications to enable shader caching: + +```yaml +# values.yaml + +webhook: + nodeSelector: + node-type: compute # Adjust based on your node labels + deployment: + image: //gxcache-webhook + version: 59bd8ec5 + secret: nvcr-creds # Registry credentials secret + client: + version: + image: //gxcache-init + tag: 1e47f722 + config: + tls: + enabled: false + metrics: + enabled: false # Requires monitoring.coreos.com/v1 CRD +``` + +### Service Configuration + +The GXCache service provides the shader cache functionality: + +```yaml +# values.yaml + +service: + nodeSelector: + node-type: compute # Adjust based on your node labels + deployment: + image: //gxcache-service:b206ce39 + imageSecret: nvcr-creds # Registry credentials secret + vault: + enabled: false +``` + +### Node Selection + +GXCache components are scheduled on nodes with appropriate labels to ensure they run on GPU compute nodes. Adjust the node selector based on your cluster's node labeling scheme. + +```yaml +# values.yaml + +webhook: + nodeSelector: + node-type: compute +service: + nodeSelector: + node-type: compute +``` + +### Key Management + +GXCache uses NVIDIA's Key Notary Service (KNS) for secure key management. +For self-hosted deployments use the in-cluster notary service: + +```yaml +# values.yaml + +service: + kns: + enabled: true + keyset: + api: /.well-known/jwks.json + endpoint: http://notary.nvcf.svc.cluster.local:8080 +``` + +### Storage Configuration + +GXCache requires persistent storage for caching shader data: + +```yaml +# values.yaml + +service: + persistence: + enabled: true + storageClass: gp3 # Use gp3 for AWS EKS, adjust for other platforms + accessMode: ReadWriteOnce + size: 20Gi +``` + +### Metrics Configuration + +GXCache supports Prometheus metrics for monitoring: + +```yaml +# values.yaml + +webhook: + metrics: + enabled: false # Requires monitoring.coreos.com/v1 CRD +service: + metrics: + enabled: false # Requires monitoring.coreos.com/v1 CRD +``` + +## Architecture + +### GXCache Architecture + +GXCache consists of two main components: + +1. **GXCache Service**: Provides the shader cache storage and retrieval functionality +2. **GXCache Webhook**: Intercepts pod creation to inject shader cache configuration + +### Data Flow + +1. **Pod Creation**: Application pod is created with GPU requirements +2. **Webhook Interception**: GXCache webhook intercepts pod creation +3. **Configuration Injection**: Webhook injects shader cache configuration +4. **Shader Compilation**: Application compiles shaders during runtime +5. **Cache Storage**: Compiled shaders are stored in GXCache +6. **Cache Retrieval**: Subsequent requests retrieve cached shaders + +## Performance Considerations + +### Storage Performance + +For optimal shader cache performance: + +- **Storage Type**: Use high-performance SSD storage (gp3, io1, io2) +- **Storage Size**: Size based on expected shader cache usage (20GB minimum) +- **Access Mode**: ReadWriteOnce is sufficient for most deployments + +### Network Configuration + +GXCache should be deployed to minimize network latency: + +- Deploy in the same availability zone as GPU nodes +- Use high-bandwidth network connections +- Consider dedicated network interfaces for cache traffic + +### Cache Size Planning + +Plan cache size based on your workload: + +- **Small workloads**: 20-50GB +- **Medium workloads**: 50-200GB +- **Large workloads**: 200GB+ + +## Monitoring and Observability + +### Metrics + +GXCache provides several key metrics: + +- **Cache hit ratio**: Percentage of shader requests served from cache +- **Cache size**: Current size of cached shader data +- **Request latency**: Time to serve cached shaders +- **Storage utilization**: Disk space usage + +### Prometheus Integration + +To enable Prometheus metrics: + +1. **Install Prometheus Operator** (if not already installed): + + ```bash + helm install --wait --timeout 15m \ + --namespace monitoring --create-namespace \ + --repo https://prometheus-community.github.io/helm-charts \ + prometheus-agent kube-prometheus-stack + ``` + +2. **Enable Metrics in GXCache**: + + ```bash + helm upgrade gxcache -n gxcache . \ + --values values.yaml \ + --set webhook.metrics.enabled=true \ + --set service.metrics.enabled=true + ``` + +3. **Access Prometheus**: + + ```bash + kubectl port-forward -n monitoring svc/prometheus-operated 9090:9090 + ``` + +### Logging + +GXCache logs include: + +- Cache hit/miss events +- Shader compilation requests +- Error conditions and troubleshooting information +- Performance metrics + +## Troubleshooting + +### Common Issues + +**Webhook Not Working** +- Verify webhook is running and healthy +- Check webhook configuration and secrets +- Ensure proper RBAC permissions + +**Cache Not Storing Shaders** +- Verify GXCache service is running +- Check storage configuration and persistent volumes +- Review application logs for shader compilation errors + +**Low Cache Hit Ratio** +- Review cache size configuration +- Check cache eviction policies +- Monitor storage performance + +**Storage Issues** +- Verify storage class availability +- Check persistent volume claims +- Monitor disk space usage + +## Best Practices + +### Deployment + +- Deploy GXCache before deploying GPU workloads +- Use high-performance storage for better cache performance +- Monitor cache performance and adjust configuration as needed + +### Configuration + +- Size cache appropriately for your shader workload +- Use high-performance storage for better performance +- Enable metrics for monitoring and optimization + +### Security + +- Use proper RBAC permissions for webhook and service +- Secure communication between components +- Regularly update GXCache images for security patches + +### Maintenance + +- Monitor cache hit ratios and adjust cache size as needed +- Regularly review and clean up unused cached shaders +- Update GXCache images regularly for security patches +- Monitor storage usage and plan for growth diff --git a/docs/v0.6.1-rc/cluster-management/index.md b/docs/v0.6.1-rc/cluster-management/index.md new file mode 100644 index 000000000..815c30da7 --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/index.md @@ -0,0 +1,45 @@ +# GPU Cluster Setup + +The NVIDIA Cluster Agent (NVCA) connects GPU clusters to the NVCF control plane, enabling them to act as deployment targets for Cloud Functions. NVCA is a function deployment orchestrator that registers a cluster's GPU resources, communicates with the control plane, and manages the lifecycle of function deployments on GPU nodes. + +For a fresh install, use the [Quickstart](../quickstart.md). The one-click CLI flow can register a GPU cluster as part of the install. Use this section for manual cluster registration, standalone NVCA installation, and day-two cluster configuration. + + +If you pin NVCA separately from the recommended compute-plane stack, check the current NVCA version before upgrading to NVCA 3.x. Clusters running NVCA 2.51.0 or earlier have version-specific upgrade guidance. See the [0.6.0 upgrade notes](../release-notes/0.6.0.md#upgrade-notes). + + +After installing NVCA on a cluster: + +- The registered cluster will show as a deployment option in the `GET /v2/nvcf/clusterGroups` API response. +- Any functions under the cluster's authorized NCA IDs can now deploy on the cluster. + +## Authentication and Keys + +| Key Type | Description | +| --- | --- | +| NVCF API Key (NAK) | Used by NVCA to authenticate with the control plane. See [self-hosted-api](../api.md) for details on API key generation. | + +## Prerequisites + +- Access to a Kubernetes cluster including GPU-enabled nodes ("GPU cluster") + + - The cluster must have a compatible version of [Kubernetes](https://kubernetes.io/releases/). + + - The cluster must have the [NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html#operator-install-guide) installed. + + - If your cloud provider does not support the NVIDIA GPU Operator, [Manual Instance Configuration](./configuration.md) is possible, but not recommended due to lack of maintainability. + - To get the most out of clusters with multi-node NVLink ([MNNVL](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-cds.html#dra-docs-compute-domains)) GPUs like [GB200](https://www.nvidia.com/en-us/data-center/gb200-nvl72/), the [NVIDIA GPU DRA driver](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/dra-intro-install.html) must be installed. See the [nvlink-optimized-clusters](./configuration.md) for details. + - For development or testing environments without physical GPUs, install the [fake-gpu-operator](../fake-gpu-operator.md) instead. + +- Registering the cluster requires `kubectl` and `helm` installed. + +- The user registering the cluster must have the `cluster-admin` role privileges to install the NVIDIA Cluster Agent Operator (`nvca-operator`). + +### Supported Kubernetes Versions + +- Supported versions are the latest Kubernetes minor release and the two prior minor releases (N-2). See official Kubernetes docs for current supported [versions](https://kubernetes.io/releases/version-skew-policy/#supported-versions). + +### Considerations + +- The NVIDIA Cluster Agent currently only supports caching if the cluster is enabled with `StorageClass` configurations. If the "Caching Support" capability is enabled, the agent will make the best effort by attempting to detect storage during deployments and fall back on non-cached workflows. +- Each function and task requires several infrastructure containers be deployed alongside workload containers. These infrastructure containers collectively need 6 CPU cores and 8 Gi of system memory to execute. Each GPU node must have at least this many resources, ideally significantly more for workload resource usage. diff --git a/docs/v0.6.1-rc/cluster-management/kai-scheduler.md b/docs/v0.6.1-rc/cluster-management/kai-scheduler.md new file mode 100644 index 000000000..809b8fd6b --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/kai-scheduler.md @@ -0,0 +1,77 @@ +# KAI Scheduler Integration Guide + +[KAI Scheduler](https://github.com/kai-scheduler/KAI-Scheduler) is an open source Kubernetes Native scheduler for AI workloads at large scale. +To use the KAI Scheduler for NVCF Workloads the following configuration should be applied post the installation of the KAI Scheduler in the cluster and the [Optimized AI Workload Scheduling](./configuration.md) enabled on the +cluster. NVCF Workloads deployed will be automatically BinPacked upon this cluster configuration changes. + +**KAI Scheduler Installation** + + +Upgrade to latest [KAI Scheduler release](https://github.com/kai-scheduler/KAI-Scheduler/releases) is recommended to get latest fixes and security patches + + + +NVCA's KAI scheduler integration expects default queues to exist with names `default-parent-queue` (parent) and `default-queue` (child); +other queues may exist in the cluster. + + +One caveat is that NVCA expects all queues used to create NVCF workloads to have unlimited (`-1`) quotas and limits +to ensure full cluster capacity utilization and accurate usage tracking. If the cluster is partitioned to serve both NVCF and non-NVCF workloads +and KAI scheduler queue quotas/limits are limited to reflect this, then [Shared Cluster mode](./configuration.md#cluster-features) must be enabled so non-NVCF workload nodes +are accurately excluded from tracking and scheduling by NVCA. + + + +Create `values.yaml` with [default queue](https://raw.githubusercontent.com/NVIDIA/KAI-Scheduler/refs/heads/main/docs/quickstart/default-queues.yaml) attributes: + + +```yaml title="kai-scheduler-queues.yaml" +scheduler: + placementStrategy: binpack + plugins: + nodeplacement: + arguments: + gpu: binpack + cpu: spread + actions: + preempt: + enabled: false + consolidation: + enabled: false + +defaultQueue: + createDefaultQueue: true + parentName: default-parent-queue + childName: default-queue + parentResources: + cpu: + quota: -1 + limit: -1 + overQuotaWeight: 1 + gpu: + quota: -1 + limit: -1 + overQuotaWeight: 1 + memory: + quota: -1 + limit: -1 + overQuotaWeight: 1 + childResources: + cpu: + quota: -1 + limit: -1 + overQuotaWeight: 1 + gpu: + quota: -1 + limit: -1 + overQuotaWeight: 1 + memory: + quota: -1 + limit: -1 + overQuotaWeight: 1 +``` + + +```bash +helm install kai-scheduler oci://ghcr.io/kai-scheduler/kai-scheduler/kai-scheduler -f values.yaml -n kai-scheduler --create-namespace --version v0.14.0 +``` diff --git a/docs/v0.6.1-rc/cluster-management/monitoring.md b/docs/v0.6.1-rc/cluster-management/monitoring.md new file mode 100644 index 000000000..c0fa6ae76 --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/monitoring.md @@ -0,0 +1,122 @@ +# Monitoring & Observability + +The NVIDIA Cluster Agent and Operator provide built-in monitoring through Prometheus metrics, +structured logging, and OpenTelemetry tracing. + +## Monitoring Data + +### Metrics + +**Prerequisites** + +To use the PodMonitor and ServiceMonitor examples below, you must first install the Prometheus Operator. Follow the [Prometheus Operator installation guide](https://prometheus-operator.dev/docs/getting-started/installation/) to set this up in your cluster. + +The cluster agent and operator emit Prometheus-style metrics. The following metric labels are available by default. The full list of available metrics are updated regularly and therefore not listed. + +| Metric Label | Metric Label Description | +| ------------------ | -------------------------------- | +| nvca_event_name | The name of the event | +| nvca_nca_id | The NCA ID of this NVCA instance | +| nvca_cluster_name | The NVCA cluster name | +| nvca_cluster_group | The NVCA cluster group | +| nvca_version | The NVCA version | + +Cluster maintainers can scrape the available metrics. See a full example of how to do this with an OpenTelemetry Collector in the cluster [cluster monitoring example](https://github.com/NVIDIA/nvcf/tree/main/examples/cluster-monitoring-sample). + +Use the following examples of a PodMonitor for NVCA Operator and ServiceMonitor for NVCA for reference: + +**Sample NVCA Operator PodMonitor** + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + labels: + app.kubernetes.io/component: metrics + app.kubernetes.io/instance: prometheus-agent + app.kubernetes.io/name: metrics-nvca-operator + jobLabel: metrics-nvca-operator + release: prometheus-agent + prometheus.agent/podmonitor-discover: "true" + name: metrics-nvca-operator + namespace: monitoring +spec: + podMetricsEndpoints: + - port: http + scheme: http + path: /metrics + jobLabel: jobLabel + selector: + matchLabels: + app.kubernetes.io/name: nvca-operator + namespaceSelector: + matchNames: + - nvca-operator +``` + +**Sample NVCA ServiceMonitor** + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + app.kubernetes.io/component: metrics + app.kubernetes.io/instance: prometheus-agent + app.kubernetes.io/name: metrics-nvca + jobLabel: metrics-nvca + release: prometheus-agent + prometheus.agent/servicemonitor-discover: "true" + name: prometheus-agent-nvca + namespace: monitoring +spec: + endpoints: + - port: nvca + jobLabel: jobLabel + selector: + matchLabels: + app.kubernetes.io/name: nvca + namespaceSelector: + matchNames: + - nvca-system +``` + +### Logs + +Both the Cluster Agent and Cluster Agent Operator emit logs locally by default. + +Local logs for the NVIDIA Cluster Agent Operator can be obtained via `kubectl`: + +```bash +kubectl logs -l app.kubernetes.io/instance=nvca-operator -n nvca-operator --tail 20 +``` + +Similarly, NVIDIA Cluster Agent logs can be obtained with the following command via kubectl: + +```bash +kubectl logs -l app.kubernetes.io/instance=nvca -n nvca-system --tail 20 +``` + + +Current function-level inference container logs are **not supported** for functions deployed on non-NVIDIA-managed clusters. Customers are encouraged to emit logs directly from their inference containers running on their own clusters to any third-party tool, there are no public egress limitations for containers. + + + +### Tracing + +The NVIDIA Cluster Agent provides OpenTelemetry integration for exporting traces and events to compatible collectors. As of agent version 2.0, the only supported collector receiver is Lightstep. + +**Enable Tracing with Lightstep** + +1. Get your [Lightstep access token](https://docs.lightstep.com/docs/create-and-manage-access-tokens) from the [Lightstep UI](https://app.lightstep.com) and set to `LS_ACCESS_TOKEN` environment variable. +2. Get the NVCF cluster name: + +```bash +nvcf_cluster_name="$(kubectl get nvcfbackends -n nvca-operator -o name | cut -d'/' -f2)" +``` + +3. Apply the tracing configuration: + +```bash +kubectl patch nvcfbackends.nvcf.nvidia.io -n nvca-operator "$nvcf_cluster_name" --type=merge --patch="{\"spec\":{\"overrides\":{\"featureGate\":{\"otelConfig\":{\"exporter\":\"lightstep\",\"serviceName\":\"nvcf-nvca\",\"accessToken\":\"${LS_ACCESS_TOKEN}\"}}}}}" +``` diff --git a/docs/v0.6.1-rc/cluster-management/reference.md b/docs/v0.6.1-rc/cluster-management/reference.md new file mode 100644 index 000000000..d40bb2c54 --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/reference.md @@ -0,0 +1,248 @@ +# Helm Values Reference + +The `nvca-operator` Helm chart is configured through a standard Helm values file (`values.yaml`) +passed to `helm upgrade -f values.yaml`. This page documents all available parameters. + + +The parameters listed below are a snapshot and may not reflect the latest chart version. +Always refer to the `values.yaml` and `values.schema.json` included in your chart +version for the authoritative list of parameters and defaults: + + + +## How Values Are Structured + +The chart values are organized into two layers: + +1. **Shared parameters** (top-level) — These control the operator image, authentication, + node placement, network policies, observability, agent resources, and agent runtime config. + Examples: `image`, `ngcConfig`, `nodeSelector`, `networkPolicy`, `agent`, + `agentConfig`. +2. **\`\`selfManaged.\*\`\`** — Used when `ngcConfig.clusterSource` is `"self-managed"`. + These define the backend configuration including NVCA version, feature gates, cluster + attributes, and manual GPU config. + +**The key field is \`\`ngcConfig.clusterSource\`\`:** + +- `"self-managed"` — The operator reads backend configuration from `selfManaged.*` values. + +```yaml +# --- Shared parameters (all modes) --- +image: + repository: "nvcr.io/nvidia/nvcf-byoc/nvca-operator" +ngcConfig: + clusterSource: "helm-managed" # ← This determines which section below is used + serviceKey: "" +nodeSelector: + key: "node.kubernetes.io/instance-type" + value: "m5.2xlarge" + +# --- Only read when clusterSource is "helm-managed" --- +helmManaged: + cloudProvider: "aws" + clusterRegion: "us-west-2" + nvcaVersion: "2.97.0" + featureGateValues: ["DynamicGPUDiscovery"] + +# --- Only read when clusterSource is "self-managed" --- +# selfManaged: +# nvcaVersion: "2.97.0" +# featureGateValues: ["DynamicGPUDiscovery"] +``` + + +The `helmManaged` and `selfManaged` sections share many of the same fields +(`nvcaVersion`, `featureGateValues`, `gpuManualInstanceConfigB64`, +`clusterAttributes`). The difference is that `helmManaged` also requires cluster +identity fields (`cloudProvider`, `clusterRegion`, `clusterGroupID`, +`clusterGroupName`) that self-managed deployments get from their own control plane. + + + +## Shared Parameters + +These parameters apply to all deployment modes. + +### NVCA Operator + +```yaml +## Container images +image: + repository: "nvcr.io/nvidia/nvcf-byoc/nvca-operator" # Operator image path + tag: "" # Defaults to chart version + pullPolicy: IfNotPresent + +nvcaImage: + repositoryOverride: "" # Override NVCA agent image path (staging/testing only) + pullPolicy: IfNotPresent + +## Image pull secrets +generateImagePullSecret: true # Auto-generate from ngcConfig.serviceKey +imagePullSecretName: "nvca-operator-image-pull" # Name of the generated secret +imagePullSecrets: [] # Additional pre-existing pull secrets + +## Service account +serviceAccount: + create: true + annotations: {} + name: "" # Auto-generated if empty + +## Operator settings +replicaCount: 1 +systemNamespace: nvca-operator +logLevel: info # debug, info, warn, error +priorityClassName: "" # K8s PriorityClassName for eviction preference +k8sVersionOverride: "" # Override K8s version NVCA registers with +enableGXCache: true # Enable GXCache support +ddcsIPAllowList: "" # Comma-separated CIDRs for DDCS access control +nvcaHelmRepositoryPrefix: "" # Restrict Helm repos to specific org/team +``` + +### NGC Authentication + +```yaml +ngcConfig: + username: '$oauthtoken' + serviceKey: "" # NGC Cluster Key or NVCF API Key (NAK) + serviceKeySecretName: "ngc-service-key" # K8s Secret name if serviceKey not set inline + serviceKeySecretKeyName: "ngcServiceKey" # Key within the Secret + apiURL: https://api.ngc.nvidia.com # NGC API URL (override for self-hosted) + clusterSource: ngc-managed # "ngc-managed", "helm-managed", or "self-managed" +``` + +### Node Selector + +```yaml +nodeSelector: + key: node.kubernetes.io/instance-type # Label key for operator pod placement + value: "" # Label value (empty = no constraint) +``` + +### Network Policies + +```yaml +networkPolicy: + clusterNetworkCIDRs: # CIDRs that workload pods are NOT allowed to access + - "10.0.0.0/8" + - "172.16.0.0/12" + - "192.168.0.0/16" + - "100.64.0.0/12" + customPolicies: [] # Custom NetworkPolicy definitions for function namespaces +``` + +### OpenTelemetry + +```yaml +otel: + enabled: false + lightstep: + serviceName: "" # Lightstep service name + accessToken: "" # Lightstep API token +``` + +### Agent Configuration + +```yaml +agent: + cacheMountOptionsEnabled: true + cacheMountOptions: "ro,norecovery,nouuid" + workerDegradationPeriod: "" # e.g., "90m", "1h30m" + secretMirrorNamespace: nvca-operator # Namespace to mirror custom secrets from + secretMirrorLabelSelector: "" # Label selector for mirrored secrets + customAnnotations: {} # Extra annotations on the agent pod + functionEnvOverrides: {} # Override infra container images for functions + taskEnvOverrides: {} # Override infra container images for tasks + overrideEnvironmentVariables: {} # Override env vars on the NVCA agent container + resources: + limits: + cpu: 1000m + memory: 4Gi + requests: + cpu: 100m + memory: 200Mi + +## Merge custom YAML into the generated NVCA agent config at runtime +agentConfig: + mergeConfig: "" + # Example: + # mergeConfig: | + # agent: + # logLevel: debug + +## OTel Collector sidecar (for K8s event collection) +otelCollector: + enabled: false + imageRepository: "" # Auto-calculated if empty + imageTag: 0.143.2 +``` + +## Helm-Managed Parameters + +Only used when `ngcConfig.clusterSource: "helm-managed"`. + +```yaml +helmManaged: + ## Cluster identity (REQUIRED, immutable after initial registration) + cloudProvider: "" # e.g., "aws", "gcp", "azure", "ON-PREM", "NCP" + clusterRegion: "" # e.g., "us-west-2" + clusterGroupID: "" # Unique cluster group identifier + clusterGroupName: "" # Human-readable cluster group name + + ## Backend configuration + nvcaVersion: "" # NVCA agent version (REQUIRED) + clusterDescription: "" # Defaults to cluster name if empty + featureGateValues: [] # e.g., ["DynamicGPUDiscovery", "CachingSupport"] + gpuManualInstanceConfigB64: "" # Base64-encoded GPU config (manual instance only) + clusterAttributes: [] # e.g., ["CacheOptimized", "NVLinkOptimized"] + + ## Authentication (optional) + oAuthClientID: "" # OAuth2/OIDC client ID (for internal NVIDIA clusters) + oAuthClientSecretKey: "" # Secret key for OAuth client + + ## Image overrides (advanced, usually auto-calculated) + imageCredHelper: + imageRepository: "" + imageTag: 0.10.2 + otelCollector: + imageRepository: "" + imageTag: 0.143.2 +``` + +## Self-Managed Parameters + +Only used when `ngcConfig.clusterSource: "self-managed"` (self-hosted NVCF). + +```yaml +selfManaged: + nvcaVersion: "" # NVCA agent version (REQUIRED) + featureGateValues: ["DynamicGPUDiscovery"] # Default includes GPU discovery + gpuManualInstanceConfigB64: "" # Base64-encoded GPU config (manual instance only) + clusterAttributes: [] # e.g., ["CacheOptimized"] + + ## Image overrides (advanced, usually auto-calculated) + imageCredHelper: + imageRepository: "" + imageTag: 0.10.2 + otelCollector: + imageRepository: "" + imageTag: 0.143.2 +``` + + +The `selfManaged` and `helmManaged` sections share the same backend fields. The key +differences are: + +- `selfManaged` does not have cluster identity fields (`cloudProvider`, + `clusterRegion`, `clusterGroupID`, `clusterGroupName`) — these come from the + self-hosted control plane. +- `selfManaged.featureGateValues` defaults to `["DynamicGPUDiscovery"]`. To disable a + feature, remove it from the list (set to `[]`). +- `helmManaged.featureGateValues` defaults to `[]`. To disable a feature, prefix it + with `-` (e.g., `["-DynamicGPUDiscovery"]`). + + + +## Related Documentation + +- [NVCA Configuration](./configuration.md) — how to use these values to configure specific features (caching, network policies, manual instance config, etc.) +- [Agent config merging](./configuration.md) — using `agentConfig.mergeConfig` for runtime config overrides diff --git a/docs/v0.6.1-rc/cluster-management/self-managed.md b/docs/v0.6.1-rc/cluster-management/self-managed.md new file mode 100644 index 000000000..59586be2e --- /dev/null +++ b/docs/v0.6.1-rc/cluster-management/self-managed.md @@ -0,0 +1,736 @@ +# Self-Managed Clusters + +GPU clusters are registered with the NVCF control plane and managed by the NVCA +Operator. Use the compute-plane Makefile in +`deploy/stacks/nvcf-compute-plane` for this workflow. It registers one GPU +cluster at a time with `nvcf-cli`, then installs the operator with the cluster +identity returned by registration. The operator reads that identity from a +local ConfigMap and authenticates through the local OpenBao (Vault) instance. + + +A running NVCF control plane (SIS, OpenBao, NATS, Cassandra, and all core +services) is required. The [Quickstart](../quickstart.md) can install the +control plane and register a GPU cluster in one flow. Use this page when you +need to install or operate the NVCA Operator after using the Helmfile +installation path. + + + +Clone the public repository and run the compute-plane commands from its root: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Prerequisites + +Before installing the NVCA Operator, ensure the following prerequisites are met: + +- The [control plane](../helmfile-installation.md) is installed and all core services are running. + +- The [NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html) is installed on the GPU cluster. The GPU Operator manages the NVIDIA drivers, device plugin, and GPU feature discovery required for workload scheduling. For development or testing environments without physical GPUs, see [fake-gpu-operator](../fake-gpu-operator.md). + +- (Optional) The [KAI Scheduler](./kai-scheduler.md) can be installed on the GPU cluster for optimized AI workload scheduling and bin-packing of GPU resources. It is required only when you enable the `KAIScheduler` feature flag. See [NVCA Configuration](./configuration.md). + +- `GPU Workload Components` must be available in a user-managed registry that your Kubernetes cluster can access. See `GPU Workload Components` under [self-hosted-artifact-manifest](../manifest.md) for necessary artifacts and [self-hosted-image-mirroring](../image-mirroring.md) for mirroring instructions. + +- `nvcf-cli` is available on the deployment machine. The compute-plane Makefile + calls it during cluster registration. See [self-hosted-cli](../cli.md) for + CLI installation and configuration. + +- The [SMB CSI driver](https://github.com/kubernetes-csi/csi-driver-smb) (`smb.csi.k8s.io`) must be installed on the GPU cluster. It is required for NVCA shared model cache storage (samba sidecar). Install it with: + + ```bash + helm repo add csi-driver-smb \ + https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/master/charts + + helm install csi-driver-smb csi-driver-smb/csi-driver-smb \ + -n kube-system --version v1.17.0 + ``` + +## How It Works + +In self-managed mode, the cluster identity comes from the CLI registration +step, and the operator consumes it: + +1. You configure the compute-plane Helmfile environment with registry settings + and control-plane service URLs. +2. From the repository root, you run + `make -C deploy/stacks/nvcf-compute-plane register-cluster` (see + [Register the cluster](#register-the-cluster)). The target runs + `nvcf-cli init`, then `nvcf-cli cluster register`. The CLI discovers the GPU + cluster's OIDC issuer and public JWKS, records them with the control plane + (ICMS), and writes the `clusterID`, `clusterGroupID`, identity source, and + service URLs to a registration values file. +3. From the repository root, you run + `make -C deploy/stacks/nvcf-compute-plane install`. Helmfile installs + `helm-nvca-operator` with the compute-plane environment and registration + values for that cluster. +4. The Helm chart renders a local ConfigMap (`nvcfbackend-self-managed`) holding the cluster + identity and the SIS, ReVal, and NATS endpoints (plus any host-header overrides). The + operator reads that ConfigMap and creates the NVCFBackend resource. +5. The operator creates the NVCA agent pod. The agent authenticates to the control plane + with a projected service account token (PSAT), which the control plane validates against + the issuer and JWKS registered in step 1, and begins managing GPU workloads. +6. Runtime secrets are injected by the OpenBao vault-agent sidecar, which + authenticates using Kubernetes service account JWT tokens against the local + OpenBao instance. Kubernetes image pull secrets are configured separately in + the compute-plane environment and namespaces. + +## Configure the compute plane + +Create the environment file from the repository root. It provides the chart +registry, image registry, image pull secret name, and control-plane service URLs +used by the NVCA operator. + +```bash +cd path/to/nvcf +touch deploy/stacks/nvcf-compute-plane/environments/.yaml +``` + +Use service URLs that are reachable from the GPU cluster. For a single +load-balancer-fronted Gateway with hostname-based routing, the agent should dial +the load balancer address and send route-matching host headers: + +```yaml title="deploy/stacks/nvcf-compute-plane/environments/.yaml" +global: + helm: + sources: + registry: + repository: + + image: + registry: + repository: + + imagePullSecrets: + - name: nvcr-pull-secret + + nvcaOperator: + selfManaged: + icmsServiceURL: "http://" + icmsServiceHostHeaderOverride: "sis." + revalServiceURL: "http://" + revalServiceHostHeaderOverride: "reval." + natsURL: "nats://:4222" + natsHostOverride: "nats." +``` + +If your GPU cluster can resolve per-service DNS names directly, set the service +URLs to those names and omit the host-header override fields. + +Host-header overrides require `helm-nvca-operator` 1.12.0 or later. With proper +per-service DNS and TLS (see [gateway-routing](../gateway-routing.md)), the +service hostnames resolve directly and no host-header overrides are needed. + +## Register the cluster + +Register the GPU cluster with the control plane before installing the operator. +The `nvcf-cli` discovers the cluster's OIDC issuer and JWKS and records them +with the control plane, then returns the Helm values the operator needs. See +[self-hosted-cli](../cli.md) for CLI installation and configuration, and the +[Cluster Registration](../cli.md#cluster-registration) reference for full flag +and output details. + + +The control plane must expose its issuer for the CLI to discover. The Helmfile +installation path enables this with +`openbao.migrations.issuerDiscovery.enabled: true`. + + + +Create a CLI config that can reach the control-plane gateway. The +`register-cluster` target runs `nvcf-cli init` before `cluster register`, so the +config must include the API Keys endpoint and host-header overrides: + +```yaml title="nvcf-cli-gpu-register.yaml" +base_http_url: "http://" +invoke_url: "http://" +api_keys_service_url: "http://" +icms_url: "http://" + +api_keys_host: "api-keys." +api_host: "api." +icms_host: "sis." +invoke_host: "invocation." + +api_keys_service_id: "nvidia-cloud-functions-ncp-service-id-aketm" +api_keys_issuer_service: "nvcf-api" +api_keys_owner_id: "svc@nvcf-api.local" +client_id: "" +``` + +Run registration against the GPU cluster kubeconfig. This is required for +multi-cluster installs because the CLI discovers the OIDC issuer and JWKS from +the target Kubernetes cluster. + +```bash +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME= \ + NCA_ID= \ + CLUSTER_REGION= \ + ICMS_URL="http://" \ + KUBECONFIG_FILE= \ + NVCF_CLI= \ + NVCF_CLI_CONFIG= +``` + +The target writes +`registration/-register-values.yaml`. It carries the cluster +identity and endpoints: + +```yaml +clusterID: +clusterGroupID: +ncaID: +region: +selfManaged: + identitySource: psat + icmsServiceURL: "http://" + revalServiceURL: "http://" + natsURL: "nats://:4222" +``` + +The `template`, `install`, and `apply` targets copy this file into `out/` before +running Helmfile. + +## Installing the NVCA Operator + +| Chart | `helm-nvca-operator` | +| --- | --- | +| Version | `1.12.7` | +| Namespace | `nvca-operator` | +| Depends on | All control-plane services and gateway must be running | + +The compute-plane Helmfile passes these values to the chart: + +| Value | Source | +| --- | --- | +| Cluster identity | `registration/-register-values.yaml` | +| Operator, agent, image credential helper, and shared storage image repositories | `global.image.*` in `environments/.yaml` | +| Control-plane service URLs and host-header overrides | `global.nvcaOperator.selfManaged.*` | +| Image pull secrets | `global.imagePullSecrets` | + +Use `global.nvcaOperator.nodeSelector`, `global.nvcaOperator.tolerations`, and +`global.nvcaOperator.agent.*` in the compute-plane environment when you need to +place the operator, agent, or workloads on specific nodes. + + +The `KAIScheduler` feature flag is optional. Enable it only if the +[KAI Scheduler](./kai-scheduler.md) is installed on the GPU cluster. The flag has no effect, +and GPU workload scheduling will not work as expected, if KAI Scheduler is not present. Omit +`KAIScheduler` from `featureGateValues` when KAI Scheduler is not installed. + + + + +For the full list of available feature flags and how to set or modify them, see +[managing-feature-flags](./configuration.md). + + + +### Node inotify limits + +The NVCA operator and agent use file watchers for ConfigMap and Secret reconciliation. +Some node images set `fs.inotify.max_user_instances` to `128`, which can be too low for +nodes running the full NVCF stack. When the node exhausts inotify instances, NVCA logs +errors such as `failed to create watcher` and `too many open files`. Function creation +or deployment requests that wait for NVCA reconciliation can then return HTTP 500 or +time out. + +Before installing the operator, set higher inotify limits on every node. The following +DaemonSet sets the values on current nodes and on nodes added later: + +```yaml title="inotify-tuner.yaml" +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: node-inotify-tuner + namespace: kube-system +spec: + selector: + matchLabels: + app.kubernetes.io/name: node-inotify-tuner + template: + metadata: + labels: + app.kubernetes.io/name: node-inotify-tuner + spec: + hostPID: true + tolerations: + - operator: Exists + priorityClassName: system-node-critical + terminationGracePeriodSeconds: 1 + initContainers: + - name: set-sysctl + image: busybox:1.36 + securityContext: + privileged: true + command: + - sh + - -c + - | + set -e + mkdir -p /host/etc/sysctl.d + { + echo 'fs.inotify.max_user_instances=8192' + echo 'fs.inotify.max_user_watches=524288' + } > /host/etc/sysctl.d/99-inotify.conf + nsenter -t 1 -m -u -i -n -p -- sysctl -w fs.inotify.max_user_instances=8192 + nsenter -t 1 -m -u -i -n -p -- sysctl -w fs.inotify.max_user_watches=524288 + volumeMounts: + - name: host + mountPath: /host + containers: + - name: pause + image: registry.k8s.io/pause:3.9 + resources: + requests: + cpu: "1m" + memory: "1Mi" + limits: + cpu: "10m" + memory: "16Mi" + volumes: + - name: host + hostPath: + path: / +``` + +Apply the DaemonSet and wait for it to run on every node: + +```bash +kubectl apply -f inotify-tuner.yaml +kubectl -n kube-system rollout status ds/node-inotify-tuner --timeout=5m +``` + +If your cluster cannot pull public images, mirror `busybox:1.36` and +`registry.k8s.io/pause:3.9` to your registry and update the image fields before applying +the DaemonSet. + +### Image Pull Secrets + +The NVCA operator, NVCA agent, samba sidecar, and image-credential-helper all pull +container images from the registry configured in your compute-plane environment +file. If that registry is private, create the Kubernetes pull secret before +installing the operator. + + +Use a pre-existing pull secret through `global.imagePullSecrets`. Do not use +`generateImagePullSecret`; it does not work in self-managed mode. + + + +Create the secret in the GPU cluster namespaces used by the operator and its +managed resources: + +```bash +for ns in nvca-operator nvca-system nvcf-backend; do + kubectl --kubeconfig \ + create namespace "$ns" --dry-run=client -o yaml | kubectl --kubeconfig apply -f - + kubectl --kubeconfig \ + create secret docker-registry nvcr-pull-secret \ + --docker-server=${REGISTRY} \ + --docker-username='$oauthtoken' \ + --docker-password="$REGISTRY_PASSWORD" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl --kubeconfig apply -f - +done +``` + +Replace `${REGISTRY}` with your container registry (e.g., `nvcr.io`). For non-NGC +registries, replace `--docker-username` and `--docker-password` with your registry +credentials. For NGC (`nvcr.io`), `$REGISTRY_PASSWORD` is your NGC Personal Key or +API Key. + +Reference the secret in your compute-plane environment: + +```yaml +global: + imagePullSecrets: + - name: nvcr-pull-secret +``` + +Helmfile passes this value to the operator chart. The operator adds the pull +secret reference to the pods it manages. Pre-creating the secret in +`nvca-system` and `nvcf-backend` prevents startup races for operator-managed +resources that pull private images. + +### Install + +Install with the compute-plane Makefile. The command copies +`registration/-register-values.yaml` into `out/`, then runs +Helmfile against the GPU cluster kubeconfig: + +```bash +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME= \ + HELMFILE_ENV= \ + NCA_ID= \ + KUBECONFIG_FILE= +``` + +During installation, Helmfile will: + +1. Create the operator deployment with vault agent annotations for OpenBao auth. +2. Render the `nvcfbackend-self-managed` ConfigMap from the register values (cluster + identity, SIS/ReVal/NATS endpoints, and any host-header overrides). +3. Start the operator, which reads the ConfigMap and creates the NVCFBackend and NVCA agent + deployment. + +### Verify + +Check the operator pod is running. The pod runs the operator, the `nvca-mirror` sidecar, and +the OpenBao vault-agent sidecar (and a `cluster-validator` init container when network +validation is enabled): + +```bash +kubectl get pods -n nvca-operator + +# Expected: +# NAME READY STATUS RESTARTS AGE +# nvca-operator-... Running 0 1m +``` + +Check the cluster identity ConfigMap was rendered from the register values: + +```bash +kubectl get cm nvcfbackend-self-managed -n nvca-operator \ + -o jsonpath='{.data.cluster-dto\.yaml}' + +# Expected: cluster-dto.yaml with non-empty clusterId and clusterGroupId +``` + +Check the NVCFBackend resource was created: + +```bash +kubectl get nvcfbackends -n nvca-operator + +# Expected: one NVCFBackend resource with version and health status +``` + +Check the NVCA agent pod is running (the operator creates this automatically): + +```bash +kubectl get pods -n nvca-system + +# Expected: +# NAME READY STATUS RESTARTS AGE +# nvca-... 3/3 Running 0 2m +``` + + +The NVCA agent pod has 3 containers: the agent, the admission webhook, and the OpenBao vault agent sidecar. +Both should show `Running`. If the vault agent sidecar is in `CrashLoopBackOff`, verify +that OpenBao is healthy and the migration jobs completed successfully. + + + +Verify GPU discovery: + +```bash +kubectl get nvcfbackends -n nvca-operator -o jsonpath='{.items[0].status}' | python3 -m json.tool + +# Look for GPU information in the status output +``` + +### Verify Workload Scheduling + +Use this check only when worker pods on the GPU cluster can reach the +control-plane worker endpoints used by NVCF. For multi-cluster EKS installs, +first validate the GPU cluster by checking `nvca-operator` rollout and +`NVCFBackend` agent health. Function deployment and invocation are valid +acceptance checks only after the worker endpoints are reachable from the GPU +cluster. + +1. Set up environment variables: + +```bash +# Get the Gateway address (from Step 1) +export GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway -o jsonpath='{.status.addresses[0].value}') +echo "Gateway Address: $GATEWAY_ADDR" +``` + +2. Generate an admin token: + +```bash +# Generate an admin API token +export NVCF_TOKEN=$(curl -s -X POST "http://${GATEWAY_ADDR}/v1/admin/keys" \ + -H "Host: api-keys.${GATEWAY_ADDR}" \ + | grep -o '"value":"[^"]*"' | cut -d'"' -f4) + +echo "Token generated: ${NVCF_TOKEN:0:20}..." +``` + +3. Create, deploy, and invoke a test function: + +```bash +# Create a test function +# Replace / with your container registry +# This should match the registry you set in the secrets file +curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" \ + -d '{ + "name": "my-echo-function", + "inferenceUrl": "/echo", + "healthUri": "/health", + "inferencePort": 8000, + "containerImage": "//load_tester_supreme:0.0.8" + }' | jq . + +# Extract function and version IDs from the response +export FUNCTION_ID= +export FUNCTION_VERSION_ID= + +# Deploy the function +# Adjust instanceType and gpu based on your cluster configuration +# Instance Type Examples: NCP.GPU.A10G_1x, NCP.GPU.H100_1x, NCP.GPU.L40S_1x, etc. +# GPU Examples: A10G, H100, L40S, etc. +curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/deployments/functions/${FUNCTION_ID}/versions/${FUNCTION_VERSION_ID}" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" \ + -d '{ + "deploymentSpecifications": [ + { + "instanceType": "NCP.GPU.A10G_1x", + "backend": "nvcf-default", + "gpu": "A10G", + "maxInstances": 1, + "minInstances": 1 + } + ] + }' | jq . + +# Generate an API key for invocation. See the API scope reference for endpoint-specific scope requirements. +# Set expiration to 1 day from now (required field) +EXPIRES_AT=$(date -u -v+1d '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -d '+1 day' '+%Y-%m-%dT%H:%M:%SZ') +SERVICE_ID="nvidia-cloud-functions-ncp-service-id-aketm" + +export API_KEY=$(curl -s -X POST "http://${GATEWAY_ADDR}/v1/keys" \ + -H "Host: api-keys.${GATEWAY_ADDR}" \ + -H "Content-Type: application/json" \ + -H "Key-Issuer-Service: nvcf-api" \ + -H "Key-Issuer-Id: ${SERVICE_ID}" \ + -H "Key-Owner-Id: test@nvcf-api.local" \ + -d '{ + "description": "test invocation key", + "expires_at": "'"${EXPIRES_AT}"'", + "authorizations": { + "policies": [{ + "aud": "'"${SERVICE_ID}"'", + "auds": ["'"${SERVICE_ID}"'"], + "product": "nv-cloud-functions", + "resources": [ + {"id": "*", "type": "account-functions"}, + {"id": "*", "type": "authorized-functions"} + ], + "scopes": ["invoke_function", "list_functions", "queue_details", "list_functions_details"] + }] + }, + "audience_service_ids": ["'"${SERVICE_ID}"'"] + }' | jq -r '.value') + +echo "API Key: ${API_KEY:0:20}..." + +# Wait for deployment to be ready (list functions to see status), then invoke the function +# Uses wildcard subdomain routing: .invocation. +curl -s -X POST "http://${GATEWAY_ADDR}/echo" \ + -H "Host: ${FUNCTION_ID}.invocation.${GATEWAY_ADDR}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${API_KEY}" \ + -d '{"message": "hello world", "repeats": 1}' +``` + + +The `backend` value should match the cluster group name registered by the NVCA operator. +The `instanceType` and `gpu` values depend on the GPU types available in your cluster. + +For invocation, the Host header uses wildcard subdomain routing: `.invocation.`. +The URL path should match the function's `inferenceUrl` (e.g., `/echo`). +For full HTTP invocation behavior, streaming, and errors, see +[Generic HTTP Function Invocation](../generic-http-function-invocation.md). + + + +You can also use the NVCF CLI for easier function management: + +- Create, deploy, and invoke functions with simple commands +- Create or update registry credentials without manual API calls + +See [self-hosted-cli](../cli.md) for installation and usage instructions. + +## Re-registering a cluster + +Registration is performed by the CLI, not by the operator. To re-register (for +example, after a failed install, or to refresh the recorded issuer and JWKS), +re-run the compute-plane registration target and then re-apply the operator with +the refreshed values: + +```bash +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME= \ + NCA_ID= \ + CLUSTER_REGION= \ + ICMS_URL="http://" \ + KUBECONFIG_FILE= \ + NVCF_CLI= \ + NVCF_CLI_CONFIG= + +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME= \ + HELMFILE_ENV= \ + NCA_ID= \ + KUBECONFIG_FILE= +``` + +The registration target rewrites +`registration/-register-values.yaml`. The install target +copies that file into `out/` before running Helmfile. The operator reads the +cluster identity at startup, so re-running the install restarts it with the new +values. + +## Uninstalling + +To fully remove the NVCA Operator and all associated resources: + + +If functions are currently deployed on the cluster (pods in the `nvcf-backend` namespace), +undeploy them through the NVCF API or CLI before uninstalling the operator. Attempting +to delete NVCA while function pods are running can cause finalizers to block namespace +deletion. If you encounter stuck resources, see [Handling Stuck Resources] below. + + + +1. Delete the NVCFBackend resource. This triggers operator-managed cleanup of + the agent deployment, NVCA system pods, and related resources: + + ```bash + kubectl --kubeconfig \ + delete nvcfbackends --all -n nvca-operator --timeout=60s + ``` + +2. Verify the agent namespace is clean before proceeding: + + ```bash + kubectl --kubeconfig get pods -n nvca-system + + # Expected: "No resources found in nvca-system namespace." + ``` + +3. Destroy the compute-plane Helmfile release: + + ```bash + make destroy \ + CLUSTER_NAME= \ + HELMFILE_ENV= \ + NCA_ID= \ + KUBECONFIG_FILE= + ``` + + + The cluster identity (`clusterID` and `clusterGroupID`) persists in the control plane + (ICMS) and in your registration values file. A reinstall reuses it by + re-applying that file. Pass `--ignore-existing` to `cluster register` if you re-register. + + + +4. Delete CRDs. This removes all NVCFBackend, MiniService, and StorageRequest + custom resources cluster-wide: + + ```bash + kubectl --kubeconfig delete crd \ + nvcfbackends.nvcf.nvidia.io \ + miniservices.nvca.nvcf.nvidia.io \ + storagerequests.nvca.nvcf.nvidia.io \ + --ignore-not-found + ``` + +5. Delete namespaces: + + ```bash + kubectl --kubeconfig delete namespace \ + nvca-operator nvca-system nvcf-backend nvca-modelcache-init \ + --ignore-not-found + ``` + +### Handling Stuck Resources + +If step 1 times out and namespaces remain stuck in `Terminating` state, or function pods in +`nvcf-backend` prevent cleanup, use the [force-cleanup-script](../troubleshooting.md). This script removes +finalizers on stuck NVCA resources, force-deletes function pods, and cleans up all NVCA +namespaces. + +```bash +# Preview what will be deleted +./force-cleanup-nvcf.sh --dry-run + +# Execute the cleanup +./force-cleanup-nvcf.sh +``` + + +The force cleanup script bypasses normal cleanup procedures by removing finalizers. Always +attempt the ordered uninstall steps above first. + + + +For the full script, download link, and detailed usage instructions, see the NVCA Force +Cleanup Script appendix in the self-hosted troubleshooting guide. + +## Troubleshooting + +- Cluster IDs empty in the ConfigMap after install: The registration values were + not applied. Confirm + `registration/-register-values.yaml` exists and that its + `clusterID` and `clusterGroupID` are populated, then re-run `make install`: + + ```bash + kubectl get cm nvcfbackend-self-managed -n nvca-operator \ + -o jsonpath='{.data.cluster-dto\.yaml}' + ``` + +- Operator pod not starting: Check the operator logs: + + ```bash + kubectl logs -n nvca-operator -l app.kubernetes.io/name=nvca-operator -c nvca-operator --tail=100 + ``` + +- Operator or agent logs show `failed to create watcher` with `too many open files`: + Increase the node inotify limits with the `node-inotify-tuner` DaemonSet in + [Node inotify limits](#node-inotify-limits), then restart the affected pod. + +- NVCA agent pod not created: The operator creates the agent pod via the NVCFBackend + resource. Check the operator logs for reconciliation errors: + + ```bash + kubectl describe nvcfbackends -n nvca-operator + ``` + +- Agent fails to register with SIS (HTTP 401): The control plane could not + validate the agent's PSAT against the recorded issuer and JWKS. Re-run + `make register-cluster` for the cluster (see + [Re-registering a cluster](#re-registering-a-cluster)) so ICMS has the + current issuer and JWKS. Also verify the vault agent sidecar on the agent pod + is running and rendering the secrets file: + + ```bash + kubectl logs -n nvca-system -l app.kubernetes.io/name=nvca -c vault-agent --tail=10 + ``` + +- Vault agent sidecar failing: The agent pod needs to authenticate with OpenBao. Verify + the vault system is healthy: + + ```bash + kubectl exec -n vault-system openbao-server-0 -- bao status + ``` + +- No GPUs discovered: Ensure the GPU Operator is installed and GPU nodes have the + `nvidia.com/gpu` resource advertised: + + ```bash + kubectl get nodes -o custom-columns="NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu" + ``` diff --git a/docs/v0.6.1-rc/configure-autoscaling.md b/docs/v0.6.1-rc/configure-autoscaling.md new file mode 100644 index 000000000..d7b2a720a --- /dev/null +++ b/docs/v0.6.1-rc/configure-autoscaling.md @@ -0,0 +1,154 @@ +# Configure Function Autoscaling + +This page explains how to configure autoscaling on a deployed function using the NVCF API. For background on how the function autoscaler decides on instance counts, see the [Function Autoscaling Overview](./autoscaling/index.md). For the full schema of the request and response bodies referenced below, see the [NVCF OpenAPI specification](https://api.nvcf.nvidia.com/v3/openapi). + +Use these two endpoints: + +- `POST /v2/nvcf/deployments/functions/{functionId}/versions/{versionId}` sets the initial scaling bounds when you first deploy a function. +- `PATCH /v2/nvcf/deployments/{deploymentId}/gpu-specifications/{gpuSpecificationId}` updates the bounds and the autoscaling policy on an existing deployment. + +All requests require an `NVCF_TOKEN` (JWT) with the `deploy_function` scope. + +## Scope of Autoscaling + +The function autoscaler operates per function version. It produces a single desired instance count for a given `(functionId, versionId)` pair; the GPU specification ID and deployment ID are not part of its decision. The `PATCH /v2/nvcf/deployments/.../gpu-specifications/{gpuSpecificationId}` endpoint is per-spec on the wire, but the resulting scaling decision applies at the function-version level. If a deployment has more than one GPU specification, set the autoscaling policy consistently across its specs. + +## Set Scaling Bounds at Deploy Time + +When creating a deployment, set `minInstances` and `maxInstances` on each entry in `deploymentSpecifications`. These are the only autoscaling fields accepted at create time. + +```bash +curl -s -X POST "https://${GATEWAY_ADDR}/v2/nvcf/deployments/functions/${FUNCTION_ID}/versions/${FUNCTION_VERSION_ID}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "deploymentSpecifications": [{ + "gpu": "H100", + "instanceType": "NCP.GPU.H100_1x", + "minInstances": 1, + "maxInstances": 4 + }] + }' +``` + +Constraints: + +- `minInstances` must be `>= 0`. Setting `minInstances: 0` allows the function to scale to zero when idle. The scale-to-zero idle timeout is set on the platform and is not configurable per function. +- `maxInstances` must be `> 0` and `>= minInstances`. + +With no other configuration, the deployment uses the platform's default scaling policy. The platform adds and removes instances within the `[minInstances, maxInstances]` bounds based on observed utilization. + +## Update Scaling Bounds on an Existing Deployment + +To change the bounds after deploy, look up the `deploymentId` and the `gpuSpecificationId` for the GPU spec you want to change, then `PATCH` that spec. + +Look up the IDs: + +```bash +curl -s "https://${GATEWAY_ADDR}/v2/nvcf/deployments/functions/${FUNCTION_ID}/versions/${FUNCTION_VERSION_ID}" \ + -H "Authorization: Bearer $NVCF_TOKEN" +``` + +The response includes `deployment.deploymentId` and a `gpuSpecificationId` on each entry in `deployment.deploymentSpecifications`. + +Update the bounds: + +```bash +curl -s -X PATCH "https://${GATEWAY_ADDR}/v2/nvcf/deployments/${DEPLOYMENT_ID}/gpu-specifications/${GPU_SPEC_ID}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "minInstances": 0, + "maxInstances": 8 + }' +``` + +The body must include at least one of `minInstances`, `maxInstances`, `autoscalingConfiguration`, or `autoscalingConfigurationPolicy`. You can't change `gpu`, `instanceType`, `backend`, `clusters`, `availabilityZones`, `preferredOrder`, or `maxRequestConcurrency` on an existing GPU spec. + +## Customize the Autoscaling Policy + +By default, every deployment uses the platform's autoscaling policy. To override it with per-function scale-up and scale-down behavior, send an `autoscalingConfiguration` block on the `PATCH` request. + +```bash +curl -s -X PATCH "https://${GATEWAY_ADDR}/v2/nvcf/deployments/${DEPLOYMENT_ID}/gpu-specifications/${GPU_SPEC_ID}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "autoscalingConfigurationPolicy": "CUSTOM_CONFIGURATION", + "autoscalingConfiguration": { + "scaleUpDetails": { + "factor": 1.5, + "threshold": 75 + }, + "scaleDownDetails": { + "factor": 0.5, + "threshold": 25 + } + } + }' +``` + +`autoscalingConfigurationPolicy` accepts two values: + +| Value | Effect | +|-------|--------| +| `CUSTOM_CONFIGURATION` | Apply the `autoscalingConfiguration` block on this request. | +| `PLATFORM_CONFIGURATION` | Discard any custom configuration on this GPU spec and revert to the platform defaults. | + +`scaleUpDetails` and `scaleDownDetails` use the same shape: + +| Field | Type | Notes | +|-------|------|-------| +| `factor` | number | Multiplier applied to the current instance count. Must be `> 1.0` on `scaleUpDetails` and `< 1.0` on `scaleDownDetails`. | +| `threshold` | integer | Utilization percent that triggers the scaling action. | +| `stickiness` | object | Optional. See below. | + +## Stickiness + +Stickiness reduces churn by requiring utilization to stay past the threshold for a minimum window before a scaling action fires. Add a `stickiness` block to `scaleUpDetails` or `scaleDownDetails`: + +```json +{ + "autoscalingConfigurationPolicy": "CUSTOM_CONFIGURATION", + "autoscalingConfiguration": { + "scaleUpDetails": { + "factor": 1.5, + "threshold": 75, + "stickiness": { + "size": "PT10M", + "threshold": "PT3M" + } + } + } +} +``` + +Both fields are ISO-8601 durations: + +- `size` is the length of the sliding window. Must be `<= PT1H`. +- `threshold` is the amount of time within the window that utilization must stay past the scaling threshold. Must be `< size`. + +The threshold can be set to zero, but this would nullify the utilization metric. The scaling logic would effectively be collapsed into only scaling to/from zero/one instance(s). + +The example above scales up only if utilization exceeds 75 percent for at least 3 minutes in any 10-minute window. + +## Revert to Platform Defaults + +To discard a custom configuration without setting a new one: + +```bash +curl -s -X PATCH "https://${GATEWAY_ADDR}/v2/nvcf/deployments/${DEPLOYMENT_ID}/gpu-specifications/${GPU_SPEC_ID}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "autoscalingConfigurationPolicy": "PLATFORM_CONFIGURATION" + }' +``` + +The GPU spec uses the platform's autoscaling policy on the next scaling cycle. + +## See Also + +- [Function Autoscaling Overview](./autoscaling/index.md) for what the function autoscaler does and what it depends on. +- [CLI](./cli.md) for `nvcf-cli function deploy create` and `nvcf-cli function deploy update`, which wrap the same API surface. +- [NVCF OpenAPI specification](https://api.nvcf.nvidia.com/v3/openapi) for the full request and response schema of the endpoints used on this page. diff --git a/docs/v0.6.1-rc/container-functions.md b/docs/v0.6.1-rc/container-functions.md new file mode 100644 index 000000000..7552a4dbd --- /dev/null +++ b/docs/v0.6.1-rc/container-functions.md @@ -0,0 +1,325 @@ +# Container-Based Function Creation + +Container-based functions require building and pushing a Cloud Functions compatible [Docker](https://docker.com) container image to your container registry. + +## Resources + +- Example containers can be found [in the examples directory](https://github.com/NVIDIA/nvcf/tree/main/examples). + +- The repository also contains [helper functions](https://github.com/NVIDIA/nvcf/blob/main/src/libraries/python/nv-cloud-function-helpers/nv_cloud_function_helpers/nvcf_container/helpers.py) that are useful when authoring your container, including: + + - Helpers that parse Cloud Functions-specific parameters on invocation + - Helpers that can be used to instrument your container with Cloud Functions compatible logs + +- It's always a **best practice to emit logs** from your inference container. Cloud Functions supports third-party logging and metrics emission from your container. + + +Please note that container functions **should not run as root user**, running as root is not formally supported on any Cloud Functions backend. + + + +## Container Endpoints + +Any server can be implemented within the container, as long as it implements the following: + +- For HTTP-based functions, a health check endpoint that returns a 200 HTTP Status Code on success. +- For gRPC-based functions, a standard gRPC health check. See [these docs for more info](https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto) also [gRPC Health Checking](https://grpc.io/docs/guides/health-checking/). +- An inference endpoint (this endpoint will be called during function invocation) + +These endpoints are expected to be served on the same port, defined as the `inferencePort`. + + +Cloud Functions reserves the following ports on your container for internal monitoring and metrics: + +- Port `8080` +- Port `8010` + +Cloud Functions also expects the following directories in the container to remain read-only for caching purposes: + +- `/config/` directory +- Nested directories created inside `/config/` + + + +## Composing a FastAPI Container + +It's possible to use any container with Cloud Functions as long as it implements a server with the above endpoints. The below is an example of a FastAPI-based container compatible with Cloud Functions. Clone the [FastAPI echo example](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/fastapi-echo-sample). + +### Create the "requirements.txt" File + +```text +fastapi==0.110.0 +uvicorn==0.29.0 +``` + +### Implement the Server + +```python +import os +import time +import uvicorn +from pydantic import BaseModel +from fastapi import FastAPI, status +from fastapi.responses import StreamingResponse + +app = FastAPI() + +class HealthCheck(BaseModel): + status: str = "OK" + +# Implement the health check endpoint +@app.get("/health", tags=["healthcheck"], summary="Perform a Health Check", response_description="Return HTTP Status Code 200 (OK)", status_code=status.HTTP_200_OK, response_model=HealthCheck) +def get_health() -> HealthCheck: + return HealthCheck(status="OK") + +class Echo(BaseModel): + message: str + delay: float = 0.000001 + repeats: int = 1 + stream: bool = False + +# Implement the inference endpoint +@app.post("/echo") +async def echo(echo: Echo): + if echo.stream: + def stream_text(): + for _ in range(echo.repeats): + time.sleep(echo.delay) + yield f"data: {echo.message}\n\n" + return StreamingResponse(stream_text(), media_type="text/event-stream") + else: + time.sleep(echo.delay) + return echo.message*echo.repeats + +# Serve the endpoints on a port +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000, workers=int(os.getenv('WORKER_COUNT', 500))) +``` + +Note in the example above, the function's configuration during creation will be: + +- Inference Protocol: HTTP +- Inference Endpoint: `/echo` +- Health Endpoint: `/health` +- Inference Port (also used for health check): `8000` + +### Create the Dockerfile + +```dockerfile +FROM python:3.10.13-bookworm + +ENV WORKER_COUNT=10 + +WORKDIR /app + +COPY requirements.txt ./ + +RUN python -m pip install --no-cache-dir -U pip && \ + python -m pip install --no-cache-dir -r requirements.txt + +COPY http_echo_server.py /app/ + +CMD uvicorn http_echo_server:app --host=0.0.0.0 --workers=$WORKER_COUNT +``` + +### Build the Container & Create the Function + +See the [Create the Function] section below for the remaining steps. + +## Composing a PyTriton Container + +NVIDIA's [PyTriton](https://triton-inference-server.github.io/pytriton/) is a Python native solution of Triton inference server. A minimum version of 0.3.0 is required. + +### Create the "requirements.txt" File + +- This file should list the Python dependencies required for your model. +- Add nvidia-pytriton to your `requirements.txt` file. + +Here is an example of a `requirements.txt` file: + +```text +--extra-index-url https://pypi.ngc.nvidia.com +opencv-python-headless +pycocotools +matplotlib +torch==2.1.0 +nvidia-pytriton==0.3.0 +numpy +``` + +### Create the "run.py" File + +1. Your `run.py` file (or similar Python file) needs to define a PyTriton model. +2. This involves importing your model dependencies, creating a PyTritonServer class with an `__init__` function, an `_infer_fn` function and a `run` function that serves the inference_function, defining the model name, the inputs and the outputs along with optional configuration. + +Here is an example of a `run.py` file: + +```python +import numpy as np +from pytriton.model_config import ModelConfig, Tensor +from pytriton.triton import Triton, TritonConfig +import time +.... +class PyTritonServer: + """triton server for timed_sleeper""" + + def __init__(self): + # basically need to accept image, mask(PIL Images), prompt, negative_prompt(str), seed(int) + self.model_name = "timed_sleeper" + + def _infer_fn(self, requests): + responses = [] + for req in requests: + req_data = req.data + sleep_duration = numpy_array_to_variable(req_data.get("sleep_duration")) + # deal with header dict keys being lowerscale + request_parameters_dict = uppercase_keys(req.parameters) + time.sleep(sleep_duration) + responses.append({"sleep_duration": np.array([sleep_duration])}) + + return responses + + def run(self): + """run triton server""" + with Triton( + config=TritonConfig( + http_header_forward_pattern="NVCF-*", # this is required + http_port=8000, + grpc_port=8001, + metrics_port=8002, + ) + ) as triton: + triton.bind( + model_name="timed_sleeper", + infer_func=self._infer_fn, + inputs=[ + Tensor(name="sleep_duration", dtype=np.uint32, shape=(1,)), + ], + outputs=[Tensor(name="sleep_duration", dtype=np.uint32, shape=(1,))], + config=ModelConfig(batching=False), + ) + triton.serve() +if __name__ == "__main__": + server = PyTritonServer() + server.run() +``` + +### Create the "Dockerfile" + +1. Create a file named `Dockerfile` in your model directory. +2. It's **strongly recommended to use NVIDIA-optimized containers like CUDA, Pytorch or TensorRT as your base container**. They can be downloaded from the [NGC Catalog](https://catalog.ngc.nvidia.com/). +3. Make sure to install your Python requirements in your `Dockerfile`. +4. Copy in your model source code, and model weights. + +Here is an example of a `Dockerfile`: + +```dockerfile +FROM nvcr.io/nvidia/cuda:12.1.1-devel-ubuntu22.04 +RUN apt-get update && apt-get install -y \ + git \ + python3 \ + python3-pip \ + python-is-python3 \ + libsm6 \ + libxext6 \ + libxrender-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /workspace/ + +# Install requirements file +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir --upgrade pip +RUN pip install --no-cache-dir -r requirements.txt +ENV DEBIAN_FRONTEND=noninteractive + +# Copy model source code and weights +COPY model_weights /models +COPY model_source . +COPY run.py . + +# Set run command to start PyTriton to serve the model +CMD python3 run.py +``` + +### Build the Docker Image + +1. Open a terminal or command prompt. +2. Navigate to the `my_model` directory. +3. Run the following command to build the docker image: + +```bash +docker build -t my_model_image . +``` + +Replace `my_model_image` with the desired name for your docker image. + +### Push the Docker Image + +Tag and push the docker image to your container registry. + +```bash +docker tag my_model_image:latest ${REGISTRY}/${REPOSITORY}/my_model_image:latest +docker push ${REGISTRY}/${REPOSITORY}/my_model_image:latest +``` + +### Create the Function + +Create the function via the NVCF API. In this example, we defined the inference port as `8000` and are using the default inference and health endpoint paths. + +```bash + curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -d '{ + "name": "my-model-function", + "inferenceUrl": "/v2/models/my_model_image/infer", + "inferencePort": 8000, + "containerImage": "'${REGISTRY}'/'${REPOSITORY}'/my_model_image:latest", + "health": { + "protocol": "HTTP", + "uri": "/v2/health/ready", + "port": 8000, + "timeout": "PT10S", + "expectedStatusCode": 200 + } + }' +``` + +### Additional Examples + +See more examples of containers that are Cloud Functions compatible [in the function samples directory](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/). + +## Creating gRPC-based Functions + +Cloud Functions supports function invocation via gRPC. During function creation, specify that the function is a gRPC function by setting the `inferenceUrl` field to `/grpc`. + +### Prerequisites + +- The function container must implement a gRPC port, endpoint and health check. The health check is expected to be served by the gRPC inference port, there is no need to define a separate health endpoint path. + + - See [gRPC health checking](https://grpc.io/docs/guides/health-checking/). + - See an [example container](https://github.com/NVIDIA/nvcf/blob/main/examples/function-samples/grpc-echo-sample/grpc_echo_server.py) with a gRPC server that is Cloud Functions compatible. + +### gRPC Function Creation via API + +When creating the gRPC function, set the `inferenceUrl` field to `/grpc`: + +```bash + curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -d '{ + "name": "my-grpc-function", + "inferenceUrl": "/grpc", + "inferencePort": 8001, + "containerImage": "'${REGISTRY}'/'${REPOSITORY}'/grpc_echo_sample:latest" + }' +``` + +### gRPC Function Invocation + +For gRPC authentication metadata, gateway address configuration, and client +examples, see [gRPC Function Invocation](./grpc-function-invocation.md). diff --git a/docs/v0.6.1-rc/container-tasks.md b/docs/v0.6.1-rc/container-tasks.md new file mode 100644 index 000000000..2f8c3f764 --- /dev/null +++ b/docs/v0.6.1-rc/container-tasks.md @@ -0,0 +1,135 @@ +# Container-Based Task Creation + +A container task runs a Docker image on a GPU instance until the process +exits. Use this approach for training jobs, batch inference, data processing, +or any workload that runs to completion. + +## Container requirements + +NVCT does not impose a server or health check requirement. The container only +needs to: + +- Perform its workload. +- Exit with code 0 on success, or a non-zero code on failure. + +GPU drivers and CUDA libraries are available on the host. Use an image based +on an appropriate CUDA base image for your workload. + +## Creating a container task + +```bash +# Minimal example using CLI flags +nvcf-cli task create \ + --name my-training-job \ + --gpu H100 \ + --instance-type GPU.H100_1x \ + --image my-registry/training:latest + +# With arguments, environment variables, and secrets +nvcf-cli task create \ + --name my-training-job \ + --gpu H100 \ + --instance-type GPU.H100_1x \ + --image my-registry/training:latest \ + --container-args "--epochs 10 --batch-size 32" \ + --container-env DATASET_PATH=/data/train \ + --container-env LOG_LEVEL=info \ + --secrets NGC_API_KEY=nvapi-... \ + --max-runtime PT4H \ + --result-strategy UPLOAD \ + --results-location my-org/my-team/my-model + +# From a JSON file (recommended for repeatable configurations) +nvcf-cli task create --input-file task.json +``` + +## Example JSON configuration + +```json +{ + "name": "my-training-job", + "gpuSpecification": { + "gpu": "H100", + "instanceType": "GPU.H100_1x", + "backend": "GFN" + }, + "containerImage": "my-registry/training:latest", + "containerArgs": "--epochs 10 --batch-size 32", + "containerEnvironment": [ + {"key": "DATASET_PATH", "value": "/data/train"}, + {"key": "LOG_LEVEL", "value": "info"} + ], + "maxRuntimeDuration": "PT4H", + "maxQueuedDuration": "PT72H", + "resultHandlingStrategy": "UPLOAD", + "resultsLocation": "my-org/my-team/my-model", + "secrets": [ + {"name": "NGC_API_KEY", "value": "nvapi-..."} + ] +} +``` + +## GPU specification + +| Field | Description | +| --- | --- | +| `gpu` | GPU name, e.g. `H100`, `A100` | +| `instanceType` | Instance type, e.g. `GPU.H100_1x`, `GPU.A100_8x` | +| `backend` | Backend or CSP (optional) | +| `clusters` | Specific cluster names to target (optional) | + +## Runtime limits + +| Field | Format | Default | +| --- | --- | --- | +| `maxRuntimeDuration` | ISO 8601 duration, e.g. `PT4H30M` | None | +| `maxQueuedDuration` | ISO 8601 duration | `PT72H` | +| `terminationGracePeriodDuration` | ISO 8601 duration | `PT1H` | + +A task that exceeds `maxRuntimeDuration` moves to +`EXCEEDED_MAX_RUNTIME_DURATION` status. A task that is not scheduled within +`maxQueuedDuration` moves to `EXCEEDED_MAX_QUEUED_DURATION` status. + +`maxRuntimeDuration` has no default or maximum: omit it and the task runs with +no time limit. + +## Secrets + +Secrets are passed to the container as environment variables. Provide them via +`--secrets NAME=value` on the CLI or as a `secrets` array in the JSON file. +Secret values are stored encrypted and are not returned by default in task +detail responses. + +To update secrets on a running task: + +```bash +nvcf-cli task update-secrets --secrets NEW_KEY=new-value +``` + +## Model and resource artifacts + +Attach model or resource artifacts to a task using the `--models` and +`--resources` flags (format: `name:version:uri`) or via the JSON `models` and +`resources` arrays. These are made available to the container at runtime. + +## Result handling + +Note: result upload is not yet supported in this release. + +When `resultHandlingStrategy` is `UPLOAD`, the task uploads outputs to the +registry location specified in `resultsLocation`. After the task completes, +retrieve the results: + +```bash +nvcf-cli task results +``` + +## Monitoring a task + +```bash +# Check status +nvcf-cli task get + +# Stream lifecycle events +nvcf-cli task events +``` diff --git a/docs/v0.6.1-rc/control-plane-installation.md b/docs/v0.6.1-rc/control-plane-installation.md new file mode 100644 index 000000000..b1b3ecb7f --- /dev/null +++ b/docs/v0.6.1-rc/control-plane-installation.md @@ -0,0 +1,18 @@ +--- +--- + +# Control Plane Installation + +For a local k3d fresh install, start with the [Quickstart](./quickstart.md). The quickstart uses `nvcf-cli self-hosted up` to install the control plane, register the local k3d cluster, install NVCA, and run basic health checks. + +Use [Helmfile Installation](./helmfile-installation.md) when you need manual release control, partial recovery, or upgrade operations. + +## Installation guides + +- [Quickstart](./quickstart.md) - use `nvcf-cli self-hosted up` for local k3d one-click installation. +- [Helmfile Installation](./helmfile-installation.md) - use `helmfile` for manual control-plane deployment. + + +For the installation path overview, see [Deployment](./installation.md). + + diff --git a/docs/v0.6.1-rc/control-plane-operations.md b/docs/v0.6.1-rc/control-plane-operations.md new file mode 100644 index 000000000..a4e46ea95 --- /dev/null +++ b/docs/v0.6.1-rc/control-plane-operations.md @@ -0,0 +1,293 @@ +# Control Plane Operations + +This section provides runbooks for operating the self-hosted NVCF control plane, including encryption key rotation, service management, and upgrades. + +## Encryption Key Management + +Self-hosted NVCF uses a two-tier encryption hierarchy to protect secrets stored in the Encrypted Secret Store (ESS): + +| Key | Purpose | +| --- | --- | +| **MEK** (Master Encryption Key) | A single AES-256-GCM key stored in OpenBAO. The MEK encrypts (wraps) all NEKs. It is shared across NVCF services in the control plane. | +| **NEK** (Namespace Encryption Key) | Per-namespace AES-256-GCM keys stored in Cassandra (encrypted by the MEK). NEKs directly encrypt user secrets such as API keys and registry credentials. | + +When a user stores a secret through the NVCF API, ESS encrypts it with the active NEK for that namespace. The NEK itself is stored in Cassandra, encrypted by the MEK. To decrypt a secret, ESS retrieves the NEK from Cassandra, decrypts it using the MEK from OpenBAO, then decrypts the secret. + +### Key Rotation Runbooks + +- [control-plane-runbook-mek-rotation](runbooks/control-plane-key-rotation-mek.md) — Rotate the master encryption key stored in OpenBAO. + +## Basic Operations + +### Service Reference + +The following table lists all NVCF control plane services with their namespace, resource name, +and resource type. Use these values in the commands throughout this section. + +| Namespace | Service | Resource Name | Type | +| --- | --- | --- | --- | +| `nvcf` | NVCF API | `nvcf-api` | Deployment | +| `nvcf` | Invocation Service | `invocation-service` | Deployment | +| `nvcf` | gRPC Proxy | `grpc-proxy-deployment` | Deployment | +| `nvcf` | Notary Service | `notary-service` | Deployment | +| `sis` | Spot Instance Service | `spot-instance-service` | Deployment | +| `api-keys` | API Keys Service | `api-keys` | Deployment | +| `api-keys` | Admin Issuer Proxy | `admin-token-issuer-proxy` | Deployment | +| `ess` | ESS API | `ess-api-helm-nvcf-ess-api-deployment` | Deployment | +| `nats-system` | NATS | `nats` | StatefulSet | +| `vault-system` | OpenBao | `openbao-server` | StatefulSet | +| `cassandra-system` | Cassandra | `cassandra` | StatefulSet | +| `nvca-operator` | NVCA Operator | `nvca-operator` | Deployment | +| `envoy-gateway-system` | Envoy Gateway | `envoy-gateway` | Deployment | + +### Restarting a Service + +**Restarting a Deployment:** + +```bash +kubectl rollout restart deployment/ -n + +# Example: restart the NVCF API +kubectl rollout restart deployment/nvcf-api -n nvcf + +# Verify the rollout completes +kubectl rollout status deployment/ -n --timeout=120s +``` + +**Restarting a StatefulSet:** + +StatefulSets perform a rolling restart, terminating and recreating one pod at a time in +reverse ordinal order (highest first). For clustered services like NATS, OpenBao, and +Cassandra, this preserves quorum as long as a majority of replicas remain available. + +```bash +kubectl rollout restart statefulset/ -n + +# Example: restart Cassandra +kubectl rollout restart statefulset/cassandra -n cassandra-system + +# Verify the rollout completes +kubectl rollout status statefulset/ -n --timeout=300s +``` + + +For OpenBao, verify the seal status after the rollout completes. Each pod must unseal +before it can serve requests: + +```bash +kubectl exec -n vault-system openbao-server-0 -- bao status +``` + + + +**Restarting all Deployments in a namespace:** + +```bash +kubectl rollout restart deployment -n + +# Example: restart all services in the nvcf namespace +kubectl rollout restart deployment -n nvcf +``` + +### Checking Service Health + +**List pods and their status:** + +```bash +# All pods in a namespace +kubectl get pods -n + +# All NVCF control plane pods at a glance +for ns in nvcf sis api-keys ess nats-system vault-system cassandra-system; do + echo "=== $ns ===" + kubectl get pods -n $ns +done +``` + +**Check logs for a service:** + +```bash +# Recent logs +kubectl logs -n -l app.kubernetes.io/name= --tail=50 + +# Follow logs in real time +kubectl logs -n -l app.kubernetes.io/name= -f +``` + +**Describe a pod for events and conditions:** + +```bash +kubectl describe pod -n -l app.kubernetes.io/name= +``` + +### Scaling a Service + +To temporarily take a service offline (for example, during maintenance), scale it to zero, +perform the work, then scale it back: + +```bash +# Scale down +kubectl scale deployment/ -n --replicas=0 + +# ... perform maintenance ... + +# Scale back up +kubectl scale deployment/ -n --replicas=1 + +# Verify +kubectl rollout status deployment/ -n +``` + + +Scaling infrastructure StatefulSets (Cassandra, NATS, OpenBao) to zero will cause a full +outage. Only do this if you understand the implications for data availability and quorum. + + + +## Upgrading Services + + +**Upgrades are not officially supported during the Early Access period.** The self-hosted +NVCF stack does not yet have a validated upgrade path. Even a full `helmfile sync` may +introduce breaking changes between releases — there is no guarantee of backward +compatibility for configuration, database schemas, or inter-service APIs at this stage. + + + +The guidance below is provided for **advanced users** who need to apply targeted fixes or +hotfixes to individual services. It is not a substitute for a validated upgrade procedure. + + +**Spot upgrades carry additional risk.** Beyond the general Early Access limitations above, +spot-upgrading an individual Helm chart bypasses the Helmfile's version coordination and +automatic database migrations. Proceed only when you understand the compatibility +implications for the specific version you are upgrading to. + + + +### When to Spot Upgrade + +| Use a spot upgrade when | Use a full Helmfile upgrade when | +| --- | --- | +| Applying a patch release to a single service (e.g., `1.2.6` to `1.2.7`) | Upgrading the entire stack to a new minor or major version | +| Applying a targeted hotfix provided by NVIDIA support | The new version includes Cassandra schema migrations | +| You need to roll out a configuration change that requires a new chart version | Multiple services need to be upgraded together for compatibility | + +### Pre-Upgrade Checklist + +Before upgrading any chart: + +1. **Note the current chart version and app version:** + + ```bash + helm list -n + ``` + +2. **Back up the current Helm values:** + + ```bash + helm get values -n -o yaml > -values-backup.yaml + ``` + +3. **Review release notes** for the target version. Check for breaking changes, required + value changes, or new dependencies. + +4. **Verify the cluster is healthy** before starting — all pods running, no pending operations. + +### Spot Upgrading a Helm Chart + +The following commands work for any Deployment-based service. Replace the placeholders with +values from the [Service Reference] table above. + +```bash +# 1. Upgrade the chart +helm upgrade \ + oci://// \ + --version \ + --namespace \ + --wait --timeout 5m \ + -f -values.yaml + +# 2. Verify the rollout +kubectl rollout status deployment/ -n --timeout=120s + +# 3. Confirm the new chart version +helm list -n +``` + +**Example — upgrading the NVCF API chart:** + +```bash +helm upgrade api \ + oci://${REGISTRY}/${REPOSITORY}/helm-nvcf-api \ + --version 2.1.0 \ + --namespace nvcf \ + --wait --timeout 5m \ + -f nvcf-api-values.yaml + +kubectl rollout status deployment/api -n nvcf --timeout=120s +``` + + +Always pass your values file (`-f values.yaml`) during upgrade. If you omit it, Helm +resets all values to chart defaults, which can break your deployment. If you no longer +have the original values file, back up the current values first with `helm get values`. + + + +### Upgrading StatefulSet-Based Services + +Cassandra, NATS, and OpenBao are deployed as StatefulSets. The `helm upgrade` command is the +same, but the rollout behavior differs: + +- **Rolling update:** StatefulSets restart pods one at a time in reverse ordinal order, + waiting for each pod to become ready before proceeding to the next. +- **Quorum preserved:** For 3-replica clusters, at most one pod is unavailable at a time, + maintaining quorum throughout the upgrade. + +```bash +helm upgrade \ + oci://// \ + --version \ + --namespace \ + --wait --timeout 10m \ + -f -values.yaml + +kubectl rollout status statefulset/ -n --timeout=300s +``` + +**Service-specific notes:** + +| **Cassandra** | After upgrading, check if the new version requires a schema migration. If a `cassandra-migrations` job exists in the chart, it will run automatically as a Helm post-upgrade hook. Verify it completes: `kubectl get jobs -n cassandra-system`. | +| --- | --- | +| **OpenBao** | After each pod restarts, verify it unseals successfully: `kubectl exec -n vault-system openbao-server-0 -- bao status`. If auto-unseal is configured, this happens automatically. Otherwise, you must unseal each pod manually. | +| **NATS** | The NATS cluster maintains message availability during rolling updates as long as a majority of nodes remain online. Monitor the cluster state: `kubectl logs -n nats-system nats-0 --tail=10`. | + +### Rolling Back + +If an upgrade causes issues, roll back to the previous Helm revision: + +```bash +# List revision history +helm history -n + +# Roll back to the previous revision +helm rollback -n + +# Verify +kubectl rollout status deployment/ -n --timeout=120s + +# Or for StatefulSets +kubectl rollout status statefulset/ -n --timeout=300s +``` + + +`helm rollback` reverts both the chart version and the values. If you made intentional +value changes alongside the version upgrade, you will need to re-apply them after the +rollback. + + + +## Observability + +For observability configuration and reference architecture, see [self-hosted-observability](./observability.md). diff --git a/docs/v0.6.1-rc/csp-end-to-end-example-installation.md b/docs/v0.6.1-rc/csp-end-to-end-example-installation.md new file mode 100644 index 000000000..81a080aaf --- /dev/null +++ b/docs/v0.6.1-rc/csp-end-to-end-example-installation.md @@ -0,0 +1,639 @@ +# CSP End-to-End Example Installation (Helmfile) + +This page provides a complete end-to-end example for installing NVCF on +pre-provisioned managed Kubernetes clusters using the split Helmfile bundles. +It covers both topologies: + +- Single-cluster: the control plane and the NVCA operator run on the same cluster. +- Multi-cluster: the control plane runs on one cluster, and the NVCA operator is + registered and installed on a separate GPU (compute) cluster. + +The commands are written to work on any cloud provider (CSP). Amazon EKS is used +as the worked example. The only provider-specific pieces are the load balancer +annotations on the Gateway, the `storageClass` name, and the `kubectl` context +names. Substitute the equivalents for GKE, AKS, or on-prem. + +For a deeper reference on each release and on values, see +[Helmfile Installation](./helmfile-installation.md). For pulling and mirroring +the bundles and images, see [Image Mirroring](./image-mirroring.md). + + +This guide assumes you have already downloaded and extracted the control-plane +Helmfile bundle and have a source checkout for the compute plane: + +- `nvcf-self-managed-stack` for the control plane. +- `deploy/stacks/nvcf-compute-plane` in the source repository for the compute + plane (NVCA operator). + +Control-plane commands run from inside the `nvcf-self-managed-stack` directory. +Compute-plane commands run from the repository root with `make -C`. + +```bash +git clone https://github.com/nvidia/nvcf.git +``` + + +## Installation order + +The order matters. Each step produces an input that the next step needs. The +load balancer address, in particular, must exist before you configure the +environment file, because it becomes `global.domain` and the NVCA Host headers. + +``` +1. Install the Gateway -> external load balancer address +2. Configure the environment -> environments/.yaml + secrets/-secrets.yaml +3. Install the control plane -> control-plane services + HTTPRoutes +4. Author the nvcf-cli config -> points at the load balancer address +5. Register the GPU cluster -> registration values file +6. Install the NVCA operator -> agent connects back to the control plane +7. Verify the agent is healthy +``` + +Single-cluster and multi-cluster share steps 1 through 4. Step 5 requires a +compute-plane environment file for both topologies. Only the cluster target and +control-plane endpoint values differ. See +[Single-cluster vs multi-cluster](#single-cluster-vs-multi-cluster). + +## Prerequisites + +### Tools + +Install on the machine you run these commands from: + +- `kubectl` +- `helm` (3.x) +- `helmfile` +- `nvcf-cli` + +### Clusters + +The clusters must be provisioned before you start. This guide does not create +them. Each cluster needs: + +- A default-capable `StorageClass` with dynamic provisioning. On EKS this is + `gp3`, backed by the EBS CSI driver. Substitute your provider's class name. +- The compute (GPU) cluster needs a GPU operator (real or the fake GPU operator + for non-GPU validation). See [Fake GPU Operator](./fake-gpu-operator.md). +- The compute cluster needs the + [SMB CSI driver](https://github.com/kubernetes-csi/csi-driver-smb) + (`smb.csi.k8s.io`). NVCA uses it for shared model cache storage that function + worker pods mount. Install and verify the driver before registering the GPU + cluster. See the + [Self-Managed Clusters prerequisites](./cluster-management/self-managed.md#prerequisites) + for the installation command. + +Both clusters must be reachable through `kubectl` contexts: + +```bash +kubectl --context "${CONTROL_PLANE_CONTEXT}" get nodes -o name +# Multi-cluster only: +kubectl --context "${COMPUTE_CONTEXT}" get nodes -o name +``` + +### Environment variables + +Set these once. In single-cluster, `COMPUTE_CONTEXT` equals +`CONTROL_PLANE_CONTEXT`. + +```bash +# Cluster targeting +export CONTROL_PLANE_CONTEXT="" +export COMPUTE_CONTEXT="" # = control plane in single-cluster +export CLUSTER_NAME="" +export CLUSTER_REGION="" # e.g. us-east-1 + +# Bundle environment file name (you create environments/.yaml below) +export HELMFILE_ENV="eks" # single-cluster example +# export HELMFILE_ENV="eks-multi" # multi-cluster example + +# Repository path the bundles use for charts and images +export REPOSITORY="/" # or your mirror path + +# NGC credential used for chart/image pulls and the dockerconfig secret +export NGC_API_KEY="" + +# Path to the built nvcf-cli binary +export NVCF_CLI="/nvcf-cli" + +# Storage class for the control-plane bundle (provider specific) +export STORAGE_CLASS="gp3" +``` + +### Log in to the chart and image registry + +The bundles pull OCI charts through Helm, so host-side registry auth must exist +before any `helmfile sync`: + +```bash +printf '%s' "${NGC_API_KEY}" | helm registry login nvcr.io --username '$oauthtoken' --password-stdin +``` + +## Step 1: Install the Gateway and capture the load balancer address + +Install the Gateway on the control-plane cluster by following the +[Gateway quickstart](./gateway-routing.md#gateway-quickstart). It installs the +Gateway API CRDs, the Envoy Gateway controller, the `GatewayClass`, and the +`nvcf-gateway` Gateway, and exports `GATEWAY_ADDR`. Run it against +`${CONTROL_PLANE_CONTEXT}`. + + +This guide's NVCA path also routes NATS. Make sure the `nvcf-gateway` Gateway +includes a `nats` listener on port 4222 (in addition to the `http` and `tcp` +listeners from the quickstart), and enable `routes.nats.enabled` in the +environment file in Step 2. + + +After the quickstart, confirm the address is set: + +```bash +test -n "${GATEWAY_ADDR}" +echo "GATEWAY_ADDR=${GATEWAY_ADDR}" +``` + +The gRPC listener is on this same Gateway at port 10081, so the gRPC address is +`${GATEWAY_ADDR}:10081` (used in the nvcf-cli config below). + + +Why the NVCA Host headers matter: the NVCA agent dials the bare load balancer +URL (which resolves through DNS) and sends a per-service hostname as the HTTP +`Host` header (`sis.`, `reval.`, `nats.`) so the Gateway +HTTPRoutes match. These are set as `global.nvcaOperator.selfManaged.*Override` +in the compute-plane environment file in Step 5. This requires +`helm-nvca-operator` 1.12.0 or later. + + +## Step 2: Configure the control-plane environment and secrets files + +This step produces two files in the `nvcf-self-managed-stack` bundle: the +environment values file `environments/.yaml` (copied from `base.yaml`) and +the secrets file `secrets/-secrets.yaml` (copied from +`secrets.yaml.template`). Both are required before the install. + +From the `nvcf-self-managed-stack` directory, copy the base template. The file +name (``) must match `HELMFILE_ENV`. + +```bash +cd / +cp environments/base.yaml "environments/${HELMFILE_ENV}.yaml" +``` + +Edit `environments/${HELMFILE_ENV}.yaml`. Every field is explained inline below. +Lines marked `CHANGE` must be updated for your cluster; replace the `${...}` +values with the literals you exported earlier. + +```yaml +global: + domain: "${GATEWAY_ADDR}" # CHANGE: from "localhost". Builds HTTPRoute hostnames (api., etc.) + + helm: + sources: + registry: "nvcr.io" # OCI registry NVCF charts are pulled from. Change only if you mirror. + repository: "${REPOSITORY}" # CHANGE: from "YOUR_ORG/YOUR_TEAM". NGC org/team or mirror path. + + imagePullSecrets: + - name: nvcr-pull-secret # CHANGE: from []. Pull secret applied to all workloads (created below). + + image: + registry: nvcr.io # Container image registry. Change only if you mirror. + repository: "${REPOSITORY}" # CHANGE: from "YOUR_ORG/YOUR_TEAM". NGC org/team or mirror path. + + workerEndpoints: + nvcfServiceURL: "" # CHANGE (multi-cluster): "http://api.${GATEWAY_ADDR}". Worker env NVCF_FQDN. + nvcfGrpcServiceURL: "" # CHANGE (multi-cluster): "http://worker-api.${GATEWAY_ADDR}". Worker env NVCF_FQDN_GRPC. + nvcfNatsServiceURL: "" # CHANGE (multi-cluster): "nats://${GATEWAY_ADDR}:4222". Worker env NVCF_NATS_WORKER_URL. + essServiceURL: "" # Empty = in-cluster default. Workers use this to reach the Encrypted Secrets Service. + nvctServiceURL: "" # CHANGE (multi-cluster): "http://tasks.${GATEWAY_ADDR}". Worker env NVCT_FQDN. + nvctGrpcServiceURL: "" # CHANGE (multi-cluster): "http://worker-tasks.${GATEWAY_ADDR}". Worker env NVCT_FQDN_GRPC. + invocationServiceURL: "" # Empty = in-cluster default. Workers use this for the invocation stream address. + + nodeSelectors: + enabled: false # Pin system workloads to labeled node pools. Leave false unless nodes are labeled. + vault: + key: nvcf.nvidia.com/workload + value: vault # Node label for the vault pool (applied only when enabled: true). + cassandra: + key: nvcf.nvidia.com/workload + value: cassandra # Node label for the cassandra pool. + controlplane: + key: nvcf.nvidia.com/workload + value: control-plane # Node label for the control-plane pool. + + tolerations: + enabled: false # Tolerations for tainted node pools. Leave false unless nodes are tainted. + all: [] # Tolerations applied to all system workloads when enabled. + + storageClass: "${STORAGE_CLASS}" # CHANGE: from "". Dynamic-provisioning StorageClass (gp3 on EKS). + storageSize: "10Gi" # Per-PVC size. Default works; raise for larger control-plane data (Cassandra). + + observability: + tracing: + enabled: false # OpenTelemetry trace export. Set true plus a collector endpoint to enable. + collectorEndpoint: "" # OTLP collector endpoint when tracing is enabled. + collectorPort: 4317 # OTLP collector port. + collectorProtocol: http # OTLP protocol (http or grpc). + metrics: + enabled: false # Prometheus metrics. Set true if you run the Prometheus Operator. + +accounts: + limits: + maxFunctions: 10 # Max functions per account. + maxTasks: 10 # Max tasks per account. + maxTelemetries: 10 # Max telemetry endpoints per account. + maxRegistryCreds: 10 # Max registry credentials per account. + +nats: + enabled: true # Deploy the NATS messaging layer. Keep true; the control plane depends on it. + +cassandra: + enabled: true # Deploy Cassandra. Keep true. + resourcesPreset: "xlarge" # CPU/memory preset. Do not use small for cloud installs (OOM on first boot). + +certManager: + enabled: true # Install cert-manager for the self-managed PKI. Keep true. + +openbao: + enabled: true # Deploy OpenBao (Vault) for secrets. Keep true. + migrations: + issuerDiscovery: + enabled: true # CHANGE: from false. Discover the cluster OIDC issuer. Required on managed Kubernetes. + injector: + replicas: 2 # OpenBao injector replicas (HA). Set 1 on single-node / minimal pools. + +addons: + lls: + enabled: false # Low Latency Streaming (TURN) addon. Optional. + llm: + enabled: false # LLM gateway + request router (Stargate). Optional. + pki: + enabled: false # OpenBao-issued QUIC TLS for the request router. Optional. + allowedDomains: "" # Required only when llm.pki.enabled: comma-separated DNS suffixes. + dnsNames: [] # Required only when llm.pki.enabled: SANs on the issued certificate. + vanityGateway: + enabled: false # Vanity and OpenAI-compatible invocation routes. Optional. + replicaCount: 2 # Vanity gateway replicas (applied only when enabled). + +stateMetrics: + enabled: false # State-metrics exporter. Optional. + serviceMonitor: + enabled: false # ServiceMonitor for the exporter. Requires the Prometheus Operator. + +rateLimiter: + enabled: false # Invocation rate limiter. Optional. + replicaCount: 1 # Rate limiter replicas (applied only when enabled). + +ingress: + gatewayApi: + enabled: true # Enable Gateway API ingress. Keep true. + controllerNamespace: envoy-gateway-system # CHANGE: from "". Namespace of the gateway controller. + routes: + nvcfApi: + routeAnnotations: {} # Optional per-route annotations for the NVCF API route. + grpc: + enabled: false # CHANGE (multi-cluster): true. Creates a GRPCRoute for worker-facing API gRPC. + hostnames: + - "worker-api.${GATEWAY_ADDR}" # CHANGE (multi-cluster): externally resolvable hostname for the API gRPC route. + nvctApi: + routeAnnotations: {} # Optional annotations for the NVCT API route. + grpc: + enabled: false # CHANGE (multi-cluster): true. Creates a GRPCRoute for worker-facing NVCT gRPC. + hostnames: + - "worker-tasks.${GATEWAY_ADDR}" # CHANGE (multi-cluster): externally resolvable hostname for the NVCT gRPC route. + apiKeys: + routeAnnotations: {} # Optional annotations for the api-keys route. + invocation: + routeAnnotations: {} # Optional annotations for the invocation route. + llmInvocation: + routeAnnotations: {} # Optional annotations for the LLM invocation route. + vanityGateway: + hostnames: [] # Override vanity hostnames. Default is vanity.. + routeAnnotations: {} # Optional annotations for the vanity route. + grpc: + routeAnnotations: {} # Optional annotations for the gRPC route. + nats: + enabled: true # CHANGE: from false. Create the NATS route (the NVCA agent needs it). + routeAnnotations: {} # Optional annotations for the NATS route. + ess: + enabled: true # CHANGE: from false. Create the ESS route (the nvcf worker container needs it). + routeAnnotations: {} # Optional annotations for the ESS route. + gateways: + shared: + name: nvcf-gateway # CHANGE: from "". Gateway the HTTP routes attach to. + namespace: envoy-gateway # CHANGE: from "". Namespace of that Gateway. + grpc: + name: nvcf-gateway # CHANGE: from "". Gateway the gRPC route attaches to. + namespace: envoy-gateway # CHANGE: from "". + nats: + name: nvcf-gateway # CHANGE: from "". Gateway the NATS route attaches to (when routes.nats.enabled). + namespace: envoy-gateway # CHANGE: from "". + listenerName: nats # Gateway listener name for NATS. Keep as nats. +``` + + +Multi-cluster worker endpoints and GRPCRoutes + +In single-cluster deployments, leave `workerEndpoints` empty and `grpc.enabled` +false. Worker pods resolve control-plane services through in-cluster DNS. + +In multi-cluster deployments, worker pods run on a separate compute cluster and +cannot resolve in-cluster service names from the control-plane cluster. Set each +`workerEndpoints` field to an externally resolvable address (the Gateway load +balancer hostname with a per-service route hostname). Enable the `nvcfApi.grpc` +and `nvctApi.grpc` GRPCRoutes so workers can reach the API and NVCT gRPC +endpoints through the Gateway. + +The `selfManaged.*Override` values in the compute-plane environment file +configure the NVCA agent's own connections to the control plane. The +`workerEndpoints` values configure the URLs that the control plane advertises +into launched worker pods. Both layers are required for multi-cluster function +execution. + + +### Create the registry pull secret + +The control-plane charts reference an image pull secret named `nvcr-pull-secret` +in each namespace. Create it in every control-plane namespace: + +```bash +for ns in cassandra-system nats-system nvcf api-keys ess sis vault-system cert-manager; do + kubectl --context "${CONTROL_PLANE_CONTEXT}" create namespace "${ns}" \ + --dry-run=client -o yaml | kubectl --context "${CONTROL_PLANE_CONTEXT}" apply -f - + kubectl --context "${CONTROL_PLANE_CONTEXT}" create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io --docker-username='$oauthtoken' --docker-password="${NGC_API_KEY}" \ + -n "${ns}" --dry-run=client -o yaml | kubectl --context "${CONTROL_PLANE_CONTEXT}" apply -f - +done +``` + +### Create the bundle secrets file + +The control-plane bundle reads a secrets file for the OpenBao migration and API +account bootstrap. Create it from the template and set the base64 dockerconfig +credential: + +```bash +cp secrets/secrets.yaml.template "secrets/${HELMFILE_ENV}-secrets.yaml" + +DOCKER_CRED_B64=$(printf '%s' '$oauthtoken:'"${NGC_API_KEY}" | base64 | tr -d '\n') +# Replace every REPLACE_WITH_BASE64_DOCKER_CREDENTIAL in the secrets file with ${DOCKER_CRED_B64}. +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${DOCKER_CRED_B64}|g" \ + "secrets/${HELMFILE_ENV}-secrets.yaml" +rm "secrets/${HELMFILE_ENV}-secrets.yaml.bak" +``` + + +Do not commit the populated secrets file or the environment file. They contain +cluster-specific and credential material. + + +## Step 3: Install the control plane + +Run from the `nvcf-self-managed-stack` bundle directory: + +```bash +cd / +kubectl config use-context "${CONTROL_PLANE_CONTEXT}" +make install HELMFILE_ENV="${HELMFILE_ENV}" +``` + +Verify the releases are deployed: + +```bash +helm list --all-namespaces --kube-context "${CONTROL_PLANE_CONTEXT}" +``` + +Expected releases include `nats`, `cert-manager`, `openbao-server`, `cassandra`, +`api-keys`, `sis`, `api`, `nvct-api`, `invocation-service`, `grpc-proxy`, +`ess-api`, `notary-service`, `admin-issuer-proxy`, `reval`, +`nats-auth-callout-service`, and `ingress`. + +Confirm `global.domain` propagated into the API HTTPRoute hostname: + +```bash +kubectl --context "${CONTROL_PLANE_CONTEXT}" get httproute nvcf-api -n envoy-gateway \ + -o jsonpath='{.spec.hostnames[0]}' +# Expected: api.${GATEWAY_ADDR} +``` + +## Step 4: Author the nvcf-cli config + +Create `nvcf-cli.yaml` pointing at the load balancer address. The static fields +are the same across self-hosted installs; only the URL and Host fields are +derived from `GATEWAY_ADDR`. + +```bash +cat > nvcf-cli.yaml < +``` + +Create the compute-plane environment file for either topology. The compute-plane +Makefile reads +`deploy/stacks/nvcf-compute-plane/environments/${HELMFILE_ENV}.yaml`, and the +`selfManaged` values tell the NVCA agent how to reach the control plane. The +sections below use different cluster targets and endpoint values. + +```bash +cp deploy/stacks/nvcf-compute-plane/environments/base.yaml \ + "deploy/stacks/nvcf-compute-plane/environments/${HELMFILE_ENV}.yaml" +``` + +Set these keys in +`deploy/stacks/nvcf-compute-plane/environments/${HELMFILE_ENV}.yaml`: + +```yaml +global: + helm: + sources: + repository: "${REPOSITORY}" + image: + repository: "${REPOSITORY}" + imagePullSecrets: + - name: nvcr-pull-secret + nvcaOperator: + selfManaged: + icmsServiceURL: "http://${GATEWAY_ADDR}" + icmsServiceHostHeaderOverride: "sis.${GATEWAY_ADDR}" + revalServiceURL: "http://${GATEWAY_ADDR}" + revalServiceHostHeaderOverride: "reval.${GATEWAY_ADDR}" + natsURL: "nats://${GATEWAY_ADDR}:4222" + natsHostOverride: "nats.${GATEWAY_ADDR}" +``` + +Then follow the matching subsection below. + +### Single-cluster + +The GPU cluster is the same cluster as the control plane, so registration uses +the current context. Create the pull secret in `nvca-operator`; the operator +propagates it to the managed namespaces after installation: + +```bash +kubectl --context "${CONTROL_PLANE_CONTEXT}" create namespace nvca-operator \ + --dry-run=client -o yaml | kubectl --context "${CONTROL_PLANE_CONTEXT}" apply -f - +kubectl --context "${CONTROL_PLANE_CONTEXT}" create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io --docker-username='$oauthtoken' --docker-password="${NGC_API_KEY}" \ + -n nvca-operator --dry-run=client -o yaml | kubectl --context "${CONTROL_PLANE_CONTEXT}" apply -f - +``` + +Register, then install: + +```bash +kubectl config use-context "${CONTROL_PLANE_CONTEXT}" + +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME="${CLUSTER_NAME}" \ + NCA_ID=nvcf-default \ + CLUSTER_REGION="${CLUSTER_REGION}" \ + ICMS_URL="http://${GATEWAY_ADDR}" \ + NVCF_CLI="${NVCF_CLI}" \ + NVCF_CLI_CONFIG="${NVCF_CLI_CONFIG}" + +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME="${CLUSTER_NAME}" \ + HELMFILE_ENV="${HELMFILE_ENV}" \ + NVCF_CLI="${NVCF_CLI}" \ + NVCF_CLI_CONFIG="${NVCF_CLI_CONFIG}" +``` + +### Multi-cluster + +The NVCA operator installs on a separate compute cluster. Two extra concerns: + +1. Registration must discover the OIDC issuer and JWKS from the compute cluster, + not the control-plane cluster. Switch the context to the compute cluster and + pass a compute-scoped kubeconfig to `register-cluster`. +2. The compute-plane environment file must carry the control-plane service URLs + and Host headers so the agent on the compute cluster can reach the control + plane through the Gateway. + + +Register with the compute cluster context active. The registration step probes +the current context for its JWKS. If the control-plane context is active, the +control-plane JWKS is recorded for the compute cluster, and the compute agent +then fails authentication at runtime with `Signed JWT rejected: no matching +key(s) found`. + + +You already created the compute-plane environment file with the `selfManaged` +values above. Create the pull secret in `nvca-operator` on the compute cluster; +the operator propagates it to the managed namespaces after installation. Also +create a compute-scoped kubeconfig: + +```bash +kubectl --context "${COMPUTE_CONTEXT}" create namespace nvca-operator \ + --dry-run=client -o yaml | kubectl --context "${COMPUTE_CONTEXT}" apply -f - +kubectl --context "${COMPUTE_CONTEXT}" create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io --docker-username='$oauthtoken' --docker-password="${NGC_API_KEY}" \ + -n nvca-operator --dry-run=client -o yaml | kubectl --context "${COMPUTE_CONTEXT}" apply -f - + +kubectl --context "${COMPUTE_CONTEXT}" config view --raw --minify --flatten > compute-kubeconfig.yaml +export COMPUTE_KUBECONFIG="$(pwd)/compute-kubeconfig.yaml" +``` + +Register with the compute context active, then install onto the compute cluster: + +```bash +kubectl config use-context "${COMPUTE_CONTEXT}" + +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME="${CLUSTER_NAME}" \ + NCA_ID=nvcf-default \ + CLUSTER_REGION="${CLUSTER_REGION}" \ + ICMS_URL="http://${GATEWAY_ADDR}" \ + KUBECONFIG_FILE="${COMPUTE_KUBECONFIG}" \ + NVCF_CLI="${NVCF_CLI}" \ + NVCF_CLI_CONFIG="${NVCF_CLI_CONFIG}" + +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME="${CLUSTER_NAME}" \ + HELMFILE_ENV="${HELMFILE_ENV}" \ + KUBECONFIG_FILE="${COMPUTE_KUBECONFIG}" \ + NVCF_CLI="${NVCF_CLI}" \ + NVCF_CLI_CONFIG="${NVCF_CLI_CONFIG}" +``` + + +`make register-cluster` runs `nvcf-cli init` (mints the admin token) and then +`cluster register`, and writes +`registration/${CLUSTER_NAME}-register-values.yaml`. `make install` consumes +that file. If you skip `register-cluster`, `make install` fails with a +"Registration values not found" error. + + +## Step 6: Verify the agent is healthy + +Confirm the NVCA operator is deployed and the backend reports the agent healthy. +Use the compute context (equal to the control-plane context in single-cluster): + +```bash +helm list -n nvca-operator --kube-context "${COMPUTE_CONTEXT}" +# Expected: nvca-operator deployed + +kubectl rollout status deployment/nvca-operator -n nvca-operator \ + --context "${COMPUTE_CONTEXT}" --timeout=10m + +kubectl wait nvcfbackend "${CLUSTER_NAME}" -n nvca-operator \ + --context "${COMPUTE_CONTEXT}" \ + --for=jsonpath='{.status.agentStatus}'=healthy --timeout=10m +``` + +Confirm that the operator propagated the pull secret to `nvca-system`: + +```bash +kubectl --context "${COMPUTE_CONTEXT}" get secret nvcr-pull-secret -n nvca-system +``` + +The agent reaching `healthy` confirms registration and the Host-header wiring +are correct. + +## Single-cluster vs multi-cluster + +| Concern | Single-cluster | Multi-cluster | +| --- | --- | --- | +| Clusters | One cluster for everything | Control plane on one cluster, NVCA on a separate GPU cluster | +| Gateway | On the only cluster | On the control-plane cluster only | +| Worker endpoints | Empty (in-cluster DNS) | Set to externally resolvable Gateway hostnames | +| Worker GRPCRoutes | Disabled | Enabled (`nvcfApi.grpc`, `nvctApi.grpc`) | +| Control-plane env file | Sets the three NVCA Host-header overrides | Same, plus `workerEndpoints` and worker GRPCRoutes | +| Compute-plane env file | Required: uses endpoints reachable from the shared cluster | Required: uses endpoints reachable from the separate compute cluster | +| Context before `register-cluster` | Control-plane context | Compute context (so JWKS is discovered from the compute cluster) | +| `KUBECONFIG_FILE` | Not used | Compute-scoped kubeconfig passed to `register-cluster` and `install` | +| Verify context | Control-plane context | Compute context | + +## Troubleshooting + +- Gateway never becomes `Programmed`: check the load balancer annotations match + your provider and that the controller pod in `envoy-gateway-system` is running. +- `make install` reports "Registration values not found": run + `make register-cluster` first, in the same directory, with the same + `CLUSTER_NAME`. +- Compute agent fails with `no matching key(s) found`: you registered with the + wrong context active. Switch to the compute context and re-run + `make register-cluster`. + +See [Troubleshooting](./troubleshooting.md) for more. diff --git a/docs/v0.6.1-rc/example-dashboards.md b/docs/v0.6.1-rc/example-dashboards.md new file mode 100644 index 000000000..0deb7dd9f --- /dev/null +++ b/docs/v0.6.1-rc/example-dashboards.md @@ -0,0 +1,356 @@ +# Example Dashboards Deployment + + +**These Helm charts are provided as examples only and are not intended for production use.** + +The `nvcf-observability-reference-stack` and `nvcf-example-dashboards` Helm charts are +designed to help users understand useful metrics, logs, and traces, and to serve as +inspiration for creating custom dashboards tailored to their observability needs. + +**Important limitations:** + +- No security hardening +- No SSL/TLS encryption +- No authentication or authorization +- No support for production workloads +- Not supported by NVIDIA for uses beyond example and reference purposes + +For production deployments, users should integrate with their own observability infrastructure +following the guidance in [self-hosted-observability](./observability.md). + + + +## Overview + +This guide provides step-by-step instructions for deploying the NVCF observability reference +stack and example dashboards in a local or development environment. These example components +demonstrate how to collect and visualize metrics, logs, and traces from a self-hosted NVCF deployment. + +The example stack includes: + +- **nvcf-observability-reference-stack**: A reference implementation of an observability backend + (Prometheus, Grafana, Loki, Tempo, and OpenTelemetry Collector) +- **nvcf-example-dashboards**: Pre-configured Grafana dashboards showing key NVCF control-plane metrics + + +The observability reference stack supports a single-cluster deployment or the +control-plane cluster in a split-topology deployment. A separate observability +reference stack for an individual GPU cluster is not currently supported. + + + +## Prerequisites + +Before deploying the example dashboards, you need: + +1. A Kubernetes cluster +2. Self-hosted NVCF control-plane deployed and running +3. `helm` CLI installed +4. `kubectl` configured to access your cluster + + +To populate Prometheus-backed dashboards, deploy the control-plane with +`global.observability.metrics.enabled` set to `true`. + +To view traces in the example stack, the control-plane must be deployed +with tracing enabled and configured to send OTLP traces to the +observability collector. See [Tracing Configuration](./observability.md) +for the required Helm overrides under `global.observability.tracing` (e.g., +`enabled`, `collectorEndpoint`, `collectorPort`, +`collectorProtocol`). + + + +### Enabling metrics and tracing after initial deployment + +If observability was not configured during the initial self-hosted NVCF cluster +deployment, you can enable metrics later so that control-plane services expose +Prometheus metrics. Enable tracing if you also want the control-plane to send +OTLP traces to the deployed observability collector. Use the following steps. + +1. Edit the environment file (`environments/.yaml`) to + enable metrics and tracing. Set `collectorEndpoint` to the deployed + collector's service address. For the example observability reference stack, + the collector runs in the `observability` namespace. Example: + + ```yaml + global: + observability: + metrics: + enabled: true + tracing: + enabled: true + collectorEndpoint: "otel-collector-gateway-collector.observability.svc.cluster.local" + collectorPort: 4317 + collectorProtocol: http + ``` + +2. Apply the configuration changes from your control-plane deployment directory: + + ```bash + HELMFILE_ENV= helmfile sync + ``` + + Replace `` with your environment name (e.g., `eks-example`). + +## Deployment Steps + +### Step 1: Install the Observability Reference Stack + +Once your self-managed NVCF control-plane is up and running, install the observability +reference stack: + +```bash +# Add and update the public NVCF Helm repository +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf --force-update +helm repo update + +# Check NGC for the latest version of this Helm chart: +# https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nvcf/helm-charts/nvcf-observability-reference-stack + +helm upgrade \ + --install observability \ + nvcf/nvcf-observability-reference-stack \ + --version 1.10.0 \ + --namespace observability \ + --create-namespace +``` + +For a split-topology deployment, run the command against the control-plane +cluster and disable the NVCA ServiceMonitor. NVCA runs on the GPU cluster, so +the control-plane observability stack does not have an NVCA scrape target. + +```bash +helm upgrade \ + --install observability \ + nvcf/nvcf-observability-reference-stack \ + --version 1.10.0 \ + --namespace observability \ + --create-namespace \ + --set nvcfServiceMonitors.nvcaEnabled=false +``` + +Do not install this reference stack on the GPU cluster. GPU cluster-specific +observability stack support is not currently available. + +This will deploy: + +- Prometheus for metrics collection +- Grafana for visualization +- Loki for log aggregation +- Tempo for distributed tracing +- OpenTelemetry Collector for telemetry processing +- Fluent Bit for log collection + +The OTel Collector and Fluent Bit cluster-scoped config are disabled in this +initial install; they are enabled in Step 2. + + +The Grafana installation in the observability reference stack includes the +`ae3e-plotly-panel` plugin for use with the example dashboards. + + + +**Verify the deployment:** + +```bash +# Check that all pods are running +kubectl get pods -n observability + +# Wait for all pods to be Ready +kubectl wait --for=condition=ready pod --all -n observability --timeout=300s +``` + +### Step 2: Enable the OTel Collector and Fluent Bit Cluster Config + +The initial install does not enable the OTel Collector or the Fluent Bit +cluster-scoped config by default. A second Helm upgrade is required to set +`otel-collector.enabled=true` and `fluentBitClusterConfig.enabled=true` so +that the `observability-gateway-collector` service (and related gateway +services) are deployed and Fluent Bit cluster resources (ClusterFilter, +ClusterOutput, ClusterFluentBitConfig) are managed by Helm. This has to be +split into separate installs due to a chicken and egg problem with the CRDs. +These services are needed for the control-plane to send OTLP traces to Tempo. + +Run the following upgrade (use the same chart version and namespace as in Step 1): + +For split-topology deployments, `--reuse-values` preserves +`nvcfServiceMonitors.nvcaEnabled=false` from the initial installation. + +```bash +helm upgrade observability \ + nvcf/nvcf-observability-reference-stack \ + --version 1.10.0 \ + --namespace observability \ + --reuse-values \ + --set fluentBitClusterConfig.enabled=true \ + --set otel-collector.enabled=true \ + --wait \ + --timeout 5m +``` + +**Verify the OTel Collector is running:** + +```bash +kubectl get pods -n observability | grep observability-gateway +kubectl get svc -n observability | grep observability-gateway +``` + +You should see the `observability-gateway-collector` pod and the +`observability-gateway-collector` service (and related gateway services). + +### Step 3: Install the Example Dashboards + +Once the observability reference stack is deployed and the OTel Collector is +enabled, install the example dashboards: + +```bash +# Check NGC for the latest version of this Helm chart: +# https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nvcf/helm-charts/nvcf-example-dashboards + +helm upgrade \ + --install nvcf-example-dashboards \ + nvcf/nvcf-example-dashboards \ + --version 1.6.0 \ + --namespace observability \ + --create-namespace +``` + +This will configure Grafana with pre-built dashboards for: + +- NVCF API +- Invocation Service +- SPOT Instance Service (SIS) +- Encrypted Secrets Service (ESS) +- Cassandra +- Vault +- Worker Pods (Utils, Init, and Inference containers) + +**Access Grafana:** + +```bash +# Port-forward to access Grafana UI +kubectl port-forward -n observability \ + svc/$(kubectl get svc -n observability -l app.kubernetes.io/name=grafana -o jsonpath='{.items[0].metadata.name}') \ + 3000:80 +``` + +Then open your browser to `http://localhost:3000` and log in to view the dashboards. + +### Step 4: Generate Dashboard Data + +To populate the dashboards with meaningful data, you need to deploy and invoke functions. +Deploy and invoke functions using your own commands or tools. The example dashboards will +automatically populate as your NVCF control-plane handles function requests. + +## Cleanup and Uninstallation + +When you're finished testing or want to remove the example observability stack, follow these +steps: + +### Step 1: Delete Custom Resources + +First, delete any custom resources created by the observability stack: + +```bash +# Delete FluentBit custom resources (namespace-scoped) +kubectl delete fluentbits.fluentbit.fluent.io --all -A + +# Delete OpenTelemetry Collector custom resources +kubectl delete opentelemetrycollectors.opentelemetry.io --all -A +``` + +If you enabled `fluentBitClusterConfig.enabled=true` in Step 2 of deployment, +the Fluent Bit cluster-scoped resources (ClusterFilter, ClusterOutput, +ClusterFluentBitConfig) are managed by Helm and will be removed when you +uninstall the Helm release in the next step. + +### Step 2: Uninstall Helm Releases + +Uninstall both Helm releases: + +```bash +# Uninstall example dashboards +helm uninstall nvcf-example-dashboards -n observability + +# Uninstall observability reference stack +helm uninstall observability -n observability +``` + +### Step 3: Delete the Namespace + +Finally, delete the observability namespace: + +```bash +# Delete the namespace (this will remove any remaining resources) +kubectl delete namespace observability +``` + + +If you deployed NVCF to a namespace other than `observability`, make sure to only delete +the observability namespace, not your NVCF control-plane namespace. + + + +## Troubleshooting + +**Pods not starting:** + +Check pod status and logs: + +```bash +kubectl get pods -n observability +kubectl describe pod -n observability +kubectl logs -n observability +``` + +**Dashboards not showing data:** + +1. Verify Prometheus is scraping metrics: + + ```bash + # Port-forward to Prometheus + kubectl port-forward -n observability svc/prometheus 9090:9090 + + # Check targets at http://localhost:9090/targets + ``` + +2. Verify NVCF services are exposing metrics: + + ```bash + # Port-forward to an NVCF service + kubectl port-forward -n nvcf svc/nvcf-api 8080:8080 + + # Curl the metrics endpoint + curl http://localhost:8080/metrics + ``` + +3. Check that ServiceMonitors are created: + + ```bash + kubectl get servicemonitor -n nvcf + ``` + +**Grafana login issues:** + +The default credentials are typically `admin/admin` or may be configured via Helm values. +Check the Helm chart documentation or values for the correct credentials. + +## Next Steps + +After exploring the example dashboards: + +1. Review the metrics, logs, and traces being collected +2. Identify which metrics are most relevant to your use case +3. Design and implement your own production-ready observability solution +4. Integrate with your existing enterprise observability platforms +5. Configure alerting based on your operational requirements + +For production deployments, see [self-hosted-observability](./observability.md) for guidance on integrating +with your own observability infrastructure. + +## Related Documentation + +- [self-hosted-observability](./observability.md): Production observability configuration +- [nvcf-observability-reference-stack on NGC](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nvcf/helm-charts/nvcf-observability-reference-stack) +- [nvcf-example-dashboards on NGC](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nvcf/helm-charts/nvcf-example-dashboards) diff --git a/docs/v0.6.1-rc/fake-gpu-operator.md b/docs/v0.6.1-rc/fake-gpu-operator.md new file mode 100644 index 000000000..815f4e350 --- /dev/null +++ b/docs/v0.6.1-rc/fake-gpu-operator.md @@ -0,0 +1,222 @@ +# Fake GPU Operator (Development / Testing) + +For development, staging, load testing, or CI environments that lack physical NVIDIA GPUs, +you can install a fake GPU operator to simulate GPU resources on cluster nodes. This allows +the NVCA agent to discover GPUs and manage function deployments without actual GPU hardware. + + +The fake GPU operator is for **non-production use only**. For production deployments with +real GPUs, install the +[NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/). + + + +## Prerequisites + +- A running Kubernetes cluster with `kubectl` access +- `helm` >= 3.12 + +### Install KWOK + +The fake GPU operator depends on [KWOK (Kubernetes Without Kubelet)](https://kwok.sigs.k8s.io/) +to manage simulated GPU device plugins on nodes. Install KWOK before the fake GPU operator: + +```bash +kubectl apply -f https://github.com/kubernetes-sigs/kwok/releases/download/v0.7.0/kwok.yaml +``` + +Verify the KWOK controller is running: + +```bash +kubectl get pods -n kube-system -l app=kwok-controller +# Expected: kwok-controller-... 1/1 Running +``` + + +The KWOK install may produce a FlowSchema error +(`creation or update of FlowSchema object ... is not allowed`). +This is non-critical and can be safely ignored. + + + +## Installation + +Add the RunAI helm repository and install the fake GPU operator: + +```bash +helm repo add fake-gpu-operator \ + https://runai.jfrog.io/artifactory/api/helm/fake-gpu-operator-charts-prod \ + --force-update + +helm upgrade -i gpu-operator fake-gpu-operator/fake-gpu-operator \ + -n gpu-operator --create-namespace \ + --set 'topology.nodePools.default.gpuCount=8' \ + --set 'topology.nodePools.default.gpuProduct=NVIDIA-H100-80GB-HBM3' +``` + +This configures one node pool named `default` with 8 simulated H100 GPUs per node. + + +**topology.nodePools must be a map, not an array.** + +Using array index syntax (`--set 'topology.nodePools[0].gpuCount=8'`) will create a +YAML array instead of a map and cause the status-updater to fail with: + +``` +yaml: unmarshal errors: cannot unmarshal !!seq into map[string]topology.NodePoolTopology +``` + +Always use named keys: `topology.nodePools.default.gpuCount=8`. + + + +## Node Labeling + +The fake GPU operator watches for nodes with the label +`run.ai/simulated-gpu-node-pool=` and patches their status to advertise +fake `nvidia.com/gpu` extended resources. You must label the nodes that should receive +simulated GPUs: + +```bash +kubectl label node run.ai/simulated-gpu-node-pool=default +``` + +The pool name (`default`) must match a key in `topology.nodePools` from the helm install. + +### GPU Metadata Labels (Optional) + +The NVCA agent uses several GPU metadata labels for dynamic discovery. On real GPU nodes +these are set by the NVIDIA GPU Operator. To suppress warnings from NVCA on fake GPU nodes, +add the following labels: + +```bash +kubectl label node \ + nvidia.com/gpu.family=hopper \ + nvidia.com/gpu.machine=NVIDIA-DGX-H100 \ + nvidia.com/cuda.driver.major=535 \ + --overwrite +``` + +Adjust the values to match the GPU product you configured (e.g., `ampere` for A100, +`ada` for L40S). + +## RuntimeClass Ownership Conflict + +The fake GPU operator chart creates `RuntimeClass/nvidia` and several namespaced resources in `gpu-operator`. Helm fails with `invalid ownership metadata` if one of those objects already exists and is not owned by release `gpu-operator` in namespace `gpu-operator`. + +For local k3d development, the recovery workflow is to rerun `make build-and-deploy-cluster` in `tools/ncp-local-cluster/`, which removes known stale fake GPU operator resources without deleting the cluster. For manual chart debugging, inspect ownership before deleting anything. If another Helm release owns the resource, remove that release instead of deleting the resource directly. + +## Verification + +Check that the fake GPU operator pods are running: + +```bash +kubectl get pods -n gpu-operator +# Expected: 3 pods Running (topology-server, status-updater, kwok-gpu-device-plugin) +``` + +Confirm that labeled nodes now advertise GPU resources: + +```bash +kubectl get nodes -o custom-columns="NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu" +# Labeled nodes should show the configured GPU count (e.g., 8) +``` + +If GPUs do not appear, verify the node has the `run.ai/simulated-gpu-node-pool=default` +label and that the status-updater pod is not in an error state. + +## Integration with NVCF + +### Recommended Installation Order + +For the smoothest experience, install the fake GPU operator **before** running +`helmfile sync`. This way the NVCA agent discovers GPUs on its first boot and no +re-registration is needed. + +The recommended sequence is: + +1. Install KWOK +2. Install fake-gpu-operator and label target nodes +3. Verify `nvidia.com/gpu` appears in node allocatable resources +4. Proceed with the [control-plane installation](../user/helmfile-installation.md) + +### If Installed After the Control Plane + +If you add the fake GPU operator to a cluster that already has NVCF deployed, the NVCA +agent will be crash-looping because it cannot find GPUs. After installing the fake GPU +operator and verifying GPUs appear on nodes, re-register the cluster and restart the +operator: + +```bash +# Re-run the cluster bootstrap +kubectl exec -n nvca-operator deploy/nvca-operator -c nvca-operator -- \ + /usr/bin/nvca-self-managed bootstrap --system-namespace nvca-operator + +# Restart the operator (it caches cluster IDs at startup) +kubectl rollout restart deployment nvca-operator -n nvca-operator +kubectl rollout status deployment nvca-operator -n nvca-operator --timeout=120s +``` + +The operator restart will re-run the bootstrap init container, recreate the NVCFBackend +resource, and spawn a fresh NVCA agent pod that discovers the simulated GPUs. + +For details on the bootstrap process, see [Self-Managed Clusters](../user/cluster-management/self-managed.md) (Manual Cluster +Registration). + +## Customization + +### GPU Count and Product + +Adjust the GPU count, product name, and memory per node pool: + +```bash +helm upgrade gpu-operator fake-gpu-operator/fake-gpu-operator \ + -n gpu-operator \ + --set 'topology.nodePools.default.gpuCount=4' \ + --set 'topology.nodePools.default.gpuProduct=NVIDIA-A100-SXM4-80GB' \ + --set 'topology.nodePools.default.gpuMemory=81920' +``` + +### Multiple Node Pools + +Define multiple pools with different GPU configurations by using different map keys: + +```bash +helm upgrade gpu-operator fake-gpu-operator/fake-gpu-operator \ + -n gpu-operator \ + --set 'topology.nodePools.h100-pool.gpuCount=8' \ + --set 'topology.nodePools.h100-pool.gpuProduct=NVIDIA-H100-80GB-HBM3' \ + --set 'topology.nodePools.a100-pool.gpuCount=4' \ + --set 'topology.nodePools.a100-pool.gpuProduct=NVIDIA-A100-SXM4-80GB' +``` + +Then label nodes with the corresponding pool name: + +```bash +kubectl label node run.ai/simulated-gpu-node-pool=h100-pool +kubectl label node run.ai/simulated-gpu-node-pool=a100-pool +``` + +## Teardown + +To remove the fake GPU operator and all simulated GPU resources: + +```bash +# Remove the fake GPU operator +helm uninstall gpu-operator -n gpu-operator +kubectl delete namespace gpu-operator --ignore-not-found + +# Remove KWOK +kubectl delete -f https://github.com/kubernetes-sigs/kwok/releases/download/v0.7.0/kwok.yaml + +# Remove the node labels (for each labeled node) +kubectl label node run.ai/simulated-gpu-node-pool- +kubectl label node nvidia.com/gpu.product- +kubectl label node nvidia.com/gpu.family- +kubectl label node nvidia.com/gpu.machine- +kubectl label node nvidia.com/cuda.driver.major- +``` + +After removing the fake GPU operator, the NVCA agent will lose GPU visibility and begin +crash-looping. Either install a real GPU Operator with physical GPUs or uninstall the +NVCA operator. diff --git a/docs/v0.6.1-rc/function-creation.md b/docs/v0.6.1-rc/function-creation.md new file mode 100644 index 000000000..b1091e75f --- /dev/null +++ b/docs/v0.6.1-rc/function-creation.md @@ -0,0 +1,87 @@ +# Function Creation + +This page describes how to create functions within Cloud Functions. + +Functions can be created in one of two ways: + +1. Custom Container + - Enables any container-based workload as long as the container exposes an inference endpoint and a health check. + - Option to leverage any server, ex. [PyTriton](https://triton-inference-server.github.io/pytriton/), [FastAPI](https://fastapi.tiangolo.com/), [Triton](https://developer.nvidia.com/triton-inference-server). + - See [Container-Based Function Creation](./container-functions.md). + +2. Helm Chart + - Enables orchestration across multiple containers. For complex use cases where a single container isn't flexible enough. + - Requires one "mini-service" container defined as the inference entry point for the function. + - Does not support partial response reporting, gRPC or HTTP streaming-based invocation. + - See [Helm-Based Function Creation](./helm-functions.md). + +Additionally, Cloud Functions supports [Low Latency Streaming (LLS) functions](./streaming-functions.md) for video, audio, and data streaming via WebRTC. + +For LLM functions, see [LLM Gateway](./llm-gateway.md#function-configuration) for OpenAI-compatible model route configuration. + +## Invocation Types + +- [Generic HTTP function invocation](./generic-http-function-invocation.md): + Invoke HTTP functions through the standard invocation route. +- [gRPC function invocation](./grpc-function-invocation.md): Invoke gRPC + functions through the Gateway TCP listener. +- [LLM invocation](./llm-gateway.md#endpoint-behavior): Invoke + OpenAI-compatible LLM functions through `llm.invocation.`. +- [LLS/WebRTC client connection](./streaming-functions.md#connecting-to-a-streaming-function-with-a-client): + Connect browser or proxy clients to streaming functions. + +## Best Practices + +### Container Versioning + +- Ensure that any resources that you tag for deployment into production environments are not simply using "latest" and are following a standard version control convention. + - During autoscaling, a function scaling any additional instances will pull the same specificed container image and version. If version is set to "latest", and the "latest" container image is updated between instance scaling, this can lead to undefined behavior. + +- Function versions created are immutable, this means that the container image and version cannot be updated for a function without creating a new version of the function. + +### Security + +- Do not run containers as root user: Running containers as root is not supported in Cloud Functions. Always specify a non-root user in your Dockerfile using the `USER` instruction. +- Use Kubernetes Secrets: For sensitive information like API keys, credentials, or tokens, use Kubernetes Secrets instead of environment variables. This provides better security and follows Kubernetes best practices for secret management. + +#### Available Container Variables + +The following is a reference of available variables via the headers of the invocation message (auto-populated by Cloud Functions), accessible within the container. + +For examples of how to extract and use some of these variables, see [NVCF Container Helper Functions](https://github.com/NVIDIA/nvcf/tree/main/src/libraries/python/nv-cloud-function-helpers). + +| Name | Description | +| ---------------------------- | ------------------------------------------------------- | +| NVCF-REQID | Request ID for this request. | +| NVCF-SUB | Message subject. | +| NVCF-NCAID | Function's organization's NCA ID. | +| NVCF-FUNCTION-NAME | Function name. | +| NVCF-FUNCTION-ID | Function ID. | +| NVCF-FUNCTION-VERSION-ID | Function version ID. | +| NVCF-LARGE-OUTPUT-DIR | Large output directory path. | +| NVCF-MAX-RESPONSE-SIZE-BYTES | Max response size in bytes for the function. | +| NVCF-NSPECTID | NVIDIA reserved variable. | +| NVCF-BACKEND | Backend or "Cluster Group" the function is deployed on. | +| NVCF-INSTANCETYPE | Instance type the function is deployed on. | +| NVCF-REGION | Region or zone the function is deployed in. | +| NVCF-ENV | Spot environment if deployed on spot instances. | + +#### Environment Variables + +The following environment variables are automatically injected into your function containers when they are deployed and can be accessed using standard environment variable access methods in your application code: + +| Name | Description | +| ------------------------ | ------------------------------------------------------- | +| NVCF_BACKEND | Backend or "Cluster Group" the function is deployed on. | +| NVCF_ENV | Spot environment if deployed on spot instances. | +| NVCF_FUNCTION_ID | Function ID. | +| NVCF_FUNCTION_NAME | Function name. | +| NVCF_FUNCTION_VERSION_ID | Function version ID. | +| NVCF_INSTANCETYPE | Instance type the function is deployed on. | +| NVCF_NCA_ID | Function's organization's NCA ID. | +| NVCF_REGION | Region or zone the function is deployed in. | + + +All environment variables with the `NVCF_*` prefix are reserved and should not be overridden in your application code or function configuration. + + diff --git a/docs/v0.6.1-rc/gateway-routing.md b/docs/v0.6.1-rc/gateway-routing.md new file mode 100644 index 000000000..6e57f6d23 --- /dev/null +++ b/docs/v0.6.1-rc/gateway-routing.md @@ -0,0 +1,809 @@ +# Gateway Routing and DNS + +This guide explains how NVCF self-hosted deployments route traffic through the Kubernetes Gateway API, and how to configure DNS and HTTPS for production environments. + +## Overview + +The NVCF self-hosted deployment uses the [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) for ingress traffic management. This provides: + +- Hostname-based routing for HTTP services (API Keys, NVCF API, Invocation) +- Port-based routing for gRPC services +- Single load balancer for all NVCF services +- Cross-namespace routing via ReferenceGrants + +The Gateway API is a Kubernetes standard with multiple implementations. The +examples on this page use Envoy Gateway, but you can use any Gateway +API-compliant controller that supports the requirements below. + +## Gateway quickstart + +Use this procedure before any remote Helmfile deployment that needs NVCF +services reachable through Gateway API. + +Skip this section for local k3d flows that already create the local Gateway and +route hostnames. + +### Install Gateway API CRDs + +Install the Kubernetes Gateway API CRDs v1.2.0: + +```bash +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml +``` + +If you replace v1.2.0 with another version, verify compatibility with the +GatewayClass and Gateway resources that you create. + +### Create namespaces and labels + +Create the namespaces used by the Gateway and NVCF services, then label the +route-owning namespaces so the Gateway can accept cross-namespace routes: + +```bash +for namespace in envoy-gateway-system envoy-gateway api-keys ess sis nvcf; do + kubectl create namespace "$namespace" --dry-run=client -o yaml | kubectl apply -f - +done + +for namespace in envoy-gateway api-keys ess sis nvcf; do + kubectl label namespace "$namespace" nvcf/platform=true --overwrite +done +``` + +### Install Envoy Gateway + +Install Envoy Gateway as the Gateway API controller: + +```bash +helm upgrade --install eg oci://docker.io/envoyproxy/gateway-helm \ + --version v1.1.3 \ + -n envoy-gateway-system +``` + +Verify the controller pod is running: + +```bash +kubectl get pods -n envoy-gateway-system +``` + +### Create EnvoyProxy and GatewayClass + +Create an `EnvoyProxy` resource before you create the `GatewayClass`. The +`envoyDeployment.replicas` setting controls the Envoy proxy data-plane pods that +handle ingress traffic. It does not control Envoy Gateway controller pods. + +Create the `GatewayClass` with a `parametersRef` that points to the +`EnvoyProxy` resource: + +```bash +kubectl apply -f - < +The `annotations` section is cloud-provider specific and controls how the +external load balancer is provisioned. Keep the AWS annotations for EKS. Replace +them with equivalent annotations for GKE, AKS, or on-prem load balancer +integrations. + + + +```bash +kubectl apply -f - < +The `grpcWorker` route is beta in 0.6.0. Enable it only when the control-plane +grpc-proxy runs one replica and the grpc-proxy HPA is disabled. Multiple +grpc-proxy replicas are not supported by this shared TCPRoute. + + + +### Capture Gateway values + +Wait for the Gateway to be programmed and export the values used by the install +guides: + +```bash +export HTTP_GATEWAY_NAMESPACE="envoy-gateway" +export HTTP_GATEWAY_NAME="nvcf-gateway" +export GRPC_GATEWAY_NAMESPACE="envoy-gateway" +export GRPC_GATEWAY_NAME="nvcf-gateway" + +kubectl -n "$HTTP_GATEWAY_NAMESPACE" wait "gateway/$HTTP_GATEWAY_NAME" \ + --for=condition=Programmed=True --timeout=10m +kubectl -n "$GRPC_GATEWAY_NAMESPACE" wait "gateway/$GRPC_GATEWAY_NAME" \ + --for=condition=Programmed=True --timeout=10m + +export GATEWAY_ADDR="$(kubectl -n "$HTTP_GATEWAY_NAMESPACE" get "gateway/$HTTP_GATEWAY_NAME" \ + -o jsonpath='{.status.addresses[0].value}')" +export GRPC_GATEWAY_ADDR="$(kubectl -n "$GRPC_GATEWAY_NAMESPACE" get "gateway/$GRPC_GATEWAY_NAME" \ + -o jsonpath='{.status.addresses[0].value}')" + +test -n "$GATEWAY_ADDR" +test -n "$GRPC_GATEWAY_ADDR" +``` + +Use `GATEWAY_ADDR` as the route hostname suffix for test environments without +production DNS. Use your production domain instead when you configure DNS and +HTTPS. + +## Use Gateway values with install paths + +| Install path | Gateway values to use | +| --- | --- | +| [Quickstart](./quickstart.md) | Do not use these remote Gateway values. The quickstart uses local k3d route hostnames. | +| [Helmfile Installation](./helmfile-installation.md) | Use `GATEWAY_ADDR` as `global.domain`, and set `ingress.gatewayApi.gateways` to the Gateway names, namespaces, and listener names from Gateway quickstart. | + +## Configure the CLI for Gateway access + +For remote Helmfile deployments, configure the CLI after Gateway API ingress is +available. The CLI calls API, API Keys, invocation, and gRPC +endpoints during token minting, cluster registration, health checks, and +function operations. + +```bash +export CLUSTER_NAME="nvcf-remote" +export NCA_ID="nvcf-default" +export REGION="us-west-1" +export STACK_DOMAIN="$GATEWAY_ADDR" + +cat > .nvcf-cli.yaml < +There is no service mesh requirement. Envoy Gateway is not a service mesh. It is a Gateway API controller. You don't need service mesh features like mTLS between pods for NVCF to function. If you already have Istio or another service mesh, you can use its Gateway API support instead. + + + +### Minimum Requirements + +Any Gateway API implementation you choose must support: + +1. `HTTPRoute` for HTTP/HTTPS routing with hostname matching +2. `TCPRoute` for gRPC invocation, optional split or multi-cluster gRPC + invocation, and NATS routing (requires experimental Gateway API CRDs) +3. Cross-namespace routing for routes in one namespace referencing services in another + + +TCPRoute is experimental. Some Gateway API implementations may have limited or no TCPRoute support. Verify your chosen implementation supports TCPRoute before deploying. If it doesn't, gRPC invocations and NVCA NATS connections won't work through the gateway. + + + +### Using a Different Implementation + +To use a different Gateway API implementation instead of Envoy Gateway: + +1. Install your chosen controller following its documentation + +2. Create namespaces with `nvcf/platform=true` labels as shown in [Gateway quickstart](./gateway-routing.md#gateway-quickstart) + +3. Create a `GatewayClass` for your controller + +4. Create a `Gateway` with `http` (port 80), `tcp` (port 10081), and `nats` + (port 4222) listeners. Add `worker-tcp` (port 10086) only when enabling + split or multi-cluster gRPC invocation. + +5. Update your install configuration to reference your Gateway: + + ```yaml + ingress: + gatewayApi: + enabled: true + gateways: + shared: + name: your-gateway-name # Your Gateway resource name + namespace: your-namespace # Namespace where Gateway exists + listenerName: http # HTTP listener name + grpc: + name: your-gateway-name # Can be same Gateway with different listener + namespace: your-namespace + listenerName: tcp # TCP listener name for gRPC + nats: + name: your-gateway-name # Can be same Gateway with different listener + namespace: your-namespace + listenerName: nats # TCP listener name for NATS + ``` + +The `nvcf-gateway-routes` chart will create HTTPRoutes and TCPRoutes that attach to your specified Gateway. + +### Not Using Gateway API + +While technically possible to bypass the Gateway API entirely, this is not recommended: + +- The `nvcf-gateway-routes` chart specifically creates Gateway API resources +- You would need to manually create and maintain all routing configuration +- Traditional Kubernetes Ingress does not support TCPRoute (required for gRPC) +- Multiple LoadBalancer services would require multiple external IPs + +If you have a specific requirement that prevents using Gateway API, you would need to: + +1. Disable `nvcf-gateway-routes` in your helmfile +2. Create your own Ingress or Service resources for each NVCF service +3. Configure hostname routing manually +4. Set up TCP load balancers for gRPC on port 10081, optional split or + multi-cluster gRPC invocation on port 10086, and NATS on port 4222 + +## Gateway Architecture + +### Components + +The gateway architecture consists of two layers: + +### User-configured resources + +These resources must be created manually before deploying the control plane: + +- Namespaces with `nvcf/platform=true` labels +- Gateway API controller installation (Envoy Gateway, Istio, Traefik, etc.) +- `GatewayClass` resource +- `Gateway` resource with `http` (port 80), `tcp` (port 10081), and `nats` + (port 4222) listeners +- Optional `worker-tcp` (port 10086) listener for split or multi-cluster gRPC + invocation + +### Resources created by nvcf-gateway-routes + +When you deploy the control plane via helmfile, the `nvcf-gateway-routes` chart automatically creates: + +- `HTTPRoutes` for API Keys, NVCF API, and Invocation services +- Optional LLM invocation HTTPRoute when the `llmInvocation` route is enabled +- Optional Vanity Gateway HTTPRoute only when the stack package includes the addon and the `vanityGateway` route is enabled +- `TCPRoute` for gRPC +- Optional `TCPRoute` for split or multi-cluster gRPC invocation when the + `grpcWorker` route is enabled +- Optional `TCPRoute` for NATS when the `nats` route is enabled +- `ReferenceGrants` for cross-namespace routing permissions + +These routes attach to the Gateway you prepared in [Gateway quickstart](./gateway-routing.md#gateway-quickstart). + +### Route Configuration + +| Service | Hostname Pattern | Listener Port | Description | +| --- | --- | --- | --- | +| API Keys | `api-keys.` | 80 | Token generation and API key management | +| NVCF API | `api.` | 80 | Function management (create, deploy, delete) | +| Invocation | `invocation.`, `*.invocation.` | 80 | Function invocation (wildcard for dynamic routing) | +| LLM Invocation | `llm.invocation.` | 80 | OpenAI-compatible LLM invocation routes such as `/v1/chat/completions`, `/v1/responses`, and `/v1/embeddings` | +| Vanity Gateway | `vanity.` | 80 | Optional vanity host/path routing to `vanity-gateway.nvcf:8080`, only in stack packages that include the addon | +| gRPC | N/A (TCP routing, no hostname matching) | 10081 | gRPC function invocations | +| gRPC worker callback | N/A (TCP routing, no hostname matching) | 10086 | HTTP/1 CONNECT callback from workers to grpc-proxy when the beta `grpcWorker` route is enabled | +| NATS | N/A (TCP routing, no hostname matching) | 4222 | NVCA messaging when the NATS route is enabled | + + +The `` is your Gateway's load balancer address (e.g., `a1b2c3d4.us-west-2.elb.amazonaws.com`) or your custom domain. The helmfile deployment automatically configures the HTTPRoute hostnames using this value from your environment configuration. + + + + +When the LLM invocation route is enabled in self-managed deployments, send OpenAI-compatible requests to `http://${GATEWAY_ADDR}/v1/...` with `Host: llm.invocation.${GATEWAY_ADDR}` and set `model` to `/`. + + + +### Invocation Path Diagrams + +For local and multi-cluster invocation-path diagrams, see +[Generic HTTP Function Invocation](./generic-http-function-invocation.md), +[gRPC Function Invocation](./grpc-function-invocation.md), and +[LLM Gateway](./llm-gateway.md). + +### Vanity Gateway (Optional) + +Vanity Gateway is disabled by default. It is available only in stack packages +that include the Vanity Gateway addon. If your extracted stack package does not +contain a `vanity-gateway` release and `vanityGateway` route values, skip this +section until you use a stack package that includes them. + +Enable it only when you need a customer-facing hostname or mapping layer in +front of the standard NVCF service routes. In Helmfile-based stack packages that +include the addon, set: + +```yaml +addons: + vanityGateway: + enabled: true + mappingConfig: {} +``` + +By default, the route host is `vanity.` and the backend is +`vanity-gateway.nvcf:8080`. Use `addons.vanityGateway.mappingConfig` for the +host and path mappings required by your deployment. If you need custom vanity +hostnames instead of `vanity.`, configure the route hostname overrides +supported by your stack package, then create matching DNS records for those +hosts. + +### How Routing Works + +1. The Gateway's LoadBalancer service exposes ports 80 (HTTP), 10081 (gRPC), + and 4222 (NATS) externally. +2. HTTP requests arrive at port 80. The Gateway inspects the `Host` header and matches it against HTTPRoute hostnames. +3. The matching HTTPRoute forwards the request to the appropriate backend service (e.g., `api-keys` service on port 8080). +4. gRPC requests arrive at port 10081. The TCPRoute forwards all traffic directly to the `grpc` service. No hostname matching is required. +5. NATS connections arrive at port 4222. When enabled, the NATS TCPRoute + forwards traffic directly to the NATS service. + + +gRPC doesn't need Host headers because it uses a dedicated TCP listener on port 10081. The gateway routes all traffic on that port directly to the gRPC service without hostname matching. + + + +## Verifying Gateway Configuration + +After deploying the control plane, use these commands to verify your gateway configuration. + +### Get the Gateway Load Balancer Address + +```bash +# Get the gateway's external address (hostname or IP) +export GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway \ + -o jsonpath='{.status.addresses[0].value}') +echo "Gateway Address: $GATEWAY_ADDR" +``` + +### Verify HTTPRoute Hostnames + +The gateway routes requests based on the `Host` header. Check what hostnames are configured: + +```bash +# List all HTTPRoutes and their hostnames +kubectl get httproute -A -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.hostnames[*]}{"\n"}{end}' + +# Example output: +# api-keys: api-keys.a1b2c3d4.us-west-2.elb.amazonaws.com +# nvcf-api: api.a1b2c3d4.us-west-2.elb.amazonaws.com +# invocation-service: *.invocation.a1b2c3d4.us-west-2.elb.amazonaws.com invocation.a1b2c3d4.us-west-2.elb.amazonaws.com +# vanity-gateway: vanity.a1b2c3d4.us-west-2.elb.amazonaws.com # only when enabled and present in the stack package +``` + +If Vanity Gateway is disabled or your stack package does not include the addon, +the `vanity-gateway` HTTPRoute is not expected. + +### Verify TCPRoutes + +```bash +# Check gRPC and NATS routing is configured +kubectl get tcproute -A +# Expected output: +# NAMESPACE NAME AGE +# envoy-gateway grpc 19h +# envoy-gateway nats 19h # when the NATS route is enabled + +# Verify the gateway exposes ports 10081 and 4222 +kubectl get svc -n envoy-gateway-system -l gateway.envoyproxy.io/owning-gateway-name=nvcf-gateway \ + -o jsonpath='{.items[0].spec.ports[*].port}' +# Expected output includes: 80 10081 4222 +``` + +### Test Connectivity + +```bash +# Test HTTP endpoints with Host header +curl -H "Host: api-keys.$GATEWAY_ADDR" http://$GATEWAY_ADDR/health +curl -H "Host: api.$GATEWAY_ADDR" http://$GATEWAY_ADDR/health + +# Optional Vanity Gateway route, only if the addon is present and enabled +curl -H "Host: vanity.$GATEWAY_ADDR" http://$GATEWAY_ADDR/health + +# Test gRPC endpoint (requires grpcurl) +grpcurl -plaintext $GATEWAY_ADDR:10081 grpc.health.v1.Health/Check +``` + +## Development: Host Header Routing + +For development and testing when you don't have DNS configured, you can use Host header overrides to route requests through the gateway. + +### Why Host Headers Are Needed + +The Envoy Gateway uses hostname-based routing to direct traffic to different backend services through a single load balancer. When you send a request to the raw load balancer address (e.g., `http://a1b2c3d4.elb.amazonaws.com`), the gateway needs to know which service to route to. + +Without the correct `Host` header, the gateway cannot match the request to an HTTPRoute and returns 404. + + +The NVCA agent on a self-managed GPU cluster has the same requirement when it reaches the +control plane through a load-balancer-fronted gateway. Configure its host-header overrides +in the operator values, not the CLI config. See +[self-managed-clusters](./cluster-management/self-managed.md). + + + + +Host header routing only works with plaintext HTTP traffic. Without TLS/SNI spoofing support in your client, you cannot use HTTPS with this method. The TLS handshake occurs before the Host header is sent, so the server cannot route based on a custom Host header when using HTTPS. For encrypted traffic, use proper DNS records as described in [production-dns-https](./gateway-routing.md). + + + +### Configuring Host Headers + +When using tools that support custom Host headers (like the NVCF CLI or curl), specify the expected hostname: + +```bash +# Example: curl with Host header override +curl -H "Host: api-keys.a1b2c3d4.us-west-2.elb.amazonaws.com" \ + http://a1b2c3d4.us-west-2.elb.amazonaws.com/v1/admin/keys +``` + +For the NVCF CLI, configure the `*_host` settings in your configuration file: + +```yaml +# Endpoints point to load balancer +base_http_url: "http://a1b2c3d4.us-west-2.elb.amazonaws.com" +invoke_url: "http://a1b2c3d4.us-west-2.elb.amazonaws.com" +api_keys_service_url: "http://a1b2c3d4.us-west-2.elb.amazonaws.com" +base_grpc_url: "a1b2c3d4.us-west-2.elb.amazonaws.com:10081" + +# Host header overrides for routing +api_keys_host: "api-keys.a1b2c3d4.us-west-2.elb.amazonaws.com" +api_host: "api.a1b2c3d4.us-west-2.elb.amazonaws.com" +invoke_host: "invocation.a1b2c3d4.us-west-2.elb.amazonaws.com" +``` + +See [cli-configuration](./cli.md) for complete CLI configuration documentation. + +## Production: DNS and HTTPS + +For production deployments, configure proper DNS and TLS to eliminate the need for Host header overrides. + +### Benefits + +With proper DNS and HTTPS: + +- DNS records resolve service hostnames (e.g., `api-keys.nvcf.example.com`) to your Gateway's load balancer +- TLS certificates secure all traffic +- Clients use simple URLs without Host header overrides +- Browsers and other clients can access services directly + +### Step 1: Choose a Domain + +Select a domain you control for your NVCF deployment: + +```text +# Example domain structure +nvcf.example.com # Base domain +|-- api-keys.nvcf.example.com # API Keys service +|-- api.nvcf.example.com # NVCF API +|-- invocation.nvcf.example.com # Invocation service +|-- *.invocation.nvcf.example.com # Wildcard for function routing +`-- grpc.nvcf.example.com # gRPC endpoint (optional, for documentation) +``` + +### Step 2: Create DNS Records + +Create DNS records pointing to your Gateway's load balancer address: + +```text +# A/CNAME records (replace with your load balancer address) +api-keys.nvcf.example.com -> a1b2c3d4.us-west-2.elb.amazonaws.com +api.nvcf.example.com -> a1b2c3d4.us-west-2.elb.amazonaws.com +invocation.nvcf.example.com -> a1b2c3d4.us-west-2.elb.amazonaws.com +*.invocation.nvcf.example.com -> a1b2c3d4.us-west-2.elb.amazonaws.com +``` + +### AWS Route 53 example + +```bash +# Get your Gateway load balancer address +GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway \ + -o jsonpath='{.status.addresses[0].value}') + +# Create CNAME records (example using AWS CLI) +aws route53 change-resource-record-sets --hosted-zone-id YOUR_ZONE_ID --change-batch '{ + "Changes": [ + {"Action": "CREATE", "ResourceRecordSet": {"Name": "api-keys.nvcf.example.com", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{"Value": "'$GATEWAY_ADDR'"}]}}, + {"Action": "CREATE", "ResourceRecordSet": {"Name": "api.nvcf.example.com", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{"Value": "'$GATEWAY_ADDR'"}]}}, + {"Action": "CREATE", "ResourceRecordSet": {"Name": "invocation.nvcf.example.com", "Type": "CNAME", "TTL": 300, "ResourceRecords": [{"Value": "'$GATEWAY_ADDR'"}]}} + ] +}' +``` + + +Automate with external-dns. The `nvcf-gateway-routes` chart supports `routeAnnotations` for automatic DNS record creation via [external-dns](https://github.com/kubernetes-sigs/external-dns). See the chart's README for configuration examples. + + + +### Step 3: Update HTTPRoute Hostnames + +Update your helmfile environment to use your custom domain instead of the load balancer address: + +```yaml +# Use your custom domain +domain: "nvcf.example.com" + +# Gateway configuration remains the same +ingress: + gatewayApi: + enabled: true + gateways: + shared: + name: nvcf-gateway + namespace: envoy-gateway + listenerName: http + grpc: + name: nvcf-gateway + namespace: envoy-gateway + listenerName: tcp + nats: + name: nvcf-gateway + namespace: envoy-gateway + listenerName: nats +``` + +Redeploy to update the HTTPRoute hostnames: + +```bash +helmfile -e apply +``` + +Verify the routes updated: + +```bash +kubectl get httproute -A -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.hostnames[*]}{"\n"}{end}' +# Expected: +# api-keys: api-keys.nvcf.example.com +# nvcf-api: api.nvcf.example.com +# invocation-service: *.invocation.nvcf.example.com invocation.nvcf.example.com +``` + +### Step 4: Configure TLS (HTTPS) + +For TLS, you have two main options: + +### Option A: TLS at the Load Balancer (recommended for AWS) + +Terminate TLS at the AWS NLB using ACM certificates: + +1. Request a certificate in AWS Certificate Manager for `*.nvcf.example.com` +2. Update the Gateway to use HTTPS listeners + +```yaml +# Update Gateway listeners for TLS passthrough or termination +# This varies by cloud provider - consult your provider's documentation +``` + +### Option B: TLS at the Gateway with cert-manager + +Use cert-manager to automatically provision Let's Encrypt certificates: + +```bash +# Install cert-manager +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml + +# Create a ClusterIssuer for Let's Encrypt +kubectl apply -f - < +If you invoke a function without specifying a function version ID, and multiple +versions are deployed, Cloud Functions can route the request to any deployed +version for that function. + + + +For gRPC functions, see [gRPC Function Invocation](./grpc-function-invocation.md). + +For HTTP examples on this page, see [HTTP Invocation](#http-invocation) and +[HTTP Streaming](#http-streaming). + +## Invocation Path + +HTTP invocation enters through the public invocation endpoint and is handled by +Invocation Service. The worker request path delivers the request to the +selected customer function and returns the response through Invocation Service. + +![HTTP invocation path](images/nvcf-http-invocation-path.svg) + +### Multi-Cluster View + +In a global deployment, DNS or a custom front door selects a regional HTTP +invocation endpoint. Each region keeps its own Invocation Service, NATS worker +request path, and customer HTTP function placement. The cross-cluster line shows +the NATS chatter that supports regional request-path state when configured. + +![HTTP multi-cluster invocation path](images/nvcf-http-multicluster-invocation.svg) + +## HTTP Invocation + +HTTP invocation uses the invocation route exposed by your gateway. In self-hosted +deployments, requests usually go to the gateway load balancer and use the `Host` +header for routing: + +```bash +export GATEWAY_ADDR= +export FUNCTION_ID= +export API_KEY= +``` + +Invoke a function endpoint by using the function ID as the wildcard subdomain in +the `Host` header: + +```bash +curl --request POST \ + --url "http://${GATEWAY_ADDR}/echo" \ + --header "Host: ${FUNCTION_ID}.invocation.${GATEWAY_ADDR}" \ + --header "Authorization: Bearer ${API_KEY}" \ + --header "Content-Type: application/json" \ + --data '{"message": "hello"}' +``` + +With production DNS and TLS, the same request can use the DNS hostname directly: + +```bash +curl --request POST \ + --url "https://${FUNCTION_ID}.invocation./echo" \ + --header "Authorization: Bearer ${API_KEY}" \ + --header "Content-Type: application/json" \ + --data '{"message": "hello"}' +``` + +Function routes preserve endpoint paths and query parameters: + +```bash +curl --request POST \ + --url "http://${GATEWAY_ADDR}/echo?name=John" \ + --header "Host: ${FUNCTION_ID}.invocation.${GATEWAY_ADDR}" \ + --header "Authorization: Bearer ${API_KEY}" \ + --header "Content-Type: application/json" \ + --data '{"name": "John"}' +``` + +You can also send multipart or binary-style payloads to a custom function +endpoint: + +```bash +curl --location --request PUT \ + --url "http://${GATEWAY_ADDR}/my-cool-endpoint?abc=123" \ + --header "Host: ${FUNCTION_ID}.invocation.${GATEWAY_ADDR}" \ + --header "Accept: application/octet-stream" \ + --header "Authorization: Bearer ${API_KEY}" \ + --form 'metadata="123"' \ + --form 'file=@"/path/to/cool-file.zip"' +``` + + +Cloud Functions uses HTTP/2 persistent connections. For best performance, keep +client connections open until the client no longer needs to communicate with the +server. + + + +Size multipart and binary requests against your gateway, load balancer, and +function container limits. + +## Long-Running HTTP Invocation + +Cloud Functions supports long-running inference over HTTP/2 for synchronous HTTP +invocations. Use this mode when a function can take longer than a typical idle +connection window to produce a response. + +The client must keep the HTTP/2 connection healthy while it waits. Configure the +client to send HTTP/2 PING frames during idle response periods. Cloud Functions +does not send those client-side PING frames for you, and enabling HTTP/2 alone +is not enough if the client library does not send PING frames while it waits. + +Long-running HTTP/2 invocation uses the same request payload, function hostname, +and `Authorization` header as other HTTP invocations. Set the client request +timeout higher than the expected inference duration and test with the same client +runtime used in production. + +HTTP/2 PING frames are connection-level frames. They are not HTTP headers, JSON +fields, or request body data. + +### Go Clients + +Go clients can configure HTTP/2 keepalive behavior with +`golang.org/x/net/http2`. Set `ReadIdleTimeout` to the maximum idle period before +the client sends a health-check PING frame. Set `PingTimeout` to the time the +client waits for the PING acknowledgement before it closes the connection. + +```go +base := &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, +} + +h2, err := http2.ConfigureTransports(base) +if err != nil { + return err +} + +h2.ReadIdleTimeout = 30 * time.Second +h2.PingTimeout = 10 * time.Second + +client := &http.Client{ + Transport: base, + Timeout: 30 * time.Minute, +} +``` + +Choose timeout values that match your workload and network path. A shorter +`ReadIdleTimeout` sends PING frames more often. A very short interval can add +unnecessary connection traffic. + +### Python Clients + +Python clients that use `httpx`, including clients built on the OpenAI Python +SDK, can enable HTTP/2, but the standard `httpx` transport does not expose +HTTP/2 PING keepalive settings. + +For long-running invocations that can stay idle while the function runs, use a +Python transport that can send HTTP/2 PING frames or contact NVIDIA Support for +the recommended Python workaround. Increasing the request timeout without +HTTP/2 PING support can still leave the connection vulnerable to idle connection +closures. + +### curl + +The `curl` command line can help verify basic HTTP connectivity, but do not use +it as the only validation tool for long-running idle invocations. It does not +provide a practical way to send HTTP/2 PING frames while waiting for a response. + +Use a production client library that can configure HTTP/2 PING behavior when you +validate long-running inference. + +## HTTP Streaming + +HTTP streaming lets a function return an event stream to the client. The client +uses the same invocation endpoint and sends `Accept: text/event-stream`. + +### Prerequisites + +- A deployed Cloud Functions function. +- A function endpoint that can return `Content-Type: text/event-stream`. +- Familiarity with [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). + +### Client Request + +The client initiates streaming by making a request with +`Accept: text/event-stream`: + +```bash +curl --request POST \ + --url "http://${GATEWAY_ADDR}/echo" \ + --header "Host: ${FUNCTION_ID}.invocation.${GATEWAY_ADDR}" \ + --header "Authorization: Bearer ${API_KEY}" \ + --header "Accept: text/event-stream" \ + --header "Content-Type: application/json" \ + --data '{ + "messages": [ + { + "role": "user", + "content": "Hello" + } + ], + "temperature": 0.2, + "top_p": 0.7, + "max_tokens": 512 + }' +``` + +If the inference container response includes `Content-Type: text/event-stream`, +Cloud Functions keeps the client connection open and forwards events from the +container response. + + +The worker reads events from the inference container for up to the global request +timeout, or until the inference container closes the connection. Do not create an +infinite event stream. If the client disconnects, the worker eventually times out +and closes the request. + + + +Streaming reduces latency by sending data as it becomes available, avoids polling, +and lets the inference container decide whether a given response should be +streamed. + +For the streaming request sample on this page, see [Client Request](#client-request). + +## Statuses and Errors + +Direct HTTP invocation returns the status, headers, and body produced by your +inference container. If your container returns an error, clients receive the +container status code and response body. + +For consistent client handling, return JSON from your inference container and +set `Content-Type: application/json`. Example: + +```json +{ + "error": "invalid datatype for input message" +} +``` + +Cloud Functions adds invocation headers such as `nvcf-reqid` and `nvcf-status` +when a request is accepted by the invocation service. Platform-generated errors +can still use the platform error response format. For platform API behavior, see +[API](./api.md). + + +Emit logs from your inference container so invocation failures can be diagnosed. +See [Observability](./observability.md) and [Troubleshooting](./troubleshooting.md) +for logging and debugging guidance. + + + +### Common Invocation Errors + +| Failure type | Description | +| --- | --- | +| Invocation response returns 4xx or 5xx | Inspect the response body and container logs. Direct HTTP invocation forwards container-generated errors. For platform-generated errors, see [API](./api.md). | +| Invocation request takes a long time to return | Check function capacity to see if the request is queued. Consider adding container metrics and logs. Set the `NVCF-POLL-SECONDS` header to a longer value, up to the deployment limit, to rule out client polling issues. | +| Invocation response returns 401 or 403 | Verify that the `Authorization` header is set to a valid API key with invocation permissions. | +| Container out of memory | Profile memory usage locally and check container logs for out-of-memory evidence. For PyTorch workloads, see the [PyTorch GPU memory guide](https://pytorch.org/blog/understanding-gpu-memory-1/). | +| Invocation response returns 504 | No worker picked up the request within the polling timeout window. Increase `NVCF-POLL-SECONDS` if the request should remain queued longer. | diff --git a/docs/v0.6.1-rc/grpc-function-invocation.md b/docs/v0.6.1-rc/grpc-function-invocation.md new file mode 100644 index 000000000..292126a4a --- /dev/null +++ b/docs/v0.6.1-rc/grpc-function-invocation.md @@ -0,0 +1,164 @@ +# gRPC Function Invocation + +gRPC invocation executes requests against Cloud Functions functions that expose a +gRPC service. gRPC functions use the gRPC proxy instead of the HTTP invocation +route. + +In self-hosted deployments, the gRPC route is exposed on the Gateway TCP +listener. See [Gateway Routing](./gateway-routing.md) for listener and DNS +configuration. + +Self-hosted split or multi-cluster deployments require additional enablement +before workers can reach the grpc-proxy callback endpoint. See +[gRPC Invocation Enablement](./grpc-invocation-enablement.md). + +## Invocation Path + +![gRPC invocation path](images/nvcf-grpc-invocation-path.svg) + +```bash +export GRPC_GATEWAY_ADDR= +export FUNCTION_ID= +export FUNCTION_VERSION_ID= +export API_KEY= +``` + +### Multi-Cluster View + +In a global deployment, DNS selects a regional public gRPC endpoint. Each region +keeps its own gRPC Proxy, NVCF API and NATS stateful request path, worker CONNECT +registration, and customer gRPC service placement. The cross-cluster line shows +NATS chatter for regional stateful request-path coordination when configured. + +![gRPC multi-cluster invocation path](images/nvcf-grpc-multicluster-invocation.svg) + +## Metadata + +Set these gRPC metadata values when invoking a function: + +| Metadata key | Required | Description | +| --- | --- | --- | +| `authorization` | Yes | API key, formatted as `Bearer `. You can also use gRPC call credentials. | +| `function-id` | Yes | Function ID to invoke. | +| `function-version-id` | No | Function version ID to target. | +| `nvcf-reqid` | No | Request ID (UUID) from a previous session. Send this to resume an existing session instead of starting a new one. See [Session Resumption](#session-resumption). | + +The data sent to your gRPC function is defined by the Protobuf messages your +function implements. gRPC functions do not have an input request size limit. + +gRPC connections stay alive for 30 seconds when idle. Close the gRPC client +connection after your client is finished so function workers are not held longer +than needed. + +## Python Example + +This example uses a plaintext local or test gateway on port `10081`. For a +production TLS endpoint, use `grpc.secure_channel("grpc.:443", +grpc.ssl_channel_credentials())`. + +```python +import os +import grpc + +import grpc_service_pb2_grpc + + +def call_grpc(model_infer_request) -> None: + channel = grpc.insecure_channel(f"{os.environ['GRPC_GATEWAY_ADDR']}:10081") + grpc_client = grpc_service_pb2_grpc.GRPCInferenceServiceStub(channel) + + metadata = [ + ("function-id", os.environ["FUNCTION_ID"]), + ("function-version-id", os.environ["FUNCTION_VERSION_ID"]), + ("authorization", f"Bearer {os.environ['API_KEY']}"), + ] + + infer = grpc_client.ModelInfer(model_infer_request, metadata=metadata) + _ = infer + + channel.close() +``` + + +The official gRPC term for authorization handling is +[call credentials](https://grpc.io/docs/guides/auth/#credential-types). The +example above sets the `authorization` metadata directly for clarity. + + + +## Connection Reuse and Streaming + +The gRPC proxy pins sessions to the TCP connection to support unmodified gRPC +clients that ignore cookie headers. This matters when an intermediary proxy for +streaming, such as Kit streaming or Low Latency Streaming (LLS), uses HTTP/2 and +reuses connections. + +![Single-client flow](images/grpc-single-client.png) + +![Reconnect flow](images/grpc-reconnect-flow.png) + + +Do not pre-allocate streaming sessions with `POST` plus `X-NVCF-ABSORB` when a +shared HTTP/2 client can reuse one TCP connection across multiple users or +flows. Two separate requests sent over the same connection can receive the same +request ID from the proxy, which can bind different users or flows to the same +Kit pod. + +Use on-demand binding through the WebSocket instead: establish the WebSocket, +obtain the request ID from the proxy, and use that ID for subsequent requests. + + + +For requirements and a sample intermediary proxy implementation, see +[Intermediary Proxy](./streaming-functions.md#intermediary-proxy). + +## Session Resumption + +Streaming applications such as Omniverse Kit need to reconnect a client to the +same worker after a brief network interruption or a deliberate pause. The gRPC +proxy supports this through a request ID that identifies the stateful session +bound to a specific worker. + +### How it works + +1. On the first request to a function, the proxy allocates a new session and + assigns it a UUID request ID. +2. The proxy returns the request ID to the client in two forms: + - An `nvcf-reqid` response header. + - A `Set-Cookie` header with the cookie name `nvcf-request-id`. +3. On subsequent requests, the client sends the request ID back to the proxy. + The proxy checks for the ID in this order: + - The `nvcf-reqid` metadata header (or `Sec-WebSocket-Protocol` for browser + WebSocket clients). + - The `nvcf-request-id` cookie. +4. When the proxy finds a valid request ID, it routes the request to the + existing worker session instead of creating a new one. + +### Sending the request ID + +For gRPC clients, set `nvcf-reqid` as a metadata header. For HTTP or WebSocket +clients that cannot set custom headers, the `nvcf-request-id` cookie works as +a fallback. + +```python +# gRPC session resumption example. +# First call: no request ID. The proxy creates a session. +response, call = grpc_client.ModelInfer.with_call(request, metadata=metadata) +request_id = dict(call.initial_metadata()).get("nvcf-reqid") + +# Subsequent calls: pass the request ID to resume the session. +metadata.append(("nvcf-reqid", request_id)) +grpc_client.ModelInfer.with_call(next_request, metadata=metadata) +``` + +### What happens when resumption fails + +If the session has expired or the worker is no longer available, the proxy +returns gRPC NotFound (`grpc-status: 5`) with the message "no existing session +found" and clears the `nvcf-request-id` cookie. The HTTP status remains 200 for +gRPC responses. This error does not indicate a control-plane problem. The +client should discard the stale request ID and reconnect without it to start a +new session. + +See [Troubleshooting](./troubleshooting.md#grpc-session-resumption-fails) +for diagnosis steps. diff --git a/docs/v0.6.1-rc/grpc-invocation-enablement.md b/docs/v0.6.1-rc/grpc-invocation-enablement.md new file mode 100644 index 000000000..265f24537 --- /dev/null +++ b/docs/v0.6.1-rc/grpc-invocation-enablement.md @@ -0,0 +1,97 @@ +# gRPC Invocation Enablement + +The self-managed Helmfile stack deploys grpc-proxy as a core service. Client +gRPC traffic reaches grpc-proxy through the Gateway TCP listener on port +`10081`. + +Split or multi-cluster deployments need one additional route. During +invocation, a worker opens an HTTP/1 CONNECT callback channel to grpc-proxy on +port `10086`. Enable this path when worker pods cannot route to grpc-proxy pod +IPs, such as when compute clusters and the control-plane cluster use separate +pod networks. + +Single-cluster deployments usually do not need this worker callback route +because workers can reach grpc-proxy pod IPs directly. + +For a complete Amazon EKS installation example that compares single-cluster and +multi-cluster topologies, see the +[CSP End-to-End Example](./csp-end-to-end-example-installation.md). + + +Split or multi-cluster gRPC invocation is beta in 0.6.0. It supports one +grpc-proxy replica in the control plane. Set `grpcproxy.replicaCount: 1` and +keep the grpc-proxy Horizontal Pod Autoscaler (HPA) disabled. Multiple +grpc-proxy replicas are not supported because the 0.6.0 worker callback route is +shared TCP routing and can send a callback to the wrong pod. + + + +## Network Paths + +gRPC invocation uses two network legs: + +| Leg | Protocol | Default port | Purpose | +| --- | --- | --- | --- | +| Client to grpc-proxy | HTTP/2 gRPC | `10081` | Sends client gRPC requests through the Gateway TCP listener. | +| Worker to grpc-proxy | HTTP/1 CONNECT | `10086` | Opens the callback channel that attaches the worker session to grpc-proxy. | + +The worker callback listener does not add HTTP/1 client invocation support. +Client gRPC traffic still uses HTTP/2. Self-hosted HTTP/3 callback routing is +out of scope for 0.6.0. + +## Add the Gateway Listener + +Add a TCP listener for the worker callback path only when enabling split or +multi-cluster gRPC invocation. The listener name must match +`ingress.gatewayApi.routes.grpcWorker.listenerName`. + +```yaml +- name: worker-tcp + protocol: TCP + port: 10086 + allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + nvcf/platform: "true" +``` + +See [Gateway Routing](./gateway-routing.md#grpc-worker-callback-listener) for +where this listener fits in the Gateway quickstart. + +## Set Helmfile Values + +Add these values to your environment file before deploying or syncing the +control plane: + +```yaml +grpcproxy: + replicaCount: 1 + deployment: + autoscaling: + enabled: false + workerConnectBaseURL: "http://grpc.:10086" + +ingress: + gatewayApi: + routes: + grpcWorker: + enabled: true + listenerName: worker-tcp +``` + +Set `grpcproxy.workerConnectBaseURL` to the base URL that workers can use to +reach the grpc-proxy callback listener. The stack passes this value to the +grpc-proxy chart, which renders the grpc-proxy container configuration. Do not +set the raw container environment variable directly. + +For local multi-cluster testing with ncp-local, use: + +```yaml +grpcproxy: + workerConnectBaseURL: "http://grpc.nvcf.svc.cluster.local:10086" +``` + +After updating the environment file, continue with the normal +[Helmfile Installation](./helmfile-installation.md) flow. diff --git a/docs/v0.6.1-rc/grpc-load-test-sli-guide.md b/docs/v0.6.1-rc/grpc-load-test-sli-guide.md new file mode 100644 index 000000000..3f1e521e5 --- /dev/null +++ b/docs/v0.6.1-rc/grpc-load-test-sli-guide.md @@ -0,0 +1,228 @@ +(grpc-load-test-sli-guide)= + +# gRPC Load Test SLI Guide + +This document describes which metrics to watch when load testing a self-hosted +NVCF gRPC deployment, what each metric indicates, and how to interpret the +saturation sequence. Values are hardware-dependent -- what is transferable is +the order in which signals appear and what they mean. + +For run commands and cluster setup, see {ref}`self-managed-grpc-load-test`. + +--- + +## How Self-Hosted NVCF Handles Load + +Understanding the request path helps interpret the metrics: + +- The **gRPC proxy** holds in-flight requests. It does not reject requests until + `maxRequestConcurrency` is exhausted -- it queues them. +- The **worker sidecar** is the throughput ceiling. Its concurrency limit + (`maxRequestConcurrency`) and inference time per request set the maximum + sustainable req/s. +- **NATS** dispatches work between components. It is downstream of the proxy's + internal queue and will not show pressure until the proxy itself is saturated. +- **NVCA** only acts on scaling when scale-out is configured + (`minInstances < maxInstances`). + +--- + +## SLIs to Monitor + +### Group 1: Leading Indicators + +These rise *before* errors appear. Use them to predict saturation. + +#### `nvcf_grpc_proxy_service_active_connections_total` + +**What it is**: Number of active worker connections held by the gRPC proxy. + +**What to look for**: + +- Rises with load during healthy operation. +- **Decouples from throughput at the saturation point** -- connections keep + rising while req/s flattens. This is the earliest saturation signal. + +```none +nvcf_grpc_proxy_service_active_connections_total +``` + +#### `nvcf_grpc_proxy_service_session_init_seconds_total` (p95) + +**What it is**: Time for the proxy to establish a worker session (first contact +for a new connection). + +**What to look for**: + +- Low at idle, rises when the proxy is busy competing for worker slots. +- A rising p95 means new requests are waiting longer to get a worker session +- Check bucket distribution: are requests piling up in the higher latency + buckets (>100ms, >250ms)? + +```none +histogram_quantile(0.95, + rate(nvcf_grpc_proxy_service_session_init_seconds_total_bucket{is_reconnect="false"}[1m])) +``` + +--- + +### Group 2: Throughput and Capacity + +#### `function_request_total` + +**What it is**: Cumulative completed requests for a specific function, scraped +from the gRPC Proxy (`job=grpc`). Filter by `function_id` to isolate a +single function's throughput. Labels: `function_id`, `function_version_id`, +`nca_id`. + +**What to look for**: + +- `rate(function_request_total[1m])` gives req/s. Plot alongside VU count. +- **Throughput plateau = capacity wall.** If req/s stops growing while VUs keep + increasing, the system is saturated. + +```none +rate(function_request_total{job="grpc", function_id=""}[1m]) +``` + +#### `nvca_instance_type_allocatable` + +**What it is**: Available worker slots in the cluster fleet. + +**What to look for**: + +- Drops as workers are allocated to new deployments +- If allocatable reaches 0 on a fixed cluster: new worker deployments will fail with a no-capacity error + +```none +nvca_instance_type_allocatable{instance_type=""} +``` + +--- + +### Group 3: Lagging Indicators + +These confirm saturation after it has occurred. Not useful for early warning, +but confirm the failure mode. k6 is the primary source for these signals. + +#### `grpc_req_duration` p95 (k6) + +**What it is**: End-to-end gRPC request latency measured by k6. + +**What to look for**: + +- Rises steeply after the throughput plateau. +- Use p95 > 5s as a lagging SLO threshold. By the time it rises, the capacity + wall has already been hit. + +**k6 metric**: `grpc_req_duration` (watch p90, p95 in k6 Cloud) + +#### `grpc_req_failed` (k6) + +**What it is**: k6 metric tracking the rate of failed gRPC requests. + +**What to look for**: + +- Stays near zero through moderate overload. The proxy holds connections and + queues requests rather than rejecting them -- failures only appear once + requests have been held long enough to hit the k6 client timeout. +- **Non-zero `grpc_req_failed` is a breaking-point signal**, not an early + warning. By the time it rises, the system is well past the capacity wall. +- Error type matters: + + - `context deadline exceeded` -- overload, expected at extreme VU counts. + - `UNAVAILABLE` or connection errors -- proxy or network issue unrelated + to capacity. + +**k6 metric**: `grpc_req_failed` (rate or count in k6 Cloud) + +#### `function_request_latency` p95 (worker-side) + +**What it is**: Per-request latency as measured by the worker itself. The time +spent inside the function from the moment the worker picks up the request. + +**What to look for**: + +- Complements `grpc_req_duration` (client-side). If k6 p95 is high but + worker p95 is low, the bottleneck is queuing at the proxy, not inference time. +- Rising worker latency under load indicates the worker itself is the + throughput ceiling. + +```none +histogram_quantile(0.95, rate(function_request_latency_bucket[1m])) +``` + +--- + +### Group 4: Stability Signals + +These should remain at zero during a clean load test. Any non-zero value +warrants investigation. + +| Metric | Threshold | What it means | +| --- | --- | --- | +| `nvcf_grpc_proxy_service_nats_error_total` | > 0 | Proxy lost connectivity to NATS | +| `nvcf_grpc_proxy_service_nats_reconnect_total` | > 0 | NATS connection instability | +| `nvca_event_error_total{nvca_event_name="TICK_ACKNOWLEDGE_REQUEST"}` | > 0 | NVCA failing to acknowledge worker heartbeats | +| `nvca_container_crash_total` | > 0 | Worker pod OOM or crash | +| `nvca_controller_runtime_reconcile_errors_total` | > 0 | k8s controller errors in NVCA | +| `nvca_event_queue_length` | sustained > 0 | NVCA falling behind processing heartbeat/scaling events | + +#### NATS JetStream + +NATS is the message bus between the gRPC proxy and the worker. + +**Early-warning signal**: `nvcf_grpc_proxy_service_active_connections_total` +decoupling from throughput is still the earliest proxy-side saturation indicator. + +#### Envoy Gateway + +Useful envoy signals during a gRPC test: + +```none +# Active downstream connections on the gRPC listener +envoy_listener_downstream_cx_active{envoy_listener_address="0.0.0.0_10081"} + +# Overflow -- TCP connection ceiling hit (should stay 0 unless saturated) +envoy_listener_downstream_cx_overflow{envoy_listener_address="0.0.0.0_10081"} + +# Envoy pod restart count +sum(increase(kube_pod_container_status_restarts_total{namespace="envoy-gateway-system"}[$__range])) +``` + +--- + +## The Saturation Sequence + +Regardless of hardware, saturation follows this order: + +```none +1. active_connections_total rises with load + ↓ +2. active_connections_total growth decouples from throughput ← LEADING SIGNAL + ↓ +3. Throughput (req/s) plateaus despite more VUs ← CAPACITY WALL + ↓ +4. grpc_req_duration p95 rises steeply ← LAGGING SIGNAL + ↓ +5. Client timeouts (context deadline exceeded) ← FAILURE VISIBLE TO CLIENTS +``` + +Steps 1-3 are observable before errors reach clients. Steps 4-5 confirm +saturation is underway. + +--- + +## Recommended Thresholds + +These are relative thresholds to calibrate against your baseline -- not +absolute values. Hardware, workload, and deployment configuration all affect +where these numbers land. + +| Signal | Threshold | Action | +| --- | --- | --- | +| `nvcf_grpc_proxy_service_active_connections_total` | > 50% of `maxRequestConcurrency` sustained 2 min | Warning: approaching saturation | +| `nvcf_grpc_proxy_service_active_connections_total` | > 80% of `maxRequestConcurrency` sustained 1 min | Critical: at capacity | +| Throughput plateau | req/s flat while VUs still increasing | Capacity wall reached | +| `session_init_seconds` p95 | > 100ms | Proxy contention -- investigate | +| `nats_error_total` | > 0 | Immediate investigation | diff --git a/docs/v0.6.1-rc/grpc-load-testing.md b/docs/v0.6.1-rc/grpc-load-testing.md new file mode 100644 index 000000000..785e7a517 --- /dev/null +++ b/docs/v0.6.1-rc/grpc-load-testing.md @@ -0,0 +1,291 @@ +# Self-Managed NVCF gRPC Load Test + +## Prerequisites + +### Self-hosted CLI + +You need a working `nvcf-cli` configured against your self-managed cluster. +If you have not set this up yet, follow the [self-hosted-cli](../user/cli.md) guide to +install the binary and the [cli-configuration](../user/cli.md) section to point it at your +gateway. + +Verify the CLI can reach the cluster before continuing: + +```bash +./nvcf-cli init +./nvcf-cli api-key generate +``` + +### Deploy the load test function + +Use the **load_tester_supreme** container for load testing. It is purpose-built +for high-throughput benchmarking and includes: + +- **gRPC + HTTP + SSE** endpoints in a single image +- **500 gRPC worker threads** by default (configurable via `WORKER_COUNT`), + compared to 10 in the simpler `grpc_echo_sample` +- Tunable `repeats`, `delay`, and `size` fields to shape request/response + profiles +- Built-in OpenTelemetry tracing + + +The `grpc_echo_sample` from the same repository shares the +`Echo/EchoMessage` proto and will work for a quick smoke test, but its +10-thread pool will saturate under moderate concurrency. Always use +`load_tester_supreme` for real load tests. + + + +The source, build instructions, and registry push examples are in the +[NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/load-tester-supreme) +repository. Build and push the image to whichever container registry your +cluster has credentials for: + +```bash +git clone https://github.com/NVIDIA/nvcf.git +cd nvcf/examples/function-samples/load-tester-supreme + +# Build +docker build --platform linux/amd64 -t load_tester_supreme . + +# Tag and push (replace with your registry -- NGC, ECR, etc.) +docker tag load_tester_supreme nvcr.io//load_tester_supreme:latest +docker push nvcr.io//load_tester_supreme:latest +``` + + +To check which registries your cluster recognises, run +`./nvcf-cli registry list`. + + + +Then create the function and deploy it using the CLI: + +```bash +# Create the function (gRPC -- set inference-url to a placeholder) +./nvcf-cli function create \ + --name "load-tester-supreme" \ + --image "nvcr.io//load_tester_supreme:latest" \ + --inference-url "/grpc" \ + --inference-port 8001 \ + --health-protocol GRPC \ + --health-uri "/" \ + --health-port 8001 \ + --health-timeout PT30S \ + +# Deploy (adjust GPU type and instance type for your cluster) +./nvcf-cli function deploy create \ + --gpu H100 \ + --instance-type NCP.GPU.H100_1x \ + --min-instances 1 \ + --max-instances 1 \ + --max-request-concurrency 1000 \ + --function-id \ + --version-id + +# Generate an API key for invocations +./nvcf-cli api-key generate +``` + +Once deployed, note the following -- you will need them for the run script: + +- **Function ID** -- the UUID returned by `function create` +- **Function Version ID** -- the UUID of the specific deployed version +- **gRPC endpoint** -- your gateway address on port `10081` (see below) +- **API key** -- the key from `api-key generate` (begins with `nvapi-`) + +Your gateway address is the external address of the Envoy Gateway deployed with +the control plane. To retrieve it: + +```bash +kubectl get gateway nvcf-gateway -n envoy-gateway \ + -o jsonpath='{.status.addresses[0].value}' +``` + +On **AWS EKS** this is an ELB hostname (e.g. +`a1b2c3d4.us-east-1.elb.amazonaws.com`). For a **local** deployment (Kind, +k3d, Docker Desktop) it is typically `localhost` or `127.0.0.1`. + + +The CLI saves the function and version IDs automatically. Run +`./nvcf-cli status` to view them at any time. + + + +## Clone the load test scripts + +```bash +git clone https://github.com/NVIDIA/nvcf.git +cd nvcf/examples/load-tests +``` + +## Install k6 + +Install [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) if you +don't have it: + +```bash +# macOS +brew install k6 +``` + +```bash +# Linux (Debian/Ubuntu) +sudo gpg -k +sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \ + --keyserver hkp://keyserver.ubuntu.com:80 \ + --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 +echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \ + | sudo tee /etc/apt/sources.list.d/k6.list +sudo apt-get update && sudo apt-get install k6 +``` + +## Create your run script + +The `run*.sh` scripts are gitignored, so each user creates their own locally. +Create `run_grpc_self_managed_test.sh` in the `load-tests` directory: + +```bash +#!/bin/bash + +set -e + +# From "nvcf-cli api-key show" +export TOKEN= + +# Gateway address + gRPC port (see "kubectl get gateway" above) +# Examples: +# AWS EKS: a1b2c3d4.us-east-1.elb.amazonaws.com:10081 +# Local: localhost:10081 +export NVCF_GRPC_URL=:10081 +# From "nvcf-cli status" or the function create output +export GRPC_SUPREME_FUNCTION_ID= +export GRPC_SUPREME_FUNCTION_VERSION_ID= + +export GRPC_PLAINTEXT=true + +export SENT_MESSAGE_SIZE=2048 +export RESPONSE_COUNT=1 + +k6 run functions/supreme_grpc_test.js \ + --vus 10 --duration 60s \ + --env TOKEN=${TOKEN} \ + --env NVCF_GRPC_URL=${NVCF_GRPC_URL} \ + --env GRPC_SUPREME_FUNCTION_ID=${GRPC_SUPREME_FUNCTION_ID} \ + --env GRPC_SUPREME_FUNCTION_VERSION_ID=${GRPC_SUPREME_FUNCTION_VERSION_ID} \ + --env GRPC_PLAINTEXT=${GRPC_PLAINTEXT} \ + --env SENT_MESSAGE_SIZE=${SENT_MESSAGE_SIZE} \ + --env RESPONSE_COUNT=${RESPONSE_COUNT} +``` + +Make it executable and run: + +```bash +chmod +x run_grpc_self_managed_test.sh +./run_grpc_self_managed_test.sh +``` + +## Tune the load + +### Virtual users (VUs) + +Each VU simulates a single concurrent client holding an open gRPC connection and +sending requests in a loop. The number of VUs directly controls the concurrency +hitting your endpoint. + +| VUs | Simulates | +| --- | --- | +| 1--5 | Smoke test -- verify the endpoint works under minimal load | +| 10--50 | Light load -- a small team or service calling the function | +| 100--500 | Moderate load -- multiple services or a rollout with real traffic | +| 1000+ | Stress test -- find the breaking point or max throughput | + + +**Default control plane sizing:** The default resource sizing that ships +with the self-managed stack is designed to handle roughly 100 concurrent users. If you +need to test beyond that, you will need to scale the control plane components +first. Starting with `--vus 100` or the scratch config is a good baseline +for validating a default self-managed deployment. + + + +Start low and increase gradually. If you see rising error rates or latency, you +have found the saturation point. + +**Fixed VUs for a set duration (simplest approach):** + +```bash +# 10 concurrent users for 1 minute +k6 run functions/supreme_grpc_test.js --vus 10 --duration 60s ... + +# 200 concurrent users for 10 minutes +k6 run functions/supreme_grpc_test.js --vus 200 --duration 10m ... +``` + +**Ramping VUs with a config file (recommended for real load tests):** + +Config files let you gradually ramp users up, hold steady, and ramp down. This +avoids slamming the endpoint all at once and gives more realistic results. + +The `k6_long_scaling_test_config.json` ramps to 100 VUs over 5 minutes, holds +for 15 minutes, then steps through higher concurrency levels: + +```bash +k6 run functions/supreme_grpc_test.js \ + --config functions/test_configs/k6_long_scaling_test_config.json \ + --env TOKEN=${TOKEN} \ + --env NVCF_GRPC_URL=${NVCF_GRPC_URL} \ + --env GRPC_SUPREME_FUNCTION_ID=${GRPC_SUPREME_FUNCTION_ID} \ + --env GRPC_SUPREME_FUNCTION_VERSION_ID=${GRPC_SUPREME_FUNCTION_VERSION_ID} \ + --env GRPC_PLAINTEXT=${GRPC_PLAINTEXT} \ + --env SENT_MESSAGE_SIZE=${SENT_MESSAGE_SIZE} \ + --env RESPONSE_COUNT=${RESPONSE_COUNT} +``` + +The `k6_hammer_test_config.json` uses a `ramping-arrival-rate` executor that +ramps up to 100,000 requests/second -- use this only when you want to push the +endpoint to its limit: + +```bash +k6 run functions/supreme_grpc_test.js \ + --config functions/test_configs/k6_hammer_test_config.json \ + --env TOKEN=${TOKEN} \ + --env NVCF_GRPC_URL=${NVCF_GRPC_URL} \ + --env GRPC_SUPREME_FUNCTION_ID=${GRPC_SUPREME_FUNCTION_ID} \ + --env GRPC_SUPREME_FUNCTION_VERSION_ID=${GRPC_SUPREME_FUNCTION_VERSION_ID} \ + --env GRPC_PLAINTEXT=${GRPC_PLAINTEXT} \ + --env SENT_MESSAGE_SIZE=${SENT_MESSAGE_SIZE} \ + --env RESPONSE_COUNT=${RESPONSE_COUNT} +``` + +### Other tuning parameters + +| Parameter | Purpose | +| --- | --- | +| `SENT_MESSAGE_SIZE` | Payload size in bytes (e.g. `2048` for 2 KB) | +| `RESPONSE_COUNT` | Number of echoed responses per request | + +## Environment variables reference + +| Variable | Purpose | +| --- | --- | +| `TOKEN` | Your `nvapi-*` bearer token | +| `NVCF_GRPC_URL` | gRPC endpoint -- your gateway address with port `10081` (e.g. `a1b2c3d4.us-east-1.elb.amazonaws.com:10081` or `localhost:10081`) | +| `GRPC_SUPREME_FUNCTION_ID` | Function ID from NVCF | +| `GRPC_SUPREME_FUNCTION_VERSION_ID` | Function version ID (required for self-managed deployments) | +| `GRPC_PLAINTEXT` | Set to `true` for non-TLS connections | +| `SENT_MESSAGE_SIZE` | Size of the test payload in bytes | +| `RESPONSE_COUNT` | Number of responses the server should return | + +## Verifying your endpoint manually + +Before running a load test, you can verify the endpoint works with `grpcurl`: + +```bash +grpcurl -plaintext \ + -H "function-id: " \ + -H "function-version-id: " \ + -H "authorization: Bearer " \ + -d '{"message": "hello from grpc"}' \ + :10081 Echo/EchoMessage +``` diff --git a/docs/v0.6.1-rc/helm-functions.md b/docs/v0.6.1-rc/helm-functions.md new file mode 100644 index 000000000..ecd2b748a --- /dev/null +++ b/docs/v0.6.1-rc/helm-functions.md @@ -0,0 +1,169 @@ +# Helm-Based Function Creation + +Cloud functions support helm-based functions for orchestration across multiple containers. + +## Prerequisites + + +Ensure that your helm charts version does not contain `-` For example `v1` is ok but `v1-test` will cause issues. + + + +1. The helm chart **must have a "mini-service" container defined, which will be used as the inference entry point.** +2. The name of this service in your helm chart should be supplied by setting `helmChartServiceName` during the function definition. This allows Cloud Functions to communicate and make inference requests to the "mini-service" endpoint. + + +The `servicePort` defined within the helm chart should be used as the `inferencePort` supplied during function creation. Otherwise, Cloud Functions will not be able to reach the "mini-service". + + + +3. Ensure you have pushed your helm chart to your OCI container registry. + +## Pull Secret Management + +All Pod specs in your helm chart will be updated with pull secrets at runtime, so any images are authorized to pull automatically. No other configuration is needed. + +## Create a Helm-based Function + +1. Ensure your helm chart is uploaded to your registry and adheres to the [helm-prereq](./helm-functions.md) listed above. + +2. Create the function: + + - Include the following additional parameters in the function definition: + - `helmChart` + - `helmChartServiceName` + + - The `helmChart` property should be set to the OCI URL of the helm chart that will deploy the "mini-service". The helm chart URL should follow the format: `oci://${REGISTRY}/${REPOSITORY}/charts/$NAME-X.Y.Z.tgz`. The chart name should not contain `-` in the version string. + + - The `helmChartServiceName` is used for checking if the "mini-service" is ready for inference and is also scraped for function metrics. At this time, templatized service names are not supported. **This must match the service name of your "mini-service" with the exposed entry point port.** + + - Important: The Helm chart name should not contain underscores or other special symbols, as that may cause issues during deployment. + +**Example Creation via API** + +Please see our [sample helm chart used](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/helmchart-samples/inference-test-sample) in this example for reference. + +Below is an example function creation API call creating a helm-based function: + +```bash +curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "function_name", + "inferenceUrl": "v2/models/model_name/versions/model_version/infer", + "inferencePort": 8001, + "helmChart": "oci://'${REGISTRY}'/'${REPOSITORY}'/charts/inference-test-1.0.tgz", + "helmChartServiceName": "service_name", + "apiBodyFormat": "CUSTOM" +}' +``` + + +For gRPC-based functions, set `"inferenceURL" : "/gRPC"`. This signals to Cloud Functions that the function is using gRPC protocol and is not expected to have a `/gRPC` endpoint exposed for inferencing requests. + + + +3. Proceed with function deployment and invocation normally. + +**Multi-node helm deployment** +To create a multi-node helm deployment, you need to use the following format for the `instanceType`: +`.GPU._x[.x]`. For example, `DGXC.GPU.L40S_1x` is a single L40S instance while `ON-PREM.GPU.B200_8x.x2` is two full nodes of 8-way B200. + +A sample helm chart for a multi-node deployment can be found [in the multi-node helm example](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/helmchart-samples/multi-node-helm-function-test/). + +## Limitations + +When using Helm Charts to deploy a function, the following limitations need to be taken into consideration. + +### 1. Asset caching + +- For any downloads (such as of assets or models) occurring within your function's containers, download size is limited by the disk space on the node. + +### 2. Inference + +Progress/partial response reporting is not supported, including any additional artifacts generated during inferencing. Consider opting for HTTP streaming or gRPC bidirectional support. + +### 3. Security Constraints + +Helm charts must conform to certain security standards to be deployable as a function. +This means that certain helm and Kubernetes features are restricted in NVCF backends. +NVCF will process your helm chart on function creation, then later on deployment with your Helm values and other deployment metadata, +to ensure standards are enforced. + +NVCF may automatically modify certain objects in your chart so they conform to these standards; +it will only do so if modification will not break your chart when it is installed in the targeted backend. +Possible areas amenable to modification will be noted in the restrictions section below. +Any standard that cannot be enforced by modification will result in error(s) during function creation. + +**Restrictions** + +- Supported k8s artifacts under Helm Chart Namespace are listed below; others will be rejected: + - ConfigMaps + - Secrets + - Services (only `type: ClusterIP` or none) + - Deployments + - ReplicaSets + - StatefulSets + - Jobs + - CronJobs + - Pods + - ServiceAccounts + - Roles + - Rolebindings + - PersistentVolumeClaims + +- A rendered Helm chart may contain a maximum of 300 of the aforementioned objects. + +- The only allowed Pod or Pod template volume types are: + - `configMap` + - `secret` + - `projected.sources.*` of any of the above + - `persistentVolumeClaim` + - `emptyDir` + +- No [chart hooks](https://helm.sh/docs/topics/charts_hooks/) are allowed; if specified in the chart, they will not be executed. + + +CustomResourceDefinitions in helm charts will be skipped on installation. There is no need to modify your chart to remove them from `helm template` output for NVCF. + + + +Helm charts _should_ conform to these additional security standards. While not enforced now, they will be at a later date. + +- All containers have [resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits) for at least `cpu` and `memory` (and `nvidia.com/gpu`, `ephemeral-storage` if required for certain containers). +- All Pod's and resources that define a Pod template conform to the Kubernetes Pod Security Standards [Baseline](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline) and [Restricted](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) policies. +- Pod and container `securityContext`'s conform to these parameters: + - `automountServiceAccountToken` must be unset or set to `false` + - `runAsNonRoot` must be explicitly set to `true` + - `hostIPC`, `hostPID`, and `hostNetwork` must be unset or set to `false` + - No privilege escalation, root capabilities, or non-default Seccomp, AppArmor, or SELinux profiles are allowed. + See the [Baseline](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline) + and [Restricted](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) + Pod security standards for fields that cannot be explicitly set. + +## Helm Chart Overrides + +To override keys in your helm chart `values.yml`, you can provide the `configuration` parameter and supply corresponding key-value pairs in JSON format which you would like to be overridden when the function is deployed. + +```bash +curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/deployments/functions/${FUNCTION_ID}/versions/${FUNCTION_VERSION_ID}" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "deploymentSpecifications": [{ + "gpu": "L40", + "backend": "nvcf-default", + "maxInstances": 2, + "minInstances": 1, + "configuration": { + "key_one": "", + "key_two": { "key_two_subkey_one": "", "key_two_subkey_two": "" } + ... + }] + }' +``` diff --git a/docs/v0.6.1-rc/helm-tasks.md b/docs/v0.6.1-rc/helm-tasks.md new file mode 100644 index 000000000..af9187704 --- /dev/null +++ b/docs/v0.6.1-rc/helm-tasks.md @@ -0,0 +1,78 @@ +# Helm-Based Task Creation + +A Helm task deploys a Helm chart onto a GPU instance for the duration of the +job. Use this approach when your workload requires multiple coordinated +containers or a more complex Kubernetes resource configuration than a single +container image allows. + +## Creating a Helm task + +```bash +# Using CLI flags +nvcf-cli task create \ + --name my-helm-job \ + --gpu H100 \ + --instance-type GPU.H100_1x \ + --helm-chart my-registry/charts/my-job:1.0.0 + +# From a JSON file +nvcf-cli task create --input-file helm-task.json +``` + +## Example JSON configuration + +```json +{ + "name": "my-helm-job", + "gpuSpecification": { + "gpu": "H100", + "instanceType": "GPU.H100_1x", + "backend": "GFN", + "helmValidationPolicy": { + "name": "Default" + } + }, + "helmChart": "my-registry/charts/my-job:1.0.0", + "maxRuntimeDuration": "PT8H", + "resultHandlingStrategy": "UPLOAD", + "resultsLocation": "my-org/my-team/my-model", + "secrets": [ + {"name": "NGC_API_KEY", "value": "nvapi-..."} + ] +} +``` + +## Helm validation policy + +The `helmValidationPolicy` field controls which Kubernetes resource types the +chart is permitted to create. It is nested inside `gpuSpecification`. + +| Policy name | Description | +| --- | --- | +| `Default` | Allows standard Kubernetes workload types | +| `Unrestricted` | Allows any resource type | + +To permit additional resource types beyond the default set, supply them in +`extraKubernetesTypes`: + +```json +"helmValidationPolicy": { + "name": "Default", + "extraKubernetesTypes": [ + {"group": "batch", "version": "v1", "kind": "CronJob"} + ] +} +``` + +## Differences from container tasks + +| | Container task | Helm task | +| --- | --- | --- | +| Entry point | `containerImage` + optional `containerArgs` | `helmChart` | +| Multi-container | No | Yes | +| Resource control | Via `gpuSpecification` | Via Helm chart values and `helmValidationPolicy` | +| `containerEnvironment` | Supported | Not applicable | + +Runtime limits, secrets, result handling, and monitoring work the same way as +container tasks. Note: result upload is not yet supported in this release. See [Container-Based Task Creation](./container-tasks.md) for +details on those fields. diff --git a/docs/v0.6.1-rc/helmfile-installation.md b/docs/v0.6.1-rc/helmfile-installation.md new file mode 100644 index 000000000..3063d3661 --- /dev/null +++ b/docs/v0.6.1-rc/helmfile-installation.md @@ -0,0 +1,1326 @@ +# Helmfile Installation + +This section covers manual Helmfile installation of the NVCF control plane and +GPU cluster components for self-hosted NVCF deployments. + +For a fresh install, start with the [Quickstart](./quickstart.md). Use this Helmfile guide when you need explicit release control, partial recovery, upgrades, or direct access to Helmfile values. + + +This guide assumes you have already downloaded and extracted the +`nvcf-self-managed-stack` Helmfile bundle (see +[download-nvcf-self-managed-stack](./image-mirroring.md)). Control-plane +commands run from inside that directory unless otherwise noted. The directory +contains the control-plane Helmfile definitions, environment templates, and +sample configurations referenced throughout. + +GPU clusters use the compute-plane Makefile in +`deploy/stacks/nvcf-compute-plane`. It registers one GPU cluster at a time and +installs the NVCA operator into that cluster. + +Clone the public repository before the GPU cluster steps: + +```bash +git clone https://github.com/nvidia/nvcf.git +``` + +```bash +cd path/to/nvcf-self-managed-stack +ls +# Expected contents: helmfile.d/ environments/ secrets/ global.yaml.gotmpl ... +``` + + + +## Namespace Requirements + +Each control-plane Helm chart must be installed into a specific namespace. These +namespace assignments are fixed and must not be changed because +service-to-service cluster DNS addressing and Vault (OpenBao) authentication +claims depend on this layout. + +| Namespace | Services | +| --- | --- | +| `nvcf` | api, invocation-service, grpc-proxy, notary-service, reval, state-metrics | +| `api-keys` | api-keys, admin-issuer-proxy | +| `ess` | ess-api | +| `sis` | sis | +| `vault-system` | openbao-server | +| `cassandra-system` | cassandra | +| `nats-system` | nats | +| `cert-manager` | cert-manager | +| `envoy-gateway-system` | ingress (nvcf-gateway-routes) | + + +Installing a chart into the wrong namespace will cause authentication failures such as +`error validating claims: claim "/kubernetes.io/namespace" does not match any associated bound claim values`. +If you see this error, verify that every release is deployed in the namespace shown above. + + + +## Prerequisites + +### Required Tools and Software + +The following tools must be installed on your deployment machine: + +- `kubectl` +- `helm` >= 3.12 +- `helmfile` >= 1.1.0 (recommended: `1.1.x`) +- `helm-diff` plugin >=3.11 + + +Avoid Helmfile 1.2.x. Helmfile 1.2.0 removed sequential execution mode, which +the NVCF stack requires for ordered deployments. Use version `1.1.x` for +compatibility with the commands in this guide. + +Helmfile `1.3.0+` re-introduced sequential execution via the `--sequential-helmfiles` flag, but the command syntax differs from the `1.1.x` examples shown here. If you choose to use `1.3.0+`, add `--sequential-helmfiles` to every `helmfile apply` and `helmfile sync` command. + + + +- A kubernetes cluster (CSP agnostic or on-prem). +- Gateway API ingress prepared as described in [Gateway quickstart](./gateway-routing.md#gateway-quickstart) if you are exposing NVCF through Gateway API +- Artifacts must be available in a registry that your Kubernetes cluster can access. This can be the `nvcf-onprem` registry for NVCF control plane service artifacts, but function containers and helm charts must be configured to a user-managed registry. See [self-hosted-artifact-manifest](./manifest.md) and [self-hosted-image-mirroring](./image-mirroring.md). +- The `nvcf-self-managed-stack` repository must be downloaded to your local machine (see [download-nvcf-self-managed-stack](./image-mirroring.md)). + + +```bash +# Install helm-diff plugin (required for helmfile) +helm plugin install https://github.com/databus23/helm-diff +``` + + + +kubectl version must match your cluster within one minor version. Using a +kubectl version that is more than one minor version ahead of your Kubernetes +cluster will cause `kubectl apply` and `kubectl patch` commands to fail, not +just warn, due to stricter server-side field validation in newer clients. + +This is especially common on macOS with Homebrew, where `brew install kubectl` +or `brew upgrade` can silently install a version much newer than your cluster. +Verify before proceeding: + +```bash +kubectl version +# Ensure the Client Version and Server Version are within one minor version of each other. +# Example: Client v1.32.x against Server v1.31.x is OK. +# Client v1.32.x against Server v1.29.x will cause failures. +``` + +If your client is too new, install a matching version directly from the [Kubernetes release page](https://kubernetes.io/docs/tasks/tools/). + + + +### Access Requirements + +- `kubectl` configured to the kubernetes cluster you are deploying to + +- Personal NGC API Key from [ngc.nvidia.com](https://ngc.nvidia.com) + authenticated with `nvcf-onprem` organization only if you pull artifacts + directly from NGC or use NGC as your registry + +- Registry credentials for your container registry (ECR, NGC, etc.). See + [third-party-registries-self-hosted](./third-party-registries.md) for setup + instructions + +- Local Helm/Docker authentication to your container registry where NVCF charts + are stored. Helmfile pulls OCI charts during deployment, so your local + environment must be authenticated. Examples: + + - AWS ECR: `aws ecr get-login-password --region | helm registry login --username AWS --password-stdin .dkr.ecr..amazonaws.com` + - NGC: `docker login nvcr.io -u '$oauthtoken' -p ` + - Other registries: Use `docker login` or `helm registry login` as appropriate for your registry + + +If you are using NGC as your registry, you will use your NGC API key when generating the base64 registry credential in Step 3. Exporting `NGC_API_KEY` is optional and only needed if you prefer to reuse it in commands. + + + +## Installation Steps + +The installation flow is as follows. + +1. Prepare Gateway API ingress +2. Configure your environment file (`environments/.yaml`) +3. Configure your secrets file (`secrets/-secrets.yaml`) +4. Configure image pull secrets (skip if using a CSP registry with built-in credential helpers) +5. Deploy the NVCF control plane components +6. Verify the control plane +7. Register each GPU cluster with the control plane +8. Install the NVCA operator on each GPU cluster + +### Step 1. Prepare Gateway API ingress + +Complete [Gateway quickstart](./gateway-routing.md#gateway-quickstart) before you +configure and apply the Helmfile stack. + +Keep these values from the Gateway quickstart: + +```bash +echo "$GATEWAY_ADDR" +echo "$HTTP_GATEWAY_NAMESPACE/$HTTP_GATEWAY_NAME" +echo "$GRPC_GATEWAY_NAMESPACE/$GRPC_GATEWAY_NAME" +``` + +Use `GATEWAY_ADDR` as `global.domain` in your environment file. Use the Gateway +names, namespaces, and listener names from Gateway quickstart in +`ingress.gatewayApi.gateways`. + +Split or multi-cluster gRPC invocation is not enabled by default. If you need +workers in a compute cluster to reach grpc-proxy in the control-plane cluster, +complete [gRPC Invocation Enablement](./grpc-invocation-enablement.md) before +you deploy or sync the control plane. + + +The Gateway address is embedded throughout your deployment. The `domain` value +in your environment file, the Gateway API HTTPRoutes/TCPRoutes, and service +discovery all depend on this address. If the Gateway or its underlying load +balancer is deleted and recreated (e.g., due to a TCPRoute misconfiguration), a +new address will be assigned. + +If the address changes after deployment, you must update the `domain` in your +environment file and re-sync the affected releases. See +[Recovering from Gateway Address Changes](#recovering-from-gateway-address-changes) +for the procedure. + + + +### Step 2. Configure your environment file (`environments/.yaml`) + +Environment configuration files define how NVCF is deployed in your specific environment. They are YAML files that provide values to the Helm charts. + +Set `HELMFILE_ENV` to your environment name and copy the base configuration. +The filename must match `HELMFILE_ENV` because Helmfile uses it to select the +environment file. The template below shows the values to configure for Amazon +EKS ([cp-env-eks-example.yaml](https://raw.githubusercontent.com/NVIDIA/nvcf/main/docs/v0.6.0/samples/configs/cp-env-eks-example.yaml)). + +```bash +cd path/to/nvcf-self-managed-stack +export HELMFILE_ENV="" +cp environments/base.yaml "environments/${HELMFILE_ENV}.yaml" +``` + + + +The following example shows a typical configuration for Amazon EKS: + +```yaml title="environments/eks-example.yaml" +global: + + # Domain for external access (used by Gateway API HTTPRoutes) + domain: "GATEWAY_ADDR" # Replace with ELB domain + + # ============================================================================= + # Helm Chart Sources Configuration + # ============================================================================= + # Configure the OCI registry where NVCF Helm charts are stored. + # This must point to a registry containing the NVCF chart packages. + # ============================================================================= + helm: + sources: + registry: .dkr.ecr..amazonaws.com + repository: + # NGC Example: + # registry: nvcr.io + # repository: YOUR_ORG/YOUR_TEAM # e.g. 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: + + # ============================================================================= + # Container Image Registry Configuration + # ============================================================================= + # Configure the container registry where NVCF service images are stored. + # These images are pulled by Kubernetes when deploying the NVCF stack. + # ============================================================================= + image: + registry: .dkr.ecr..amazonaws.com + repository: + # NGC Example: + # registry: nvcr.io + # repository: YOUR_ORG/YOUR_TEAM # e.g. 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: + + nodeSelectors: + enabled: true # Set true when using dedicated node labels for NVCF workloads + vault: + key: nvcf.nvidia.com/workload + value: vault + cassandra: + key: nvcf.nvidia.com/workload + value: cassandra + controlplane: + key: nvcf.nvidia.com/workload + value: control-plane + + storageClass: "gp3" # Customize to your storage class + storageSize: "10Gi" # Customize to your storage size + + # ============================================================================= + # Observability Configuration + # ============================================================================= + # Enable distributed tracing via OTLP (dsiabled by default). + # This must point to an OTLP-compatible collector. + # ============================================================================= + observability: + tracing: + enabled: false + collectorEndpoint: "" + collectorPort: 4317 + collectorProtocol: http + # Example: + # enabled: true + # collectorEndpoint: + # collectorPort: + # collectorProtocol: + +fakeGpuOperator: + enabled: false # If deploying locally with no GPUs, true + ubuntu: + imageName: alpine-k8s + tag: 1.30.12 + +accounts: # Default NVCF account configuration + limits: + maxFunctions: 10 + maxTasks: 10 # Note: Tasks (NVCT) are not currently supported for EA + maxTelemetries: 10 # Note: BYOO is not currently supported for EA + maxRegistryCreds: 10 + +# These static global values are processed in the values template +nats: + enabled: true + +cassandra: + enabled: true + +openbao: + enabled: true + migrations: + issuerDiscovery: + enabled: true # Recommended true for EKS - discovers OIDC issuer automatically + +# Ingress Gateway Configuration +ingress: + gatewayApi: + enabled: true + controllerNamespace: "envoy-gateway-system" # must be set by the environment + routes: + nvcfApi: + routeAnnotations: {} + apiKeys: + routeAnnotations: {} + invocation: + routeAnnotations: {} + grpc: + routeAnnotations: {} + gateways: + shared: + name: "nvcf-gateway" # must be set by the environment + namespace: "envoy-gateway" # must be set by the environment + listenerName: http + grpc: + name: "nvcf-gateway" # must be set by the environment + namespace: "envoy-gateway" # must be set by the environment + listenerName: tcp +``` + + +#### `domain` and `ingress` Configuration + +The `domain` and `ingress` sections of the environment file are used to configure the external access to the NVCF control plane. + +If using the above example directly for EKS, replace `GATEWAY_ADDR` with the Gateway load balancer address from [Gateway quickstart](./gateway-routing.md#gateway-quickstart). + +```yaml +domain: "GATEWAY_ADDR" # Replace with ELB domain +``` + +If using the above example directly for EKS, your ingress configuration would look like this: + +```yaml +ingress: + gatewayApi: + enabled: true + controllerNamespace: "envoy-gateway-system" + routes: + nvcfApi: + routeAnnotations: {} + apiKeys: + routeAnnotations: {} + invocation: + routeAnnotations: {} + grpc: + routeAnnotations: {} + gateways: + shared: + name: "nvcf-gateway" + namespace: "envoy-gateway" + listenerName: http + grpc: + name: "nvcf-gateway" + namespace: "envoy-gateway" + listenerName: tcp +``` + +#### `nodeSelectors` Configuration + +The `nodeSelectors` section of the environment file is used to configure the nodes on which the NVCF control plane components are deployed. Disable this unless you have a cluster with node selectors pre-configured on node pools within your cluster. + +If your cluster uses dedicated node labels for NVCF workloads, enable this section with the following configuration: + +```yaml +nodeSelectors: + enabled: true + vault: + key: nvcf.nvidia.com/workload + value: vault + cassandra: + key: nvcf.nvidia.com/workload + value: cassandra + controlplane: + key: nvcf.nvidia.com/workload + value: control-plane +``` + +#### `cassandra` Resource Tuning + +Cassandra needs enough memory to complete first boot, commit-log replay, and the schema migration hooks. The default self-managed stack uses `cassandra.resourcesPreset: xlarge`, which maps to a Bitnami Cassandra preset with a 3 GiB memory request and a 6 GiB memory limit. Do not use the `small` preset for cloud installs. It can OOM-kill Cassandra during initialization and cause migration failures. + +Common preset values: + +| Preset | Requests | Limits | +| --- | --- | --- | +| `small` | 500m CPU, 512Mi memory | 750m CPU, 768Mi memory | +| `large` | 1 CPU, 2048Mi memory | 1.5 CPU, 3072Mi memory | +| `xlarge` | 1 CPU, 3072Mi memory | 3 CPU, 6144Mi memory | +| `2xlarge` | 1 CPU, 3072Mi memory | 6 CPU, 12288Mi memory | + +All listed presets include a 50Mi ephemeral-storage request and 2Gi ephemeral-storage limit. + +If Cassandra pods restart with `OOMKilled`, or the `cassandra-migrations` job fails with a consistency-level error while Cassandra pods are restarting, increase the preset in your environment file: + +```yaml +cassandra: + resourcesPreset: "2xlarge" +``` + +Then apply the change to just Cassandra: + +```bash +HELMFILE_ENV= helmfile --selector name=cassandra sync +``` + + +For local development, a lower preset may be acceptable when the environment also reduces Cassandra to one replica. For cloud installs, start with `xlarge` or higher and tune from there. + + + +#### `helm` and `image` Configuration + +The `helm` and `image` sections tell NVCF which registries to pull Helm charts and container images from. + +- `helm.sources`: The OCI registry where NVCF Helm charts are stored. Helmfile pulls charts from here at deploy time (requires local authentication. See [Access Requirements](#access-requirements)). +- `image`: The container registry where NVCF service images are stored. Kubernetes pulls images from here at runtime. + +```yaml +# Helm Chart Sources Configuration +helm: + sources: + registry: "nvcr.io" + repository: "YOUR_ORG/YOUR_TEAM" + # NGC Example: + # registry: nvcr.io + # repository: 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: + +# Container Image Registry Configuration +image: + registry: nvcr.io + repository: YOUR_ORG/YOUR_TEAM + # NGC Example: + # registry: nvcr.io + # repository: 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: +``` + + +If you have mirrored NVCF artifacts to your own registry (e.g., ECR), update both `helm.sources` and `image` to point to your mirror. See [self-hosted-image-mirroring](./image-mirroring.md) for details on mirroring artifacts. + +When upgrading to a new `nvcf-self-managed-stack` version, re-mirror all artifacts before running `helmfile sync`. Each stack release may introduce new or updated container images and Helm charts. If these are not present in your private registry, pods will fail with `ImagePullBackOff`. For split installs, mirror both the control-plane and compute-plane stack resources listed in the [self-hosted-artifact-manifest](./manifest.md). + + + + +Pulling directly from NGC is the recommended approach and avoids the need to +manually mirror artifacts on every upgrade. If your environment permits it, +configure `helm.sources` and `image` to point to the NGC registry (`nvcr.io`) +and use your NGC API key for authentication. This ensures you always have access +to the latest artifacts without additional mirroring steps. + + + + +These settings control *where* images are pulled from, not *how* Kubernetes authenticates to pull them. If your `image` registry is private, you may also need to configure image pull secrets -- see [Step 4](./helmfile-installation.md). + + + + +Quick start summary: If you are using the example EKS environment YAML directly +and followed [Gateway quickstart](./gateway-routing.md#gateway-quickstart), you +only need to change: + +1. `domain`: Replace `GATEWAY_ADDR` with the Gateway load balancer address +2. `helm.sources.registry` and `helm.sources.repository`: Point to your Helm chart registry +3. `image.registry` and `image.repository`: Point to your container image registry + + + +#### Overriding Helm Chart Values + + +The environment file (`environments/.yaml`) controls global +settings like `domain`, `image`, and `nodeSelectors`. However, you may need to +override values for a specific Helm chart, for example to increase Cassandra +memory limits or change an image tag for one service. + +Helmfile releases support a `values` property that passes values through to the underlying `helm install`/`helm upgrade` command. To add chart-specific overrides, edit the release definition in the appropriate file under `helmfile.d/` and add a `values` block: + +```yaml +# Example: helmfile.d/01-dependencies.yaml.gotmpl +- name: cassandra + version: 0.9.0 + condition: cassandra.enabled + namespace: cassandra-system + <<: *dependency + values: + - ../global.yaml.gotmpl + - ../secrets/{{ requiredEnv "HELMFILE_ENV" }}-secrets.yaml + - cassandra: + resources: + requests: + cpu: "2" + memory: 4096Mi + limits: + cpu: "8" + memory: 8192Mi +``` + + +When a release inherits from a template (`<<: *dependency`), specifying `values` +on the release replaces the template's `values` list. YAML merge does not append +lists. You must re-include `global.yaml.gotmpl` and the secrets file. + + + +The `values` block is a list of YAML mappings. Keys correspond to the chart's `values.yaml` structure. For example, to override a deeply nested value: + +```yaml +values: + - api: + image: + tag: 2.223.9 + env: + NVCF_REGISTRIES_ACCOUNT_PROVISIONING_ARTIFACT_TYPES: "CONTAINER,HELM" +``` + +Values defined here take the highest precedence, overriding both the environment +file and `global.yaml.gotmpl`. Use `helmfile template` to preview the rendered +manifests after adding overrides, then apply to a single release: + +```bash +# Preview changes +HELMFILE_ENV= helmfile --selector name=cassandra template + +# Apply changes to just that release +HELMFILE_ENV= helmfile --selector name=cassandra sync +``` + + +#### Worker Image Version Overrides + +The NVCF API uses the worker image versions in its `nvcf.sidecars.*` +configuration. To pin worker sidecars for one Helmfile deployment, add an inline +remote-config override to the `api` release in `helmfile.d/02-core.yaml.gotmpl`: + +```yaml +- name: api + version: 1.19.3 + namespace: nvcf + inherit: + - template: service + values: + - ../global.yaml.gotmpl + - ../secrets/{{ requiredEnv "HELMFILE_ENV" }}-secrets.yaml + - api: + remoteConfig: + enabled: true + configData: + nvcf: + sidecars: + init-container: "${nvcf.sidecars.hostname}/${nvcf.sidecars.repository}/nvcf_worker_init:" + utils-container-image: + go: "${nvcf.sidecars.hostname}/${nvcf.sidecars.repository}/nvcf_worker_utils:" + niclls-container: "${nvcf.sidecars.hostname}/${nvcf.sidecars.repository}/nvcf_worker_niclls:" + needs: + - ess/ess-api +``` + +The `${nvcf.sidecars.hostname}` and `${nvcf.sidecars.repository}` placeholders +resolve from the stack image registry and repository settings. Re-include +`global.yaml.gotmpl` and the secrets file because defining `values` on the +release replaces the inherited `values` list. Keep the existing `needs` entry on +the release. + +### Step 3. Configure your secrets file (`secrets/-secrets.yaml`) + +Secrets configuration contains any sensitive data required for NVCF operation. The image pull secret credentials you insert here will be used to bootstrap the NVCF API with registry credentials for all worker components (function sidecars), function containers and helm charts. + +These credentials will then be used for function deployments. Note that if the registry credentials are not correct you can always update them using the steps in [third-party-registries-self-hosted](./third-party-registries.md). + +Copy the secrets template using the same `HELMFILE_ENV` value from Step 2. The +filename must match `HELMFILE_ENV` because Helmfile loads the corresponding +secrets file. The example below shows the required structure +([example-secrets.yaml](https://raw.githubusercontent.com/NVIDIA/nvcf/main/docs/v0.6.0/samples/configs/cp-example-secrets.yaml)). You must +replace all instances of `REPLACE_WITH_BASE64_DOCKER_CREDENTIAL` with your +actual base64-encoded registry credentials. + +```bash +cd path/to/nvcf-self-managed-stack +cp secrets/secrets.yaml.template "secrets/${HELMFILE_ENV}-secrets.yaml" +``` + + + +```yaml title="secrets/example-secrets.yaml" + +# Required structure for any environment secrets. +# This is the minimal set of values to provide. + +# Notes: +# Cassandra: +# The password should match the value set in the cassandra keyspace migrations +# +# API: +# The value for the registry will be used in three places, as it is +# expected the same registry is used as a single source for all images. +# openbao.migrations.env[1].value +# api.accountBootstrap.registryCredentials[0].secret.value +# api.accountBootstrap.registryCredentials[1].secret.value + +openbao: + migrations: + env: + # Stored in OpenBao shared secrets (written by migration job) + - name: DEFAULT_CASSANDRA_PASSWORD + value: "ch@ng3m3" + # Stored in OpenBao KV for nvcf-api (written by migration job) + - name: NVCF_API_SIDECARS_IMAGE_PULL_SECRET + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + - name: ADMIN_CLIENT_ID + value: ncp # <- keep this value + +api: + accountBootstrap: + registryCredentials: + - registryHostname: nvcr.io # ECR: .dkr.ecr..amazonaws.com + secret: + name: nvcr-containers # ECR: ecr-containers + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + artifactTypes: ["CONTAINER"] + tags: [] + description: "NGC Container registry" + - registryHostname: helm.ngc.nvidia.com # ECR: .dkr.ecr..amazonaws.com + secret: + name: nvcr-helmcharts # ECR: ecr-helmcharts + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + artifactTypes: ["HELM"] + tags: [] + description: "NGC Helm registry" +``` + + + +NVCF supports these registries for function containers (set in +api.accountBootstrap.registryCredentials): ACR (Azure), ECR (AWS), NVCR +(NVIDIA), VolcEngine CR, JFrog/Artifactory, and Harbor. + + + +#### Generating Base64-encoded Registry Credentials + +Registry credentials must be base64-encoded in the format `username:password`. For detailed instructions on setting up credentials for specific registries (including IAM user creation for ECR), see [third-party-registries-self-hosted](./third-party-registries.md). + + + + +```bash +# Replace YOUR_NGC_API_KEY with your actual personal NGC API key from ngc.nvidia.com +printf '%s' '$oauthtoken:YOUR_NGC_API_KEY' | base64 | tr -d '\n' +``` + + + + + +For AWS ECR, NVCF requires permanent IAM credentials. You must first create a +dedicated IAM user with ECR permissions. See +[ecr-registry-setup](./third-party-registries.md) for complete setup +instructions. + +Once you have created the IAM user and obtained the access keys: + +```bash +# Replace with your IAM user's access key ID and secret access key +ACCESS_KEY_ID="" +SECRET_ACCESS_KEY="" + +printf '%s' "${ACCESS_KEY_ID}:${SECRET_ACCESS_KEY}" | base64 | tr -d '\n' +``` + + + + + +Once you have your VolcEngine Access Key ID and Secret Access Key (see [vcr-registry-setup](./third-party-registries.md) for full details): + +```bash +# Replace with your VolcEngine Access Key ID and Secret Access Key +ACCESS_KEY_ID="" +SECRET_ACCESS_KEY="" + +printf '%s' "${ACCESS_KEY_ID}:${SECRET_ACCESS_KEY}" | base64 | tr -d '\n' +``` + + + + + +Set kubectl to the control-plane cluster context before proceeding. Steps 4 +and 5 run kubectl and helmfile commands that target the current context. In a +multi-cluster setup, verify the context is the control-plane cluster to avoid +installing to the wrong cluster. + +```bash +kubectl config use-context +kubectl config current-context +``` + +### Step 4. Configure image pull secrets (conditional) + + +Skip this step if you have mirrored NVCF artifacts to a CSP-managed registry +(e.g., ECR) and are using a CSP-managed registry with built-in credential +helpers (e.g., AWS ECR with IAM node roles, GKE Artifact Registry with Workload +Identity, Azure ACR with managed identity). Kubernetes can pull images +automatically in those environments. + + + +The secrets file you configured in Step 3 handles API bootstrap registry +credentials. These allow the NVCF API service to pull user function containers +at runtime. Separately, Kubernetes itself needs image pull secrets to pull the +NVCF control plane service images (API, SIS, Cassandra, etc.) from your +registry. + +If your `image` registry is private and your cluster nodes do not have built-in credential helpers, you must create Kubernetes `docker-registry` secrets in each NVCF namespace and configure the helmfile to reference them. + +1. Create the pull secret in each NVCF namespace + ([create-nvcr-pull-secrets.sh](https://raw.githubusercontent.com/NVIDIA/nvcf/main/docs/v0.6.0/samples/scripts/create-nvcr-pull-secrets.sh)): + +```bash +export NGC_API_KEY="" + +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system cert-manager; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - +done + +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system cert-manager; do + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="$NGC_API_KEY" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +For registries other than NGC, replace `--docker-server`, `--docker-username`, and `--docker-password` with your registry credentials. + +2. Reference the secret in your Helmfile environment. The Helmfile propagates + `imagePullSecrets` to all NVCF charts automatically. Add the secret name to + your environment YAML (e.g. `environments/.yaml`): + +```yaml +global: + imagePullSecrets: + - name: nvcr-pull-secret +``` + +This replaces any need for a separate admission controller or policy engine to inject pull secrets. + +### Step 5. Deploy the NVCF control plane components + +Confirm your kubectl context is still set to the control-plane cluster (see +above). + + +Ensure your local environment is authenticated to the container registry where +your NVCF Helm charts are stored (see +[Access Requirements](#access-requirements)). Helmfile pulls OCI charts during +deployment and will fail if not authenticated. + + + +Before deploying, preview the rendered Kubernetes manifests: + +```bash +cd path/to/nvcf-self-managed-stack +HELMFILE_ENV= helmfile template +``` + +This command will: + +1. Render all Helm charts with your environment and secrets +2. Run validation hooks +3. Display the resulting Kubernetes manifests + + +Review the output carefully to ensure: + +- Container image references are correct +- Storage classes match your clusters + + + +Deploy the self-managed stack: + +```bash +HELMFILE_ENV= helmfile sync +``` + + +The initial deployment takes approximately 5-10 minutes for local development +and 10-20 minutes for cloud deployments. + + + +#### Deployment Progression and Monitoring + +Helmfile will deploy services in the correct order with dependencies: + +Phase 1: dependency layer (5-10 minutes) + +- NATS messaging service +- OpenBao (secrets management) +- Cassandra (database) +- Helmfile selector: `release-group=dependencies` + +Phase 2: control-plane services (5-10 minutes) + +- NVCF API Service +- SIS (Spot Instance Service) +- gRPC Proxy +- Invocation Service +- API Keys Service +- ESS API +- Notary Service +- Admin Issuer Proxy +- Helmfile selector: `release-group=services` + + +Monitor for account bootstrap failures. Once Helmfile reaches Phase 3, open a +separate terminal and watch events in the `nvcf` namespace: + +```bash +kubectl get events -n nvcf -w +``` + +The account bootstrap job runs as a post-install hook and is the most common +failure point, usually due to environment or secrets misconfiguration. If it +fails, see +[Recovering from Partial Deployments](#recovering-from-partial-deployments) for +recovery steps. + + + +Phase 3: ingress configuration (1-2 minutes) + +- Gateway API Routes (if enabled) +- Helmfile selector: `release-group=ingress` + +GPU clusters are installed after the control plane succeeds. Use +[Step 7. Configure the compute-plane Helmfile environment](#step-7-configure-the-compute-plane-helmfile-environment) +and [Step 8. Register and install each GPU cluster](#step-8-register-and-install-each-gpu-cluster) +for the split compute-plane bundle. + +Open a separate terminal to monitor the deployment progress: + +Monitor each deployment phase: + +```bash +# Check namespace creation and preparation +kubectl get ns + +# Phase 1: Check dependency services (release-group=dependencies) +kubectl get pods -n nats-system # Should see nats-0, nats-1, nats-2 +kubectl get pods -n vault-system # Should see openbao-server-0, openbao-server-1, openbao-server-2 +kubectl get pods -n cassandra-system # Should see cassandra-0, cassandra-1, cassandra-2 +# Note: It's normal to see cassandra-initialize-cluster pods with "Error" status. +# The initialization job retries on failure - as long as one pod shows "Completed" +# and cassandra-migrations is Running/Completed, the deployment is progressing normally. + +# Phase 2: Check control plane services (release-group=services) +kubectl get events -n nvcf -w # Watch for account bootstrap failures +kubectl get pods -n nvcf # API, invocation-service, grpc-proxy, notary-service +kubectl get pods -n sis # Spot Instance Service +kubectl get pods -n api-keys # API Keys service, admin-issuer-proxy +... + +# Phase 3: Check ingress (release-group=ingress) +kubectl get httproutes -A # Gateway API routes (if enabled) +``` + + +Cassandra initialization pods showing `Error` is expected. The +`cassandra-initialize-cluster` job runs multiple pods in parallel and retries +on failure. It is normal to see one or more pods with `Error` status. The +deployment is healthy as long as at least one initialization pod reaches +`Completed` and the `cassandra-migrations` job completes successfully. + + + + +If any pod remains in `Pending`, `ContainerCreating`, or `ImagePullBackOff` state for more than 5 minutes, see [self-hosted-troubleshooting](./troubleshooting.md) for issue identification commands and solutions. + + + +#### Recovering from Partial Deployments + + +Do not attempt to fix a partially failed deployment by re-running `helmfile sync` or `helmfile apply`. Helm releases in a failed state will skip initialization hooks on subsequent runs, leading to incomplete deployments that appear successful but don't function correctly. + + + +Redeploying dependencies if needed: + +If a dependency service (Cassandra, NATS, OpenBao) fails or gets stuck, you can +safely redeploy it individually: + +```bash +# Redeploy only Cassandra +HELMFILE_ENV= helmfile --selector name=cassandra apply + +# Redeploy all dependencies (NATS, Cassandra, OpenBao) +HELMFILE_ENV= helmfile --selector release-group=dependencies apply +``` + +Recovering from services failures without destroying dependencies: + +If the `release-group=services` deployment hangs or fails (for example, account bootstrap failure due to secrets misconfiguration), you can recover without destroying your dependencies. + +1. Monitor for failures: + +In a separate terminal, watch events in the nvcf namespace: + +```bash +kubectl get events -n nvcf -w +``` + +2. Check the account bootstrap logs if it failed: + +```bash +kubectl logs job/nvcf-api-account-bootstrap -n nvcf +``` + + +The bootstrap job auto-deletes after ~5 minutes. Monitor events to catch failures in real-time. + + + +3. Check the NVCF API logs for detailed error messages: + +```bash +kubectl logs -n nvcf -l app.kubernetes.io/name=nvcf-api --tail=100 +``` + +4. Fix the root cause, for example correct your + `secrets/-secrets.yaml` file. + +5. Destroy the services and downstream releases: + +```bash +# Destroy services release group +HELMFILE_ENV= helmfile --selector release-group=services destroy + +# Destroy downstream releases (ingress, admin-issuer-proxy) +HELMFILE_ENV= helmfile --selector release-group=ingress destroy +HELMFILE_ENV= helmfile --selector name=admin-issuer-proxy destroy +``` + +6. Clean up the service namespaces: + +```bash +kubectl delete namespace nvcf api-keys ess sis --ignore-not-found +``` + +7. Recreate namespaces and labels. Gateway API routing requires these labels: + +```bash +kubectl create namespace api-keys && \ +kubectl create namespace ess && \ +kubectl create namespace sis && \ +kubectl create namespace nvcf + +kubectl label namespace api-keys nvcf/platform=true && \ +kubectl label namespace sis nvcf/platform=true && \ +kubectl label namespace ess nvcf/platform=true && \ +kubectl label namespace nvcf nvcf/platform=true +``` + +8. Re-sync services. This triggers fresh post-install hooks: + +```bash +HELMFILE_ENV= helmfile --selector release-group=services sync +``` + +9. Sync remaining releases after services succeed: + +```bash +HELMFILE_ENV= helmfile --selector name=admin-issuer-proxy sync +HELMFILE_ENV= helmfile --selector release-group=ingress sync +``` + +Full restart if dependencies are also broken: + +If dependencies are corrupted or you prefer a clean slate, follow the complete +[Uninstalling](#uninstalling) steps, fix your configuration, then redeploy from +Step 1. + +#### Enabling Vanity Gateway + +Vanity Gateway is optional and disabled by default. It is available only in +stack packages that include the Vanity Gateway addon. If your extracted stack +package does not contain a `vanity-gateway` release and `vanityGateway` route +values, skip this section until you use a stack package that includes them. + +Enable it only when you need a customer-facing hostname or path mapping layer in +front of the standard NVCF service routes. + +In stack packages that include the addon, set the value shape in your +environment file: + +```yaml +addons: + vanityGateway: + enabled: true + mappingConfig: {} +``` + +By default, the route host is `vanity.` and the backend is +`vanity-gateway.nvcf:8080`. Put deployment-specific host and path mappings under +`addons.vanityGateway.mappingConfig`. If your deployment needs custom vanity +hosts, use the route hostname overrides supported by your stack package and +create matching DNS records. + +After confirming your stack package includes the `vanity-gateway` release, +preview and apply the service plus gateway routes: + +```bash +HELMFILE_ENV= helmfile --selector name=vanity-gateway template +HELMFILE_ENV= helmfile --selector name=vanity-gateway sync +HELMFILE_ENV= helmfile --selector release-group=ingress sync +``` + +Verify only when the addon is present and enabled: + +```bash +kubectl get deploy,svc -n nvcf -l app.kubernetes.io/name=vanity-gateway +kubectl get httproute -A | grep -i vanity +curl -H "Host: vanity." "http:///health" +``` + +#### Recovering from Gateway Address Changes + +If your Gateway or its underlying load balancer was deleted and recreated (e.g., due to a TCPRoute misconfiguration or infrastructure change), the external address will change. Services that depend on the `domain` value -- including Gateway API routes, SIS cluster registration, API hostname resolution, and the optional Vanity Gateway route -- will break until the new address is propagated. + +1. Get the new Gateway address: + +```bash +GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway -o jsonpath='{.status.addresses[0].value}') +echo "$GATEWAY_ADDR" +``` + +2. Update your environment file with the new address: + +```bash +# Edit environments/.yaml +# Change: domain: "OLD_ADDRESS" +# To: domain: "NEW_GATEWAY_ADDR" +``` + +3. Re-sync ingress and services that depend on the domain: + +```bash +# Re-sync gateway routes (picks up new domain) +HELMFILE_ENV= helmfile --selector release-group=ingress sync + +# Re-sync services that embed the domain (API, admin-issuer-proxy) +HELMFILE_ENV= helmfile --selector release-group=services sync +HELMFILE_ENV= helmfile --selector name=admin-issuer-proxy sync +``` + +4. Verify routes are using the new address: + +```bash +kubectl get httproutes -A +kubectl get tcproutes -A +``` + + +If you encounter issues during deployment, consult the [self-hosted-troubleshooting](./troubleshooting.md) guide for common problems and solutions. + + + +### Step 6: Verify the Installation + +Verify the installation is successful by checking the pods are running and the helm releases are successful. + +```bash +# View all pods with node assignment and status, should all be Running or Completed state +kubectl get pods -A -o wide + +# Check helm releases status +helm list -A +``` + +#### Verify API Connectivity (Optional) + +If you configured Gateway API ingress, you can verify the NVCF API is accessible by running the following commands. + +1. Set up environment variables: + +```bash +# Get the Gateway address from Gateway quickstart +export GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway -o jsonpath='{.status.addresses[0].value}') +echo "Gateway Address: $GATEWAY_ADDR" +``` + +2. Generate an admin token: + +```bash +# Generate an admin API token +export NVCF_TOKEN=$(curl -s -X POST "http://${GATEWAY_ADDR}/v1/admin/keys" \ + -H "Host: api-keys.${GATEWAY_ADDR}" \ + | grep -o '"value":"[^"]*"' | cut -d'"' -f4) + +echo "Token generated: ${NVCF_TOKEN:0:20}..." +``` + +3. List functions. The list should be empty initially: + +```bash +# List all functions +curl -s -X GET "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" | jq . +``` + +### Step 7. Configure the compute-plane Helmfile environment + +Use `deploy/stacks/nvcf-compute-plane` from the source repository for each GPU +cluster. The compute-plane Helmfile installs `helm-nvca-operator` and wires it +to the control plane values returned by cluster registration. + +Create an environment file in the compute-plane directory. Use the same Helm +chart and image registry mirror as the control plane. Set the service URLs to +addresses reachable from the GPU cluster. If the GPU cluster reaches the +control plane through one load balancer with hostname-based Gateway routing, +keep the URL pointed at the load balancer and set the host-header overrides to +the route hostnames. + +```bash +cd path/to/nvcf +touch deploy/stacks/nvcf-compute-plane/environments/.yaml +``` + +```yaml title="deploy/stacks/nvcf-compute-plane/environments/.yaml" +global: + helm: + sources: + registry: + repository: + + image: + registry: + repository: + + imagePullSecrets: + - name: nvcr-pull-secret + + nvcaOperator: + selfManaged: + icmsServiceURL: "http://" + icmsServiceHostHeaderOverride: "sis." + revalServiceURL: "http://" + revalServiceHostHeaderOverride: "reval." + natsURL: "nats://:4222" + natsHostOverride: "nats." +``` + +If your GPU cluster can resolve per-service DNS names directly, set the service +URLs to those names and omit the host-header override fields. + +Create the pull secret in `nvca-operator` on the GPU cluster before installing +NVCA. Helmfile references the secret from `global.imagePullSecrets`, and the +operator propagates it to the managed namespaces after installation. + +```bash +kubectl --kubeconfig \ + create namespace nvca-operator --dry-run=client -o yaml | \ + kubectl --kubeconfig apply -f - +kubectl --kubeconfig \ + create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="" \ + --namespace=nvca-operator \ + --dry-run=client -o yaml | kubectl --kubeconfig apply -f - +``` + +### Step 8. Register and install each GPU cluster + +Register each GPU cluster before installing NVCA. Registration discovers the +GPU cluster's OIDC issuer and JWKS, records them with SIS/ICMS, and writes the +cluster identity values that the operator chart consumes. + +Use `KUBECONFIG_FILE` for multi-cluster installs. It makes both registration and +Helmfile target the GPU cluster instead of the control-plane cluster. +For a complete Amazon EKS example, see the +[CSP End-to-End Example](./csp-end-to-end-example-installation.md). + +The compute-plane Makefile runs `nvcf-cli init` before `cluster register`. Point +`NVCF_CLI_CONFIG` at a CLI config that can reach the control-plane gateway. + +```yaml title="nvcf-cli-gpu-register.yaml" +base_http_url: "http://" +invoke_url: "http://" +api_keys_service_url: "http://" +icms_url: "http://" + +api_keys_host: "api-keys." +api_host: "api." +icms_host: "sis." +invoke_host: "invocation." + +api_keys_service_id: "nvidia-cloud-functions-ncp-service-id-aketm" +api_keys_issuer_service: "nvcf-api" +api_keys_owner_id: "svc@nvcf-api.local" +client_id: "" +``` + +Run the compute-plane target from the repository root. The target writes +`deploy/stacks/nvcf-compute-plane/registration/-register-values.yaml`. + +```bash +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME= \ + NCA_ID= \ + CLUSTER_REGION= \ + ICMS_URL="http://" \ + KUBECONFIG_FILE= \ + NVCF_CLI= \ + NVCF_CLI_CONFIG= +``` + +Install the NVCA operator on that GPU cluster. The `install` target copies the +registration file into `out/` and runs Helmfile with +`HELMFILE_ENV=`. + +```bash +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME= \ + HELMFILE_ENV= \ + NCA_ID= \ + KUBECONFIG_FILE= +``` + +Verify the operator and backend on the GPU cluster: + +```bash +kubectl --kubeconfig \ + rollout status deployment/nvca-operator -n nvca-operator --timeout=10m + +kubectl --kubeconfig \ + get nvcfbackends -n nvca-operator + +kubectl --kubeconfig \ + get secret nvcr-pull-secret -n nvca-system + +kubectl --kubeconfig \ + get pods -n nvca-system +``` + +For a multi-cluster EKS install, this is the expected GPU cluster validation: +the control plane is installed, each GPU cluster is registered with its own +kubeconfig, the NVCA Operator is installed on that GPU cluster, and the +`NVCFBackend` reports a healthy agent. Repeat registration, install, and health +checks for each GPU cluster. Function execution also requires worker pods on the +GPU cluster to reach the control-plane worker endpoints. Do not use function +deployment or invocation as the acceptance check until those endpoints are +reachable from the GPU cluster. + +Repeat Step 8 for each additional GPU cluster. Use a unique `CLUSTER_NAME` for +each cluster. + +## Next Steps + +After the control plane and GPU clusters are installed, proceed to +[Self-Managed Clusters](./cluster-management/self-managed.md) for NVCA +operations and troubleshooting. + +## Uninstalling + + +This will delete all NVCF resources including data stored in persistent volumes. Ensure you have backups of any important data. + + + +To remove the NVCF installation: + +```bash +HELMFILE_ENV= helmfile destroy +``` + +After `helmfile destroy` completes, clean up the namespaces: + +```bash +# Delete NVCF namespaces +kubectl delete namespace cassandra-system nats-system vault-system \ + nvcf api-keys ess sis \ + --ignore-not-found +``` + +To also remove the Gateway infrastructure created by [Gateway quickstart](./gateway-routing.md#gateway-quickstart): + +```bash +# Delete the Gateway and GatewayClass resources +kubectl delete gateway nvcf-gateway -n envoy-gateway --ignore-not-found +kubectl delete gatewayclass eg --ignore-not-found + +# Uninstall Envoy Gateway +helm uninstall eg -n envoy-gateway-system + +# Delete the gateway namespaces +kubectl delete namespace envoy-gateway envoy-gateway-system --ignore-not-found + +# (Optional) Remove Gateway API CRDs if no longer needed +kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml +``` diff --git a/docs/v0.6.1-rc/http-load-test-sli-guide.md b/docs/v0.6.1-rc/http-load-test-sli-guide.md new file mode 100644 index 000000000..f497454ca --- /dev/null +++ b/docs/v0.6.1-rc/http-load-test-sli-guide.md @@ -0,0 +1,223 @@ +(http-load-test-sli-guide)= + +# HTTP Load Test SLI Guide + +This document describes which metrics to watch when load testing a self-hosted +NVCF deployment using direct HTTP invocations, what each metric indicates, and +how to interpret the failure sequence. Values are hardware-dependent -- what is +transferable is the order in which signals appear and what they mean. + +For run commands and cluster setup, see {ref}`self-managed-http-load-test`. + +--- + +## How Self-Hosted NVCF Handles HTTP Load + +- **Envoy Gateway** routes HTTP requests based on the `Host` header and is in + the return path for responses. It does not queue -- requests pass through + immediately or are rejected. Envoy enforces TCP connection timeouts: if a + connection is held open beyond the LB timeout, Envoy closes the socket + directly, producing an `EOF` error at the client with no HTTP status code. +- The **Invocation Service (IS)** receives direct HTTP invocation requests, + dispatches to the worker via NATS, and holds the connection open until the + worker responds or the configured hold-open duration expires. +- The **worker pod** serves inference over HTTP. Its concurrency limit + (`maxRequestConcurrency`) and inference time per request set the maximum + sustainable req/s. +- **NATS** dispatches work between the IS and worker. +- **NVCA** manages worker scaling. It only acts when scale-out is configured + (`minInstances < maxInstances`). + +**Primary data source**: the Invocation Service (Prometheus `job=invocation`). + +--- + +## SLIs to Monitor + +### Group 1: Leading Indicators + +These rise *before* errors appear. Use them to predict saturation. + +#### `axum_http_requests_total` (rate) + +**What it is**: Request rate at the IS per method and path. + +**What to look for**: + +- `rate(axum_http_requests_total{job="invocation", method="POST"}[1m])` + gives req/s. Should rise with load during healthy operation. +- **Plateau while VUs keep rising = worker throughput ceiling reached.** The + IS continues dispatching to NATS as fast as the worker can consume; + additional requests accumulate as in-flight HTTP connections rather than + increasing throughput. The IS is not the bottleneck. + +```none +rate(axum_http_requests_total{job="invocation", method="POST"}[1m]) +``` + +#### `axum_http_requests_pending` + +**What it is**: In-flight HTTP requests currently held at the IS, waiting for +the worker to respond. + +**What to look for**: + +- Non-zero at idle = IS under pressure before load even peaks. +- Rising without a matching rise in `axum_http_requests_total` rate = requests + are stacking at the IS faster than workers can drain them: + +```none +axum_http_requests_pending{job="invocation", method="POST"} +``` + +--- + +### Group 2: Throughput and Capacity + +#### `nvca_instance_type_allocatable` + +**What it is**: Number of worker slots available in the cluster fleet. + +**What to look for**: + +- Drops as workers are allocated to deployments. +- If allocatable reaches 0 on a fixed cluster: new worker deployments will fail with a no-capacity error + +```none +nvca_instance_type_allocatable{instance_type=""} +``` + +--- + +### Group 3: Lagging Indicators + +These confirm saturation after it has occurred. k6 is the primary source. + +#### `http_req_duration` p95 (k6) + +**What it is**: End-to-end request latency measured by k6. + +**What to look for**: + +- Rises steeply after the throughput plateau. +- Use p95 > 500ms as a warning, p95 > 2s as at/past saturation. + +**k6 metric**: `http_req_duration` (watch p90, p95 in k6 Cloud) + +#### `http_req_failed` (k6) + +**What it is**: k6 metric tracking the rate of failed http requests. + +**What to look for**: + +- Stays at 0% through moderate overload. +- **Error mode is `EOF`** -- TCP connection closed by Envoy, not the IS. + The IS holds each direct HTTP invocation connection open while waiting for + the worker. When the Envoy connection timeout fires first, the socket is + closed directly. The IS never sends an HTTP error response. The client sees + `EOF` with no status code. +- **Non-zero `http_req_failed` is a breaking-point signal**, not an early + warning. The system is well past the capacity wall by the time EOF errors + appear. + +**k6 metric**: `http_req_failed` (rate or count in k6 Cloud) + +#### `function_request_latency` p95 (worker-side) + +**What it is**: Per-request latency as measured by the worker itself -- the time +spent inside the function from the moment the worker picks up the request. + +**What to look for**: + +- Complements `http_req_duration` (client-side). If k6 p95 is high but + worker p95 is low, the bottleneck is queuing at the IS or Envoy, not + inference time. +- Rising worker latency under load indicates the worker itself is the + throughput ceiling. + +```none +histogram_quantile(0.95, rate(function_request_latency_bucket[1m])) +``` + +--- + +### Group 4: Stability Signals + +These should be zero during a clean load test. Any non-zero value warrants +investigation. + +| Metric | Threshold | What it means | +| --- | --- | --- | +| `nats_event_disconnected` | > 0 | IS lost NATS connection. in-flight requests will stall | +| `total_nats_errors` | > 0 | Cumulative NATS errors from the IS | +| `nvca_event_error_total{nvca_event_name="TICK_ACKNOWLEDGE_REQUEST"}` | > 0 | NVCA failing to acknowledge worker heartbeats | +| `nvca_container_crash_total` | > 0 | Worker pod OOM or crash | +| `nvca_controller_runtime_reconcile_errors_total` | > 0 | k8s controller errors in NVCA | +| `kube_pod_container_status_restarts_total{namespace="envoy-gateway-system"}` | > 0 during a run | Envoy gateway crash | +| `envoy_listener_downstream_cx_overflow{envoy_listener_address="0.0.0.0_10080"}` | > 0 | Envoy HTTP listener TCP connection ceiling hit | + +#### NATS JetStream + +NATS is the message bus between the IS and the worker. + +**Early-warning signal**: `axum_http_requests_pending` is still the earliest +IS-side queuing indicator. NATS connection counts and stream lag now provide +independent cross-checks. + +#### Envoy Gateway + +Envoy Gateway sits in both the request and return path for all HTTP invocations. +It enforces TCP connection timeouts and is the direct cause of EOF failures at +overload. + +Useful envoy signals during an HTTP test: + +```none +# Active downstream connections on the HTTP listener +envoy_listener_downstream_cx_active{envoy_listener_address="0.0.0.0_10080"} + +# Overflow -- TCP connection ceiling hit (should stay 0 unless saturated) +envoy_listener_downstream_cx_overflow{envoy_listener_address="0.0.0.0_10080"} + +# Envoy pod restart count (the April 2026 SIGSEGV signal) +sum(increase(kube_pod_container_status_restarts_total{namespace="envoy-gateway-system"}[$__range])) +``` + +--- + +## The Saturation Sequence + +Regardless of hardware, HTTP saturation follows this order: + +```none +1. axum_http_requests_total rate rises with load + ↓ +2. rate growth decouples from VU count ← LEADING SIGNAL + ↓ +3. axum_http_requests_pending climbs ← QUEUING AT IS + ↓ +4. Throughput (req/s) plateaus despite more VUs ← CAPACITY WALL + ↓ +5. k6 p95 latency rises steeply (>500ms, then >2s) ← LAGGING SIGNAL + ↓ +6. EOF errors appear (Envoy closes held connections) ← FAILURE VISIBLE TO CLIENTS +``` + +Steps 1-4 are observable before errors reach clients. Steps 5-6 confirm +saturation is underway. + +--- + +## Recommended Thresholds + +These are starting-point thresholds to calibrate against your baseline -- not +absolute values. Hardware, workload, and deployment configuration all affect +where these numbers land. + +| Signal | Threshold | Action | +| --- | --- | --- | +| `axum_http_requests_pending` | > 0 at steady-state between tests | IS already under pressure | +| IS throughput plateau | req/s flat while VUs still increasing | Capacity wall reached | +| `http_req_duration` p95 (k6) | > 500ms | Approaching saturation | +| `http_req_duration` p95 (k6) | > 2s | At or past saturation | +| `http_req_failed` (k6) | > 0% | Breaking point -- Envoy closing connections | diff --git a/docs/v0.6.1-rc/http-load-testing.md b/docs/v0.6.1-rc/http-load-testing.md new file mode 100644 index 000000000..465fa6965 --- /dev/null +++ b/docs/v0.6.1-rc/http-load-testing.md @@ -0,0 +1,257 @@ +(self-managed-http-load-test)= + +# Self-Managed NVCF HTTP Load Test + +## Prerequisites + +### Self-hosted CLI + +You need a working `nvcf-cli` configured against your self-managed cluster. +If you have not set this up yet, follow the {ref}`self-hosted-cli` guide to +install the binary and the {ref}`cli-configuration` section to point it at your +gateway. + +Verify the CLI can reach the cluster before continuing: + +```bash +./nvcf-cli init +./nvcf-cli api-key generate +``` + +### Deploy the load test function + +Use the **load_tester_supreme** container for load testing. It is purpose-built +for high-throughput benchmarking and includes: + +- **gRPC + HTTP + SSE** endpoints in a single image +- Tunable `repeats`, `delay`, and `size` fields to shape request/response + profiles +- Built-in OpenTelemetry tracing + +The source, build instructions, and registry push examples are in the +[NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/load-tester-supreme) +repository. Build and push the image to whichever container registry your +cluster has credentials for: + +```bash +git clone https://github.com/NVIDIA/nvcf.git +cd nvcf/examples/function-samples/load-tester-supreme + +# Build +docker build --platform linux/amd64 -t load_tester_supreme . + +# Tag and push (replace with your registry -- NGC, ECR, etc.) +docker tag load_tester_supreme nvcr.io//load_tester_supreme:latest +docker push nvcr.io//load_tester_supreme:latest +``` + +:::{tip} +To check which registries your cluster recognises, run +`./nvcf-cli registry list`. +::: + +Then create the function and deploy it using the CLI: + +```bash +# Create the function (HTTP) +./nvcf-cli function create \ + --name "load-tester-supreme" \ + --image "nvcr.io//load_tester_supreme:latest" \ + --inference-url "/echo" \ + --inference-port 8000 \ + --health-uri "/health" \ + --health-port 8000 \ + --health-timeout PT30S + +# Deploy (adjust GPU type and instance type for your cluster) +./nvcf-cli function deploy create \ + --gpu H100 \ + --instance-type NCP.GPU.H100_1x \ + --min-instances 1 \ + --max-instances 1 \ + --function-id \ + --version-id + +# Generate an API key for invocations +./nvcf-cli api-key generate +``` + +Once deployed, note the following -- you will need them for the run script: + +- **Function ID** -- the UUID returned by `function create` +- **Function Version ID** -- the UUID of the specific deployed version +- **API key** -- from `./nvcf-cli api-key generate` (begins with `nvapi-`) + +#### Obtain the gateway address + +Your gateway address is the external address of the Envoy Gateway deployed with +the control plane. To retrieve it: + +```bash +export GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway \ + -o jsonpath='{.status.addresses[0].value}') +echo "Gateway Address: $GATEWAY_ADDR" +``` + +On **AWS EKS** this is an ELB hostname (e.g. +`a1b2c3d4.us-east-1.elb.amazonaws.com`). For a **local** deployment (Kind, +k3d, Docker Desktop) it is typically `localhost` or `127.0.0.1`. + +## Clone the load test scripts + +```bash +git clone https://github.com/NVIDIA/nvcf.git +cd nvcf/examples/load-tests +``` + +## Install k6 + +Install [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) if you +don't have it: + +```bash +# macOS +brew install k6 +``` + +```bash +# Linux (Debian/Ubuntu) +sudo gpg -k +sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \ + --keyserver hkp://keyserver.ubuntu.com:80 \ + --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 +echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \ + | sudo tee /etc/apt/sources.list.d/k6.list +sudo apt-get update && sudo apt-get install k6 +``` + +## Create your run script + +The `run*.sh` scripts are gitignored, so each user creates their own locally. +Create `run_http_self_managed_test.sh` in the `load-tests` directory: + +```bash +#!/bin/bash + +set -e + +export GATEWAY_ADDR= +export TOKEN= +export FUNCTION_ID= + +# This local/self-managed example uses `http://` because it does not assume TLS +# termination. Use `https://` if your gateway terminates TLS. +# `/echo` matches the inference path configured for the load_tester_supreme +# example. If your function uses a different inference path, replace `/echo` +# with that path in `HTTP_SUPREME_NVCF_URL` and the curl example below. +export HTTP_SUPREME_NVCF_URL="http://${FUNCTION_ID}.invocation.${GATEWAY_ADDR}/echo" +export SENT_MESSAGE_SIZE=32 +export RESPONSE_COUNT=1 + +k6 run functions/supreme_http_test.js \ + --vus 10 --duration 60s \ + -e TOKEN=${TOKEN} \ + -e HTTP_SUPREME_NVCF_URL=${HTTP_SUPREME_NVCF_URL} \ + -e SENT_MESSAGE_SIZE=${SENT_MESSAGE_SIZE} \ + -e RESPONSE_COUNT=${RESPONSE_COUNT} +``` + +Make it executable and run: + +```bash +chmod +x run_http_self_managed_test.sh +./run_http_self_managed_test.sh +``` + +## Tune the load + +### Virtual users (VUs) + +Each VU simulates a single concurrent HTTP client, sending requests in a loop +and holding the invocation connection open while waiting for a response. The +number of VUs directly controls the concurrency hitting your endpoint. + +| VUs | Simulates | +| --- | --- | +| 1--5 | Smoke test -- verify the endpoint works under minimal load | +| 10--50 | Light load -- a small team or service calling the function | +| 100--500 | Moderate load -- multiple services or a rollout with real traffic | +| 1000+ | Stress test -- find the breaking point or max throughput | + +**Fixed VUs for a set duration (simplest approach):** + +```bash +# 10 concurrent users for 1 minute +k6 run functions/supreme_http_test.js --vus 10 --duration 60s ... + +# 200 concurrent users for 10 minutes +k6 run functions/supreme_http_test.js --vus 200 --duration 10m ... +``` + +**Ramping VUs with a config file (recommended for real load tests):** + +Example `k6_rampup_config.json`: + +```json +{ + "cloud": { + "projectID": 3695020 + }, + "scenarios": { + "rampup_scenario": { + "executor": "ramping-vus", + "startVUs": 0, + "gracefulRampDown": "30s", + "gracefulStop": "30s", + "stages": [ + { "duration": "1m", "target": 5 }, + { "duration": "2m", "target": 5 }, + { "duration": "1m", "target": 25 }, + { "duration": "2m", "target": 25 }, + { "duration": "1m", "target": 100 }, + { "duration": "2m", "target": 100 }, + { "duration": "1m", "target": 500 }, + { "duration": "2m", "target": 500 }, + { "duration": "1m", "target": 1000 }, + { "duration": "2m", "target": 1000 }, + { "duration": "1m", "target": 0 } + ] + } + } +} +``` + +```bash +k6 run functions/supreme_http_test.js \ + --config k6_rampup_config.json \ + -e TOKEN=${TOKEN} \ + -e HTTP_SUPREME_NVCF_URL=${HTTP_SUPREME_NVCF_URL} \ + -e SENT_MESSAGE_SIZE=${SENT_MESSAGE_SIZE} \ + -e RESPONSE_COUNT=${RESPONSE_COUNT} +``` + +## Environment variables reference + +| Variable | Purpose | +| --- | --- | +| `TOKEN` | Your `nvapi-*` bearer token from `./nvcf-cli api-key generate` | +| `FUNCTION_ID` | Function ID for invocation routing | +| `HTTP_SUPREME_NVCF_URL` | Invocation URL for the function inference path: `http://.invocation./echo` | +| `SENT_MESSAGE_SIZE` | Size of the test payload in bytes | +| `RESPONSE_COUNT` | Number of responses the server should return | + +## Verifying your endpoint manually + +Then verify the endpoint works with `curl`: + +```bash +curl -v -X POST \ + "${HTTP_SUPREME_NVCF_URL}" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Nvcf-Poll-Seconds: 5" \ + -d '{"message": "hello", "repeats": 1}' +``` + +You should receive a `200 OK` response with the `Nvcf-Status: fulfilled` +header. diff --git a/docs/v0.6.1-rc/http-soak-testing.md b/docs/v0.6.1-rc/http-soak-testing.md new file mode 100644 index 000000000..dbd1cec8f --- /dev/null +++ b/docs/v0.6.1-rc/http-soak-testing.md @@ -0,0 +1,389 @@ +# Self-Managed NVCF HTTP Soak Test + +This guide walks through running a sustained HTTP soak test against a +self-managed NVCF cluster using [k6](https://k6.io/). The test sends +a constant arrival-rate load to one or more deployed functions and +reports success rate, latency percentiles, and throughput over an +extended period (default 48 hours). + +## Prerequisites + +### Self-hosted CLI + +You need a working `nvcf-cli` configured against your self-managed cluster. +If you have not set this up yet, follow the [self-hosted-cli](../user/cli.md) guide to +install the binary and the [cli-configuration](../user/cli.md) section to point it at your +gateway. + +Verify the CLI can reach the cluster before continuing: + +```bash +./nvcf-cli init +``` + +### Deploy the load test function + +Use the **load_tester_supreme** container for soak testing. It is purpose-built +for high-throughput benchmarking and includes: + +- **gRPC + HTTP + SSE** endpoints in a single image +- Tunable `repeats`, `delay`, and `size` fields to shape request/response + profiles +- Built-in OpenTelemetry tracing + +The source, build instructions, and registry push examples are in the +[NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples/function-samples/load-tester-supreme) +repository. Build and push the image to whichever container registry your +cluster has credentials for: + +```bash +git clone https://github.com/NVIDIA/nvcf.git +cd nvcf/examples/function-samples/load-tester-supreme + +# Build (multi-arch) +docker buildx build --platform linux/amd64,linux/arm64 -t load_tester_supreme . + +# Tag and push (replace with your registry -- ECR, NGC, Docker Hub, etc.) +docker tag load_tester_supreme /load_tester_supreme:latest +docker push /load_tester_supreme:latest +``` + + +To check which registries your cluster recognises, run +`./nvcf-cli registry list`. + + + +Then create the function and deploy it using the CLI. For HTTP soak testing +you can create multiple functions to simulate broader load: + +```bash +# Create the function (HTTP) +./nvcf-cli function create \ + --name "load-tester-supreme" \ + --image "/load_tester_supreme:latest" \ + --inference-url "/echo" \ + --inference-port 8000 \ + --health-uri "/health" \ + --health-port 8000 \ + --health-timeout PT30S + +# Deploy (adjust GPU type and instance type for your cluster) +./nvcf-cli function deploy create \ + --gpu L40S \ + --instance-type NCP.GPU.L40S_1x \ + --min-instances 1 \ + --max-instances 1 + +# Generate an API key for invocations (default expiry: 24h) +./nvcf-cli api-key generate + +# For soak tests longer than 24 hours, set a longer expiry: +./nvcf-cli api-key generate --expires-in "7d" +``` + +Export the key so it can be passed to k6: + +```bash +export API_KEY= +``` + +Repeat the `function create` and `function deploy create` steps to create +additional functions if you want to distribute load across multiple function +endpoints. + +Once deployed, note the following -- you will need them for the k6 script: + +- Function ID -- the UUID returned by `function create` +- API key -- from `./nvcf-cli api-key generate` (begins with `nvapi-`); export it as `$API_KEY` + +#### Obtain the gateway address + +Your gateway address is the external address of the Envoy Gateway deployed with +the control plane. To retrieve it: + +```bash +export GATEWAY_ADDR=$(kubectl get gateway nvcf-gateway -n envoy-gateway \ + -o jsonpath='{.status.addresses[0].value}') +echo "Gateway Address: $GATEWAY_ADDR" +``` + +On AWS EKS this is an ELB hostname (e.g. +`a1b2c3d4.us-east-1.elb.amazonaws.com`). For a local deployment (Kind, +k3d, Docker Desktop) it is typically `localhost` or `127.0.0.1`. + +The gateway routes invocation traffic using HTTP host headers: + +| Host header | Routes to | +| --- | --- | +| `api.$GATEWAY_ADDR` | NVCF management API (function CRUD) | +| `invocation.$GATEWAY_ADDR` | Invocation / inference service | +| `api-keys.$GATEWAY_ADDR` | API Keys service | + +For the soak test, set `BASE_URL` to `http://$GATEWAY_ADDR` and `INVOKE_HOST` +to `invocation.$GATEWAY_ADDR`. + + +The CLI saves the function ID automatically. Run +`./nvcf-cli status` to view it at any time. + + + +## Install k6 + +Install [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) if you +don't have it: + +```bash +# macOS +brew install k6 +``` + +```bash +# Linux (Debian/Ubuntu) +sudo gpg -k +sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \ + --keyserver hkp://keyserver.ubuntu.com:80 \ + --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 +echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \ + | sudo tee /etc/apt/sources.list.d/k6.list +sudo apt-get update && sudo apt-get install k6 +``` + +## The k6 test script + +Save the following script as `k6-nvcf-http-soak.js`. The script uses the +`constant-arrival-rate` executor to guarantee an exact request rate per +second regardless of response time, which is critical for soak testing where +you want a steady, predictable load. + + +The latest version of this script is maintained in the +[NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples) +repository. + + + +```javascript +/** + * NVCF HTTP soak test script. + * + * Pass a FUNCTIONS JSON array with one entry per function you want to + * load-test (one function per GPU node is typical). There is no limit + * on the number of functions -- each iteration sends one request to + * every function via http.batch(). + * + * Total TPS = TPS_PER_FUNC × number of functions. + * + * Required env vars: BASE_URL, INVOKE_HOST, FUNCTIONS + * Optional: API_KEY, TPS_PER_FUNC, PRE_VUS, MAX_VUS, REPEATS, DURATION + * + * Example: + * k6 run -e BASE_URL=http://$GATEWAY_ADDR \ + * -e INVOKE_HOST=invocation.$GATEWAY_ADDR \ + * -e 'FUNCTIONS=[{"funcId":"uuid1"}]' \ + * -e DURATION=48h \ + * -e API_KEY=$API_KEY \ + * k6-nvcf-http-soak.js + */ +import http from 'k6/http'; +import { check } from 'k6'; +import { Rate, Trend, Counter } from 'k6/metrics'; + +const invokeSuccess = new Rate('invoke_success'); +const invokeLatency = new Trend('invoke_latency_ms'); +const totalRequests = new Counter('total_requests'); + +// ---------- Required config ---------- +const DURATION = __ENV.DURATION || '48h'; +const BASE_URL = __ENV.BASE_URL || ''; +const INVOKE_HOST = __ENV.INVOKE_HOST || ''; + +const FUNCTIONS = (() => { + const raw = __ENV.FUNCTIONS || ''; + if (!raw) return []; + try { + const arr = JSON.parse(raw); + if (!Array.isArray(arr) || arr.length === 0) return []; + return arr; + } catch (e) { + console.warn('FUNCTIONS JSON parse failed: ' + e.message); + return []; + } +})(); + +// ---------- Optional parameters ---------- +const API_KEY = __ENV.API_KEY || ''; +const REPEATS = parseInt(__ENV.REPEATS || '100', 10); +const TPS_PER_FUNC = parseInt(__ENV.TPS_PER_FUNC || '125', 10); +const PRE_VUS = parseInt(__ENV.PRE_VUS || '250', 10); +const MAX_VUS = parseInt(__ENV.MAX_VUS || '500', 10); + +export const options = { + scenarios: { + invoke_all_functions: { + executor: 'constant-arrival-rate', + rate: TPS_PER_FUNC, + timeUnit: '1s', + duration: DURATION, + preAllocatedVUs: PRE_VUS, + maxVUs: MAX_VUS, + exec: 'invoke_all_functions', + }, + }, + thresholds: { + http_req_duration: ['p(95)<5000'], + invoke_success: ['rate>0.999'], + }, +}; + +export function invoke_all_functions() { + const payload = JSON.stringify({ message: 'randomString', repeats: REPEATS }); + + const requests = FUNCTIONS.map((f, i) => ({ + method: 'POST', + url: `${BASE_URL}/echo`, + body: payload, + params: { + headers: { + 'Host': INVOKE_HOST, + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${API_KEY}`, + 'Function-Id': f.funcId, + }, + tags: { func: `func_${i}` }, + }, + })); + + const responses = http.batch(requests); + responses.forEach((res, i) => { + totalRequests.add(1); + invokeSuccess.add(res.status === 200); + if (res.status === 200) invokeLatency.add(res.timings.duration); + + check(res, { + [`func_${i} status 200`]: (r) => r.status === 200, + [`func_${i} fulfilled`]: (r) => r.headers['Nvcf-Status'] === 'fulfilled', + [`func_${i} body not empty`]: (r) => r.body && r.body.length > 0, + }); + }); +} +``` + +## Running the soak test + +A typical soak test uses multiple functions at a sustained TPS for an extended +period. For example, multiple functions at 125 TPS each for 48 hours: + +```bash +k6 run k6-nvcf-http-soak.js \ + -e BASE_URL="http://$GATEWAY_ADDR" \ + -e INVOKE_HOST="invocation.$GATEWAY_ADDR" \ + -e 'FUNCTIONS=[{"funcId":""},{"funcId":""},{"funcId":""},{"funcId":""}]' \ + -e DURATION=48h \ + -e TPS_PER_FUNC=125 \ + -e API_KEY=$API_KEY +``` + + +Run the soak test inside `tmux` or `screen` so it survives SSH +disconnections: + +```bash +tmux new -s soak +# run k6 command above +# Ctrl-B D to detach, tmux attach -t soak to re-attach +``` + + + +## Tune the load + +### TPS per function + +The `constant-arrival-rate` executor guarantees a fixed number of requests +per second. The total TPS is `TPS_PER_FUNC × number of functions`. + +| TPS_PER_FUNC | Functions | Total TPS | +| --- | --- | --- | +| 1 | 1 | 1 (smoke test) | +| 25 | 4 | 100 (light load) | +| 125 | 4 | 500 (moderate soak) | + + +**Default control plane sizing:** The default resource sizing that ships +with the self-managed stack is designed to handle roughly 100 concurrent users. If you +need to test beyond that, you will need to scale the control plane components +first. Starting with 100 TPS total is a good baseline for validating a +default self-managed deployment. + + + +### VU allocation + +Each VU is a virtual user that can hold one in-flight request. The executor +creates new VUs if existing ones are busy. If you see +`insufficient VUs, consider increasing maxVUs` warnings, increase +`PRE_VUS` and `MAX_VUS`: + +```bash +-e PRE_VUS=500 -e MAX_VUS=1000 +``` + +A good rule of thumb: `PRE_VUS ≈ TPS_PER_FUNC × avg_latency_seconds × 2`. + +## Environment variables reference + +| Variable | Description | Default | +| --- | --- | --- | +| `BASE_URL` | Base URL of the gateway (e.g. `http://$GATEWAY_ADDR`) | *(required)* | +| `INVOKE_HOST` | Host header for invocation routing (e.g. `invocation.$GATEWAY_ADDR`) | *(required)* | +| `FUNCTIONS` | JSON array of function objects: `[{"funcId":"…"}, ...]` | *(required)* | +| `DURATION` | Test duration (e.g. `30s`, `1h`, `48h`) | `48h` | +| `API_KEY` | `nvapi-*` bearer token (exported from `./nvcf-cli api-key generate`, see above) | *(required)* | +| `TPS_PER_FUNC` | Requests per second per function | `125` | +| `PRE_VUS` | Pre-allocated virtual users per function scenario | `250` | +| `MAX_VUS` | Maximum virtual users per function scenario | `500` | +| `REPEATS` | Number of times the echo endpoint repeats the payload string | `100` | + +## Interpreting results + +k6 prints a summary to stdout at the end of each run. Key metrics to monitor +during a soak test: + +| Metric | What to look for | +| --- | --- | +| `invoke_success` | Should stay above 99.9 %. Drops indicate gateway or function errors. | +| `invoke_latency_ms` (p95) | Should stay below 5 000 ms. Rising latency over time can signal memory leaks, connection exhaustion, or pod restarts. | +| `http_req_duration` (p50 / p95 / p99) | Overall request round-trip time including network. Compare to `invoke_latency_ms` to isolate gateway overhead. | +| `total_requests` | Total requests completed. Divide by test duration to verify actual TPS. | +| `http_req_failed` | Percentage of non-2xx responses. Should be 0 % for a healthy cluster. | + +To save results for offline analysis: + +```bash +# Plain text log +k6 run k6-nvcf-http-soak.js ... 2>&1 | tee soak-run.log + +# JSON output for Grafana / post-processing +k6 run --out json=soak-results.json k6-nvcf-http-soak.js ... +``` + +## Verifying the endpoint manually + +Before running the soak test, verify the endpoint works with `curl`: + +```bash +export FUNCTION_ID= + +curl --request POST \ + --url "http://${GATEWAY_ADDR}/echo" \ + --header "Host: invocation.${GATEWAY_ADDR}" \ + --header "Authorization: Bearer ${API_KEY}" \ + --header "Content-Type: application/json" \ + --header "Function-Id: ${FUNCTION_ID}" \ + --data '{"message": "hello"}' +``` + +You should receive a `200 OK` response with the `Nvcf-Status: fulfilled` +header and the message repeated three times. diff --git a/docs/v0.6.1-rc/image-mirroring.md b/docs/v0.6.1-rc/image-mirroring.md new file mode 100644 index 000000000..869aff228 --- /dev/null +++ b/docs/v0.6.1-rc/image-mirroring.md @@ -0,0 +1,567 @@ +# Image Mirroring + +All required self-hosted NVCF artifacts (see [self-hosted-artifact-manifest](./manifest.md)) must be available to be pulled by pods in your Kubernetes cluster for a successful installation using the split stack bundles (`nvcf-self-managed-stack` for control plane and `nvcf-compute-plane-stack` for compute plane). This page provides examples on how to pull artifacts from NGC and push them to your desired registry. + + +**Mirroring images is not the same as configuring image pull secrets.** This page covers how to copy NVCF artifacts into your registry. If your registry is private, Kubernetes also needs credentials to pull those images at runtime. For instructions on configuring image pull secrets for the NVCF control plane pods, see [control-plane-image-pull-secrets](./helmfile-installation.md) in the installation guide. + + + +## Mirroring Workflow + +Use this page to mirror NVCF artifacts into the registry your cluster can access. The examples below show the commands for pulling from NGC and pushing to a private registry such as ECR, Harbor, or another OCI-compatible registry. + + +For ECR mirroring, make sure your AWS credentials have permission to create repositories and push images. Verify access with `aws sts get-caller-identity`. + + + +## Prerequisites + +You must have access to the NGC `nvcf-onprem` organization to begin. + +1. Navigate to [https://org.ngc.nvidia.com/setup/api-keys](https://org.ngc.nvidia.com/setup/api-keys) and ensure you have selected the `nvcf-onprem` organization in the upper right. +2. Create a Personal API key with the required scopes to pull entities. + + + Personal API key creation for nvcf-onprem organization + + +3. Set the NGC API key as an environment variable for use in any subsequent commands: + +```bash +export NGC_API_KEY="nvapi-xxxxxxxxxxxxx" # Replace with your NGC API key +``` + +## LLS-Specific Artifacts + +If you plan to deploy **Low Latency Streaming (LLS)**, you must mirror the following additional artifacts beyond the core NVCF control plane: + +**Container Images:** + +- `streaming-proxy` - Streaming Proxy container for streaming + +**Helm Charts:** + +- `gdn-streaming` - GDN Streaming Proxy Helm chart + +**Optional (for streaming workloads):** + +- Streaming application images (e.g., `usd-composer`) + +See [self-hosted-lls-installation](./lls-installation.md) for LLS deployment instructions. + +## Pulling Artifacts from NGC + + +**Important:** The examples below show how to pull individual artifacts. You must pull **each image, chart, and resource** listed in the [self-hosted-artifact-manifest](./manifest.md) individually. These examples demonstrate the process for one artifact of each type - you will need to repeat these steps for every artifact required for your deployment. + +**Complete the following for each artifact:** + +- Pull each container image from NGC +- Pull each Helm chart from NGC +- Pull each required resource bundle from NGC +- Push each artifact to your target registry (ECR, Harbor, etc.) + +See the [self-hosted-artifact-manifest](./manifest.md) for the complete list of all required artifacts. + + + +### Pulling Images + + +**Platform Architecture Mismatch** + +When pulling images, Docker pulls the architecture matching your local machine by default. If you're running on an Apple Silicon Mac (arm64) but deploying to an amd64 cluster (most EKS/GKE clusters), you **must** specify the target platform: + +```bash +# Pull for amd64 clusters (most common) +docker pull --platform linux/amd64 + +# Pull for arm64 clusters +docker pull --platform linux/arm64 +``` + +Failing to specify the correct platform will result in `exec format error` when pods attempt to start. See [image-mirroring-troubleshooting](./image-mirroring.md) for more details. + + + +1. Login using the Personal API key you have generated in the previous step: + + ```bash + docker login nvcr.io -u '$oauthtoken' -p + ``` + +2. Pull the image (specify platform matching your target cluster): + + ```bash + # For amd64 clusters (most EKS, GKE, AKS clusters) + docker pull --platform linux/amd64 nvcr.io/nvidia/nvcf/nats-box:0.19.7-nonroot + + # For arm64 clusters (Graviton-based EKS, etc.) + docker pull --platform linux/arm64 nvcr.io/nvidia/nvcf/nats-box:0.19.7-nonroot + ``` + +### Pulling Helm Charts + + +Public NVCF charts in the `nvidia/nvcf` namespace are NGC Helm +charts. They are not public OCI artifacts. Pull them from +`https://helm.ngc.nvidia.com/nvidia/nvcf`. + + + +**Public NVCF Helm Charts** + +```bash +# Add and update the public NVCF Helm repository +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf --force-update +helm repo update + +# Pull the chart +helm pull nvcf/helm-nvca-operator --version 1.12.7 + +# Prerelease charts require --devel when searching +helm search repo nvcf/helm-nvcf-vanity-gateway --versions --devel +``` + +**Other Repository-based Helm Charts (Non-OCI)** + +The GPU Operator and the Omniverse DDCS, UCC, storage-service, and +discovery-service charts are also available from traditional Helm repositories +rather than OCI registries. Pull them directly from the public NVIDIA NGC +Catalog. + +```bash +# Add the NVIDIA Helm repository +helm repo add nvidia https://helm.ngc.nvidia.com/nvidia --force-update +helm repo add omniverse https://helm.ngc.nvidia.com/nvidia/omniverse --force-update + +# Update repositories +helm repo update + +# Pull charts (downloads as .tgz files) +helm pull nvidia/gpu-operator --version 25.3.1 +helm pull omniverse/ddcs --version 5.0.0 +helm pull omniverse/usd-content-cache --version 3.0.3 +helm pull omniverse/storage-service --version 1.0.2 +helm pull omniverse/discovery-service --version 2.3.8 +``` + + +Public NVCF charts, the GPU Operator and related components +(gpu-operator-validator, k8s-device-plugin), plus the Omniverse +DDCS/UCC/storage charts, are available from public NVIDIA Helm repositories. +You can either: + +- Pull directly from the public repository at runtime (simplest approach) +- Mirror to your private registry for air-gapped environments (see below) + + + +**Converting Non-OCI Charts for ECR** + +To push repository-based Helm charts to Amazon ECR (which requires OCI format), you must convert them: + +```bash +# Pull the chart from the traditional repository +helm repo add omniverse https://helm.ngc.nvidia.com/nvidia/omniverse --force-update +helm repo update +helm pull omniverse/ddcs --version 5.0.0 + +# Login to ECR +aws ecr get-login-password --region us-east-1 | \ + helm registry login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com + +# Create ECR repository for the chart (include your repository prefix) +aws ecr create-repository --repository-name nvcf-self-hosted/ddcs --region us-east-1 + +# Push the .tgz file as an OCI artifact (include repository prefix) +helm push ddcs-5.0.0.tgz oci://.dkr.ecr.us-east-1.amazonaws.com/nvcf-self-hosted +``` + + +ECR will properly track both container images and Helm charts under the same repository name and version, so you can use consistent naming for both. The repository prefix (e.g., `nvcf-self-hosted`) must match your `global.image.repository` environment configuration. + + + +### Pulling Resources from NGC + +**Using NGC CLI** + +First, ensure you have the [NGC CLI installed and configured](https://org.ngc.nvidia.com/setup/installers/cli) using the Personal API key you created. + +{/* docs-version-sync:BEGIN image-mirroring-resource-examples */} + +```bash +# Set stack versions +export STACK_VERSION="0.6.0" +export COMPUTE_STACK_VERSION="1.0.6" + +# Download a specific control-plane stack version +ngc registry resource download-version \ + "nvidia/nvcf/nvcf-self-managed-stack:${STACK_VERSION}" + +# Download a specific compute-plane stack version +ngc registry resource download-version \ + "nvidia/nvcf/nvcf-compute-plane-stack:${COMPUTE_STACK_VERSION}" +``` + +{/* docs-version-sync:END image-mirroring-resource-examples */} + +### Downloading `nvcf-self-managed-stack` (control plane) + +The `nvcf-self-managed-stack` repository contains Helmfile configurations for deploying the NVCF control plane components. + + +Use the control-plane stack version shown in the artifact manifest. The stack +and its listed artifact versions are QA-qualified together. + + + +**Download and extract:** + +{/* docs-version-sync:BEGIN image-mirroring-stack-snippet */} + +```bash +# Set the version +export VERSION="0.6.0" + +ngc registry resource download-version "nvidia/nvcf/nvcf-self-managed-stack:${VERSION}" && \ + mkdir -p nvcf-self-managed-stack && \ + tar -xzf nvcf-self-managed-stack_v${VERSION}/nvcf-self-managed-stack-${VERSION}.tar.gz -C nvcf-self-managed-stack && \ + rm -rf nvcf-self-managed-stack_v${VERSION} +``` + +{/* docs-version-sync:END image-mirroring-stack-snippet */} + + +If you don't have access to this repository, contact your NVIDIA representative. + + + +### Downloading `nvcf-compute-plane-stack` + +The `nvcf-compute-plane-stack` repository contains Helmfile configurations for deploying compute-plane components. + + +Use the compute-plane stack version shown in the artifact manifest. The stack +and its listed artifact versions are QA-qualified together. + + + +Download and extract: + +{/* docs-version-sync:BEGIN image-mirroring-compute-stack-snippet */} + +```bash +# Set the version +export COMPUTE_VERSION="1.0.6" + +ngc registry resource download-version "nvidia/nvcf/nvcf-compute-plane-stack:${COMPUTE_VERSION}" && \ + mkdir -p nvcf-compute-plane-stack && \ + tar -xzf nvcf-compute-plane-stack_v${COMPUTE_VERSION}/nvcf-compute-plane-stack-${COMPUTE_VERSION}.tar.gz -C nvcf-compute-plane-stack && \ + rm -rf nvcf-compute-plane-stack_v${COMPUTE_VERSION} +``` + +{/* docs-version-sync:END image-mirroring-compute-stack-snippet */} + + +Use both stack bundles for split-stack local and self-managed installs: +`nvcf-self-managed-stack` for the control plane and +`nvcf-compute-plane-stack` for compute-plane components. + + + +### Downloading `nvcf-cli` + +The `nvcf-cli` is a command-line interface for managing NVIDIA Cloud Functions in self-hosted deployments. + + +Use the CLI version shown in the artifact manifest for this stack release. + + + +**Download and extract:** + +{/* docs-version-sync:BEGIN image-mirroring-cli-snippet */} + +```bash +# Set the version +export VERSION="1.10.3" + +# Set your platform (linux-amd64, linux-arm64, darwin-amd64, darwin-arm64, windows-amd64) +export PLATFORM="linux-amd64" + +ngc registry resource download-version "nvidia/nvcf/nvcf-cli:${VERSION}" + +tar -xzf nvcf-cli_v${VERSION}/${PLATFORM}/nvcf-cli-${PLATFORM}-${VERSION}.tar.gz +mv nvcf-cli-${PLATFORM}-${VERSION} nvcf-cli +chmod +x nvcf-cli/nvcf-cli +``` + +{/* docs-version-sync:END image-mirroring-cli-snippet */} + +The extracted directory contains: + +- `nvcf-cli` - The CLI binary +- `.nvcf-cli.yaml.template` - Configuration template +- `examples/` - Sample configuration files for different environments +- `USAGE-GUIDE.md` - Detailed usage documentation + +See [self-hosted-cli](./cli.md) for detailed configuration instructions + + +If you don't have access to this repository, contact your NVIDIA representative. + + + +## Pushing to Your Registry + + +Ensure all artifacts listed in the [self-hosted-artifact-manifest](./manifest.md) are mirrored to your registry before beginning the installation process. + + + +### Example: Pushing to Amazon ECR + +This example assumes you're configured and authenticated using the AWS CLI. + + +**Identify Your AWS Account ID** + +The examples below use `` as a placeholder. To get your AWS account ID, run: + +```bash +aws sts get-caller-identity --query Account --output text +``` + + + + +**ECR Repository Naming Convention** + +The Helm templates expect images at: `{{ registry }}/{{ repository }}/image-name:tag` + +For example, with environment configuration: + +```yaml +global: + image: + registry: .dkr.ecr.us-east-1.amazonaws.com + repository: nvcf-self-hosted +``` + +The resulting image path would be: `.dkr.ecr.us-east-1.amazonaws.com/nvcf-self-hosted/nats-box:0.19.7-nonroot` + +In ECR, you must create repositories with the **full path** including the prefix, e.g., `nvcf-self-hosted/notary-service`, `nvcf-self-hosted/nats-box`, etc. + + + +**Initial Setup** + +```bash +# Set your repository prefix (must match global.image.repository in your environment config) +REPO_PREFIX="nvcf-self-hosted" + +# Login to AWS ECR +aws ecr get-login-password --region us-east-1 | \ + docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com +``` + +**Push an Image to ECR** + +```bash +# Create ECR repository with the full path (including prefix) +aws ecr create-repository --repository-name ${REPO_PREFIX}/nats-box --region us-east-1 + +# Tag the image for ECR (include repository prefix in path) +docker tag nvcr.io/nvidia/nvcf/nats-box:0.19.7-nonroot \ + .dkr.ecr.us-east-1.amazonaws.com/${REPO_PREFIX}/nats-box:0.19.7-nonroot + +# Push to ECR +docker push .dkr.ecr.us-east-1.amazonaws.com/${REPO_PREFIX}/nats-box:0.19.7-nonroot +``` + +**Push a Helm Chart to ECR** + +```bash +# 1. Add and update the public NVCF Helm repository +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf --force-update +helm repo update + +# 2. Pull the Helm chart from NGC +helm pull nvcf/helm-nvca-operator --version 1.12.7 +# This creates: helm-nvca-operator-1.12.7.tgz + +# 3. Login to AWS ECR with Helm +aws ecr get-login-password --region us-east-1 | \ + helm registry login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com + +# 4. Create ECR repository with prefix (must match your environment config) +aws ecr create-repository --repository-name ${REPO_PREFIX}/helm-nvca-operator --region us-east-1 + +# 5. Push to ECR as OCI artifact (include repository prefix) +helm push helm-nvca-operator-1.12.7.tgz oci://.dkr.ecr.us-east-1.amazonaws.com/${REPO_PREFIX} +``` + + +Replace `` with your AWS account ID (run `aws sts get-caller-identity --query Account --output text`). The `REPO_PREFIX` value must match your `global.image.repository` setting in your environment config. Adjust the region as needed. + + + +### Example: Pushing to Volcano Engine Container Registry + +This example shows how to push images and Helm charts to Volcano Engine Container Registry (CR) using the web console, Docker commands and Helm commands. + + +**Volcano Engine CR Repository Naming Convention** + +The Helm templates expect images at: `{{ registry }}/{{ repository }}/image-name:tag` + +For example, with environment configuration: + +```yaml +global: + image: + registry: cr-example-cn-beijing.cr.volces.com + repository: nvcf-self-hosted +``` + +The resulting image path would be: `cr-example-cn-beijing.cr.volces.com/nvcf-self-hosted/nats-box:0.19.7-nonroot` + + + +**Docker Authentication** + +```bash +# Set your Volcano Engine CR endpoint +CR_ENDPOINT="cr-example-cn-beijing.cr.volces.com" +CR_USERNAME="your-username" +CR_PASSWORD="your-password" + +# Login to Volcano Engine CR +echo "${CR_PASSWORD}" | docker login "${CR_ENDPOINT}" \ + --username "${CR_USERNAME}" --password-stdin +``` + + +Replace `cr-example-cn-beijing.cr.volces.com` with your Volcano Engine CR endpoint, `your-username` with your username, and `your-password` with your password. +Navigate to your Volcano Engine Container Registry instance web console to get the username and password. If you haven't set the password, you can set it by clicking "Set Repository Instance Password". + + + +**Push an Image to Volcano Engine CR** + +```bash +# Set your registry endpoint and namespace +CR_ENDPOINT="cr-example-cn-beijing.cr.volces.com" +NAMESPACE="nvcf-self-hosted" + +# Tag the image for Volcano Engine CR +docker tag nvcr.io/nvidia/nvcf/nats-box:0.19.7-nonroot \ + ${CR_ENDPOINT}/${NAMESPACE}/nats-box:0.19.7-nonroot + +# Push to Volcano Engine CR +docker push ${CR_ENDPOINT}/${NAMESPACE}/nats-box:0.19.7-nonroot +``` + +**Push a Helm Chart to Volcano Engine CR** + +```bash +# Set your registry endpoint and namespace +CR_ENDPOINT="cr-example-cn-beijing.cr.volces.com" +NAMESPACE="nvcf-self-hosted" +CR_USERNAME="your-username" +CR_PASSWORD="your-password" + +# 1. Add and update the public NVCF Helm repository +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf --force-update +helm repo update + +# 2. Pull the Helm chart from NGC +helm pull nvcf/helm-nvca-operator --version 1.12.7 +# This creates: helm-nvca-operator-1.12.7.tgz + +# 3. Login to Volcano Engine CR with Helm +helm registry login ${CR_ENDPOINT} \ + --username "${CR_USERNAME}" \ + --password "${CR_PASSWORD}" + +# 4. Push to Volcano Engine CR as OCI artifact +helm push helm-nvca-operator-1.12.7.tgz oci://${CR_ENDPOINT}/${NAMESPACE} +``` + +## Troubleshooting + +### exec format error + +**Symptom:** Pods fail to start with `Init:CrashLoopBackOff` or `CrashLoopBackOff` status. Checking the logs shows: + +```text +exec /bin/sh: exec format error +``` + +or + +```text +exec /usr/local/bin/docker-entrypoint.sh: exec format error +``` + +**Cause:** This error occurs when container images were pulled/pushed with an architecture that doesn't match your cluster's node architecture. This commonly happens when: + +- Mirroring from an Apple Silicon Mac (arm64) to an amd64 EKS/GKE cluster +- Mirroring from an Intel/AMD machine (amd64) to an arm64 cluster (e.g., AWS Graviton) + +**Solution:** + +1. **Delete the incorrectly mirrored images from your registry** (e.g., ECR): + + ```bash + # Delete all repositories with your prefix + aws ecr describe-repositories --region us-west-2 \ + --query "repositories[?starts_with(repositoryName, 'nvcf-self-hosted')].repositoryName" \ + --output text | tr '\t' '\n' | while read repo; do + aws ecr delete-repository --repository-name "$repo" --region us-west-2 --force + done + ``` + +2. **Clean local Docker cache** to ensure fresh pulls: + + ```bash + # Remove all NGC and ECR images from local cache + docker images --format "{{.Repository}}:{{.Tag}}" | \ + grep -E "(nvcr.io|\.ecr\.)" | \ + xargs -r docker rmi -f + + # Prune dangling images + docker image prune -f + ``` + +3. **Re-mirror images with the correct platform**: + + When pulling images, explicitly specify the target platform: + + ```bash + # For amd64 clusters (most common) + docker pull --platform linux/amd64 + + # For arm64 clusters + docker pull --platform linux/arm64 + ``` + + Then re-tag and push to your registry. + +4. **Force Kubernetes to re-pull images** by either: + + - Setting `imagePullPolicy: Always` temporarily in your Helm values + - Deleting and redeploying the affected StatefulSets/Deployments + + ```bash + # Delete StatefulSets to force recreation + kubectl -n cassandra-system delete statefulset cassandra + kubectl -n nats-system delete statefulset nats + + # Redeploy using helmfile + HELMFILE_ENV= helmfile sync + ``` diff --git a/docs/v0.6.1-rc/images/apipersonalkey.png b/docs/v0.6.1-rc/images/apipersonalkey.png new file mode 100644 index 0000000000000000000000000000000000000000..03b10453bc7ec4c960dc58289199dc8afde684d4 GIT binary patch literal 181344 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

5U(^zUZ-13;gv+HP&yooM~L(&8m1W(L?sL=5~jt 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�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 literal 0 HcmV?d00001 diff --git a/docs/v0.6.1-rc/images/grpc-reconnect-flow.png b/docs/v0.6.1-rc/images/grpc-reconnect-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..d2158234f26d038153479d7ba2839e4e31ad43ca GIT binary patch literal 172632 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 z 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 AnE(I) literal 0 HcmV?d00001 diff --git a/docs/v0.6.1-rc/images/grpc-single-client.png b/docs/v0.6.1-rc/images/grpc-single-client.png new file mode 100644 index 0000000000000000000000000000000000000000..d2a0a2a296a819a6b5eff3d0d80787ccf40876b2 GIT binary patch literal 97920 zcmc$`WmJ@H|1~U^pdbBAK>j_qlR~_+0}EdFErj(R97?*7N!rB5~w&D^0djHY>_93ami}fj~84A#iTc z7k*Ai>^E}$*qhuWB#>*gNS1aI4QejEXHS(>oj>7T1@ zs|7O^FNCG0ZhJlVekp%A(lB#Ql;#MgRVL?v!il11@XMcqY2T^Bh5qy46^iozJVe5c z`TLYIDH2Ti_Z=Xm|4&|oxeK}2YS^BFouOgD7CJi_#1o6H!%^?Lfb9);;`t1bHBeUk z>}YHcAZdVK@4Vc?T@2r+cj;|{MeTb^Dj*uhN$8TY6r5=b=%DZ z5%gJqugR?DEmT*0U40|A+{Q8LNW2qH7GYZS_0Tkqw|A;xdy58@CdzVxLokb5=LuSD zowmQ@e_kHx7=UHn`omhn%GHoR;ZYDZuv-cvl8jb5P`=z>s&~lM%VMiJpKji~jeTj3 zt+&C7q5tmRypO;axK{RY%)f1BtNKL2j=B7IS+-lQFoKC?vPN7^(iz7P?EP!cs6(4r zrv9GFO>^%}Dwpl)npW40TbSv6L30d8$6WY%z{bG9RtE9R&a&>A^9}Yacx>nQCkb^N zL1Fb`Rvir}7~yCKEA6vhRAUyheviE(W6nW|_;KXoGqXi?zRAymq5uXB#g{-VdDHEOavj zi16!o+6V3|D-wUj3%hR;5pS8iN6n6Emos23MMf2UpYL4QE2m1c!BWKpIl}TM+Qr_# zJN%l3UQxWUbw~a!5$s44Gp(z5E_CKa4Tn!wYx^C|M>sme+J?3ykH*gEtS^HMCmEuH zB#+w)GC^yYbzwM^&H=_j^QVi??2QU5POPn>YNir&)?L0nSjGKp&jjM$Z z#k_}w)U6SnXp-n^WDpk>}0K7ZsNoJXUbxW>KYXJEu8X6gAd=&i95 z$3F#(e)5t$sWF3}x%XV+f2G-&T`YAco|HUqBJeijW&UA#B*^ikQS+vu}O ziBlFY*y9eFf)_=r3THcSEqz_23SO2tcgr8oB3af0Pgm`nk_DewEL1y9e|3}%0kS-OSW&Vy~KPx=okO{haNNH%XsWDeWcudqzBS*2LFS97Y;ZFwy9G z1o{z*yrYcJRiiJ;Jrb?E<iE{O>$oJo@%(xU*>#F(=AO1ud->8aS@BMK~+~TJ}+=6N0^uY zb!?}_Q)J9SIDL9~Q~)$+eZ;}OgiGa_;uX#)v#olxIbUnf8H`Bq-s%=bJv7>1@6tww zYf`hFl{Q5g5*I~Nggi+;rtRRkiIc}FZ)BZFwD({D%SZ~3I z&vZT;2)K+?YrYVD&yqEcRm-yJ?bTcl{!Q2q>6UrNIQraW~&1+q5_~`?O z2U?%tZmOp$_gk5JDEhRKpyCvl!q@wKY5S&8{QR-1zSSOXx3wN{xE@vX4=!(pYK-JO zEAgYqPGzHGvOX+$#2eR%mmF^<8evoBiqM)hJuJZCwH+)A;#LTQvUJT%W9rF=VBh4E9dJ$%Sf)`=74#}sm@KW%RW zhb_}Y^dG;Yj~Z8X!SaLz(b=_hdKWr++o7{r=MjxwO$}j!dU6$TmRq~Pm2O>AxG$w9 z>*ukZRvW&vw>lalbjX!ls0^VLU*DODe+3=eZ>9V}*NseJn-t9m+sNwjd?Wdmo>$Ih z`-J%}QJ#ER=(6{5-o5o6LJh}|vY!u0M@CuFCMc4By0>?WTM|OuPt#c)vr#qseo#A~ zlxgp4P->(#5cBV!BHdG3;VkBo+;PvP^dJfYEVyrALcc_#RwtcWhL#Uxx^AD!PrQYm za@wM9Pq%v7&kz63wQ)D6sANF6raqd8-z82qnl9E){?U#X18zHlTz`f11n3)MpYNXJ zsoam#xL0UWqrjn}AfBo%Tt9)jgiBrAD8xVjW00dIYsiU*P6Bh|4o^Qjv zi;P*d|K(WIN^=-_VoVmV?-LzhdofXUcg%n87X4*7LW7j~FgMAS7fDHRQPr%4ai&Rc ziv_vqYZh+f&blDf#e70~XY67+d}yUN;i3&4^^&gBbm=^5-y<7`A=IcoJUfe2yZMuZ zpbgn^GFg?-Q4sfk?hrQ5fUsh4ZEq~7Zmm(#e~jOxV;St{KiurOE^E4PTQjb38s&Q- zKlrp*z=Q+sciC+b3_$zHpH=!pf(u>U5}7L6BT2&|A6bu#PW5Wvh<=I8w;X7~HsS9^ zPh6*YKgKHOgW2@6%aDI@ zk26EXqlqBdYdVm~j1);ly`kjQ)%+p)^>T8DJZV#e8`wIik{A0OM&&Q>{gesgaN2QmnDcVBh{Lq!UGCz!V{Pa z`$+sPG!sP@G!ySRc30W&qP|c0cS^D+FWIhQO+$PClPy3nFb)2p#namiF7=uqY4fb- zy&B;+g-jN6vTpOk>C?tm_%XyFELibyVlSR9vPp5;ME zifym|NU-oEn<9rB4V5o<*h&Z%S}f$O=@pr`_H*w->Yb?oddX3>BJf_Q&xuYoweCF# zbxy>69+MIGM!1%+73Wak6pjLRviK6uojG1494&1gIKS^tD5`NU6ujeWJ0IHas%SXz zS-@yn@qgk)0P~aI14*I6E;KV;3jIrux- zpxla7U7T}k`G{Wf#xsBNV#Pl);COJw#XBoL+#S99$AQR&+t%N#Gem!N7=%Oka|CZ)XCXTJYhnfxu5|FIhB|m$b*DD1XP5D^uF3Hqz)g|hw5*Ifj?o*dleag( zceDt#MbEL>Y1c2?F9#IQ1)+ZTaQVpo-qp0B(}TKx&+Tc!m^DZjSsUwL?2WMJgiu4i zlGxx{PinQ|i&33Lr$cL)Y_`A91z{Bf&i&B%JQOrtdcm{4p@x>n8U1|*%KZO3od181 zJplW7Hsw827fzpBD`v%8Kaj3F)Ln)=U=U_|d@D+Z&VGsKHDlWHfM7pZm+#6B}UXmQ5k3z|`4ui8RUweJ>q zS@&5nBM#GzAzPT3{=q=+zi@PO@+34Zx2@Z*yWQ1;fPd+1apRM+C%lAEePMp3n2>G;}pY+27?xXG&2e`O5FU)-N9M3| zGru$X?g9Kuh@cXo_p#?6l9qxmCfB9k!VVBNZ?rA38PC zeso~-Oc;IiP^wI^^4)0h(S~2zT7#~uHA>`OZuZv_FtAjw*c9--*I6D z>_KCfn}&Y9uPKnnCJc0E$n=&L1)5C_$D2^tYu)JpHTlPe4F5|THvXP%mRw> zl5EQ|`&skxw$Ix@^94fuMtvOipi9uTu4Y@Aemf8W;yl(UIBVG{L-5!TVRxTzA8EtMgxFQ2c zTGyr1fwWk3IX}heF9R1at$@o_MxQoNy$VF857swAT3UGrP1S#9Yu( zHSR)3cr1HeOHdeJ^Hl;qV8gc87+3GPzA57=bIQsj=M#*r-F;N^szno^lT*%VfnOD2 zHiPv1Lr+AT=C4t+tjTyaVy6PUbw{h#n_SM0b2lyf2~QH6SL(es7q^-T=HC7CO;e9) zKD&uFRkrEmT&D?~Q;lMA#Lk|ZH=Q;J7}$=7V@fjI_9~YLW@LAX0)!YnE z!V%10%P$G}S;Xvpy|B6E#_6yIt$eGDOZ@^*M`-cHw=1mdt>U z4u;$(gJ`JTd7hZB=IEq3Gg{@K&K3`|2R@?CY;u1*%m3s!x+T&uTV9--ewt;r!YFKP zGZQw~rq&=>BD9dBonmDPs5Z=_jVGI1y^=FE5w7Q_pC>nKYAQBwvYsY2%zujI9TV#A z1Pb|5F(jyCgl&X~iyNA9u9@=H`VyJ15`mr4mV91w?sG7(TdW+S$t#r4oeSpuPdY}k zoqv%fo>5LNCNg@KG~tVzsvU-yiw(=>x8h&o>q)ML){YcyKQzL3Z%m$~J5D!NQ3&W+ zwP$L^uX&MNEU+(|yjmzj^NwwY?+qb3z4&Lvh6-C}Ir~9QhY&W-T{rs3E`6gOUeBzPIq!8L$zQkavo@#0#T34(7 zJT6T~6aYSEAX2O`K*yB{rrDI1T;Y<`K&DOzJ&`>hIkZH z`3+oRftu1zm1;{^kk2H~QvK{sb@$;r=ZQ*hW{F125_+3iYZ>1?tCw~AqN$!|PwpvYycly5_xvkTzJic8BHYWeyv$D2dh*vHfbyDzqemPz7_({DB7CuH%)fXuO3 zzhJNnRMQQYrmF9b!+FVmYx&9e_SjM10_B}hcah=`9_|m8$$V#;Q4>Sz#UOv(@LA4z z&-Ll~t@WGQS_~&p`P8iTJ5YGT9G@85>&|RZ_`*k6Zbg>2LxEnfA{t#bl!=j1a zaQ?P!7_45NT8;V>qv-0|sOyGppQ&{;^P7=EJH~$$>@z|B6zRUU#ph{S#Q-qil7ti+ zSLK9H16y5nw)+kIpuaUlv{taBaiVFrV*mxxTRMz}*AaL!3Au{Sa<=DM1=9$^3h#i>0Y(VR2;2!x!fx^FbKOu*J+3GjbE0a!0mRi&uaj19JR7;~6;AhkgMwpb!C@%fb2-iX<5Zsf=}H{K&ACZT zUD_+n&e-qTIUog>wKt+3#b`0V)mTw+LNVI9b8-YFOTL9-5T2OORysaXZGfG0Ths$6eC$z`Gd2nj>!UAe z+^YsO)1s6IcHu>Fw-oV-?pKJS&(}1MCf&O?RvDPka?y0 z&RY~3Qb@iTHl*B|HOy8C#ddK+sADvdzJ+G>2YwGoUZ)hHm9tfH6VsyYEQ&=|uAQ5~d}-NL*q36u^4?_1@+*t9<- zRg??-J-O??&`Ow`rQNh6pV~Q1ro}zzlN!=-(t_5G6ps7%_?uL3c-f_SRxb(>`dN2b z#I1uD>j-k1q$lOwpT4fRiO+F=d@Je-mj^>XRQvsG2U!JkK= z+c;6!iJ+wS*4~#HSJIQ%CnD}P-3b-j;seV@6%1_j0*@7FuLZs&@7@@HvT^p)_p2ut zCiUuZ08&sh{d)6;!Anpu?Cyc))wgQ`N>4P_Gks$YN(oVdBiG+!zjhioLIoOoYPR7rPRp zfRY6CSsgx=PpMY=9}tI|vcDCapjQEfxhFcrb0_4Lw1V|T0(hC-7i2AhK(IuY(au_& z&BMr&!ztZY2VWWuQ;2oNxg7i zCVWV8k5j$XO)2g@04+hBoeEA3PkWqM^N$*$BO^ZxL#MVH>s$@d67AX@2wZ*sd zc0YA7>E4T)Bq5^D=7ccAuO&ZS=3{EE;Z$aX>Mkqao*FX%u5?}jJi-=PufHc`CnhPYxTqI ziDNuTjBqtA1TNLc3ck;U8S8A4-_&=rHDExY-u|vB`$|R+!aD|em=Zu;k}>?<0^pzc zN`5oaWWXIZ?I_&p=qLZUH)4o3^zQhJW) z@J=pjRi>m-#XTJ!jQ_Vp^Y?Y(SEHlPq14pyo-nm}nRT#t+#=%69-w$0a$`=Q<^bYy zLq~ZFm3K}Zq0YKdOSIMse1Wf`6%|_auO4fi+qFd}Q~Qy7-M`%dQv-zG9*q!B_m}Jn zb#BVpxfP7o89_bXbb4U72A8Q3D>r1&`m2j{d4kEXus>tJisB?&&MRuS9ZzTizvq4S z1N#Z6ux$Rx2`%{y1Fu_BcVdaX_c?CxTVW+`4-R!xV}%I~i-oCO}cyn|3#1fL(tib`Yrb;GEbol*1?KewZ{d9Vwv zE|G20n3hEz3}+hn!LAt}_@{OPqv_!NRjv02;j^<)3VnSz#soj}z-3Gd9e3L1`^A&2 z=9kQNZC1s1Em!?Vtg{pfBPBEK z#ed}9$p9sO(~!^Pf8RXOX|8Cw*8^ACe#OXkbtbZW%Sx+n))Bx=Chw5rru_&ee1aok zhCB8E7O+i&&a4#1uZ8gfti|`XHE)6ZZ?R?KSq3rgG4jNzPF8-qabMdq|Hb^wg5LI3 z8o7;oFAAM(@}#79#%wpn1QM|R2bSp#;0Hyfbu9ogkoFSaj`nt_i` zM1|k*@ym$6E9dnLKZ;7oWnS)++o$oe%h)JO%;3h1ckYsl&rk$DA?3SRw7B>s!+7HM z(jT>A-=)0~A=;d};HP5&(&zWN+0A7i7y zO=%)kWZdYj`}XBL&w5=iruw2&Ei|+j8ZRO&&RX?lPt13xmcrT7#>`;>8R2ZY>Dwq6 zLc>%X!pjfg{`w4Z(O(t3t-xwb@hXz}s50QX5z*pe@9UJmtuZpWw(luqsZUnc$Rlx` zcTlI&@8T?LR6Fp89LXXD9iiPbl26+7iInJ01GjjTlzUGt^}a`j&A7$A?x)G&zWMd- zz1NC=8F2c4%GEV(Fiem@PRA4${msskm6p?XDt3hqnLBA-6Wf!Qqw(${+>;)HN9n=< zX*bsZCX8JwSXAE4qk{=nD)+4G&O5cox;@p-vOGGO{u-T3^O-+q{7Dd@X9N^+3wF<* zg>Xj+o+>#=A#Q2%<3&*Ll~aE!wm*C1f>NTA68@n$kr0%i>01qm(zlbSRIdpXwlhk{ zUM{dmFKa|BHu()_A|uB|uWO(q&x#>q%tO*IM<;V}Xckj;RtnLE!ld)#aF(~ZEG}6Y zwX)0q^wlT#DG*YLCl_p3h2}VN$J))pETanSX7EzY<1a3!2hs`bz3*hs4<68+prPX$ zKW6>Ve`j z+VETCu5V3hCI=_s4;ni@+tj(;Cgw(Wfw~{7-j!a?c-O%+;+v{d<{zfv4DRJJfcHSR z?#I8m0E`(^#qwWjxD4m9{jNcc4(7{ul|w{}?bNpie$uC%Pt-o>m+OuqBkb<9Wfk_4 zOA3TZF%Xs#7mqMi;Wn#p``lp1ZAM!g6%mHP^5Ykpe-$dl-8s>#$dDYOA6#9G%rc@~=WJ!eKUK z+Kb8qM)DfchQJb)Bi?=L2T2@Z#AsM<2C+#KzE z+1tQ7gGr_9)sd7$=o*;=r?0OJ*MIAp%i2iFb>Pncf~N=_&R!en=}@@8M=V&4ouZud z#0Lqsdld^_rlN4Gyp%IIAqp}Y$gF5mBe=huI`YAN4dj04|C7dgqX`_%#081Er$*+x zE{4`Mz-ynWG7b6xD!2asD7cNjl>1F@G^@37el%{E5u#|ThdC)Bo{Bb<8P&N36vu03%IJxv8PBo%??5y=X~+AU0|=9; z(h*!-otWK;Dw8R|4O314#I@Fjlv!Ym?*TgX7_AeMk!I zY=PYcTm90KV3I1mZv^Q0VU!!Kf;v>G8Td`?wrzj9UynDKId^r)4v^O`HZCrXP+{JW zu9I#|`-Y_iEk_xtRclq)_Lj}qL$0w?Hi7MKG(Wvg!*&xT1Z}Qx92E)AqIHn+Sb18^ z=*v+a0$57MH)l|aFCmSdf&C+?5#lBngn?6@ z&Kkr6NbKNh_!*#~)q2kaNS>9`p7f0r6;KI5WiX>>Yd=a_MJZ$Af3JG>r_0B5cWR@f zSYg~@4GjV`xM9R%G!%7gN7R6H5XP|8Tm5h?1eGMwmzFY2$m1y|6=FOS;^JoUP zE#WjqTc_i=YS`>wM*wU`Oo7rr!i&H^dQ$lLpDk%44)S_Z5OnwU8K?lOn)Myva#v%( z5&S@UTmu%e7i1oA_~-AQ3BD-&9b$oa*odES+yVf|A7Inq?19HE&eM(3F9aBVyex^D zq=Gn+CCjpI?LplFFHY=^7NQl`;PEv67bnYeiNUlaQjBWCr`+?DmS=~f7U;E84K-}u zrmGW~QMTQmg6A%S*%~9`N`Lo5M;&RPbTE8Ws7e=R9~l^W8_~qlU#+=qTPX^#|S0i;VwNxlB!5XumMN6!_e0Cfy={ol8%7$r9tAp9qqc=s;TXOPkP>R&MO!;aE#s~&ngIUlus!2eBU0NF{a>TwFElxA3w2$c& z&77=pwQywb2GG_Z9%!1jBOPT{=!x}7(n~5pQW>W^b@-`N3 zoEShXk@)uj9{HQI*}N2l1I$Ka7e|T+uwFBP)zPz4yGpdj50!Ou*4pu|clR z*1utF<-QPjDhx;-+Eu+|Nf6JoCE>g3$j8WBY^F*?@D}gWiA+3fjtWnC{01W9<1oAfaN+q4 zE3ZMz(TLZO9I8SZfn`ec3{IqiT3-@#d%DIoqbe6@H{rJ;=GwwiQt8cdI`;p(wAftg zOLJ7zACYv!VcJhu9%A;ws97~VTC*O5IeY8xNW@~qL7uN>)X_U2+ZgZvUSInQWX#WJ z&v_7qu`l$BO{LdOy#a}=KHnghckr&d+K%t958xS~4kz4uw^=o6d~hdu5e=z(s4u>9 z2koce$wj`drW4di*lJ+tZ5hpgZ z6?C1(IIMZS1f?+0iBuN@*_v~?e%%x@DsLCjznSeuJU_xVb4)@1#n!mG!iyuuc6aeu z*Ra2pck+8|EayG&C(?|0kD^Hw42E+n2I}rA*tjdKS0km8v_KVHpRdFT(!<5y`ux__ zDNu-fc^CiY@K=Y1JNd^pOK0fr0t>i7+vs3Svm=Y#bHiv%A1)WWO(hPk;y z2Oh2bwJ>BxkxElQpRC@Lw8qem0)LD7K9|v(;IKi579Bq_iUExRW72h2yMJ&QrZ$&` z3Pc_kLG6FGK6T)8{XQ`!2ij6{b3 zqc&n8q$OPEITC_63-!=UhoS}E{u**x1gix}(fGMqcoe^hu^z*EBF? z{O1XG*rbI_znEf^`MyE+9sRHBwZy@}GNj(FzNe0yd0sYz4^j;ZjW7f1ow&HxqcryX zJh8yDW2j9`Yh-~Tc?{e0GFm<5=R%nu0er>|pUB7q-6#ILf0qllBg!+$->drR>b8N) zXVB2fN{39IA;ecw<|jvA=N7k-xs7VLO~x!hO?9g{lK>vK7(URWs?;s8s+2) zlF$JwOex zYwfby|NbP=IB@UNZ9&sA%U@34sN1Oopvdr%!GRL*?dfo13?#42^tHVSR7gH+*T(5YB9GF9%y}5QBl6jKY#i|7L*~4|dD2_U^S3DVeUHeUsx9`dQy0NR*ZkC2eeP%TC z(9?(5);e9|cs(_2i_aH<4jDzYJoP~V-7MVgy}t-FlBqJJT0hGOz%&n$|EJSurgp9k z(WNymM9FGNg=g!dk|2*Ue99sk%(L84JNVThG%4*scES$O`JI)+w74+OYLwu1eW?yr z$TpB(f%0S^Ve^G5HH9tKDZ;aYDzhvyw8m*%+>z2VIc6u(B5)iU>&!WIlW{6XoB4_C zw{-CsXX`s3t*0tK)_2`>fg7Ic1Pp7c<}n3Z|NaK4rZ!hA&JT|SyUsLEwWmz<&+$tU zx=ulk6Wz2^2;p_B-?6|t8f>arFjmiR(C(0Xw)Sz-3kf7b$qDr`!Z#9 z@UtskFooZ)jVAW|4}e~VkpO+>0y@a3OL6qvH^VQxLCGrNK!&u^F(}ZusZ}QA@np?& zAhPLbV|ePB7_48l%i(&1!1PqM%+TZ*qe1c3gH7xxXQTNyfEVjyh`!O4pDX$mu$dwl zE@ujAhhDj%T-_Q^!aWuUgje${O!16EOrLGN4j21GNHYrjK7pD z|6Dpjs+nnK;V&!g2eRQk(TKB+aC*K8F&^SqS!YtFPJB>4?W)N0f-hCKrE?e_p+BBO z3oCfwELCx#v?o+3$0_J1eo)PR;H@LcYIrI&+h-)>8DKIo)+BSF*Ht*l7PM$??BoRI zAxv_?aSC;fH!%G#1)zV@ty@jU@;qUyxT(mwS5*g^ zt&O#9ArCf3Vym4UiT^m?GjFU_84a&=9WVbCMM>zpKRM3%_fP~zgd3v){`6^qKQf)#iqZgb?N?S(Df1M7sR>`r~jBuI)6P0?9Ni77Y!f$RO40GU8^ zo-Y-Z{-jy=5oPFsHFk@+26`cPoiS9^Onu3Hl;{Rn2uZvzJ*WJ=ecsIAu11xs|2I>+ zq#~7MZ0i&;4tJiaG6r(!KRz|Q<$gy+j^h_RrMXc~`}2)k4-gZ7IZNZnTQds|aT4HV z&^J=*67n+QDS5PA*sDQl%I@{l-*a3kGAIGzj4{CeE>C_^!DvLhkc21SMQnKKYLi` zc|x_<{dTC6G#Rv(N3RtUKD+Y%gyj2pj#hH)n25u@XGItWi0#wc_k91_5VJXfH55<$ zQ?g(CL0p&|3^KBK_mR$qTvXcqW@yU?#-EK;%o3F{9oMN@N)Puj$UVm|{9NT)+{q{W z&Z?E;Pk8lvWdx|u(36MX0>wR}n3lfAy0+ARx@^ov`KZDY>9&JpaNCNlXR-wAbqV64HIFOa4KDB-`&Ze+_W-M#V$O^G&_SGuKi&U z!(%OB=ess(m1+IK;(=fC_t9}y9m~I#```O)LtmqUY4bZf)*dQn!?)dr_ujd4eHyI; zD)tE^pRI~3A~G}VCcyd9q_4H{15D!c*U|4!7)LWzDC6{kBQW>42E+`>iOcAZV`4jG zF{W=w0h`(9V!xv;iPVQzgO4YN*NYqauC2WS(}fn=K9p~#8F;KpI9#`Le)UWq#|+H1 z8h!UC0jTSI)#1KeHG3uLYa8?SM)RikytV4=SCdLQ zivr~`RUiIQ0l^Zt*5b#ai%%Y=j-naxm{6LstbTG=6<~9l$F%3gdRd`WkH!KgfN38% z;wgch`8HUJja*)U2fzpwtb7w4>w)*_qd2aH-~ZVOFjHr#b)YJag9TU=VUxft-@)_C zxJXd+Z&iF6iNSmPMlu5L-gB%qb`2d7bHiZj>0+oNUKxQy-sqqzq1F2!Mw<5rmN^>f z6s)BGpNKB#VYYBmP2tie$>&gx+z{&#`v(E$s)2G#|I1|w6#&^=OC{~ElO;nWXf1+c zJ@?*sCkWGLz5wv{lf+l&2o|p291-fA@NR@B6bVui{RMdUX`5f2lI3!$EDbBo+ z)FzXE{4?)^!dymx?NgAxN-YFz6GB2JHBG()w3pg#{*tZRaE74ckaw`nV(z?jV#-_K3;g&*H9vLije0C;x)=`GS19DZWb&JK; zT}KL&RkF`Z=gpCzc>u~_Q6CKHotJ9M>7h3Dgqzh=&6|U#dhZ^kaycmSZ$=)J8JVeTC<#!8?jznb(`DB(d zG@J9&P>%Kk4pmyC&|y;UEg!Nv&G>sRK*ZuDC9h;l`ao30L3*!U}^M@YFH~ zN#4M`2gV=q!Na7YP3oH{;P9Andm_0w$bon#K2Ii}cTV4L@_woF;A6ynE}Ro(neEhM z?OVAsoO{kVU(~;@j09yq4UtB`8db_sDm`M3G-5}GNL&TIaq+SRAST}<&UJsq#jNLM&k?qxFkh99^SkmzW=TQ5+#ie8I@uXLi??fj-wc&igV%pisQ@N z!BRJtS!R4+Ae_S1uZ~7EnsWg__01v}eZ_hkkhCOTznN~VQ@IjQ_mGrnAj^O;{|+FF zma~(@*m5H$EoEZfEhF;eciZV1q*d?4SdVmO5f{4?`Sx5vkJvk{rS%1p*jrS^S8u8} z3;Paa+kj1=Wq??El06^NJ}B@Th5i-h4Aih;&TWMCy=<87wlEY=YvLtv)%ZZZ7>);%($0z;`00BF4R5#lAY^G0UF@G{mjez zQf&UkMYKu>z6UDIxpngJqL)+MqXhM{5!z>Fg@$5+prkkI zZ#mlap8ZmWQamq10BS7mnay;t6dOjC_j>ZA2Q*n-JYIYSsd10`WbAK!`!9u>K3J3M zSp!(VM?LThDn-uME%P1%(1!lEiVa|+aT+I%_Hd<6$;|ef96EQ&7Ps;mQaAqDUKgO` zh<%*iToNM!t$zHRyDMB(m`{>5@-c1I`DZ`{Jr$JGOr`bxI0(B**ETB*jR}xne}=<1 z)4mIHf9JAzCj`Slj(`e({mQJ@*!SxN@~^THtRDK``ZgQve%DvpQMd!IF-!xS0m510rc{@#1M=(;NfShqYtA!q3~RdW|7XE2}h9LeE%4| zndfQzMD5lOFF;UwzNCHY*8f%LtvSRa0m1%29jWpK304aNa6|MypaU`l8X^4c=2}DQ zcPL{*FEw3&D?w58APV&|pVjY)mhqMVtN(5D*JN26ItJ36^lPh9(WZ5(-m70U{e7Cgrp9P4?~VPqCP(OUXa#YK#Sp`t1I<#P>g56m3*oRgeu$qrEPc%2>IG zok%`E3rx;~sA*8tFL)84O-0CVyb?nSYw16E}Z=w6G( zO?cxJ?~SoCG=l3HAlN^0ot~`T|9O?hkO>+L*o+2?0kImtZ51tV3r8TL?mp@=d-v;h zu~xEpaR33o1$5c_4(#&pE46#VuJnI>)I9|wo=eb!zey5Fwj}6N; z4NNe;HvsbhC-(5WL(P6$b?7wD5u4 zS{$$T*D)~ws7r2i%mPHe#*?;FCEY>Nc}}V)T^?KAD*+6n>|@Mb{-O1E;Xac9d@kNC=11yHwrJ8fcsRi z$pu3*H8-r$b7uatTsqa@jb7_WjH=ed&WznC?`jN(bDAXsXHPLaoM<`d{TSfTWSK%)WU+^o6JTu3lj)UfpDMKqYx4fuo_#3JA}v2P7Ayx2=bd02HsmLYfzs^0u5 z%zjZTPrjEb?M_z`r?5+yAu6ED$wf9ZsSy8)t_E3~`|tj8AY;l`0Ngbj@+bws^d{K~ zsA@G0K%8OM6)Yr0;gM%ts~BF8F*MoND95D6B_aLeFcFJ7H;HoQ8k(%-28<6cfMXjD z=q9rUFE6VPq|tZ8S^@WbT=+u@Xf052@NA60Ie;*%_{Mcf+Q|)WdK_RlH8vODr`ugx z`kCw9ej6awddfD_fKUqzI)RZsmy!96m=Y70x?kL1FpI~#3(jn$aB@-oC9-7tQVUVN zeqfqn63j5iyBx3MLN0EzgcMf-ZZ=i#DLk0M2uYW8O&12FJ&Db!hS4RJ z9gwxAbh#XyWm=-H8!#Zh62-8nWo`f^ru>{UKKOuA zIg2r)`~8Zp-JB>yeB#(QpzjlR=Gv+ibb6roL5`#h<6L*wY!c8Hoo}9p0{b8fvf2wi zFRu)z2dB*(p9>G#{6V>XOp^PS|b< z&Tu8p5`}v#ye06BAr(YN9Q+@0F_6oI(w8Li``w^kUeP3&MhZ|fLcUv-Mgf}hL}NCp zbjB$|T?U=l}{cg2+he2#6R+NGlS`5Q>CI=YXUl-M+_#_x;|_{cP{IeSg-EwXHSG zTydWJvDYaeP=3HV$I+e9A-O?Sym8WnLkDqxzNFoUGLenJ#Rf&*fW*$Dx&j%qf%Cc6 z&+p^Sf(fdf0bf*ql(NWHepSo(y-z|1TU&H50QU`s=r}WH8j5Kx62>=l*ru6rb>;mRmru|XuAwqx%%zwa*~RT8?Wb=S(Q1#jg*&%GzV2}W}OCq%apV* zZ~j0zRKbQi#z_&W&aIsuw-Q-)e>O_$ zMOtN?MUYX)WkJOnZ>ndgFn^AviK^bfWN`*_R5H~;6j!YK_2#p1pJPwRwSMItZ#_hd z!(SfYAwM=c{gM3mNiI&w@P+;n3vL!rDnDXDAB>_LhQHMYP6i9zaQ^?N_I1puxc(f?@%nkv0UrE}*mFGEm{Y{Fo2LZN$3{ za-Bk$rmQK)M0C&gr&9#2Kbw9gWL|9hOS>|8pOMGc6uwy@7z-X^!PD8MeAl*?zE|kS z@^Bk{d4zltq!K4Jw-(ze_Kx}*=fCc!5%g(%aSm8%wMl(?SP|*zT?R@k(m|x3?#h1( zk)^VO_T@ztGX+g{Fh%!=)3Y$YjEF!!=|3a`tSZf*E04&OPhhVB!YTtNWPLVPKc=hh zk8J4>&9&huULH!_@{!CuNss>k)Om@FkP)RT1N;a?y@O^5q5v)67ckY~lXdKY@6M=k z3A_^hK&d3&kh<`>^ObEsbofZ?@DPU}$5gn!;=@UWg~H&c8V}qTIfWqbbvy!5{P6D^ zYF^Uk-uEqmnDgzWO*^&;mhO^}w>fEck(7h1oa7E66gS7Hh+$ma)a`f)ZEKepgklyp z6Vr%+T*AW?&Cv48z-nLaFMk`n^zF1$fG#)OOy+|-5AC9_c>j+FrycSs^QX^+a;NJf zLlr%OzIAB|UNRzuNIp~GJh=hj!t2lW)(-wCzA`BDn;W|a;Vdoj@K#>xQBb>-I)zbq zjoYE9Udmq~KYq*xw;SK_|Ls&kR`CCg^Un=|{Y7iXkpsf-YnCc72?2`z#>k6hMv9Ms zpARLzU`dTr9thNfB>?%lf1!62hv4Vmr3Yk*7Y&A*rX@rAhJW>ywL?44CoKPDXA4JH zha1}hI9qzYegIOFV$|gx6$SR(*4!@7S>& zUEDZ9+-ATnOd(*(I>E21A9$_`F_R?z_BgZ)s3A%&Bfll;lg`&Jym$pa!)y=SG_vE} z2Qs+qAc#}f$c_yGFInOyL$t&`!vzs*(vJq84Ly^QvIgk7d$8K=0C5!J1YJ-VP#3z~ zeX&?(Xe(ZGodRiIbsi%9B|n6{t9!VTLmzjO;k&-q`)9yHir?4i29Wj3E?)4cN6KcH zo9y&&l*f41yBS)V#?7J>yyxo5r^?D=5JUMly55}EQ+8?OS^JNsq6h`|b=aB{o_^hrQKop)h?#6P z3+yKVZvo(j2~h6$Pq^G&kfjb>webu{Lla)5!@Iw~vQ<`JgM?r6_`%b!Q(pi#Yb-`| zO{!RIGqHbg;A9^jB{H)FmWi+S!Lx5)yMBJOtkz~kT|rzv$Xi)4mSk&}p&5$!Nzn4f zJvb1qIOmg033u3qetv;X?=)<<*WiAfwe8kyk{7b<&NN0Rgi_yFJT>4s3Fe)GilK0p zJ`fWxFXSIDz5y>-?^QSU5u?mia;%K6*s(-!V9N^`m3l?be%8OZy}4F=4vJrjMn8n* zzyBD^TLDu>`Ti@Ft;-LCfW}E&EJeVUI9-ybceDE-TzHQQUyKEJ!J}NL-)F<%y9uVJ zawxs(`sbhT_Se!NGv^wIbMfPz zn7BK5npAcKu%0=Yf2P#?N$wfk&N8W=D}M#w1yr^D%Fq5o_Zu`cX&|DGRd)RF&&t@t zb3KC;VWQQ5p$yT5TrI6|sY7z5%PC&$cCNtz^?)g7%g;=gVS0EC`?>gg>4>6y&vEz? z8=R_znRXzLVqalsXP2-{MkVIo9ktG|{r&^5v3(RXcOSJO;nkMxyT$tz$bP?tAb~7* zOslqfXl6fjhrP>r-w@G<`-K zGoB+O`WqbHqC5>qbIX<4(W7yDJTHc(%EEjmQ_-NE6a9EW?S9jqn^ij6VXCeaxeOr- z3x^*U8a^}BM=kq4^{yAVMwN;ZXZx?^DjCvZm#HtXc8t9MEHfyRdsyUA(QyBgC9$<& zUAnC69}#|c0`#QkovhD(fs;*r{#g--9j@@*4+v8{SEw zOcTtDm+^yqzw*x^q*#_DAzf$VK9f9_WJ(}fQ`-&gZM4Eo)#$=EVS*QR78=ItroANN zP7tQQ=VjoxeSHlHp>fLf=}sSh=ruAcV;K*HH-Fyk{SyC0WaDML^&YQ46@&L|2d1;O zW_Tx+wiRru!b0Oxf2rslh^;C^VS9(OAEit>|0v1PJ%PQXOb4r}rcpp5ieBdclWxsK zDlW0zFyY2R%3v~u^PT8QSM1(am&}K3K6tw4^o$r9gChmtT3AjSze0Ufzv4eZ`~C-& zxp0r~m3Np8$%Eky1@`6s`5nmbjpPWTUeH$DGoLUBV;=O|JyW;jRl7Rlw$T!di`u$c zX0cb**Lh@Qgr39}`t8N(lB)}Xs?`)#-lq`j`5Ff=`5EU`(5BvoV&YA$t%m@?v~vW-uHPLm$157sybAfFX-GWR9iaQel`+Mv-x^7N%LdC!H`!JgpSlr zy`&P&X_2Y7FReI+=g_4|j>@<*#D!z^a%yiZm}p4|#UULY-MkJ{*RcayUxV0!#0>e) z9|Bjx*jT|fWZdiML?tPV(wtvHOr?Xd;mt<3s66L_#vUY*fk!!=$?fc%wnDhR%-4{m zAhbaVVT9$^#U9*_{a|Mw-Mwd@bGWId9MR>E1ENVh<(dzaMTa;4iMI3$35`3_>Vy5r z|Gh2Cqylh`2C;Eiee$>S&HcP;Hogh9A8FmQt*{x2v(MgH1dH<3X$kyRz6hOW#{uH1 zL}A!)$S?}&|AJ!WbF(yi%5?6D)DMW4!Z5J0HRdRkr6 z*Gf|2rkMi-TeC*ivU!NgKhmczsiQ1zZaTHank`wq4v#dex2R|U5^~3|JJInG!}I9K z#~`wkl?*QGi^-vM}8Hw(jbB(%nDADSNf1FBv$o1BTe!nf$(K{Y1 zsUu-G)mU##kdi&16UXS!ESxKh%YJen3NIrUW)MB@h|m}_7_vQF{?v?UZ|AEWkIm6G zZY=Gevxt_u6wnWLIe;bd44lo3L3cSDthlmHFbWqi0m%t-*B!ylL|-dsu!!A*JBB#c zA{pUJxz6prUCfPbuUwycalJ?yw{LHoE$tLO=L4R^YAp0jjdJ%OYKM>Bxh|4d#6bO0 zL=vzbdjq!2a9k%yC2kn_ceU`LM&RDxS+$@=hj{*f*$^A9Ik{O0%YTNjGa12%kF_YU zF9-nmei_LRF~~4=U)8e7K$sW)a?*ejTcc9ihVDtV9;y?|Q+|>7OoVVhT0US{X%pzY z{1_!>;+~yff@8y;-B|f$PMLuHus>POsSg0DQ2Nr|IDXX;=+F3hw!~{T{rc4NkO$w9 z_C-(dItkX&&LXQAAdK&EsEfjN&-IYHQ1Xddj$&4|@Y3B2{HX6B+NcUAX^d!MKNeJsuukh>pw z*jXHxtr|GzxGE-$`^TB!=X*fhGksY%`Q!<7 z9keji0`U4gj!--)K)da5wE&uC24OQR1Bz7u{njOL2EsO_FJ6~7CZ)+a-A*~;exFJ0 zE6E%DU`)oy&OE#I+POV_slk{_f zJbw+mD{hA6V&?kd#QBXz-=cn@lv}h6J=^_2sds(?VbDP4mjd$~$=8Z&+e@E>n4>>C zB_a~fSbveb)vAl|ynx{Br^y{ly-O1hy4JcX0XFI;nGdFLAFGV9!~y6WY(<7Ca^4%K zdbZe2&Q0wz2-<_jNImo^ECqpQF?{~a{D&QHLg|S_T=XjT{b+r>a`)IcKU0ryw015)~JO=aM3xvDG=2_OoPA?pN5S-361mZWUST>6jJ_Ub54&?~EFaasF71f`4m9zAp2 zm;?hL0?4FY!gtKG*t+KZuH(%l@#l7P#Faj}?kfyZUcgoZF`35X2jl9?S)IT%MkSF~ z`I=-ez?I{^V~t{Q^qwN~mHv~KX!qrm(D6T+{+o!XzW=6m{qfX&e|^;dVkZ*J2o1w9 zcx6uyR#n+YMq^F`c`x(na_7qiVG6}VO$U^)l`ghPCLjk)@m_?-m@EC|-?o4CmVm>< ziH`IPXLPU^TeS+P=Z%pMOwET{RE*&N`mqM6-S}lnsOaYloiA-Vlh&7}Z4)1P{uT`9 zpTSEqGyGi{#edw}R1}uqBXP|BL!{AXc>1IdVY`1(L47H(rXjz`M6>eAiwaZlit{mN zu=jAuFb=wLnx-EHvv2;dXlD7eNeaVqfTx66s@q6P3Z~rw_sCk);i5f7{z6~AlVOuN z8;iW>3)_;{wr?D79g5g22pe$Pf<)5I7SjF~Dr0vH<+6asnF$tPA-Z?;+Tj;5T+iou z8<@lom`>4TeWy%|1?p)>Wc<)`we%V%uyHR1nLKqT+>-{!!!OFl5fFM7+E z@icH__YX_y^fms9Qh#c23z7Ls14G1Mn_AHA@GXp_nCGzSOof99=14NI6N;ylL#^qp zB;Mg4|KCSnPmy`|Pe;148hQFVVLT&&|Fwg#hnq?u37?V^rl83bxB0C1$Dnn9H@k3% zlK2gUZ9gG@jQ1=!%q9!0X`@n^C4YSS`ZX%+D7HeI0F6Q12X)f5ZoH>cJwP~NrYZ=J zR8}(Bnx15yJwHVS3QmGHYdxn`;Mp&BbGwkq+8o#b5t?i#9kjET;Qs*zA#&#mFv7N` z3v#D1SqoW+k#*{J^0%l{_>RLx1wyY?xv9$X6JH)FUX~wtVjeDSb{h#qt*7G<5w}A= z{%rq`e@Poa@<$r6yA0enmQ71TmaLM1=~KvnTCNr_RSM*oOJzwVolPq1e}#jXSmeX2 zFDFQtUxjJZ51C3~mGf@_sp}SNzYP48h@JSszh2-``ZXj|2AsoAF%d@gxh<> zlnNb4DSe2$3N9icm>iXA!&L!iYqgmOkm zH2(&%hozvjbcvRMmz7?gTh^gZK*d|T%GO60;$45dA;1!CXXXWfx(ii?NY_vTC}|f0 zf{GU?fv+vIj{VGi@!FTy&ij_J{3Q34cS}i;CmAgqcP=>B0gf>s(ROwlYiOE~L3(?< zAuM$LrJyZ~u$cgIKRBW|8(okM?jFA?Qr^)V@UyS`9pt0(4`hf%swl!;YCw_o@EoLZ zSTdNaL?anLlqJKaN%72V?*r?&bipPJ=8+dswJmmsG!~H-$#`>4nOw7`X|Ej_l>E>WQ$*qIeLRX$ z^o+pJG;csTqRsK~$6XtJRy_;AMD1`Y5Q00cI`HF4F<3R$R+Npw74AEcU33_+`5Ir|eKuG|po3 zL~A?Dp=YE-UwyMYFg~F0S9LL6ykdRg3KSH~AaNYq^r{={lh5ija% zdN$LAxU$ANB#UWQbr-oO{F|JPYOG`;ruQ{g&H_)l^lNZ1d$Rpm1#DN3QnBib=|TWM zDxiz5A5p1IZP;lS5fychphg$G!c9<>CFOFb8!?^#N?g(c68uzpWi9*2G`y zpwI#t{~?Hhj3bgdHZmulpJ-o|4#K(^9bb1Cm1x+9gd&=JM`xmbLx2jZe@)+b=3fEE z$XDUw7k|Ka0U&%8SB_x1?~)PeuZL?9xN#jn!R?0>Gafy{xf5@w8KO+39CrnRo-g z6*N?YfoI@qD#~&>N@+dRRUKcVr4*u!pxJt*_7R}8TA{QbcN-K6Vmg$wG#`Q7A7uVM zHC7AjRIPu@>jTRE!VM80R;?{a?1p)Xpn zG!-!(az|5CQ2|3|AGOgk% z0RP-U$%vNheF*`~V2F=;rZEmcnt2*WB}uCI3YhKDCcq}*J`WQ;|3kpgw!y06eJXZ{ zUF#0c9DuM_Y%XU2Jhd;QJyrgsjCJuQxeUsHDpYr3XcPIs3J9L{9H&uj?Bf^Rn0XAC z|0?YQFrMA_b}O^Z?8lly14{;6`9G0lWgyC+X>-8J7Cg z;=&As2xB^G;TQKnYndK0RQq6+SB>`3<$+Cd!km$~kA`R1?i~AlJt``QW_$~gL2GJ7 zRuOkIHiz0Qe-B16Q8t})7`}{|X^b)xt7^$ijBkF;eZu*pTg7L0**q=WH~%@9$}N() zDJ1m{7#~v(Vn7>RrRZG6O1P=@KH~BQ#TSPSl1FMX^>+`fK@Oj<7dgG)5-#_ap@dqGNC(2H?Zlf&nS zpQ|c%7Itl&wovi-M=enjY|nd=2j>JNjf7=cy+u-HCQX$xw#|#?Ato}0I@QK-hK)431#dU&!alN@ZD9OFVT+D9 z+33LTg|F!_@C_pSJKcga-5Tj)dj6(M+%AjrNk0OKJ=MSGQKr6m@3gDtYFKyzp7n2P zkviz;NpYT(qnwSEFCSJ#$Fz_4L3AVlEM+qbn6{?k?rYd?{>xrmUFj znB-wz?%uy z<&?qAPjq)A1yt)9u|%gf;fcCCbZI#l^p*&Fa^)Gd7erc9&C4+I%tmzV&b(jzMgwJ< z5y84J{<+Xi>X&fIOcPPW(Br7Y0SaC;je9c3K%MBk{9s}(?b?95g{^p8)Hxv3Or21{v!W+|?2h`rsp+T3&Eu@ej_t_Q7Oha_r~y=h{IucUnrqpN zeDTfD?dO{yBpOpcLp_YPF}N_f`e}M|dcl=MaQ|bZ$*PNY4r&m=Mwb$+jvd%lGp_&e z>gpc_Een&La1Rs-N6C4qlbFm!Bh-pJVZ&Qvcu~7&g#O;FNV)btI-nT^XXiJ#BF!Qg zl{~dIZ0^Lz_*QNf<2e(|XM1mTaaM~lryMX$8tcwBfAQE8$>c7zDh;06c6smYzE5zO zwspMgt5VXi>xUBV-_K@WrnInkFVA9^uzkN}SNEYV#>v$uNgmChug?fwZgnoMy*O-j zj$$rx?DKC3*83yx?UOk@7#Y0h&AIJSx+<}|*Vn5Wv;!w*ju~ReWg2JMn*?Y`8C43M z+(~Vf1Y7mWH@tMi#h66gSpdf4k#<7t_3D3q6VBC(Rkoa0+1>ZSxj>JvO{keAo;H(D zfA+S>zoh>Y+$=EBzmB0Ns0|4{vyxCw^}@{WZav+$2fzDH)2HZ7y`4PfzxxczfRO9{ z8y^1m_>+*>0dlL7@_6Bl?cOjsvv`qMS|7w`l`8dO6yopRHVzn8T8s`3vQ#o zx`4u?xT*G-@SDW+8eUc5x<9&i6b664f|5tsORM59{(+#1b>duF*F5vq`5fLE^@3fB zF~(7hKLXl}38>A2u|Tni6vo`36Zs-BLFF?-0_fTd`@|=-e^F(#v1E*vsl_V3$5;=) zDP9tHlG?=ibxw~y_{{B?-PZDtpJtq6s8Y>6g>kI5v&9Bmr#_aaBl9KM)$!y%5j``v zuKh(}S~rf5<5pbwc^$E{ly+?6Q@Omnwh!0wbPH#58TUg%=RJuy6r#B2Sc`C9B7r5z zLZq#^d@Cp_z|tkcDdAYN`@xOl>TX(1Z!`q(NFbblmi!L4u=N4^o=c$q=ID=jmU>0I z^WjSb*|-UYc=Z}e@oMTB33G2LPs5K)aRFQ+uYA;h8T#D#%Zf5<;rW&#peyhEQZPJe z?iWmAXj{)tjVJ-$HE??xvQO9Wm9uCuOgKtibnsTj5yn4qB*_z|3s&Yq4%N=fW2@@;ph@>BdMSA&;MHkan%GrslIc(=2bq7tw4 z=+~FU%9K;apQg)^8~*TB=b5Fcy@udiv0#rL{cg2`1t5EojZZS)@VI}H_Br@A^)ua- zYp*1bt3T}Hr|tRpSOAyr&eA#wJVV!Xhi3Npk=~Im%V%BR(FTF4VGfO|ar|1#X#rLl zkDMZ#6?%m73bpt}atWzmIA89zkbSiDtU`x##QN`w=Jw=^*K^A(>9%MNd10%~dx5{* zz8hYOHiORCnzsP*h_6353f=8ApaZ*DD$2vx5sAt|(dXU-6^QL{u#m}&it@2X0F`v^ z?#IM5Deb*Ww~LiUUK)IPN9R7!7Hd9sJ=}I|F{K#OkH2*0LNuoWB`SlRVAJ#I-ai&L zyT4wT?x@ z{UGG<9bShwZ`*;o+qt{S%o0~>GhEuPX|Pw+xB0|3{Pje7(kQ=~R`tyh+ijD z^ru%8<;bEzd`~pImywcBdGnmT-y39$1Mw9<_3jN(2GuNLz{Z#Ki1Oy4WoEn!Wmf+- z(L)v_BR$Q8{lQ>5=r!`9&aX2ykW~G$_0zZ3C;Uy;^&nG1GfPhj6{d;hUq?-H*xk-x zd&Y&K-~t-Nmh(iC!*ECIgmlDjs7O+pW0So-fQ)t|p511Pz{o66ZHw>~wrT}-oc z!%ka(_IJhW6uI=u^P$4{vob+2a0^kL>ifw%U_{9{B_f?2M28D%(-#Hucf>6 z??#J?I8!;{gpNKnl25gP2Fgw};Rb+cukcTmxa?=<8D8_TcARUAEsJI^V&zJQ%mouXd(-l69LZl{;4sH!82=ek zcye@moMD%QVM?V8RBxl_P!?Lk5sZT#EAUNcMkN1AHF)&T1$!_%H)&<-35FXUA=nLF&U@E6@HAn6 zP+h|T1CFe0KZl0 z%R`o?^(xB(a^vTEl3r^1fz7iIMhMRN;~i;nclZ^SV@HQg8ignM)t4W{@WWX`zh+i_ z_&Ho}@Vn{3#KpOk99^6s?)&ZJ>FzMXC!OHv(srfJhH0nzIK?%CuAqA@vS$Wln4!=I ziHRCs)#yZH+okQxk623F5T4(UJ8aJ;g1RtZc&v88+hROKkVh|4rmuxgCtV|`fJv$; zav^|dfqgE``|?QjALHYU*IqXr^pNdK+6)hMJa=CprSGx5QRo?uK!IVG4Dfn|*X`Mj zu9rw&5r|wN>Lsx>3hSEgR}HK0{p1n(OHet~=UD#Gq;ZEA(p0qRSgUwAibB`mOB2QM zhvmnJb=u-Q8v7VmJ|Ze3L0e$*Uf%cMJlmkGm*|MM!qBif=Z+2rsZ`%72fvqaj3ot%(YwzgSSuXNZ8LKxS?S5+mOCK z{78Td$dzFn`rYtS`*(gtxWyCOv1aF^=ywl3rilnE8Nofa#&8>qa78YhJVC~Rv(IG! zr}ISqif*QaJ*mVB{Eh#MHvD&|(ErP+6;;Xi(-Lo52#r4WZc#?GW8cmEIpR!VYmlHN zuWoPr`mxtTeClhw_Kr)dPU=Iv8?T_EY!%`d-1&jbiW<2{olDliLk^WHqujE{zul1+ zb(x4Gp!Jj^{Wma+SA0oRhclHmj_%v4LQsGQ)_*GEs8n@iC}NBJPl3-$hOAPq?8&=Tsvq+_diGI`tKlYB>#zr z^a3)g*oUo+{j6b;^*?PN@Bp#yZGcEn8_R4l@vk6^su#5o^ypGl0`MA-K-xNJ=)x>h zgjn&|u0!r51F<+Bgy9xmr?XFTA4=oo;oLKc62Bi`C}Y%YCx{6W5ud#LyT^av$c)Su{Xcp2)5<^98E=Bk=t0HPrEQGD** zK#(9@cpB`|hCl0sabfd4m-FC6zPO2NSnRHd0mKPRoGwa=lRPyG`|^MKuU6%+tf%26 zK?wK{%Ol8CfamR*LD?I`E@U}PLby;bU$k_R0DIROvXseiyz~*94 zt0}{aQ-;~i0`Q)L53QSd+fVL?vyU`xjLbudRl~(S?Q%aPU{P89myPC#62SUF3Mls5 z=yhRc+P{?YzGJRitE2I7Y)HgUAna5H?a^C!!BYewV${#fJ-bLfb_BF0wcI7l~uC zVOH4}l%(#%?Qo^9A;!1;njxi3qBUTybi4*MX$7p(0&y9^wJaV-tb zpGbN87O2XfV4^EfdXs0-v~c%8S?IH;27j|KOo$J3hXF@Cw+#u9F{4NIdLL0_98rN5 zo)xf2mH}ET1J~9dxd5uJt_rC6zj2esmOEC{e1|d<#M4*`=d_f=OLyiYHfhIT=L#g; z)cWG2-(5G~b-2=ImnH`gM3NApzlGy zR2&>C!0UmWuHyr>6;Jd(8Q$ABCK$TdTBqhls{!GY8e7MoA{&4$qkb3dl7yxc<2_xj7EGj z8^oEBae~~64_JjGQMN(Q`P)6+Ga8rl5NG7zY zCP=#Ox{Z+@_YLn7fM4#$wzOz0+%=YJ9fbg%O{6MoI4m4~`@hA{?$l%fF<6vSbWRy9 zrhD=8BNq!dp)s*nB9av7J~*PIO9;BR)RvAk>_i44T<;R`Y_&$w8mze25XxgQ2EkLl zxnh)VIJM?KE_gp|xary5zTLSe51QP-Oqt+#^T+<+yrMhr|1x3T?Y;+hGg?KWf^Y{4 z*IDO@p^fx;vk)K7WDpjQim0DxhKjTbekmR#{KKfwdw%eB<@~V?UZ&R|>_YW=+`D?U zZo##$b_s3@dBhr>FLG<`aGlcB`ut~`A`7hEVcHez^x?tO$~ znXN4Hs+3G^%sv0qe@8~uK^WR=kT84m2IWlk{JcGB&Ajc1Zc)_Hs$FmPG?`$XHY8vTq!*6hb~tG+(CAk?H|3_WHz(<#t~y(T!3sMmPZ zNL7HnJKnhoOyoQ}3%?I1=56AAy`(4(WHAN}DU~ks`d8J%HdD7bue zw_a#qL>w$>2~l_&YIMSBp=a+U-urnV7LFG&QBZF9Q5U~RJq7>t_}e@6Z=OQ}OGz|v z8oKBIO;*D{{i&CHK$Ks^k{rbdlqmZ=hj(#&KC%=b)nBpNhT%CLNbj9A7g#J1`kz~^ zgtE?f>!JH5V+^u{M6IXNy5mh4;;ZverR1(Z0KkmZl`vH!8|kg|k6}UU?9D+oJ^7WH?)D|9Cj#u! zSVvf|?+|kh90&i8O|-I?0nrp&b@#C$NwJQTn8hPU8>q3=VRcm)@fr7TtqfjSP~c#g zH?88@c6FGNs)GGk3Bx4vW9_~N)kL8n?c9|xM)_%1MOO7TmE5kNPNZl21C?6 zzzyf@jy7`A>o^EbGovo5@IJfGgv~`PGkD~GX;9JK%?2nTSUOuj(+7k9WC@diPF5 z4(GZvSd88HvvR|{C?A9bFVM@b-Zi+pcNi>-nXM~ks1VI%&l(xq0U zVQ!nm=zE5M)c@=ke}q4@pg?rh5ml3>Kzmc+$~z1??Mo~sfm0{)(S*PEBlT-uWC5M~ zOuiuPndWX)!uK#?wQmV|mUi`#Iw0R*01qDLqOluW!{WF633FG$;tFh}sl;tlR+9s` zy@L0cg)M8#R8H2*&)*9YYSp_97T_!4#*qNAig|{|j*_#C@`MIRc4LT?ytmrpKx3`w^Zg|$8hn#iNvzdoup*_3#E|^#)phTv3#yVNcGYZenW?ZF4aa zFa`;OcG1^_3o#@ZJR>*VHQs($5QwT+rS|jjLEwqLSs<<2)Lb8&c@b*I>siWbiv;+d zdE#m9kw&MbW$Q47sa2U`G+R{l7jxXC zl2@ObJ0>&2100G;3-#fni(tI()#&5Gc{&$*==|Gr3KTx}C^TBs22T#GPy;m09e%Vl zY0&GJ7iR{WbFuy#PxA{X!}U#7@o(kCD&jO34ssr>dw!csF#TAaG2x+%{i6aQoni=Y zWb<;+`)F4cIgPX42V!E72Nv>EMg01pNLTj6I_ANeZ>b*wk#KGM2i|#NvrgXQJdZ19XzH>x;Y*5-IK9Bf- zf}>NyR>8?Ra#Gk$lLplTCaW%!)4EZ61hpPYS&7SaQ5v*2Ch0!m5?A0TrGkJ0uUy|q z!^x-Ay{ZF6w_^6<@)O?fQguDdy&R8PL&EyK7mMrVS$%7?rK@{db#WwES0yye$Z*|c zk{e`eeRf83IVusWC%uz2L(5*Mw4$r0wd?V@0~i~n;qiWWfnKU6g>qz%OCt~-Qq`(g_94RJmz3J5My;3o&1f*Ie z<>W!il~pf`DmAkJLK!J$C-L{6_6OgC3%6NC-s6Hva4f-uYjg5>qAiY&2{(@4ClcPF z=4zk0$IiQ&zKmIZR{}y&9XeY?=``kAn|hH~Twcez2RQv}y+)zZKSaJ*KRd6@sb%R3 zXqBtvO#X$W+tun+;b8Zmo3poQrf5ZhT|-z@LcU*;AoaV(R()- z!RrUcC9_8tsuP+aFIfhFLAAc_I$>#Ub4DbndOPqCL;!CO8hD5H!kxKXW6GBQ(4XqE zx%wNawN(?=5f_MB0movp%6qITyt6>V{B=>AdxhJ*ICV(<+=AwUFva?e_RuD7aWU(m zLxEc^ua)yM3|Pk)<;fJV?N@4UlMOtY zhDpHQ@@^N$*pr|EOv!F>3@342MP`QkE$f>02~%F`pC{t-4P2(92NezZco zK5p1S=z-C6F_L}_G|HWRW#amXXqZI!Ur~IG1|1E~z^^{yAr@S^o3f0~1@ms{aHJK{ zDz>j}*S#vD6&>MA0Fr#nk;?i8NFY&!flr63C_~+piLM0WT+_CAOCVBQ+~&z@@_Cvf z$Ju;Y+A7ls)$~x@lV*6iZ9kVYUFwlLOla{gHGFwGbom=!p>dPgK&2D9oK@4#>x_2^ zzlO11f%HSs5=T(Leln6*X3ezggLZ_VFFLO0Rv(6n?K}QnL15*Kd7u!^Y}M3wg=w%4 z$LD2`5PGf<-eDj?@H02vT;CKPe9n-`jwwZ)b1-1~!x`z%nXqZ2M)5;Skh>5yWRsUS zsnaF|-y( z>*Tse9S1}%G|oHFtv$rc_y$&%mEfKd*TMrkz9@CIc#p9xN5gp3E=+5+#FhF46k?@x|PiC>& z>EOIs)NwV@#S9E)DAKJX51iM=EpFSb=7i<}spLQY_(u^3$rWHgzM2KIPvwbQ!ZuM1 z!Ud=UnJ4T;&6HhPcx6l<6(@fFFC!hSDp#?{@O`T0UI)zqixamSO;pwz{EM9yD@}2) z{hR;`Dc!syi;^J2#{56p2;ne_LE(5df1Sx8qD*`tX@ZrPfB0UTbJz*?#s>%_JoWt{ z7wrMn!>o!ABsP^o8IGP)V>@%@j4x&IXt!Yl5`BhpS0jJHMcN=b_1$eeSYnuMm{tVU z&=$A-uj$b&J5>Wl0*y;10mHnyD(LTyS<#0z-s@kIdhzK*z2r4hj#5Zico8$NJK<<53ih* zFZDWMsAB(lypn^zUFy$~m%4Y95JnIB!UX5*7MUh>8WuP$qYZ-BeVgIww1GWsl|sMVWyc% z^cjh8vML@ZC)vH_1xS>GeJ6N7yeWX%Fq?spIMH&wbr^o7Z6AW}&2 zFp67F8&)|byI{*42+)nDzcHdIqIdO!B>$ha|~E;W45uDatL|f=V7| z$h8?QR^ksAiH3qZQGAFP9Pjxw`tb**uq9OpA17;H-eD;LGo|YWavDX`D{-y$54UKG z?UzYPaQ<)GCBuX7$lf*AZYQRwglhIt#VUfDU133j$|`2&ohs9^2SIus&uvX}cLlwJ z48_|Z)jNX$-|fBNsY@Esraoxos5EpaFn@Y%j0xn7GO+7ro8;D{ zF*@Q68~JMpAQ~@3o6}KKme&{$yYmz=R1dxV?hyPrlGJw*+Z$F<6t$Jf$b>YM33qxz@5^YPuc^N#o&c%p}W zOZb-e#Jl!d6y-)L#57bmsd(+U9$*^x?X(_no`7C3LhK8(cI*L;el6nu_a3+Xv**z1 z$W29Pq09(I%vyCd{*XI+hlJV53-|UlQ46iaL4DN3$J3q6Wc6#E%BmWpJcdE-MnWzesvU>1~VWs2wYKqlq~p?b`IXy;2jRyJfX_Z9bpbheYf zd=7Rmx+5Asw1k%tXqVY8t>q|)ih|2hWIg{Um`J)0_ui!#{x~`%)$Y~vX|p4ATzuEA zam8gc$%RYD;D=MnW`@hw#LdJSBvm@IcehUSbLy=SKbg^ANRdnrLdNW0UWa^6zK$(E z-Ric?7w`;b-aYrfOCkQ9S1JMJrJO-9@B>}^7i~x_yv6^9LB}*)^`XcM(?lmdHlkN& zJx0jrprz>&82~0p`o3EAm4pB|80G7MldA<@IPK{2ak22b7?InW%tu33=Mqs=z;?-| z1@DIHw1qpava#%j0P(H}vp0vPG!*tS*XM$Y{1YS>rB<<#4XCy%Ly~1-^ZP~+UJQLD zTxUi(A_r6N5xXq>m2%V_wfWiS!ka{II#nRR!G$w&l!nG99wey$=Ehy!T%223oD;;4 zd*rxbKj99t3u?GE`QCCp!R}0CRQTG!rn*Rb`Gna;B!uxra`%4Ur5I< z7D%Uk0XInEM)ffcDsqm3vm~^T=1Lnbs;LM+C=#puGow(j1_#P?2OHf0`bN6^-p~e- z26e$fBF0pm1iY^6)1@_vhVnm1Kx01ykFl@9I~aS^gXW$0ixDQ|^e*4&MZA6j83S0T zPBNqi!uIdUIDl~4B=(MdF*)GQ&_QcJ*wPl3HlaeZc%UoxxppS$Np7g1OzlbZF7f*& zKV|Yz`UW+|?2K0%_55MscD8V@@}v@q_Grx<3D>yxv#$=u-yjH;CGBg4>=HjZt>=E$ z1z`$Ma+wcTC{@`J7}U9*=+puMTzVC_v$ ztN5JiJp+DIqKX4-B`v}fwq?Z{3!~2C9wyS$v0JbiXrkVk$duL1Cx$X9 z`x+xVRuekQB{cIo$MGfACcKjaJ!v^%ls}!awiNV1hhqTQVAbB|hj|qn*K3;^=g)=T z{U>4Nt|?l;HH|8P)XO=O80A!(@rvPJhV(US%={m9#1wu}J4kFq-JVc-&d0BnG`cqx z^fXQ?*PM;70?LdOEgw=sF8MvJMD7e!C22{#N z-TKYAi^Cn%POeVoiN#M=qvXQ4F zmt0jd5@Ut>!UX2|-{F`9kr3!;>v`XfA}kjk!U@W)^y-}I&fpX~R(kMtX-%DAepUqb zIa>2*I2U`I@mw}dnK(K95>8h%#nT?b;l*B-XMik^9t&zXf1@y|KMsG`8lHw}cIeEz<=g9&3 z_&*9_u2hAD0uIv$lB@)o`H83TXZ_XD80}2M6X`Nvq)=Ez4b;$z;p?O3u9S`VrZ;my z!TqeOeJdD6p<8^ztDCudmnpBn9`=>gL4uaCmtVP3Dl`79-!{dX8i$ut7R*ec8Xm)| zFuOBRZy65Xj`&3%G{!jZ^3dMZ-H4iRH7Tazy~kYT{#f2c1^DFmmDD^C=JCh$hjCK4 z?8hA>*;;Yv;=Il-aPX;bd$rXAMBq}p-bP$X_50!j1Af^_zb7=zRrd39wM&INAQrJG z{X5{b*Mkh)g%TrP{CKy9aJ;|xo~rcE3BNLr?eU>ogJWk3*`CZC6fgo9`6%ZuCpTR6 zzvn?fnkI0-kSfyWfU|n{@k$vX{44Dq^KG=wp%BlnVnE2`2 z*GW)Oe6QW#RhihYqn4~JJ728 zS_n>KIHc*y(f&1sOO>D&*4wcZ>h`Fy_@H8;B&%jo}Ve^la4qhVuCqe00J&epvPzl;&aCrV*79@|XR;n3< zsFH^akCZ}OTxn729mYtC3RMtdUvXrrhHcKH}LWOJ*_v_PjUElBT`rY?& z+<%=8XPBAK%;)`Dp3ld#3xv1l9Y49NsGc2oTXGyiinWpr$boHu#8CPGSRIa2`Qe0s zh<@9dB{8xqO{n6qFy`J*oO-HDX#MCQm(O$FcQG|pnu{(%f3!IoE{;^|NYvI_X{e^M zT*+dpF+EAQmc2WOkLR=gHXc$wHc@=UM2Ijq(tEFBg511$g4=Q*j+)b z;bCvMpy#$+j{?ooJ($F^(qDvdA&HsmU#YiiKLw0KPOlbUpE_fjRTwzvR9fnGvHTnv|gK=csaOF5W?M2n4Gs5w=imQZn=)?PN z`wy~W=c~i|A{N7pwL6=e&lN#Kdm*MFh`OVWZ{KMZqYt0j2RGjHZc; zQ;HAoF06p+$f7!3wXwPY_JV%{*vj0?K)dt=p%uOQET2n8~;Tf!c(G%suf8?M%(T0^jp9PhAB;6Fy>rVP)yy z96pE(i0la0wp3t8@!}Yyz+Cb8RN)l;iIx_(lLMaqGh3>cq^?1upFytT&pi^})tow|A00-7-Fq^L*1c zquK7nV?Hb=t*05c06l(>2`07(S$U(4e%=5Hsj@2p5wNw>xQLZuoG$DH-8cjX>qZjA1D zbn?F$R7n`3?y0~NJq2F0G)otQHR;zCS||Omv3{ozvl@aZL1d5SC(?G~HNKmm6!cAR z(63-aL$DS&DTIm=j6E4K*3IjZxQT(Wz8x}wcpb| zExMX!zzS`E53O(@NY^ah&Pc|6*2RBp9^8@cnkNLx*8kL3 zacm>;cU5Swr9Y%e&|D^&s#SK%Pbji}mcv+s4U@o2G4=_}t`TW3&E&h|aEShg_e$G( zr2ld{(wGm(kp_4mR*sh66Bk=PnaXe*gfk6xSE)2@~> z?t4jtFhjA{W`?4F^XR~r4yIs@Tfv59`2`X7VT6b0RI z(wbbeFXFDAEVbi2mcom{C?m?+#r;%VP=(Qs#oEAS~nH`qA_ zc4AnQb0)sR`YfnJTgG=#i!Lvy8Y4^L-Ho_9Lcr#|Rm@&-myEYO`Lo+PXBs_^k;C5d zG}7?ncGDAOdn(3{c(U8he7^bP=^rCH4brgc4Dc_Tixa98vNDY0xW7looMUR9)eNe=DzJRu?=b$N>qtcKWghcTXr%@FC z-dD{SI=}&#qeOzNxa`d*u_G^MsSFnVb4{6wypt9ai{j>H>awg@b2maRQPo$(3DKSj(=y4@_is z-|*$c^4%|^%f74V|6MW;pre}BVd97WvrrK~J|kV$4OI6>TyJ>qFtId?1ee&N`IE7k z4trR>&Ex;cWxr!v5lu-Md4AWrJKz`)d-aw=T0LM;6@KH*X*p zoWU`2DUg2Eq4<*7V|iJcxMP>^)*KIz4&pz zUfzSvNT!6*y!sEaDn0wEIZXYd%CO-`3~T?X&Y1@sM63xvTfA5o?t5~9lTdoXH_`ei ze)?$9hdrY|BaePHJn(;Lk@xi$oB_GIf?qy&2yL4657h~*2aC0N&)N6w=$5x4MlHp! zwqQX#QtS^JTW_e8k@ZA!gR#dH0*h1M=O0yaFKeM+NWnzl+>CSKa~MyKD3cN^1?c&B z_1vQ9EJarxSP2>?vkiI&K zqoB+>B>qpHploFqtX}w4KHr3_UypNdzdi_%^Ki~&sn^?T759jr1WB>fZ}anxGt)Es zc%OOx-eKHvx-$DT$yn&jHxfPcR zX}8$?si#*5S9d#?I^xoN; zh>s7=V3^ZQ2h%sp8Ug4MkIYX-iAYhaVUq>jwTD`#mA{=hJxkZqO;jC>Ffls)c(SG$Yj|f47S+^@qiV zFFW?!#Ik^Ue?wt zaXtV>^31$vz1pif$-?KEW+Ki}Fo^jix+)@ZwU9>ckiEK|QMr}IVm1G~Q}U7ghlQ@_ zr_N(M6c1=BXwp?lxDS9U~f%wOtoA>^J_SwEQCY zLq7^cNmn0n#zJv8)N|{elkOef0~qi1Ei0w;Oz!-zg?{M!GqsAp1#+F=%BTWTNE&H< zrpWBaDv73G)?UF~qYCMNqdNjr!OOPfKXN@LLgD7LM_9tU-?VFgxF_=XuRqu-KO@9Z zpwNt5bnD~ZfjJzNW!IclX+H10jI|BaA4_)tm|0iUyecomv8A7!l6ebJalcPFt%pBr z;P?KT5%ElZ`2>1D(ehUddH#tn1&4a1wc5aObm7gsVh86t*PUGuC#<@w13f4Oa zIQK?uUwhHrYHqh8i;&$vF4Ul)fS}as^Zs{kqrbdG`T==&)k;wUUUlgA6F&z;tSkLs zpLG^uy#EfNf9z_o9iyDZCZQzN%1NIA5{2$3L#18`-btstjl0S zkz%)aPP=UOS*c}VP@lwErA^F<_e6<*vFcUyD4HAtH9fer)debb2*sF%tArzI5$28Z z{k}D^h4v6%)jlPR8!yyCpDAh;c&B56)B6dPN2qUs{;zv}ENkh#pcwK#IWHdai^CZeMe&*C>p+$qArh?8tNfit{Ek2(bU1kH^`eXO)sFA#}%IR^Z8 z%M5Odcsa_&v5WiBp84+xXH4(r`SQX(6+2NHOOnTa!%<5!?amX^daoBc3cp=o*sL!+ zvH{o7!wBdRfM@a0HxH59GJ2bmb>1F#gD94ELvgacrDDn2%O}pXU_7mY zwPtqEoM(B+cq{#@tI4oox%mbDar&0iE(jR(S+B*_jZqF~lp|rWNc-R5G?UXacO_`l zYLTP|*F)BWE%Y{bK6($ZeP`G;h68$lK8UsQLKJ5hQQZ8qlTLd0{G_dL3Th*!<@ual z8f=Q*$N1$hZ3R{Ki=?_}HBTB8X7HhNSU6w*(8K>tmfrAyO~D!>u4{#S2VzqFX|qTD zL-ARI?t+OK#mdfIUu>SKaFCywzs+^x-DxBU{QJQ8E8GDP!Pzp+-{Lx{tXJQv;tz;q zPV*!WhAyK|u4QvgLMYFFaIPz;B#uhJx&ET@vFlKIP_zWI78ijEpe^wqmN%MJ^6?E` zyr|xViJ8_g<5jVrBy?#u;>&XT9v=D`YVRa4v1!Y&Qf@he+x^^Ue4A+o_vzMV&>=d3 zFWW(HIoDF8(h^uhx{YV`baKnIy6=jwP!2T*`8lktP@Fo0Mn&a>pd=`0<6EIT((6@R z5i#p-_e$P0`GD#(exkTy{U>Y_Z9Y>_kZbj4{B`T>IYpt+=-Ejx`}KGDw6VVSt?u^i z?VfNcbLebZ!VkKY+fA7hKkO=h6z$6V{W38(Ve(ZI zaLPkQnypnQ_gFRNr}_#&1(=_wTmF^PsOQ4vNp>}(#2CTB4i89KKNYsGBR`4Dxrb`} zKu|ur=c`||IgI6B;fbUVU&mi-`Z3|Ir8r}h9@!l$yXRb7tosfXb*hY zha?}q`v&|1v_r;+6oq5_{P;8iN*NDWc^7YtUlAFUj2FqPdnxB5_~0G-5H>E^qM%AB ze^x^pOY|cZ;IKAFjWIR{iiex&?y!avlvTY~WJ7UxlOrY-m)^;L-ShRZN4fp-Es9D_ z`|pq1^Wwj)0%4~0^HX(1fTGZ%Qz-d_Pp6yCmfr0+BxS_gL1*YfJlj?uDGX{HW=R!4 zg{fMf(PE(nP$OvMocJ`pu^jlb?u~b1Urb+EKWcLueNBD;a^TYDvf7|&VqbP&%8$J# z^@3c?wL=?yPb`k#Y+fg+4+NDJTZX?~4xZdqCE z(Cmg_m1j+JO%a%FJqq_%-~Mr}{8Tk)D7aum%(;iKMaB`rnNWm8N$1y*StSfj;5xIu z4jW_ZTWD)hc2h!P@u4H(ytI)fZnZg25KG!$tU58$_#Y^zb^LtMJ4UI2LA?c@QMPLZ zb&MDlYQy{e`6Q-ar-h%&*gezgJl1z!jf&AbDMiquly;xvv615`u_)of#uwkYO=5rK zi20kjkX8?5d~c)wiEaPCVi}kikQqhwfH3ob_qT`K-kuf6QJ;Tpv@gHdT!Jns1&Nb+obsV>x} z$@P@G$70uWceh=>cxh)6{YwpZTIy8KH7V*x-uL|sqZQtNR$jxAfc=~0)kcI-+TWQt zm#lr(a$8uM!mnSTF+qnMmWDa7FC_Y?UTVln1Lfh@*YN_G2EPWpK?XC7{#Y8j25YCu zLUnUMRds-RaXYQXnFc9tK?-D69SjNf_>Q4k*&*82^3dfDarIs-JK6z4@11fYIISI~ zJ5wls17%O8t5_c^JiG$pcNs+P|96bIyJ=6a=L2)4`OlE)#4kPq3#mHLPvYNqF$RJh zNjYw{)_ZakK=V1(Q>-gOn%CbTfVUWN38>8c|5n%Q@IRjeFjdn#A9pZLNpT>3OnbLd zYGf1!_YJd~?K(Rb=V6~(XxN){H~w{iZO)$u4TA5xzNYfW=8aaxBY9f+3%^*{j7E5E zZ!1?>!3I(1Qf^E!hnx7L-k^zf+o)iNKkl<&XMD;>n=i7!tTF(+Xb;B_GfID>Lv@m~ z8dq|FqXr>a`G#0H`B*?}0FeV+iR9c!SBtI?cy{M-)0_Ozq};r>YM7qWPV zfompEy9hU8&z4BW z?{SK~<-s$m7*imBRT)w+eNZ^q@@<$S>ID_i>WlC;L&%tGyjM#foqBw^U1Z25eth6k zs5ugxHgav;x8y=oBV=zCEgl8FEJuk0;GJq#RXj&OrobdFo_EU|_7ZjA1$u$l7Z8wF zyrjyBj+*mn|FJ*Ab`NXt*K1CTYq*Fewq1gOJ3e4)7aNx4fO!X#^K{hGM!Lz$-xz>E zy_v|jkZQ%@Xz!veDk(+HDu#E<#mo=JI+T+|#PDqxWr*dM5Zoy;qQ5{5E3aqteg=lK z^M>kW2t}2TxI(m+ESd_Z$QBN9Fmz5+gQc#CaQJgSGkFF{$?A8)kk@!oBSN96SXqu zYNVC|y^q5-crX|ohN3=CO`RrQ-~I~F_1$2l%)U7P=;L;-xG!3;#Tb#XcHFUtSQQUG z?@JfXFtnF|_BX(N({mK6jez4XL!`ihxoK8YXj(V2zEirqd#YEGEapg_G`}eq68XHqA{)1!A;7w2@ zceB{?q6QB7KF>YmPY%y%wv<5)k)$*oaJS81zx|sh&n>#bO z31ltqXNHxoj_V>5@!{?2T$DfDU@Tsf-r(m8GtJ1{QnurNrTg^4tpLz0FW&yzU$8jS zdUOrp{zIeeQ>edZtK}};^8AA=KCZUxKQl%#!AtFT`%>(D)>4X(sO(>0==W^PsAf+! zexoL1PE~7H7*VhHmg%igW~MNO#Af-n&JIf$ypjLccq(6R+?O1awJ%{gFw(M+t8RsI z3ii<3Q)^f^fbw1@#<0%gYGY)woa!%bng1YWdsLz*=?` zH@Yy_ifd1Hvt1$K$J^U^(2&T+M(@hdR!LPBPLsyM^9W;Y>_9bHRAYh$}S`4 zGz)AQACgamn?Irre26-@+`5ITx~C#bHNjZhV+%sG1>Z#KzuAfmb##!_F<<&vOUwOLmnRv z$!0|sC!{7eDy7xUmXmJ>_hMDhu2= zWK6s}+1MY(UzNFx#)eTvtu1uFd{v54V)rI|`T8?zgQ7n1k3QMO-1E?w6ZU?grW~8~ zY3wVL!Lb6+LyT=xVg^Egk17ysauCv~rfXALtZj~1r!@edyXZIy;`Mef^% zC&%uGH?+%7`Vw!B#*weUL-1q44h*F}(cMxDfZDvVt79LnH9n{zW*>6DHL@EAZM62E z3BtsSj8eO^Xj+<~)&9?Z!k#9c_Lk$-eBPodI2|tuwe&=7jHhg6nv}{0QJ1SDlhunk z{@PfO>C>c5%`pE+x`TBO)#-9o7MZ+gM2yN?yA1bYztp9)bHMc1u63}vKIe4)o)Y)} zHDe>$fg+l)_OS5ljN=2D=YQqYFYmK{>n`whlK7vq0WS3aE5xWSzFEZQHkC7-XDF_d zWx^GA^1%Bi=dYwle62Tp1;V9hZF-iDzCQ&mZ4eH<;+%b=5S1`<@50q3rNXe_mp<(Rc<8aOUv4^4^A4VP@^hu~lf#MV-9e*xr%L5z`&wV2#rrq`l_3+kcZMzOrs>L^m75-$ z{+sMKJMwzE+i$4tG-~bmPHL_7ghugeyyyO|)jQwKy|cZc(|-3ugone61&;Pik1gkq z44u95HykSaOi?2FNI=wa6h)E1?Y*}xcHn{u^MU;UCLsC;#Jpu^e4xqOa9b9lx?-zs zn7Th4E86DmznotNYpkX-4EncGm_LP4awGWy$%hQOd48uUI8(*M+JXu*4t7_kQ06{z z0g*=-XImYS=t>Y6Vte0?@@4VVmsF{VjkL~RM38}DnnC-=g;5G!{q$>@9@r8T7B_6Y zc6WW~i4cWNPt2#chLnSQlHB2>ElbsGamihWIzROIk+832V_dDKgIw|Q&eN{A>9OM0 zh1`9@IYUj&L;tZ)CmWNcQo2>)QMcwa=+Klx~Zuah2-O; z?EPhs#VkUwgVxBKNv~5KT%N_7v0sl};kGs$Exr<$CIfb&Z(NF;@>nxzdr{Os3e5bvrKpiTF$ z3Z_ln_2p>x&dX8EHLxX9ET>XFz!dOOJLUO16R*mN#;}L`kd3tXp3#k;o5$YY{iAmR zb8Zdd>yI%40T#P1)o#Ihqx>9$^0Os>P`?gZsRV|OO2TCu^^;d*ASLN;c^dtrDUy50}Nh8aAs_VXH%WAOCHInhKS_mUA$%&){Pz;$tF z-m35v?8lwm?zstdrDFe4ELCoEZ+YGAJ%cJ3IjRAE@`>??|jIU?Bs|I`bxGW)+VgJD?2v2&_ zR&QQ-LU=dmJA&Fagdf7V$1{wnG@Z8zL>A()HHtRat*#}L)jvJ58#~d)f}AkrUhhiE zHQ)7Q;ux(pUW5c?x-)+}>L+&$ZOJjL4DcSibf)l5GF?8&guCF8K$$R2!Z(Tzk>@^v zOc__~Rc%#2Q*L3yP~RqUoWL1F#AXoAMvM^1F_#pnbR&~m?-64cpL&MJW()}r6)UCg zL_4h>YmvyCUdX`8EL1cKGLUET7X`k0U&bkq8xD!L?Tj@u=#(F=7Y1 zy^Y=kCiKli!f-hs_n2pnjBr=sGvOwbB5qY)Hf)cPODXi3@xjwKJJ#a@yay+Iv+H=r zd9KDWnUPQaCm9?$ILra@dCK`r#0f>7t>T>HeP>y8MXkT+Ud)j7icih+{q=+?D}V zk}G=&zOy4HO^LOTeXrrSn4I6%8$rL)12Dlxl`*Qg;Krm+2=04Bb0Xtt!4ipsY6cV3O+$k&nS{|et6CcrHWr* zXm7c;FqI}RRDwq-sZn?K&5Y2N(56c$<(4EE(acX@>X?0d505X*wtC^7>m z6E9&ECE0NECz5sL^-*>P_O$7%$$(iwx-(IXsnI3V>~Sq{<6%bd>WEFCE39aL2`w+C zpkb;E>|leDcToP|+q1BoTevNSZ4W#^9AGBf59=MItGttj(VOA9Y6m=maillo7C9t? zUKBkNp{(HT{zslDm|wdVoVd0!t5$R=ecqKnx9LXxRn{Vm?CeL~Iyo!Ze7WQlXhK z`G-h$4^?G)*YDt@|FU7jcI7sR)y;>|yfz2O2Z=ltd(_>RR0Sxa3IeIh&@b1QCUL0M zRlHrGrHxTWXX9<|WNp^lN8aI|6DCP^Rm$Y&lxD7#vkbI`Kz?-$r_a6zEe4gSN74Rd zoQ0BrJi@8Y=j1)0d3`@-%0JZo&r*ITosY?*eOogLA}i&=H5NC6t5YU*8I%VUYx9=W zyDB_|#HNqXCT*T!7$F4IdFN>n{95a-5i*CA%X~Fmxz%3U5daJ+JVKa^`86P@BV^O* zy=K#MWpw-JrmvLh>jj2f6%|3h6I1@4skNpKA@M@($v=1&QH{pmm=Bn4=!{7>vQ0^^ zoFCsZI+W1IJNr>&ru6WTc9sPHVb2^GqGcW4=!xoMwvN)q{Ht(FNQEe*Wg7#oX6D(x z95dDa>GqCZo$orB^e@*V@2x6ieY4#hTU~PRdnR9YeZu@*6mcWq!MP|+&kF}-WZORr z#r~|{&i=-eMO|WS>EU)03f#05VyhG2gTSNEcoN5Eu%!eGT%z0Pd^tTcddgdl6hs=i z8I3IOLAXlV10}0q=mw@Gk}4V^%qBwcQRV&@^{P;9(@fVptdafi163H%H|B>03@-73 zy7!x~SXOVp=Z8(cx*Hmo6K(WAou_}Jno~a-ek;*E#Kc*>*?RJx(@_Qm^`r;ik|mgQ z{C=~g?c-J(){=Q)Y>*E@2@qNv*cm3sFgzuq1z7- zV_;}njogAV4QXRjVgvvoUrj5W4JZCHWrdeNFsuF5SrF}AooGqs4w0q4tv5t_)lG99 z4Hl*g*OTTErNIzbCHKA-=6~C@dc=vsWb}N}TW}_;+`)YiWct`Zbb6JHX1rpAYyi{4*0&LBTYh>i2v+GTKjZTWOv!z6rJShdQ1} zJup5pGcilqM73%ZLE)M6LPKDoYdcnIbU$*Nm}0UI_QC z@Sz%>7_aAiWqD!H)OPMi_&j9513Ow%AAM zKn}-)GzNz`!lwJVN|;gdt*5G32Env;Wd#`ZN+S1k2`cCByaGcVM~lASbD*){YY2wa zb+JF;n0Ki_jZiWR7glV^v}%PVYt-Y`Xa67);>-M$R>0g!R7kJQ0p-*5AYHEonfFfm zWQ)lyYYl6V%}Ch7LLf^Y3MtGG?m@Hh0D2g-%jW>sEyvM+I+9#0qGG5DE7+7B3{Q&U0LTRGb=s5+F5sS(#!h42i5NtQ+888uNQ8GE>#uLu#!&FX zciVmkVD^iz^OVf)z8nLj*90W<7%2VaJ&0DpRNgzk?JPOz{Kwl=uYusc@a$g@;XVfq zolC+aatGLPGZ)weX1oQm=Ywd|*j)hMa0z?2`8couIqdS^oB*0MH;23@*gP4fe^~Fu zp1(s^SMP@|ED*4EkNm`}s5StCy(m+kM`ql5di1GYpe=ZCyZdIVWR$``iE(#*6 zLRK=zrX*j5PrkOGskC5Xvc8_XBT^ab*Mt$Nq|^c~trjtt$ZS)?VMlm}y~Gmkxdw5j z(tBvhznI#*5I96VXa_6fubpW9Imco$bMo|gXUh2a^Ad(QrKB7^aV=~&A$acO-4n|< zVAQy<3oBlqm))`qFC>*j)=U=hd08hP}O%K6juEdp4}Z#xVogsn>=;M zC5H-a8nsa_tWlI7bn4;#+m5ay@iBl-SU7a@5pkD&qA0-k&_(Xse2=n=tw)^@uFP{l8&x11W)B`w3}&5e^La9elWXvU0kKPa@WNujGDxVhYMrLDG_*;zH2+ zsF*(1voKWknEiIyC2!4gAt~|2$0xC<{% z&E8mPAz^T(+x*$93KbrOjli=I7ahai{6G7B^AKi*lOjCzIR4wxD4w9mMVD`FR>>o5 z=GZTgCjFn?EJsPcaRY?N6lHq)fIBPXep)dPrPL`0{g7S#?C*YIxJ7(ro?y_w!<_u# z1k%i3x!-!h-WS>UkZM8Bh&2Lr1$;dU#7qqCuSbIL9qxi1t%X$-bb688yOC7k*cMd8 zq=bq+fexVYK-02($P#eiY1TYAq956k!m&;N*k-Y?6RmmvJ48GXL;POf1fivIxyQt) z`EviIp7aYhmv-vhix4O9qH0MTXII4P&7fDn|K|k zu{h%SqUQ)q<^d?xjjT63AnHNaQU-Yf6L$RK>FE9_T?Rd}DyF{I@HXQ5reo}{fNj-t zL=KNEArN+Ewe{l0O*qEr-fx}$>o}Q;ham!s!8ZaZ;O&4|&EBfTEoswMgowCL0=(`d z@WWo>VE_q|mK;E_WEV7o+2UuH{**@H$skbAdxEYSvF9Q_KrTImB=R>v9C(Si&Ff~Q z`WDCss)U#-KM{31m*-Zd7r#J`Tm0z%N2a-MlW8ylZ4%&?R&iR9A6=EJH}l!`U`7ddw`$x7QhQ^kuFu1jNUi~-;=ZCaC( z0W4?>_8-QtzWRT*R2dcA!ma0xk*G;=o&QNA-%dnh_c)_Zo;M`WwWV=BSN-&JiMBYe zGlh#2?led(^zQ&Hq%OkV`akNUzxhy$h+n1>z1a)XIaWCWLOc zHmtrsSMeyKyY^q@QYB)D!af5+ZJWS#~F}izX00aCr}tF zX|6F2K)Yzd_Dbqsh`+{{$QtP{q!cDA=eey#2Cn`b*Wu5!PRjWGMMsn&qySV)a}*pDMq@itafqPv6ihi!gOo1333Z4()A{ z#~;Yj`3{30)fp{^n`5E74Udz2+h7)(G8z2&CRp_lsAn?Tw21`?Kvy158T0!6j=Xg+ z>O;(pDjhIFTN0=plwgSGNhr~=u=oaIond)rAy<`^LG3Zto&AERcY*;A?q2uvH*B7? z=A)Y}qJ@rB!*UP=Yi9yjlAfxKOjHelUT|0(V;%A1@>|dAu)&54St&$Y4RzVt33SPo zfx;L=35rI33o>6a{Rr-q(Z|m|e?!tRADt-qg3uoZP@vlMJziw~SHp-P3z4nc{JVcX z$h++UgGDvMLVU5@dH6iQJ3m1LDWY3PR7{9}#Un71>*8bEiu^01wG|E8VC3L}At4*gig2?;?X}vWK;FcL~Q@4#Tc7a*fc^L9|q$76$nE!VXvYeuSRd}(4 zdjqH-!@R0VKqH{q>9u!Z4|KO#$`zmf-kg9uFsuS@=hHBO*s=pK6L&}Nc`O2@1bMHt ziPS!$PMU&Kp8*>49dgzKyqf;h@Tp$SvmofjxMSN0N*Gm9Z^-|_yO0Dh7H!EWdCb_= zUmqig@qn7kZ$&LRC1_omH@1Ob?J3BObn(z9gPe^*+KAG-fw|e19taG(DLiO8LofKh zJLw7iFMg8{Od$?iBqJ^_E^jYb4?ev)TI>D(JLulEyP=zEcd;wURE|3JSr00;WHlz$n>Rs$N5H)&(v#_$?t)nLdSfi!VQVBo;yfk7T zStkiAXKvBg`N#gnj(H~>gE*gHd8=&N0Jlx)^ZB4o{|>TQ1q?2G9N{j&-Kx3-2~5Wr zjam}Om~O;>L`x2&e1UHcMQU=0oUgy`ZN^9-yG2`FypH6gct!p?-Sj(!vC8YP_2v8t zq}`AdPc)B2^2n9Fr@adAkvw^5H-YC{y(ruPZ}}RfMA!>|d>$D3bG$i~-Ww2_w!cM$ zUx4Y-_pBcNMnalzL^!cZd*?fdaz2%k@91-;yKZf7u5h@CvJ)x$kp{eA+E|8AB(jW) zG}%uv@9hw0azr9sGk9>Zg-XIMa0ncon!&dFy*5}*Lq7w-s36!PoM2v|oTRTeyeGU% zIf_-_@{bDHLX*NIUE1`(2H`<2d))G$t_wQ@4Zwm*{XH%|mAd^2>P=)Jp01me6z4uL z11VnkMbTF`f%a0hDBLu<2Yuu-wB(MT2!(1^*^s?}f34feG`|)eM;=s;XYRj9%gbmK zB6Q=cNNnfUsz|T4N>&raN%RIh!1t_?p`e|ROt7eF?Xh_tpJN^Q5H&W=xat=~S%8)G+(g z{ctQ!lXO=?9@SjeGulHrLCD<2eJX8_6)F(~Hm8X!==~(oF+~D|rXjYOf^4XFi!vb< zpuy;7SF&W1jcP=A_wx;%%5NGQl-GL1jCF8cd00Edi`yI}r1KN8gM|U)#Fm@SZ#Z@;Gv5+QX(vHcM&g98>?)jnH;w}_8F-pvk@>@N=RRlDQ= z-3$K*F`!~^%oSscZK73eS;0C?{lcD~YE5K+>D6}>N3mBg>UU$=E9RBzs`vv(@vAlX zZt_9*$r)}1o+ybWNQAZsp^g|HgR#;TjJsLe=4F2Ov4_`0BS>Wz*tYb)E0k^C;h5qX zWL*xAsr~(yA%7I^U`J21-(TQwF+C9TQ+_XYu6xq?ZqO`**^Um!wOs)B9KH_hU^3Ke zmookGR@A8V&s_VlUH{BziDvvdV1wVVqiW3f$9urN!J_31cU>PpxBaXt=0VZjLSLBZ zbmn~LaX$Rt$bT^^ioU<+>6H&>t5Z$HzIf+zJLaCfH>MwD`|w-bwS2TRe)&%1r7!(Q zD1PyVwNiZTTzW3KXm54K#SpMhvpOUz7(_lQ>`{hX}u1G2gy!{)nrsjJBjS~=ckrh zD<)s?i&J0358pijl@36pCc^r^^1P&xM5?V(wb?8u8Z)`2YccF%c93as#I5sAO!MW$ z>GIeGJ*39VKc@SQ?}*==YQWrsdl{m~t!gj7d$dMXznpOOHv0BGgs*p2>j?*4*pGKI zktXp%ZaonagLO|KqW2VC*!}D|IU|?GVt-op{s_+4?eEoRKAOG@RLn%;llK)h7C`sZ z-;f{h1&!6Y->Q_t(bB|mNE)Hr&~G^8-2v&y$X9RgZ`-1kY|9Cf&fE`wNHz@?YfZq9 z|1Yt;@X*T4L`&B=M|Zocd-EM`eNN^78{u;EdTzXx_nq#T1p_3c^e{E9t*t;EVy;0~ zk~t4SkE*_v1{TcLX?asa5EbeuMvbeM_#5uM3I2grgm#X&g#S|4sXC_~x?v9RWN3J* z1hM?ciNOe2iT(eSXheE1hpa&OdjLB)_>Y~ENc=rE?G0T>@TThiK@z9MPrW8TE~&<9)Ldvi}RNr65Ti z3tG9$C+`>u8}!_Bbv_S9AT+u2^IEqC=uJNSb6QsAVoRhgqh*Nq#?On-f;N5l9xHR? zx+o)2qyMvU1*N>;Y0!VIuZ2d^#H_B!yPA}1@=t$R`!SY_J0B6d2I&G8A8)KJ%pn-= zLg#5)aRYl`Rj7$ve_5lm4RCcqwnYck2{3tfKnAI>+YMA%{~8=t9~&_zS2s+KbXBsB zFDcFcN+=2Z^h0F@f$o~V$_c0tsA*PDkDk;YW(s8xx;g0iB)Ge4w`u41m2)a1jsiJ9 zy{CC9wap6XKePwMT{U@*?W%f$oUic$8b|}wqy3L6_Fu)NrF~Ww_;<=+k1_y;^Dh!8 zuF6yvdQ9D7&4{@i2AL+DW1`hxgRA=j!XYzBh(8ihD7|}p?gMl!pF98+YrhL3!}Fk3 z`Gg?*Z!;Z0atqEwSv3kKYhM(L{>P_RPG3WER}qyQoHfs(P^g2C$B-W&;m;hS(;Edy z{R@Cn+aW!E!){Z<`mLIL>iVCto9mzI6xS_${4UAerQ~6S`)?J~%8P`H#)K30nwrsz zq4I^{Pc4&I$P+#kQES36-D4B>^3f{sz1uF2UwmuQ*153g_Isg0?d4E()r+Rwr#G^z zZk|4U`s9c2JMYUvSCMJY^v@;`&=gH0z`JW8P)nDa4I*{IvpvVF)(EQGfCSy@fVqKJ zZJO(`NFW4wV$zPa8y14;U5%ji7G4OXVeB*(NPvL$?@+62$Zi#j&lWap>>vdg464eY z0Lpv;C81R403zd!R9JQBk4DP$_+~6TXaB>piy9DEd6?k~5)j}A59+D%>_PASZu1Cf zazGo>H3Poa>b(%q4pKGFB`JV|{;plH(_y6M;|wZ)gd~?@|747Ke- zbDTd1<}Ni8uhqsLdl#_jMr*WNpWTq8HV$HMd$UwsOyHRi8-2>S&~6%0UT}q z;B~feJ5rDZE^?K_a;bChtA=+;^HBEL22#1b|A3Y*?U>;)tmRN>yTG+tASb^7M|d4_ zQll*K%4$}C&XV>jZAYCVNV#F$f-YegxTGGzaIG$l915P?QJ|`OEk^JF=ZL5;Y?Z0p zgRrNUr_w-M1>|fmdhV$%vuZe0EN$;MJk7je%|@@Kz(R62+K1ef!|1~_5biSK=LUCh z#w+%{uXjLCmw|3wR=?On%W;$BL=eaR4x&@g?n0`HGkJTtqapj^>_J)|NWU7rZxC&B zN4NA1={&7?OO@Z;z}kBsV2)m*I5Xa8ob`nQzxxO+vuq2e$Q`=0u$S`c{bFhX|@>+d^5pkqY(RJwyw?X~Q`zJD5#dPK4qGvC^rL5EAunRah_m@QwK-P>OWRPy;pW+% zD7g!bL3Pk%nd%E4KP^i(?R!Zxuj>42I()@Z(n#1zk9DSfJj{=UQ}qeJlJH+*IQ2E2 zLBy8F2ns1(VH^GWxiHjr@5&*siln4-Mw%D+0)B{~XLH(T?6rQ(m1Y>iS}atuT`9}` z6@@a&Q_z!=Zx@+G;J@jc8+*pIW(4|uLI$FFcO#Dv9;$$Mg_#y|~yZTw|eVG`5i zo<(Rs=8}VPcjRux4zI>P)-765*fb!SN4%1-R526jJ+SSF;24sdIPm2F(C(GW-q`+P zb9Fl9G#=kxaZx}9O4&O6Tad{ToIirT+VJPZVBD7e$3%WkYO*u*dNYTb>o8NF$2Q@8 ziajuK_dsfgkNMBmgS|veA-a7_2_M@J#{Q6#*$>i5lW)i~WT~|rSLrU)?(8M12|cCh zQu5dP$`DM(e1)#G;t$Ht>+viPLF)-8zj1cjMl{cG`NfE*jQ%f$wjj2uN?kaSH*(zf zuCUd~?-H#~TOW&!HdXGYKl)30_q~WfJ)P;1pWUY%50ekfSWIy6ha25tvxaHDVX z=qwK;-WE1?WoXTtrb8LiUd8L*w+@Xw6cL~2da-G}tH^TW#EOn^xnx4FJ9p%K_|vr6 zOIK~MEiq2p1g#~UHPcEB!dx$$s3C;rkKtduXIkMB5hWXjh=c+8WZ6tOb~NJF>t{pl zH@O0hlW7%bihw&m#JBe|=XgeIgy+#+4;Q4aE0Aa2u#OLZ_rMe`ik# z*|r5r=X^-@UMg_TX1v#tA5X;9o*n-jIl_vqJ5gezjN$l4VYRT)`l=)&$GNcXD-Vff ziILPRHqy6*BR4RDyZ$gyBfqT5MC^&>Q|UxOvq{pQzE6fKLsmVf$Uo zaPc!Kw*it=tFdWRu8cx%4Cnt`OZbWqQydvM*~5BPQib`JMEAK!vM$%=ZFrD#6kRTB z?4f;|c4XanG>Z7X4*rUHrm_Va1lGJ{31lQ<4xp)05rX1j0NJu*oGQL0yZ>k+Jn(oy zt%IM%B2ho#z$#qz;>2dY6q|tazT)6dL-~9|ce;PQ0_YC-Q^-q!%;Nt2Ey(C0d*||H2Bqj4M4#$@grVTw>R6ig+a5 z-Cpzxt*R6Eexz8@-S?iY5)uK3w*xsgZ?_;}*6wU8Ms;UQ?gKJ;_Q(+0spr;Sno;l` zc+q@I(Ecsa&jkoJ|H<}*zLQeJClGCTDs!*JR${8*x!zPR`D!FVDNm2)10;talh7hf z`X$0hmZ~?_IU~FcswbaM_8rI4p1BIcyEh76I;@rp2UW`UmM$@7LN^49c%l1ArLAHpR? zra@N@kKvP(Oe~w12ee^WRrPMrWPbkWrW;?bco0;LPoUZT4qLIRAc@>EaW{#FIw2rM zhjC!Qcfjtr*d6ZF-~3oBlQkx^3yTrkkthcG!izeaD@{5sKZE#%_=`8TU7N0kyp35~ z{zIyS&(^CR@L@M<2vH7)@$VOfuy{Fks(#_D`##?JZ-B8l57X~YEw`B$cRs;b4`Hf) z04z8MPBROm2F2!`n}8{C-z~q`YF>zJ@@jIOJ|Z{ZXYerFHmPMUeK`UA`3WvGU*-WS zehmT81|z14b71q-CyQKekK=X9uZ3macMy;GLp7;oaCLbXS?gqaHM)KAXbz{lWcmlH zQj1cG&a7UZMULiB5d=r4%A3FA)omQbKUGsb!!3WGeNfGcBAJDw9@<5zWP34q}O1ol{-dJW4RG5w-! z&A@@*d;Iw|ua^r%`)6D+*I3e;;}LauHzVtOzwcPUazT%q&J%{kRVc&H-CTu|AvLvcU4Y4 zI$ic1F#{ZC!yZ_tZNu-&QP^0F zpIF$@Mb$kB(9vAsm>zl1v)b!WSRA_Ot|ZN-_b3caJ@T6K=HQKr|Hk|EO$f;;Qqegb z3T4EKC6ty2Sz)%i?Eu5xwFgHK$8BOG&|? zL5NcqQAz|Ss0_`?*vPVmwX;=U_PtI*z_i~&P-3NxS=<%ZVXi(B*#b*|&r&prQ{&tNLaasbPNglSdDZPge>IvHNljj#pw zP00g8+uQ_QqhDC!P(nFBjFR8oyV9{~5^*F=(=1uyqVv8TpZ!aJEXBx}@3jw)8Gkbl zu}cuox!ywS4jDj+HQa$0H`&qn?J^7<%zsV1xB-q4+Z$~XWz!fh zk_3S_{Ln%Ki|su%6)sbP`fM9h939`hN+1KBf+M;@fRjqn?6n%gOxOQr%Vj99#nM;s z4AR#(V1=YU*bA?uS2{5oxlbCzeKnL@2|gx_bH%LMc1CzuoR#_qDobbniq74j)6hED zF%sM4Xt*4XMLa#2>o%~pGLh6X)eI{*@h2R!u5XL2^@RtG(J!;ua$RW^TDbQHfe$U? zcR*IIn~ZpEy@xdG<~OpJ6n;Gq>mf0c1ya7z!nhpouAfOAK2SzSU{9w!RLH67u)K>_wk0sjL+~-0X#rPWEM+S>cu z@%emyzdv5LTQ^?CIp?~r=i`2V+{eD{$^vv!$};|zihxVq%FuQZM;7lnj<_c#8=3Q5 zzS}bwHQyHRE#h<`XR2%@rd(~aYbtkobZVW{*+%vJ%)olOihYw%N_nj{Q9m4u4{&?2C&sa8~t3{Se6{CmQS>}S$&QJ!dvwsnKfJcb4Lm>SVj65x@n!v zZXdezHglT%F8%n(htRJX^U6D-nj2Af*~R57F6XXmX!hHHCZP^{9=r1%q8Z{gF;x$U5az= zEy`IpzVWg+dW`wZa!qY$0|(^=ppp9tbbIa?R#2m{iai;AJaZ$4p7*iw12IjWk?!JsPSuAFX|Sb` zBs#h<=BAc`&*f{5Wfo+3sg2xzFT^*ggSy#G-%D?6qqu6W-#b{WKEWgPzq7-X zn5E0svuabMpc1d-h27WvQpQ3s_rM(ctEDs)S{r3}G|s@toJI3yqEix48i$^xTijPZ ze2eEAUW_Hiu2n=sL5eG2KJddR?aq=p4;neAJD4_$m=cdb$sUaKYoq`|+OY^tX)gi= zUr{5yeZg=2z}tKDM;BO}^(-t60O@o*7|&Ye%B!2jr(Q&+~U26?n zDp&pbqmj&JIxR(sc;4nQt&X9=mR;UvpRwBL>+2m$HRfZb5B8-*T975Y?`mZ zjKlVQr2UY6BLBd$5123~lv>t|i>fu#+Gp0v-mG(a@z#53bPUIz+R2FYOpNn9y}v1J zyn9WhiD%A>(1lu6>5aVpTV=@+FOVdSX*_4`5m9^DS?7rNW83j(PFp1Q-(TaH3pkJ+ zGxL6w3LPgctF38`$Oua!nTw>uQ5DZ9`wRUgTUYDO{;PbhhbSlMlzyQVvvsKj=MRw> z#W{bW?`G>>!lN)6D4`C)e8Wg8p0DcQ@X6yMv5EV&PaY?5ZW%=4MEG>Np6MnO+9%r! zR7G9Cw%x9>9(Qc4aW^?)z9GhovD4#JN1~GVsI>;4(<9g9s1K((r%oMjIt6d63nBe< zz1{KNhu4evT=-sFH|ZU3GCbZi`ll-_ zP^wSqGGmR(@E%Kp9N;vuo^jM3LYbDqmSH@oj{b$-8ia}KmMRIv_V6(wUfj2<40v-d zc?zV?HpHTzOqfYAi@b0?&LS8@7jrSg=0wa49m_H77Ep$%UsIKTw8%2mXN&M*ca$-; zlh)Vh<=))#<%8D3N>14;WtKW)!lg|U3h&YM+bBL5#ab^wu>;7;rXh7C+n|HijE|C)>0nQ!qRQ56X~ zkP>f~k21XuaX8vyoB5s|WHYk)atZJ}|AwmE0A>)(szdi*^r{8Fce7UEu^jjpoJgZ% zF(S?-JQO31$ab;7Jx8K_$v~+!FQ(-t5f$Ft|OTG}mDyeY<}( z#&)~v&$8z3+poKY%~G9DB}aMw%-t*Bc8dGMd3CF&J!k@q5>mj&mBmI8^$I9xcpu8) zSLbArPCzsWN??L}D^l+=-TUqpvSv+(I%>-=5V85%#7e&y2)#5&7H+z$Qr0|wTTTs_ zts86!S{h3K#&sj4VR}zkvG)Uzo-fopP}tu=t0IbxgnA_{6#}trVw0y zeaak}7GsQ{A!zgn|nac(AmsM@a+Yn(%m2+yMLPcQzzC*_v z0wHc{`0zz~-4^(%NJzbjyU@&B>GG*%b3FnABh)v4KACe1-m+T%Oj`>;wGS*tru6??R)u;68}zHPLlsnP zRhVEbRa8<23wPC*w-uBK94Tv!-?gT+c@!#JocU}w8hZgYw82hA4&>?H3pK<*Ma4y%^NQ$b`lpuW@~ zWKrzSvs3~im^s_s72AFUM8*kdx{ll{-H`gOnEd75?Rm_%jF{`!W=7>xZshUJ+CtU9 ztKj?#m+QfaU!u7^%;pNLDE$(@m044=Ev?F#4I5)PT_e!=sj<6SA_8=c5L|pIYkYS* zbJt;48tmS5KUC*SM*$Bw|4L)6r^`WfHR3+uOF(i=2d%uaMPdeevmXa4JQ;7apuG1J(|b?^b#Z_UFF*xzQ%FBHzmW)d_^n|A+z$$sF=hlA&uy3D z{)wIL-A&sRww1aIA&OH!ir;KOg#T$~$KGT`3G=M*KvVxLcWaQs%&nF0enPshEp0HG zMfRXXdfk5&deYbnk+5=7(oY;HLEetn4~@ge@D6D|wxYT}7PqG6NBQjFhb$Wt_OMzk zBPFS;{p_Y{_T90Dahe;Arn&o}>`+sewe^8&_l3ZfuXDjW?YqAqd~C2}X}sC$11J@V z+|O?okZL7tH%%bH-Z*(#eUb<-mu`rl&4LlXj9mzmLioWqcRaMUxD+7~xaJ&!Q9mc_e(%*p zFF%e%i_fe>PC`$`BVca6Jw$!(gLg4$KFvCcBcq+-)hB=SpXYXy+Fc`?72cJ1!2iuW? z1=G#h26W25K50GeLj~q3jF$sMmfW^8!;Q)f|G2+=dVf{Er^Lv8KVIt-aXA;XX;pj- zaG9FOQD#>{24>S&jM+oBL7Z%?*1h|ue7D$gDSs7VXtp&mC6ru<{7o|;6xqq@57~JW zlZSH%Rj~vWX{Yg0%B3cEh&8@?BB1E9 zKvPvg87sXd?&i5vuUW4&TsfUw;744Sl62H-E$6Q022f~(=}BHu+MmdW+@XX+q1bzS z`sf-sn-A0aA*!%|^@n9~TKcHv61blxlA;qTduJr;C{;?1ABd ztnTiK+IoBk!3q?=k-ZU**>h;lyE|1sN(dmRAwlJ6IpWOPv!s583 zolH7XPkR&gs~Kl;4is%(hp&1GL6Flv2`q~OFoy~St&8iMcrKOwU;cb|h_~E2b8$Yb z#iu54;*f`A{s>;iW1sfV(~L_xbY5zK^yc;NvPyZwY*k#ML-fO!FBCp!=RBGaMHp^3 z`t-|TAT;APt)=}y78wR6ZdkKgOGpuLLg!(Q=Pi(0a@PwH3_>dsFGih-sZfXByAs|Z z>F5MfRd0fzuCZDYXVPypiE>P2O)#OrcjDbjsv((5n~$km)(P@KIZj0-5Pl2&-eVuL z{)r2BN%$@1PNUY(qvven77dQEMKxQmz;>E>=E%RJV_Pq;?x8tKOF)G z)7Y{oyc0Yxjz>4JrAA2A)Y1goi*yR&EMx5<8iC%^A&Qh+IsB)j2i1MVN5wor%l6>G z;68XY<^gTB7PwJ?nrUTlekCb5_8j2cn`M;$h|VLLiO<5!o@A>nm5H7VjVC`iBI_>& zSGaRX`ioL&EIbrp9k|2-3l`3pYnTRByh4F7fj+`7h5-%@sC#hqD1b2&zniE9vY7vT zh4NABX`94)A`g*PRjDBEh>(wW5c zW@_8@A>aMBZC#pE8}zfE#f4u&GmTwBTak@%Zmv-0dLP6;lUW*Ogn&jY*;vJrX-;RT za{itZLy%W^kS<{(CyV@bIP_P*d7WCpE1{P+Vk%Hi^$2hO`3<=!EuX8_hB+p;CqXCB z+tJxr)xOx{R_V3rf7qx!Xlb=x`=!^-)8qGYKc%*)Tx{cIKR*0BH#+gBlgTJWz~@JlH##0kGw6 zRN-mo-iU4D9SAMdDm54FCEwNfB?=GCx2rnF9(nmrYGMg5Mq!w5dD-HpEG^#Y$=?)@ zUjDX@EU%gxe5`r$_H3MX9^f_0gQ)x-j~z+c;3Iq81_OTFT;w>|P$bSX$o{3~StWCu zM-k62MEQM{b1=^%+W!s=c+o(*2oeZUVOkR|b})9ezKjV$!s?O3OAkW@`H^)DReXr*nEVGj3DZVIatpr7Ti)Ms1?!*{<8HQrdMU0jjGVkM(TFL7a@_F z(+7z2bggOZ%2g9k9xNuR02Qk-|dU4NFJ* zX(Vpb8WN=XC5!t;QlxENN1lN&+2Y*NiSO(@z0w17=^F$St>|w1~BNatk0a5nD z#^%awr2<4$RddfqFT~wf)jE1OpDIHFW_r`84?k({($kRp_V$$3Gy1S->OBZn3nea0eJR-;58<`?z> z5>Yfj6>~}*0Y%ZffNER<57ai%vI|a66E+}tVFsBYQ!eMeL&;5r2~)vRX<^ay2Ra(k zP{Gh!(huvh3rMVdA9NFVe)=B2lIHLApaH4$7V*D4??{^_ckI$<-0+%Fx=>faVR>b{ zM?*jEZbJWsgy7RBadb-?$jaX&E;15|&U&iNDcd+JI=KgYQatZ{Ayvkp8{-$4DkMM`J(Nylhzm+F$u2;M@zy(Cb?uSXID3nWGg1@($yQ1krETtad7ju%XYn``9KpuSrk!0O;kBF_R8=dHEbYP)jS=zBJB?R~8K_ z`z@yJtP@`_V?!`ErdE{U%*dYGJ<<{2X_tSFkiQ*Du%Y9A6EZWw-Y{*s( zh{U*|j@!H*Jrny~mk9QL4Z&*#M^~r5HZN%_5cJE;M7qgP7EDZZ=$3=y>RKGhC|}Sad#<^ zoi78t@f;bW%1V>JgT5xmlj$e3A5w^UY?H$3-I3cK5yQvrKbeXqq#f>oRXWJIyL;%= z2lG(w5_5f#FZL$MT&_2cJ_m>9f5Zu#G0&MC`B$kZI^8NvV#v8AKz~J1{*X6oSgJ9p z5s%Ufz&Mc`FJ;?1khlbgO1;@xBtx&l6J=F%QmaK!qmf4tn61pTav8WOqZpdnR16}G z7+v=eL;YGIF*EC!wbDaPl$JEHfpgCZjf^BY{GAu_RT;}d*U{^gGj7^-dV5%8>WAc8 zJu=d_(fyRGWx8r!tU7YvZHo5(312d2Bk`RHa_d>a#bU-aNDSX@;2nqRDjx0m678K9 zpfbS5Y!Lr^ZMGp>J@+HZkpWj$c8yq3pw{`Gdd|$5POh7YMRrj3OWgcMX3(euz7xCM zzOszb2>6KaJ(A>hjzK-`;G2IBj{=gK`Y`ygRW1DYF(7(j!*>B6l&^@_`t&0ta*)%$ z2YqWm7|$`L_n~h`q~OM#wVowC3hrQg%G@=a9h!*xh#NC1>B&DSWcGzG5HD&XA2k|KWRf3?a?IUY&%4#kIO+eOO~3F)!@(|vB+_qdWWeK{K|F(w zuFM;Yx|mSLTr_7G3{{QTL>dC`w^Q3;QDpH;e z$kX-g5%J8j5$?pI3g>^vi$X?w@?8wZIKw8u1&OxaGUBzJXU2s$@NEbuJfR?p8$$B6 z0}Z7+b$n5Ua$lJ3t)H6-#)woNmfSX5{8F3w52%>RFv2;w(qF*%1q0{e_Xo4;z>a5^ zDL4Tm?|YQvT&*#!Pz(2>n;UhQ!3Q3K_qECZ%eRCY|CADOz1(T>S!o~$eXj&ZwL~Vz zrHfqy;1UG@k(wh8W)?N`E>~W>Y6FaI*O{uahGUHXVW_%}$-F0g_f5&fp|^OUhfxza zf_7wMM0iniGM75%k`azG`8l5-I1US|N~5-Q><4OU@m6%6$}K!>9@tgfKV0^`sgLUK z)P^sR%7HijKl5mV1)292_mYH0Ws|Q&KqWy#kooApFNAJCiFRm4v#)*q4=iis4B^FL zi|ovAzx{Ix-2^^1Mb479IUFM-at!^eqs}K|AF{x^2q+O-^0@>?VIpojS__tz5)L}n ztfgAc;|c?*KyH?6xk|XiR^m%-)8Aj~qLB{1s;& z_(y3%;33RoG8^i|*;jh%ZHa$^L20tO389(q8Sn99kS-#6?{2OJ@ zG0H?Zr2$NSnp9AKs#?sk8W-I@kLZX0hg5IF0;zs23m4(nEDcLB9Rpmic`n=&M@fvdGf`o&1+UX$O`LMe)zq>z} z!xAQVz6}`r5RWFt2?09@$&UKF42M4vLg3!8NZkZdvl`KbF`q1*1wnGj#`5H|-|P<3 zD{}rcG(nDr1nqxz5mS}9NXpO~L>h(Aqu(z!0m06Ow0J>Q{anmQ^(XgXonpurNS^hU zf|j&F6WA^V0xdlJW{EB(c@BuY{_es=fF2iJdLmHOjtbjT(shB;q(u-%Og4Th#K91s zD~83Fi;0;_Ke+T zK^U}FnSAA!J4Jke1^x&_dD_>Mbk#3V428&;Yu3NE<%b`5-~j7cSBNs5gW>vOa$36@ zE}!`J4sL_Sq6*&D&ZQrves7V$)oa0KI04lge{Im-z`!-FLWufaqd z9f}0Q9tb#myF`lGucPvuXVSKS6!Ad%Ysell;uZaD{n$++$vARTyxY0cw8Hf@_l%>I+LiP>;Bx5l|#T0dzFZ55HM=d2n#|YbzztqN}n; zg&(Yv+#@bi758WNx9M&&`Y~86t^*jn0mh3LudGW7r~5w^^BlkaoJpI&Eky&e6*+rE zOwMw)Ms`dI5;^}00wE76O^-wS2FL_b@A^QSoyd9spP#ZC@ZW#sbr2`(0b!W39`$R8 z$`G~;`P=8;U;FhiaMkkT3kG^Q+BN7$a%WTjo$!utd>`UFhO#vrKm-|aQ*hpyT*GIB zA(*K<$B0vP9!#OB)raC}a`q{IEB*?0;`HB}pQVsyyy>nNN$)PgXnM`8N;m%f6a@M_ z2=kJA{^$aF%$l<|+TSaX%K%5hG!#`s48a9N^bUx@P(y z?%aG5eh%$N48w3`*A}As9_?;q?y5v;=^H~nURQ1gk?_YvZH83omJM;OBHxucv8~9$ zAfOf9K(Skma0nbR^#^z32UxeH+to}N9?ES8J%#)p;~-)rlBy!krX%~4L{6<~#b+qt z2nA*iX?07{6v%&k%5<4dBgMY0wqSTvCB@6_$NSc%$^tO{HYV;zH-L?tsy?Hzc#sHQKxHv(>MB%e|apJ5^a%Kuo|aQP$vMOgEJaI}olEdS9`&N|RKBVy{76 z0QFAVb(Dj~@OZ`On(mk59O-KVFDc;gqiI z4TY!IaVe}EOd3kfERW%NE<%T~q6~r}u}6JlEXkSdbQ7MGt@JdNG2>qcv(YV@e}0*I zp&Y|`u@-To@3sNy&!z(Z$PkP;$Rav3SzaxIfv9yeN=x_JEkWs;!y|nDL8Gdx{a{;R(zGXZBAbFSxGIji_0+VY#HFN8 z@WxY@X}QH`k+lzYc%cLu>isn(wA8b;#YU&Mp=tHL`{7-%`C9%?bq^Xg^nXDo$Ho^v zkMIRz_*{2##nQ?1al)nqi9EJ9#_1Cavb7$NA zaT;29W&5WokKWJj$i;keu>5S4-CB1=nAX!z=uVEK|Hw2;vmI=TTlv94P_(^BCpTMn zaVkV>)#8v_>nfY@0QR5P=M}CZg6D(B;8YVEqNA6yv#&WBO(WvJhkda$Q%WPZ^!-KB zIj{k>GWJAb;gx-f{=qoM4ZGSA{C8BrtvBK$ES!644y8;X8mB>NDSG1|-A6g>P-8+T zxZ%JZ1eWA$a(p^$Mn|glms>CC(%i9irI*V}Z8$&-Y20hy5VFTeZnCz}B;772Ct&^R zO>#>JO|y7VB~{r>-`XJG{n>#AR%L?|Ru1ZQk%!2^fF9E9>W;>}QR+sWV{HoYPG26n zXYwh(aLdrYh_ic-cB@wSrqI}B(_Y?gw$+;gV}Q3;hfAs6GJf$_mw|Ea@h0FpCyGqF zYp72M#MgWq~cj$o1L>!|>G5c#<1p zP~(&0pv*?XlwZ&a7cvM0v(QqtwtSUzxmhY|;IH8I*3@7i!It9EuVx`FB zxLM@GiL`xaRM;u>1PZ@gkf8jXvHZ}>3y4JMvIM)*ew*gy^O~3b)Vz$Ama`z#V6j8_ z99U(B7*U<+YfhuS+bI~^i$>Fed>cXmRg;q+y;`MNQ^p+GaKSdc!T~mHIubnvFBaH7 z<_~3WcL`Z)WUnM`dG7s%ZZ9!m%ec3?Z|nHTnV*+&h^FF{<6&w)c#8VdxfnECU@`$QhfI zN!zpas<5`vU$Bq|`~bp-6P{>;7$WFSjX4Lt(|z=Fm@TDi2pe8b4HCsO4~^9FMk_Np zPB>-6#1IvM!Hu2I$wWi5jbN$eKb35oI&RT5t4^VOZ0r(fo5RCLwThU^OTCWgf43vE zY9uW1I189drjDilxUDDdTU|4D67P~9A4MK^dWE?@Hrw1RtMOExCD?b+ZkwEB;>@bU zAO3WdIH4~S!WOL;x3)8$Ec|_Z($MEd500kEA3?^D4L6ehS@af#X8_GZn zx3%WTg#7Rfk{9doIO>3oih;nvV2ORVzlKNBO3@lq#;kM0>*)-oA2LnXo$h*JZG(PK z7_m#lw!wNQLFiUvdIJZ6{_!=!AKW#5%*lWjhba?QM*-UXlqGG7a+{$l8XrgDEn_7X zqn<=Ah@516JQ2{}6Hvt3k@QXas!3TuySJ$0Be!$CaShx7Pd|+~s<7)2*kOb!q>*?R zq(YL`K+B+bR~gUARx!tXy?dLhq*BJKbhCQ1-h+s*ekG77Dw~M$v2T_fwD%?)CKcou zhja5NUQ6X;KQ;eEjRr46O_tgE6mi#Uu|J%N-8dH$y>Bs6A!Xr8c(TxKTMHF#SoFT* z*bGfNz!Y|iYoNsI8Mty&YXkJf4v=ntc-!tqQMdRyY8^0P4MkD%Uz#*>i)&;>VtKtM zkLUaIhh|Jy-ua5MUu8cOued~;vKwW}w_jyayP>d&pC!)jfswE9ZH?NS=u zBea7b)TMlD&p+@5n_T%!RD$m^s`JFhJ99b=#?bD8hOs;=BupXxYd0}AG(s|;AFl;n zCTV{pE>%vutg=t13dev${FD#OW`8S639N&d;8bb}jFE*7=(9He;B(W=e8E`S;~{a2 z?36N!?xCDr(&55W&_xm*9@YDlr~nbW>6{igcoy%faN=)sRpe$xKG(?*0#}(RGB5IN zfm!AQAdKD`UaVEPB+HcqCGQdhgjZ!42jm=RAZmP)(W4enPJHf|N;`J>4xhmFGmjoz zmS&*Mu4j&2Njdk7`1FX-2v>}fk-LNUsnuu<=A7T9<~Y zit7%Bk#;$y;rgS}7FKyoG56eo1(Z*C{Gfo~6<~&Q_#>R+^}WhOLn5{cS1nB#W%?E- z*O6Bi&jf%EXvaE6wyQw7GCcMvtJ+g}%g)KqkuE0QFjh?Wn15mvu{N5LiUfWHPC5+Z zpQ5A3B2sl)h8?2a$G(2>Nz~H97p9h;+#yR=Oms}bjx&01>*!@^Y+yDH|yA_>!e}0i4IiGb8*Xl4C@&M>^_*vTuIF z0Q5<`U$UnfYxQ3i2)GmG?n1RlzU}!|nbq}B7tgC3urz1`ru}+u8LNN(C5OOO+O(_S zjS)5){n1u`N8fC;3bSgs{|+ZDx6$vOZ3s@puFuK`3vS1IKG_Xic8GZ(nr&DR@EgGh5sD94(3&a*@Tj4R5tCYkdGMZ3fP*SI{1)kuMa zDQLRotNR=uhk0x-m~jdx-?+d0ABhr_N%*0>Jrq?!DrX+-g~s*Nz67B*K@TJHz}l#% zfhVzH_Zsm9#+8$#4NPhgpYDdo8?E17*MN&SFgr1P|1*}DR#4%^=WFMF`#I+8U=LuWt4j6$%JW(xz^Y(+oIE3W6ZF*LKJ#wT0!K6o$H=Km zI4DiO|US8CXky3sSJQ>%hppM;ek zXw|*0jyXLQX2%wz*N_}TkG&ku!&Yj?hm?Ad)GR+?l8Q8Uy4IKcS>z^BERMaDt6>%U zf*GmZxc3D+n{sFLYZS9m6c=f^&13G~Z@fXA{`7-jhU?TI-s7g))tXL{Dy#W8Svo|n zdc^~+b`k#{N(6LUCBwMz0tS5SdhBOCUwgmbbBrkN?L>?r*jF*5#@`_`_DP_~zBzQp zJqqJ`7Ky`I^UWZ}2izD+i=5bVA-*+D(epC2v4ygh`=+N`(&X7)M(E`(7hLgY@qmkX zfKi3@n`4&S0!_BfI_H+BJW;z;4n3s0Dy08O)tHNczqrpchRHHd+UZfOHKV3s{AidP z9r|a{W1nTsvhjUrWS)FYO_Z$h%1f`{~c(yyhPb5Er<`KF1;H(zEAr8fm_hx;Y*; z0gt0x6Ipv7E(zUf3>z&tw55obV+dnkn%J}j!{)^DOkhSBZQ5_(b$ssG_+LIvW@Qae zU8n4-xHOG5WbXWaHV3)OdZcuPt0{cVD`ST&URkvM0qWts+-D`_$oT6ZUrM_=Gsue+ zwzk{hxXT3exfwbeS%rIy5l(0al!Mavq{`cjvSD|neLGiJ2F7x7XvJy1 zf$f+pp=<-`M+u;NC6J<*TWTCqMH{Qf!Q!keQXux-PVW>)ARd)8c;^#0aAxN2@B!fk z>_)I}9xbjM#?!uOvZRugs{rVHu#raiHF^p>T5`P`1Gpm^uA=L58}yBlA$lO-=3-B6 zcS^g&5Xic~rUlI7?R^dg8GVWFyC)|OrL@A#Gdf-G$cN|zMpB=Dh30Xu7j}23C)M6N;7y)e->mm?_!_>z5vk-#Mq@OiF6I0 z*8V31beF^$+AOC0m!mxamJyVr|K~YiT&UbY$i&@{8m>e)hnrQy$jS?^&-_2CUAZV~ zAarM8&l;=@P2SRxu(xm;y_;bh00WRV>%5~8g(dN}Onnr6U>mkq_GEtkzkdg7gIr(u zYq?AptLs(bdQsMtzKT!ESJ=*BY*Taf$Fh{k9-?;z#jIc&{{M-75rc~NLklVP4Kj8Q z2Cv>1GfooBqLspe?(~AMPj=;9Gbt>FZ0HZ2OKkI4d^^IZjJfiHl-W47j?oq}?C2LX zVcfZxEAk7`g?fhd{rg$3S19AALkWH);|82xi`Bn4KV)ge|G!3SBNI2s-Yd1Ds9l5| z9e418m+t;sl4HSrlrmV7$#>+037_6Yh(#Yxu7&Al6V*a%k1p4)Uk2S$)}~d`!Z*J* z%i0EQ!q;1MU6+Es=&^!ot)n|5#s_UN~KSQ00BHG|Yj z#oM`$T}qHV7FuC3UZz5i4stiq zlgoEj-l*ihH(pp!I^>!UQsVcO)}NUT4(U%5jS6T%r`ezKazdFPHNwS-kNfjua0lDa z-!lWN=~@WO>$T!&Gt|&-7TU_!_v&05Ux}He6M$^y0F92f@gMN+pK{W-8>OwWt3p4~ zn;LF$pOBC}#Fcu}E$wkeZiW8Vy64&FW|gmjPHk%TsoZX4Gg6t{XQiqvpyVep-{|@~ zqqBP+*zA*C)mEf1mr+4U?+Jf%M%%dsh5dPtQgdIcCqM97R{!-}7dF*>;6}{d9{!yS zd;AL*$Kqw4+J069Ldz>NV5yP29;j}ZBuBEy?noj>x~h+>4k`#_(TEXLN%xt`r;6>avFTBIx9~roO$N{@^qJ5dD>~{U|RZY#Ea88VCZvkaUc5_JLA8x6?UvsM>J=|oIhTo(Vi9N+iB`_*{4 zL;ZgC97O9!uJ^-GiB}DMAVX-a>x}8Rd*>r-B!A;>?nJw^z9>TP47{oU?Jh2&zTvrF4pUHO0bQ8GiRANqC5laoL_tW;f)hTNw>&!<4;+;_K7QYvp$ag?1cLaJa}`aR|=FGBktKe0X%=f z$pI&GaJAqB*7VjoZkU@vtCh(;`(zbRZoYn0VfA25eXjiKi5^npRlSU$xNB zEu@&z9Uw)`gH~;tVL$IBsRJkli63O#`ITajOLV3 zyc&sw*~36|D_|C)RdO4+HePWjszE=s?Y=^Dz&*?(;|?w5$Xem?s*|w|wb3!b*O7uX z{DV@YblT#yL?J4msaZC;K4uaK^dAfdx8u1aPG=irWoWw>q$;^R%+b|KIeNA!wc0zJnOk#!N0=Hz%NJHowXUUJWCW3qrZa3!; zmsX$e=xut)TER*_7s{dyySTlZ^he|g^Ylr=Yh;P{_XxdiKY)IXjEO9`to_3=%GD<+ zsPVJPuB<`2GJ@HL_1W*h?i-HHV$r@mu6WJqH?H@u&pLk1NN=5F?yRZv|kWgS{h&-H8MTbv1Q0}fvLt%cC~!YjRP!PIJ6ZtV!I z{hQzX(G41WlOI8j)e;(4%QY=0b*PLV(aPW4qtQ+1 z-nT*lvGMk48%+$Cd9+#Wrg2#^1~Xf`GatNrll^5fBBr`HR*#F?saUCTVyKK&;C^+? zB^3xn5-;_|dZT+ryrKk3>iQUU6F-#$DH>+^Yi6-BBKc?J=ryQ?R@i5<5c@ym7(xf3VH4xuZtbjez z^z-$MwLl*GmWO5KtZ+}YvRK09K{$xgxr7C#&X@%x2q)+zLjuQN4Q1+ECP$l|QI?yn z3f-wq}SoDq}df)@W*4OJH^35)k={Tzp=n+O%V|m^^;q@vR6hB?dZ}LRMP# zk)uddt@U(G+G#L_D^;enCSnN#f3rESOtF3PznEWHUS z&^Zh)+I0aT85kX$yvP;BX``vnZw>K*2IlKMZKkS}(kiy%d@Y9FXthf`O~wzY+3q$X zyd=M8*OE(M+rdO`(LzXJ);D^bKs-7o`>!=Ucd*j7CgeL4R1MaKPKc3&3D+Xlgi;rs zgkpLAd@bicQN$pbK@oSmpnu6Tqg*A4EtHLu@`#2lY+?D!;ZVWvVyc-1`yC=`4;``& z36}%K)iEb^73{R|V8P8t)TRQe{#N6x(+tmVdpQc+>2ON1pRC0sy zM_uq!$#sEg@j31>B+HY)rQDmTmTx1U_&HM^wURxc%~JZtG~Xp498Hj`N>MAYW4s_C zCeY1sj#XltI9ct|>S1j5yZg?Ek@izPhVySY*o5;q_P)+-Q?WXZ@&s1AjbDb&as4kjhvE0O<;Du5@tp^=>CuoRq)n%dQP|Z(ruthPDyPpaeFpj72A>HuUI#keb)888BQvQv=qFq{Y36h5^a=cv%XdOr9dJ`e7AJrII0 z&@I7Zlq8%m+4caUU^v{xG{F;uy#6*=3RZt?X|)G-Z4M-%?yyL62P#6Ij3e;c4{_BR zYTzHDJGcIo8N>(h!_!R^iL?_#{muB{xJMyrrz&kQB7l|?5wQDL9K*c@Q#EdNCw0nnigf3bTY&NYdr2xilER^+ho@cU(qP}g-h}48quh? zz=^k{H%RZy4(^x$>OKY@a>@aSruv^GoBxPh{v)QttN+_t8up#}Z;-eJ2RptKU55lE zoenFX0D}p~fF!{}=U5g93-sGWmH1#ljs8za9}NG{pj?QyGyp^egKv7r2X>&grWIKSf=)NzMxaL;Vz$ONV2)<9n>>MW_5g*2QMNivc1+eCXXcQw!H;U7x^_j>FkmpZXy;e=|4y(1OIuOS zxUb^UJ%ctWD(5pE3R9wqd-;S0*Rx`80RX{cb06VOS8GU91M8-C?9&hCWiaoFSaZUT zhx`qY+fR^FUraei7=tT&mMD=8K3Rpu`qs5J)%oi&oYL>-pBkr;{BSr})?ecpxto4= zaimt#Hn#>+6cq%cQNkpek@RK3I|$c+bWNuTuYEmd>y3y~9+yJm9{U5(XBbhe*;YnM z$Ep{*=wB@$PzF>uv!#?#cwSKv6;Bf=Pb}H%C^zX8GLiFK{0u1CP1eucn6ibWB60a~ zoi0R=d>i($SewgVvLf~|Uy2bnN}&QRMC1ot)KMj2*#IB!%$`@%B3U$R`4Awg@7i(x zIJJ9ADZ?BN5U>mpf)bCm@X-FvGlE~A=^mrROH?)3`sgkL{*lNwTjxd9feTio@0flp zmwk$bD``v#;MUupw<4ss3Of9i;F+pOMZ9cDqNXApI4bC#hCsyQU8hUa>L9pf+W)gR!IAC%vQ5XyY91p5c7Gd3(w`Mt?qbH=YTlTIo6v#>QjCCL5Z8>tuah#$ zyUr{0^3T;bkATsH(5BC;5wCesjuOQ?ca^Xti6RImFwL&)h~?lcjvw;Fd|B)BikIYL zP3Gv{x0JmfZpLCu3*`w24#@*yJJYM`sjsh0u0xRaq{S{^tn{-PMIiq!GBx_Bw%j6b zaigQ74%zdF1G+46s`*uUkRFbIRB2T_$o`2c+g=3xhSSw3QTrd3fVyooma6 zC=YoQHR`FRGO}?fPLyq1)aP-cPUUb|O9^@)gQv2&q)JJ1JE;IKbOqB)e1h4ep2W4K z8FWLGnD{+#>UGnmwXdH%!#4F9(tW!jNzG)vNsc;T{#GOEXJxwloYlqUcY0jV+R+WM zb+TU+udLh#Ixq`YaXYyIL%f5DEB(3vh=M`$%WXqfVpzedCp$?e!`6>=IQsjzuQmVr zesLKqvo8FhG~;4u&-mEr;Gy?ubTZ~{x|iLC7H*V8e&no)|GKrq@OTDgYke18OCd`1 z67v_OpPqvHi}&Xy2U@F5$M$wGh;}V%X+JvSyyTo?6l}Az`J6B0U7}Z#^sX0wQ^hNP zks(l7*o^j<$PnEJJ0D5)0%d4UqqSZzTs=+ix$v=Xq;q6%>`;qjr57%D@vOett8pT0Di1 z_$fOy+{++WXk>)e!f>r{NYDvoIcpYop$eA};U}0nLX@2xk1GS!6m7Fl(>8o>u*c&C zR}>4URMGDM=W-uSoNe5wZ`+9=s;!J)-13l=c2Hl#5k|qU`HE*WB!sA%xCB2MGmSnG zbn`m>D6~c!<75(H8Ceb4V!%@ddCTgkdif#D zQoPhSnJ@b@ul8yB<_|?zhO71oyMI|Ixc_I2b33m@aI{JLxV5P}XJu=s0WMppkg=f6 zW86;QZPQ|+b2UeV1C!H4*= zs(FIHs20{%d9vO2uUntlqzNQzXb&y~cCM*xq65P<#<#5V-8MB&WM;Q7cCVG#4gL;{ z3uuTBIKo)Ir>)*kMRRTt zmui~W%9u|tYX^f^NMWZ)SDIqh>*r7Ay^4fgPFj7wcS(2f-X-}wdY9R)11?6kBlp+K z2*>jzyW7@;JH9afOfQr6c>PoSH~ru}>eKCoXVXSU zjv2=9a4I}G2}3_377um5TC->3am!v7`--smbWtS^{-b?A{mkSxVi|druDDL95ip!; zhf6NM&74PAA~s{!?U^<4xx|N~RI-=Gl927vr2S7l)Z~c;Gk$)$Z3&v~@+j z*U9}XQnb(enaO3A8X6Y^EB|5rztJ(>l=tK3asQ9C?~bRs@BhEDM>vNf*+-;ovbT&1 zmAaB5JCeOOSvfYz%8GW$E@W@AqO$jvy~jEDy*{q{{@(Zf|L>3M@#t|Cj&nYr&-?Xy z&bKAvSk9aiy%APTVk$-spG&^#o92l1rdnNNdnvdpKa1n`d0IJdF^a3a%JwR^D{)tY z?R95tlzfxnK3s>-1e+XW_kM&(`N1V*@cpWk{Q?L7)=T;4f%58mAJp^gFxFSpaW@4A zYIdXs&})J2uZPQdUdvhf4L~r@K-@yT%c@UQWsA$Kr-M&vhraqgX>Yu*qTiTwcXY|& zCc{3}K|p2DlXdxxP0D>*%XtmBkQ1-89xZaxtY`T-M(p5bQ&Uq!?4$Y%E%cB(5561x z^82(duNHsBS6&_78`f4I2>P12jF+EJ+2k5{TB)KJ8&#p>#x>E@B>5G&0D)T@U};0W|OmS(43|6ve#4+BuLL%nE>u z10Y>tLqkN*v9C2y&-z74eC`11+KzNs%&b{Ac4f!1_=Au_o$?;7lq ztT@@Gu!xqu3c_qSx?lZ|s{Owiu7BIi|6?tH*L-hYZOu~Ex7sn=bCojoYyzeE2@=p` zwfYrmdi?Ts{8_U;s{P3hWM=_f*QqxrCLDxq&jQt8k7#~>m)>LDf{DR=X&3ey8&w{T zb-Lw-vnpXcu_<15&#@-QN$JhpC3~y;6ddDrDlH@)hcX8D>;{VFBUbjJ`hVB-`eyU& zL9)5-DSs#&s1^g;CLdePrKy_Iz}jRV-=ls5&SwJN24kX4q`|Qa*DA9s@vo+&LMbysH>Fm#gqSV0b>r#x5ckoy z{&Dvf-PzcUY+!^wL+m9IZ(0dP0G0Q@6exCqPbzH)XVB{hUS(Mxl*cN2?@60-AtFON z1o9wC5l#MgY4AynBj%Qhhx+6CQCQU`47}G=3v)0}kGD0Kg&Ek0XOQ;7C}t^>2?xkhw8ihPB~YV1 zk%)Pu%=Skk#UN<<5f&vm20)K-y~UjYqr4nJ@0q> z+<%NUKz7|OR3itl>}mQ+WME2%o$m(I2Kp&@@Jf)XxP3d2@5T`!TK+4u!?cxqGfZ^t zzO)=Y;}9^yMK3-+1RtS<24BX31KAXe+TW01zh-W>AhCo|Be@pR{Dsf0-XU}8q83qB zV4qfg4O{rHpk;syQc%BM*EvQ>s`GKC=ZPQ=B-otAIbr~VvU)hdoEn}y>AeF??TaU) zRen|RJ4nk>8Yu_2UvB^@{UPiH3*WF;sw={Zy&N28C$voaA_e{<8NHGuK{UdZsQ+yB zsNpQAw~N!`6dU6+A>l*Hwx`6%C|}MOU*ZE|AeB}viQ(s_-KC9Mx_hcrFp8v6qLxtQ$-Twt}K$7Fo2_Up@EUJdA8o}8W8y{ps z^o;ofZa=;n@Ms?}uQdxR-dEwQtO04*wGuTU?OdS1xKppzm7<^XC7+dz8l~>J&sU25 zi7?d5Ic3WtM{`DU=$)NWzbbMzKUo3~pyqSn0^)vR4B1!-YzdJ1S=p!rdZtwfg~mGD zEo~Ec03X>yV!in8;36bO)eZ0q2>b^|GOH&zb_*zPDzoBu7ckpNr$V;q5-Eq+P#;S! zvOg_l1ORd%o<|!4mI~SRVR&R{{kp{(bWXlPH_@lx-m=;*@jx@`a+~X)UoG3Q3iQS* zQnUQ1zSEtIV&qI+5PMW&3%YY4h^+Y`Gc^ibPLT4yrS}rWDocTJosaf zCXx+*a}oBQIk+*B-K%1(FzfW(x@uh^+Vyn$d}lU;M+a0H^_R1b_qoP*=_N?r zjV4Z=3vAPfuWC^@OK`I` z<+9{esS+e4khv!l9!|PrqSZ~O?e8U^{j_$co~O>||MtBnH!tTIzu|HuTBc}GWHwP! zy)vdYKFsx(F?yTsI*Ei$ZRuK~H!4HE{JwNKQ?LRlK`F39^tU4M9j>-f=O6Ta8=It^ zB^@fcK)8h+(9#FMY~&bj{>wOwIKkdhHSCnI#$(%r0o5sV@viduWDeTSoh>#x;(TQ~ zTP-+zu^2do88E0INrt@Vo*DPEb9NgIz~Y;8GgEo_g;h>|s3rC~#{|WW$-QmWzgGo} z9VV>pXbWRuh0Atgko&MYM!q6NbyCe>7hv1NyB?VqRS*f`kb`{l|>Lh z7$jQBNrmRNr!!)cZ{i~eC}|u`^4Z;?ecS%7?t7~lx*P450Q^Sze9PN0^;Jen|1-7kBgJ02}w`l%o9axeM1n%#j zCbtwEh;+?l0jom%_M3x8b^~eKv(ol*B?9_v-3}Gr(mAF=EWOFj`Zv0fsq^*ZH>ZPn zQhAmGtgmuamSAj@?-Y(3H>Xfj*^<9mXf!&-?9H&4@;q5Kz$Z$?N#MJh%&N>wDxTYU zon1Q5<%VXjs1NQr7x|Rfb#E7`!fxgaU-;!D>{TAz`{uSd=J{J&Qr+4)<#Q>oDR;DB zso`EawVYC90bj5@F5k2z)hj={;sk$^ADyk8Zw_}ply7o7^qpIAno{bQEUxP8lK4RQ za1)X&AzxFY0QtwiV!yt!!TWUdN?ccy91uAMDUKEVLQ=L+3(x)3;lulznF0LO{)*=Q zCo4DTno3%Pl%i5(By54ocAD|Own>grT3t0^i<>=OCo-$urh*kjn^spjX>12%nVVBc ziDpeKEio#)A=13lYev)K)f65>yB_aRf2B*-%Bpyf(Y;F|m8K!p>66Ob|vX`U;w>Vg)-{PM~d)_wqGkad6TVD3%J?@k% ztVNVdS7bQM9!bz&$*izE4;RO@QiqFizx6=ILiN_zzJpKF`zu9g)*t#7Ih?&|3*Wc4 zvO*7Qw%)P*uuB@cs-R4iSCF;hv?;MqcMw)Nk7H&!Dx`5xJG+0riJi7NP(H+-W5pGdlb`^eIsO{tvIKEaiWhFNy@TJyJ$w-Bs+n_)LB zW9jmmp7K8}T0$ySDvy_t%KE*Qj7k+`3Aynzkw;;#E0ITV`fWP0h#34)_kv~nU|s`H z;$yh3(fqUWSK{_tIm^2PiKF5sv6U?qC$v`*eP;LVX~tP=TwVz}+!*_YgXJYe-);YG z6|(lJD`ZyXMI*obL73^h0=yUS|GWI)8uH8FWbueS4_9fMTaA3vO3(@q)5ED9CFu@T zmiLqo-%#<;!9Myt{%h7^P36^;?hv2R*O8CB)?xKo1Zo9)vW^fNf|jtet<64sdL;+A zcLe&}JLObJkICVoF2K@;1^%m7pe9&34~jXfzx&@I&5TeGS@S#Xdb9rYf8=rnJHSW% zJQt2$%w$`?SO~!?+n$1t<@J@PNLJK&RZgw?_LR6b5D#f*ZR5j^;XQv!l0%0A%>NNf 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

  • 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 R)C&}0@O1TaS?83{1OOR)5(WSO literal 0 HcmV?d00001 diff --git a/docs/v0.6.1-rc/images/low-latency-streaming.png b/docs/v0.6.1-rc/images/low-latency-streaming.png new file mode 100644 index 0000000000000000000000000000000000000000..0dac60b3fa9a3e55bab1e4fe1c194f64319ebb7c GIT binary patch literal 206499 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

    2pfb5e@WQ>g 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 BuGCZvW?E9f9@5v9ZkJb9)b|PW%c_rF5ly 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 + + + NVCF banner + NVIDIA Cloud Functions banner with NVCF logo mark, GPU workload stack, and NVCF text. + + + + + + + + + + + + + + + + + NVIDIA + NVCF + NVIDIA Cloud Functions + A world class reference for a global inference service + + diff --git a/docs/v0.6.1-rc/images/nvcf-gpu-cluster-targeting.svg b/docs/v0.6.1-rc/images/nvcf-gpu-cluster-targeting.svg new file mode 100644 index 000000000..a38a230c8 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-gpu-cluster-targeting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/v0.6.1-rc/images/nvcf-grpc-invocation-path.svg b/docs/v0.6.1-rc/images/nvcf-grpc-invocation-path.svg new file mode 100644 index 000000000..a243d8901 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-grpc-invocation-path.svg @@ -0,0 +1,98 @@ + + NVCF gRPC invocation path + Client traffic flows through the public gRPC invocation endpoint, gRPC Proxy, NVCF API and NATS stateful request path, worker CONNECT registration, and the customer gRPC service. + + + + + + + + + + + + gRPC Invocation Path + gRPC traffic uses the gRPC Proxy and a stateful worker connection path. + + + Client + gRPC calls + + + Public gRPC + invocation endpoint + port 10081 or TLS + + + gRPC Proxy + session binding + stream relay + + + NVCF API + NATS + stateful request path + + + Worker CONNECT + backend registration + + + Customer gRPC + Service + model server + + + + + + + + + bidirectional stream after worker connects + diff --git a/docs/v0.6.1-rc/images/nvcf-grpc-multicluster-invocation.svg b/docs/v0.6.1-rc/images/nvcf-grpc-multicluster-invocation.svg new file mode 100644 index 000000000..fae6d579e --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-grpc-multicluster-invocation.svg @@ -0,0 +1,136 @@ + + NVCF gRPC multi-cluster invocation path + Global DNS selects a regional gRPC endpoint. Each region has a gRPC Proxy, NVCF API plus NATS stateful request path, worker CONNECT registration, and customer gRPC services in a GPU cluster group, with NATS cross-cluster chatter between regions. + + + + + + + + + + + + + + + gRPC Multi-Cluster Invocation + gRPC traffic is pinned through a regional proxy after worker CONNECT registration establishes the backend path. + + + Global DNS / gRPC Endpoint Selection + routes clients to the regional public gRPC endpoint + + + Region A + gRPC invocation services + + + Region B + gRPC invocation services + + + Public gRPC + port 10081/TLS + + + gRPC Proxy + session binding + + + NVCF API + + NATS + + + Worker CONNECT + backend registration + + + GPU Cluster Group A + + gRPC Function A + + gRPC Function B + + + Public gRPC + port 10081/TLS + + + gRPC Proxy + session binding + + + NVCF API + + NATS + + + Worker CONNECT + backend registration + + + GPU Cluster Group B + + gRPC Function C + + gRPC Function D + + + + + + + + + + + + + NATS cross-cluster chatter + diff --git a/docs/v0.6.1-rc/images/nvcf-high-level-stack.svg b/docs/v0.6.1-rc/images/nvcf-high-level-stack.svg new file mode 100644 index 000000000..ae42cdd1d --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-high-level-stack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/v0.6.1-rc/images/nvcf-http-invocation-path.svg b/docs/v0.6.1-rc/images/nvcf-http-invocation-path.svg new file mode 100644 index 000000000..9cc5fb010 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-http-invocation-path.svg @@ -0,0 +1,98 @@ + + NVCF HTTP invocation path + Client traffic flows through an optional Vanity or custom edge, the public HTTP invocation endpoint, Invocation Service, NATS and worker request path, the worker sidecar, and the customer HTTP function. + + + + + + + + + HTTP Invocation Path + Standard HTTP function traffic is queued to the selected worker path and returned through Invocation Service. + + + Client + HTTP request + + + Optional Vanity + or custom edge + front door + + + Public HTTP + invocation endpoint + *.invocation.<domain> + + + Invocation Service + auth, request encode + response stream + + + NATS / Worker + Request Path + queue and delivery + + + Worker Sidecar + decode, attach, relay + + + Customer HTTP + Function + container endpoint + + + + + + + + diff --git a/docs/v0.6.1-rc/images/nvcf-http-multicluster-invocation.svg b/docs/v0.6.1-rc/images/nvcf-http-multicluster-invocation.svg new file mode 100644 index 000000000..11acf0610 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-http-multicluster-invocation.svg @@ -0,0 +1,132 @@ + + NVCF HTTP multi-cluster invocation path + Global DNS or an optional custom front door selects a regional HTTP invocation endpoint. Each region has Invocation Service, NATS worker request path, and customer HTTP functions in a GPU cluster group, with NATS cross-cluster chatter between regions. + + + + + + + + + + + + HTTP Multi-Cluster Invocation + HTTP traffic is routed to a regional Invocation Service, then delivered through the regional NATS worker request path. + + + Global DNS / Custom Front Door + regional selection, TLS, or custom domain handling + + + Region A + HTTP invocation services + + + Region B + HTTP invocation services + + + Public HTTP + *.invocation + + + Invocation + Service + request encode + + + NATS / Worker + request path + + + GPU Cluster Group A + + HTTP Function A + customer workload + + HTTP Function B + customer workload + + + Public HTTP + *.invocation + + + Invocation + Service + request encode + + + NATS / Worker + request path + + + GPU Cluster Group B + + HTTP Function C + customer workload + + HTTP Function D + customer workload + + + + + + + + + + + + + NATS cross-cluster chatter + diff --git a/docs/v0.6.1-rc/images/nvcf-llm-invocation-path.svg b/docs/v0.6.1-rc/images/nvcf-llm-invocation-path.svg new file mode 100644 index 000000000..13f1e01d2 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-llm-invocation-path.svg @@ -0,0 +1,113 @@ + + NVCF LLM invocation path + Client traffic flows through an optional Vanity or custom edge, the public LLM/HTTP invocation endpoint, LLM API Gateway, LLM Request Router, the LLM worker sidecar, and the customer inference container. + + + + + + + + + + + + + + + LLM Invocation Path + OpenAI-compatible traffic is handled by the LLM-specific invocation path. + + + Client + OpenAI API + + + Optional Vanity + or custom edge + front door + + + Public LLM/HTTP + invocation endpoint + llm.invocation.<domain> + + + LLM API Gateway + auth, model routing + request validation + + + LLM Request + Router + backend selection + + + LLM Worker Gateway + pylon sidecar + + + Customer Inference + Container + /v1/... model routes + + + NVCF API + LLM auth policy + + + + + + + + + diff --git a/docs/v0.6.1-rc/images/nvcf-llm-multicluster-invocation.svg b/docs/v0.6.1-rc/images/nvcf-llm-multicluster-invocation.svg new file mode 100644 index 000000000..c468080c0 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-llm-multicluster-invocation.svg @@ -0,0 +1,149 @@ + + NVCF LLM multi-cluster invocation path + Global DNS or an optional custom front door selects a regional public LLM/HTTP endpoint. Each region has LLM API Gateway, LLM Request Router, NATS usage sync, and LLM worker gateways in GPU cluster groups, with NATS usage chatter and request-router paths to worker gateways in other clusters. + + + + + + + + + + + + + + + LLM Multi-Cluster Invocation + LLM traffic uses the OpenAI-compatible gateway path and worker gateway tunnels to regional model services. + + + Global DNS / Optional Custom Front Door + regional selection for llm.invocation.<domain> + + + Region A + LLM invocation services + + + Region B + LLM invocation services + + + Public LLM/HTTP + llm.invocation + + + LLM API + Gateway + auth and validation + + + LLM Request + Router + Stargate peer + + + NATS Usage + gateway state sync + + + GPU Cluster Group A + + Worker Gateway + Model A + + Worker Gateway + Model B + + + Public LLM/HTTP + llm.invocation + + + LLM API + Gateway + auth and validation + + + LLM Request + Router + Stargate peer + + + NATS Usage + gateway state sync + + + GPU Cluster Group B + + Worker Gateway + Model C + + Worker Gateway + Model D + + + + + + + + + + + + + + + NATS cross-cluster usage chatter + + + remote worker gateway paths + diff --git a/docs/v0.6.1-rc/images/nvcf-lls-session.png b/docs/v0.6.1-rc/images/nvcf-lls-session.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a8df439e43ee01f7e002be0fb6a6e051eff473 GIT binary patch literal 79347 zcmZU51yodR*EZlF3`h?sNaxTYNJ=*dLpmZIf+F3`(9#S;iF7vtN=r$Xf`TF~-JmGV ze+GP>_kF*At+QCH!OaWlp-H@EI(Kpm)HO+WcCRuR=$z_<=p7XN=^N@*6;7{)y^%Q@3eZI`#tq=UlDHm zW303%MaGATz9MiC9bPjcx(iaO7YxUdOAwWJO0kJ_qP?SRW`ryAy9&65OhX;iz7KvK zew;*%O=6m`t=2xfO~|(Hyl36d%ko5ZW20QXlvCr;Q!G|fELIo;LT2DnsNV}I$DGjo z2yu@EN9~A2!q~r1Nh9h1d#$$TgIi}m8?uRmL{o#Se&%e=HIK{Tt}3&tt|&8l#ABVR zgejN0>$B>`j9@=(2dTG%Hl#5W#`H^914#Y1mX@gp^1*nmJZWri?g6JkSdut8TtB9s z{T5VMdMwUzTa^|ize9liF5Zyqn}K#xtw0IK3m(hsz1~2KE+s81ZDG({8Y3@zhn3Qt+e&N8unfNEgKK3xp>HdF` z+1(&}?b9DD;cAjxlpi0b3DPrKugSIJT*>zVf=ut12dVg9{atI358+8-Do4FD>9D)4 zFNMjOc!K!BeDwt46_~n|Y9fAqb~Ud1zm~~%0vXnVKTFU1CuOj75UgslzY!>bQ1+$= zf>Q&*pH=65N=+^LqyDoFVhr@!9DP71#=y$8?;9UISQKa|BUwep{=W4&J)ee^aDuPN zqR|wn12__W3`m0Y?>3cL5cV+}>iyoNPpSGgH_O#G2D_B5rFF=#7g#C^J1G4y0$NmP zZ{@$T%jWBm@6BJ&>&yU=QyIxVDljqtq!~HG7$Xmfqh>IuEzOn9PO;vHFGj z&+%#hs3>Qm^PRKaYx5xlU6hMyAit*l^E`xM9*UNhG9O~DWVef%@+Eg-py{Y;5OX;_b3b!Jh*d7mMr?Lm>pN@VTIj87}-{Kkf%x;fg1$=%S;f8BzuDO ziV1G5=udx?1W`=F+Sog`^o$8c6#lwRJ7_9Mdg78wHk(^gg4U7~>{_~igQcrNofI3e zIK&iggX?#)uR5Svfvrowb-6le=zTZZcUP7aC7r_IEp2Z7znUVJ z!aCggG+6q=iZf9jZ7gWBXT=K8S~pSMx)$nR(JHo+r%7{x4XcbF*jXW=|12tPh)eC` z_jRdZvvV1Dq2bry`L)GB69a40q-nqs!rLQzeYPpQ%vEiFJo`2RXwseaSYbQ}@uYpy z|5;PK_!lE-GZn7y<_4cCUj99SZ|x|ViJYlKKBxRIu(wfw(V=A8AEUQJ9}B9?a+WoPLf3h6q|IG6#bjQU* znhdGD@W_0rqyG&rn)HeNA>9;@`OE8$D9qNfyLF_DiTHkl#J+UN0b)c89c`7baJdS{ zTv)8ctfO$LJ&eBU97y+t+=B?KY7fLP5_z$}xswJ~J^#@)0!7jn5?lRc04L1B-pv8- z_R=osGEF)PY)V?Mq!cB-umWfsDcgQ72srgDSH=>`90bQ#QOy}l3VXrWW~gLbRiw+q zy^4;hm}E9;SemVVI;lrfbGMPtYBL?6aUQEJw6p;~HbSTT&SC<>(_D(~*V#R?$9l+G z-JGQIkac!zWjYhBeL+O~?evd|9Is5bnb;E!CKXEE4UIKR44iqc?IGS9OZdV0kG;`e zLb!5f`tP=|*z=e_tf6IDBHlUfRbGn?d~Z7<%fkqVlGwte$;7==f!F767hc=OR0@VI z?O`pC_FL1IOzpQzhSzpP{}%MrqcyP>^h(^}Y3k$ywxOL@3F55NeKH=_&7I|q?#`V9B1KsjYfpj2j)QEzYzc#GP|-?x~n z%oljDhL{Uf`DnD*=7^_lj)= zqT;3t?daKLm-!p>;%%_Tty}Lzs@xy49cT!1B_zsIs=Q5MWO*Fl|CJ|?{o_nuCuou6 z$ISGi6YVrUdLPPyhfODKeRKqr{t2%8Vs9T~aQo#mJMpUh zR>#46Y~ia#GZ|!^9}@Ie$2Sg98@E1x0fKlwG7!h=Dk8#O2YR@0j"#|?B?PLT$L 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

    j57$ojZNY@yUC2ul2*GgA(cq! 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 literal 0 HcmV?d00001 diff --git a/docs/v0.6.1-rc/images/nvcf-multi-region-multi-cluster.svg b/docs/v0.6.1-rc/images/nvcf-multi-region-multi-cluster.svg new file mode 100644 index 000000000..9b92630f6 --- /dev/null +++ b/docs/v0.6.1-rc/images/nvcf-multi-region-multi-cluster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/v0.6.1-rc/images/self-hosted-min-topology.png b/docs/v0.6.1-rc/images/self-hosted-min-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..a2ce37eb35fa719b956a9c64c252c79be64130a4 GIT binary patch literal 97396 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 F6972gvc~`b literal 0 HcmV?d00001 diff --git a/docs/v0.6.1-rc/index.md b/docs/v0.6.1-rc/index.md new file mode 100644 index 000000000..814711d7a --- /dev/null +++ b/docs/v0.6.1-rc/index.md @@ -0,0 +1,16 @@ +![NVIDIA Cloud Functions banner](images/nvcf-banner.svg) + +This guide provides information for deploying and operating NVCF in self-managed environments. + +- [Quickstart](./quickstart.md) + : Install the control plane, register a GPU cluster, and validate the deployment with the one-click CLI flow. +- [Deployment](./installation.md) + : Compare the one-click and Helmfile installation paths. +- [GPU Cluster Setup](./cluster-management/index.md) + : Connect GPU clusters to the NVCF control plane. +- [Configuration](./optional-enhancements.md) + : Configure gateway routing, registries, and optional enhancements. +- [Using Cloud Functions](./api.md) + : Create and invoke functions using the NVCF API and CLI. +- [Managed (Legacy)](../ngc-managed/cluster-management/ngc-managed.md) + : Documentation for the legacy NGC-managed NVCF platform (BYOC). diff --git a/docs/v0.6.1-rc/infrastructure-sizing.md b/docs/v0.6.1-rc/infrastructure-sizing.md new file mode 100644 index 000000000..eca83d8a2 --- /dev/null +++ b/docs/v0.6.1-rc/infrastructure-sizing.md @@ -0,0 +1,197 @@ +# Infrastructure Sizing + + +The recommendations on this page are approximate starting points. The node +types and storage classes are AWS examples. For another cloud service provider +(CSP) or on-premises Kubernetes environment, choose equivalent node shapes and +storage classes that provide at least the listed vCPU, memory, storage, zone +spread, and GPU compatibility characteristics. Actual requirements depend on +workload characteristics, function count, and request concurrency. Use the +[self-managed-grpc-load-test](./grpc-load-testing.md) and +[self-managed-http-load-test](./http-load-testing.md) guides to validate +throughput and tune your control plane accordingly. + + + +## Infrastructure Components + +The self-hosted NVCF control plane uses dedicated node pools, each isolated by a +Kubernetes node selector label. Separating workloads onto dedicated pools +prevents resource contention and simplifies scaling. + +The table below lists the stateful infrastructure workloads deployed by the +helmfile stack. These are the core dependencies that must be running before any +NVCF services can start. + +| Service | Namespace | Kind | Replicas | Node Selector | Notes | +| --- | --- | --- | --- | --- | --- | +| Cassandra | `cassandra-system` | StatefulSet | 3 | `nvcf.nvidia.com/workload=cassandra` | Wraps the Bitnami Cassandra chart. Configurable via `cassandra.replicaCount`. | +| NATS | `nats-system` | StatefulSet | 3 | `nvcf.nvidia.com/workload=control-plane` | Wraps the upstream `nats-io/k8s` chart with clustering and JetStream enabled. Each replica has its own PVC for file storage. | +| OpenBao Server | `vault-system` | StatefulSet | 3 | `nvcf.nvidia.com/workload=vault` | HA Raft cluster for secrets management. Configured via `openbao.server.ha.replicas`. | +| OpenBao Injector | `vault-system` | Deployment | 2 | `nvcf.nvidia.com/workload=vault` | Sidecar injector for Vault agent containers. Upstream chart defaults to 1; overridden to 2 in `global.yaml.gotmpl`. | +| GPU Workers | *(operator-managed)* | *(varies)* | Workload-dependent | `nvcf.nvidia.com/workload=gpu` | Function instances requiring GPU acceleration. Sized independently from the control plane. See [GPU Worker Nodes](#gpu-worker-nodes). | + + +Each Cassandra replica must run on its own node to ensure high +availability. Do not co-locate multiple Cassandra pods on the same node. + + + + +Node selectors are only applied when `global.nodeSelectors.enabled` is set +to `true` in the environment file. When disabled, all workloads are +scheduled without placement constraints. + + + +## Sizing Tiers + +The tables below cover control-plane infrastructure only. GPU worker nodes +are sized independently based on your inference workloads. See +[GPU Worker Nodes](#gpu-worker-nodes). + +### Development + +For local development of the stack or functions, CI pipelines, or quick demos, +you can run the entire NVCF stack on a single machine using k3d. This setup +uses a single Cassandra replica, fake GPUs, and ephemeral `local-path` storage. + +See [local-development](./local-development.md) for full step-by-step instructions. + +### Staging / Demo + +A minimal deployment for validating the stack, running demos, or developing +functions. All NVCF services can be co-located on one or two nodes with no +dedicated node selectors. + +| Node Pool | AWS Example Node Type | vCPU | Memory | Nodes | +| --- | --- | --- | --- | --- | +| All services (co-located) | `m6i.4xlarge` or equivalent | 16 | 64 GiB | 1 | +| Split across | `m6i.2xlarge` or equivalent | 8 | 32 GiB | 2 | + +Storage: Approximately 50 GB AWS EBS `gp3`, or equivalent CSI-backed block +storage for your CSP. + +Use this tier for: + +- Initial evaluation and proof-of-concept deployments +- CI/CD pipelines and automated testing +- Demo environments + + +You can also run the full stack on your laptop using Kind or k3d. See +[local-development](./local-development.md) for instructions. + + + +### Minimal High Availability + +The recommended minimum for production workloads. Core infrastructure +(Cassandra, OpenBao) is fully redundant; the control plane has enough capacity +for moderate function counts. + +| Node Pool | AWS Example Node Type | vCPU | Memory | Nodes | +| --- | --- | --- | --- | --- | +| Control Plane | `m5.2xlarge` or equivalent | 8 | 32 GiB | 2 | +| Cassandra | `m5.2xlarge` or equivalent | 8 | 32 GiB | 3 | +| OpenBao | `m5.2xlarge` or equivalent | 8 | 32 GiB | 2 | + +Total: 7 control-plane nodes + +Storage: Approximately 500 GB AWS EBS `gp3`, or equivalent CSI-backed block +storage, across Cassandra and OpenBao volumes. + +Use this tier for: + +- Production workloads with up to ~2,000 registered functions +- Environments where core infrastructure HA is required +- Moderate request concurrency + +### Production (Full HA) + +Designed for high-throughput production deployments with a large number of +concurrent functions and full high availability across every component. + +| Node Pool | AWS Example Node Type | vCPU | Memory | Nodes | +| --- | --- | --- | --- | --- | +| Control Plane | `m5.4xlarge` or equivalent | 16 | 64 GiB | 3 to 5 | +| Cassandra | `r5.2xlarge` or equivalent memory-optimized node | 8 | 64 GiB | 3 | +| OpenBao | `m5.xlarge` or equivalent | 4 | 16 GiB | 3 | + +Total: 9 to 11 control-plane nodes + +Storage: Approximately 500 GB AWS EBS `gp3`, or equivalent CSI-backed block +storage, across Cassandra and OpenBao volumes. + +Use this tier for: + +- High-throughput production with more than 2,000 concurrent functions +- Full HA required for all components including OpenBao +- Deployments serving multiple teams or tenants + + +The Production tier uses `r5.2xlarge` (memory-optimized) for Cassandra to +provide additional headroom for large datasets and compaction. For the control +plane, `m5.4xlarge` provides double the CPU and memory per node compared to +the Minimal HA tier, reducing the number of nodes needed while increasing +per-node headroom. + + + +## GPU Worker Nodes + +GPU worker nodes are not part of the control-plane tiers above. They are +sized based on your inference workloads and can be added to any tier. + +Self-hosted NVCF supports any GPU instance type compatible with the +[NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/). + +GPU requirements: + +- NVIDIA GPU Operator must be installed +- NVIDIA device plugin for Kubernetes +- Physical GPU hardware on worker nodes + +For development and testing environments without GPUs, install the fake GPU +operator to simulate GPU resources. See [fake-gpu-operator](./fake-gpu-operator.md) for +instructions. + +## Storage Recommendations + +| Component | Default | Production Recommendation | +| --- | --- | --- | +| Cassandra | 20 Gi per replica | 50 to 100 Gi | +| OpenBao | 20 Gi | 20 to 50 Gi | +| NATS JetStream | 20 Gi | 50 to 100 Gi (depending on message volume) | +| Control Plane Services | 1 to 10 Gi each | Defaults are typically sufficient | + +Storage sizes are configurable via the `storageSize` value in your environment +file. See [helmfile-installation](./helmfile-installation.md) for details. + + +Some cloud providers have minimum PVC size requirements. For example, AWS EBS +`gp3` volumes have a 1 Gi minimum. + + + +## Scaling Beyond Defaults + +The default control-plane resource sizing shipped with the helmfile stack is +designed to handle approximately 100 concurrent users. If you need higher +throughput: + +1. Benchmark your deployment using the [self-managed-grpc-load-test](./grpc-load-testing.md) + or [self-managed-http-load-test](./http-load-testing.md) guide. Start with + `--vus 100` and increase gradually. +2. Scale node pools independently. Cassandra, OpenBao, and control-plane + pools can each be scaled without affecting the others. +3. Increase pod resources for specific services by adding `values:` blocks + in the helmfile release definitions. See [helmfile-installation](./helmfile-installation.md) + for override examples. + + +- [Quickstart](./quickstart.md): One-click fresh installation walkthrough +- [self-managed-grpc-load-test](./grpc-load-testing.md): Validate control-plane throughput +- [self-managed-http-load-test](./http-load-testing.md): Validate HTTP invocation throughput + + diff --git a/docs/v0.6.1-rc/installation.md b/docs/v0.6.1-rc/installation.md new file mode 100644 index 000000000..dc7f68246 --- /dev/null +++ b/docs/v0.6.1-rc/installation.md @@ -0,0 +1,113 @@ +# Deployment + +Self-hosted NVCF installation includes the core components required for NVCF inference. Optional components such as caching and low latency streaming support are also available. Vanity Gateway routing is available only in stack packages that include the Vanity Gateway addon. + +For a local k3d fresh install, start with the [Quickstart](./quickstart.md). The quickstart uses `nvcf-cli self-hosted up` to install the control plane, register the local k3d cluster, install NVCA, and run basic health checks. + +For a full list of required artifacts, see [self-hosted-artifact-manifest](./manifest.md). + +![Self-hosted component overview](images/nvcf-high-level-stack.svg) + + +Want to try NVCF locally first? See [Local Development](./local-development.md) to create a k3d cluster, then use the [Quickstart](./quickstart.md) local k3d flow. + + + +## Choose an installation path + +| Path | Use when | Starting point | +| --- | --- | --- | +| Local one-click CLI installation | You want the fastest local k3d install and cluster registration path. | [Quickstart](./quickstart.md) | +| Helmfile installation | You need manual release control, partial recovery, upgrades, or detailed Helmfile operations. | [Helmfile Installation](./helmfile-installation.md) | + +The control plane and GPU cluster can be the same Kubernetes cluster or separate clusters when you use Helmfile or the explicit CLI install primitives. The quickstart supports only a single local k3d cluster. For a complete Amazon EKS example of both topologies, see the [CSP End-to-End Example](./csp-end-to-end-example-installation.md). + +For remote installs, prepare the Gateway API ingress path and CLI endpoint +configuration before registering GPU clusters or running post-install CLI +checks. See [Helmfile Installation](./helmfile-installation.md), +[Self-Managed Clusters](./cluster-management/self-managed.md), and +[Gateway Routing](./gateway-routing.md). + +## Overview + +Every installation path follows the same high-level sequence: + +1. Clone the [NVCF source repository](https://github.com/nvidia/nvcf) and run + repository-based commands from its root directory. + +2. Make NVCF artifacts available to your Kubernetes clusters. Pull them + directly from NGC when the clusters have NGC access, or follow the + [image mirroring instructions](./image-mirroring.md) to copy them to a + registry that the clusters can access. + +3. Create or select Kubernetes cluster targets. You need a cluster for the control plane and a GPU cluster for function workloads. These can be the same cluster or separate clusters. + +4. Install the self-hosted control plane. Use the [Quickstart](./quickstart.md) for a local k3d install or [Helmfile Installation](./helmfile-installation.md) for manual Helmfile operations. + +5. Register a GPU cluster and install the NVIDIA Cluster Agent. The local quickstart performs this step for the local k3d cluster. For manual installation paths, see [Self-Managed Clusters](./cluster-management/self-managed.md). + +6. Install Low Latency Streaming if needed for streaming workloads. See [LLS Installation](./lls-installation.md). + +7. Install optional enhancements, such as caches, low latency streaming, or Vanity Gateway routing when your stack package includes that addon. See [Optional Enhancements](./optional-enhancements.md). + +## Kubernetes Cluster Requirements + +### Cluster Version + +- Supported versions are the latest Kubernetes minor release and the two prior minor releases (N-2). See official Kubernetes docs for current supported [versions](https://kubernetes.io/releases/version-skew-policy/#supported-versions). +- Support for dynamic persistent volume provisioning + +### Required Operators and Components + +#### NVIDIA GPU Operator + +Required for GPU workload scheduling. The GPU Operator automates the management of all NVIDIA software components needed to provision GPUs in Kubernetes, including: + +- NVIDIA device drivers +- Kubernetes device plugin for GPU discovery +- GPU feature discovery for node labeling +- Container runtime integration (containerd, CRI-O, or Docker) +- Monitoring and telemetry tools + +See [NVIDIA GPU Operator documentation](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/) for installation instructions. + + +Fake GPU Operator for development and testing: + +For environments without actual GPU hardware, install the fake GPU operator to simulate +GPU resources. See [fake-gpu-operator](./fake-gpu-operator.md) for full instructions. + + +#### SMB CSI Driver + +The [SMB CSI driver](https://github.com/kubernetes-csi/csi-driver-smb) +(`smb.csi.k8s.io`) must be installed on every GPU cluster. NVCA uses the +driver for shared model cache storage that function worker pods mount. Install +and verify the driver before registering the GPU cluster. See the +[Self-Managed Clusters prerequisites](./cluster-management/self-managed.md#prerequisites) +for the installation command. + +#### Network Policies + +Your cluster must support Kubernetes Network Policies if network isolation is required. + +#### Persistent Storage + +A StorageClass must be configured for persistent volumes. Common options: + +- Amazon EKS: `gp3` (default) +- Local development: `local-path` +- Other platforms: Any CSI-compatible storage class + + +Some cloud providers have minimum PVC size requirements. For example, AWS EBS gp3 volumes have a 1Gi minimum. + + + +### Cluster Sizing and Storage + +See [infrastructure-sizing](./infrastructure-sizing.md) for node pool specifications, storage +recommendations, and three recommended sizing tiers (Development, Minimal HA, +and Production). + +![Self-hosted minimum topology](images/self-hosted-min-topology.png) diff --git a/docs/v0.6.1-rc/llm-function-enablement.md b/docs/v0.6.1-rc/llm-function-enablement.md new file mode 100644 index 000000000..b35e5ee2d --- /dev/null +++ b/docs/v0.6.1-rc/llm-function-enablement.md @@ -0,0 +1,209 @@ +# LLM Function Enablement + +Enable the LLM addon before creating or invoking functions with +`functionType: "LLM"` through the LLM invocation route. The addon deploys the +LLM API Gateway and LLM request router, creates the external LLM invocation +route, and configures worker pods to use the `pylon` sidecar for model-aware +routing. + +For LLM function payload shape and invocation examples, see +[Function Creation](./function-creation.md#llm-functions) and +[LLM Gateway](./llm-gateway.md). + +## When to Enable + +Enable the LLM addon when NVCF should route OpenAI-compatible requests by +function and model through `llm.invocation.`. The gateway extracts the +function ID from the OpenAI `model` field, applies LLM-specific validation and +rate limits, and sends the request through the LLM request router. + +Standard HTTP, gRPC, and LLS functions do not require this addon, even when a +container exposes paths such as `/v1/chat/completions`, `/v1/responses`, or +`/v1/embeddings`. + +When enabled, the stack creates: + +- `llm-api-gateway` in the `nvcf` namespace. +- `llm-request-router` in the `nvcf` namespace. +- The `llm.invocation.` HTTPRoute when Gateway API ingress is enabled. +- LLM worker pods with a `pylon` sidecar that forwards requests to the function + container on the configured `inferencePort`. + +## Helmfile Configuration + +Add the LLM addon and `agentConfig` block to your Helmfile environment file +before applying the stack: + +```yaml +addons: + llm: + enabled: true + gateway: + replicaCount: 1 + auth: + grpcInsecure: true + metrics: + serviceMonitor: + enabled: false + requestRouter: + replicaCount: 1 + metrics: + serviceMonitor: + enabled: false + loadBalancer: + config: | + { + "default": "power-of-two", + "request_algorithms": { + "power-of-two": "power-of-two", + "round-robin": "round-robin", + "random": "random", + "groq-multiregion": "groq-multiregion" + } + } + +agentConfig: + mergeConfig: | + cluster: + validationPolicy: + name: Unrestricted + workload: + stargateQUICInsecure: true +``` + +Use `replicaCount: 1` for local or single-node test clusters. Increase +replica counts for shared or production environments. + +`addons.llm.requestRouter.loadBalancer.config` configures request-router +algorithm selection. Function model specs use underscored `routingMethod` +values such as `round_robin`, `power_of_two`, `groq_multiregion`, and +`random`. The request router configuration uses hyphenated algorithm IDs such +as `round-robin`, `power-of-two`, `groq-multiregion`, and `random`. + +When a function can use a non-default `routingMethod`, include the matching +algorithm in `request_algorithms`. If the request router does not have a +matching algorithm entry, invocation can fail with HTTP `400` before a backend +is selected. + +If you mirror images to a registry that does not use the stack's default +`global.image.registry` and `global.image.repository`, override the +`pylon` sidecar image passed to generated LLM workers: + +```yaml +api: + env: + NVCF_SIDECARS_LLM_ROUTER_CLIENT_IMAGE: //pylon:0.2.1 +``` + +The LLM API Gateway and request router images are resolved from the same stack +artifact registry settings as the other control plane services. + +## Local Plaintext Transport + +Local development clusters commonly run the LLM API Gateway to NVCF API gRPC +hop and the worker `pylon` sidecar to request-router QUIC tunnel without TLS. +In that case, add both plaintext controls. + +The complete Helmfile example above includes these settings and the +request-router load balancer config. If you already have an LLM block, include +these plaintext-specific fields: + +```yaml +addons: + llm: + enabled: true + gateway: + replicaCount: 1 + auth: + grpcInsecure: true + requestRouter: + replicaCount: 1 + +agentConfig: + mergeConfig: | + workload: + stargateQUICInsecure: true +``` + +`addons.llm.gateway.auth.grpcInsecure: true` configures the LLM API Gateway to +talk to the NVCF API over plaintext gRPC. + +`workload.stargateQUICInsecure: true` configures generated LLM workers to pass +the plaintext QUIC setting to the `pylon` sidecar. + + +Use these plaintext settings only for local or isolated test clusters. +Production environments should use TLS-capable service configuration instead. + + + +## Apply and Verify + +Apply the updated control plane environment before creating LLM functions: + +```bash +make apply HELMFILE_ENV= +``` + +Apply or refresh the compute-plane stack for each registered GPU cluster so the +NVCA operator receives `agentConfig.mergeConfig`: + +```bash +make -C deploy/stacks/nvcf-compute-plane install \ + HELMFILE_ENV= \ + CLUSTER_NAME= +``` + +Existing LLM function pods keep their current sidecar arguments. Recreate or +redeploy those functions after refreshing the compute plane so new pods get the +updated worker transport settings. + +Verify the LLM control plane components: + +```bash +kubectl get deploy -n nvcf llm-api-gateway llm-request-router +kubectl get pods -n nvcf | grep -E 'llm-api-gateway|llm-request-router' +kubectl get httproute -A | grep llm +``` + +After deploying an LLM function, verify the worker sidecar: + +```bash +kubectl get pods -n nvcf-backend -L FUNCTION_ID +kubectl -n nvcf-backend get pod \ + -o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.image}{"\n"}{end}' +kubectl -n nvcf-backend get pod \ + -o jsonpath='{range .spec.containers[?(@.name=="llm-worker")].args[*]}{.}{"\n"}{end}' +``` + +The function pod should include an `llm-worker` container using `pylon`. For +local plaintext clusters, the `llm-worker` args should include +`--quic-insecure`. + +## Troubleshooting + +`404 no_eligible_candidates` from `llm.invocation.` means the request +reached the LLM Gateway, but the requested function or model was unknown or was +not registered on the selected request router. Similar `503` candidate errors +mean the router knows the target but has no active eligible backend. Check: + +- The LLM function is deployed and its pod is `Running`. +- The request `model` value uses `/`. +- The function's `models[].name` matches the model suffix in the request. +- `models[].llmConfig.uris` includes the invoked path. +- `addons.llm.requestRouter.loadBalancer.config` includes the algorithm selected + by the function's `models[].llmConfig.routingMethod`. +- The `llm-worker` sidecar connected to `llm-request-router`. +- Local clusters using plaintext transport include both `grpcInsecure` and + `stargateQUICInsecure`. + +Useful logs: + +```bash +kubectl logs -n nvcf deploy/llm-api-gateway --tail=100 +kubectl logs -n nvcf deploy/llm-request-router --tail=100 +kubectl logs -n nvcf-backend -c llm-worker --tail=100 +``` + +In healthy routing, the request router logs show a reverse tunnel connection +from the worker and at least one routing candidate for the requested function. diff --git a/docs/v0.6.1-rc/llm-gateway.md b/docs/v0.6.1-rc/llm-gateway.md new file mode 100644 index 000000000..68382e90d --- /dev/null +++ b/docs/v0.6.1-rc/llm-gateway.md @@ -0,0 +1,203 @@ +# LLM Gateway + +The LLM Gateway handles OpenAI-compatible invocation for NVCF LLM functions in +self-managed deployments. Use LLM functions when requests should enter through +the LLM invocation route and NVCF should route them by function and model. + +Self-managed deployments usually expose the LLM invocation route through the +gateway load balancer and use the `Host` header for routing: + +```bash +export GATEWAY_ADDR= +``` + +Requests use the OpenAI `model` field in this format: + +```text +/ +``` + +The gateway uses `` as the routing key for NVCF authorization and backend selection. It forwards `` to the upstream model server. + +Requests must already be OpenAI-compatible when they reach the LLM invocation route. Any model-specific prompt formatting or tokenizer behavior belongs in the upstream model server. + +## Request Flow + +The LLM Gateway path has these runtime components: + +![LLM invocation path](images/nvcf-llm-invocation-path.svg) + +1. Client sends an OpenAI-compatible request to the LLM invocation route. +2. LLM API Gateway extracts the routing key from the `model` field, validates authorization, applies request and token rate limits, and validates endpoint-specific request fields. +3. LLM API Gateway forwards the request to LLM request router with routing metadata such as request ID, routing key, model name, routing method, token estimate, and cache affinity key when present. +4. LLM request router selects a healthy backend for the requested function and model. +5. The `pylon` sidecar on the selected workload forwards the request to the user container through the configured inference port. +6. The user container handles the OpenAI-compatible route and returns the response through the same path. + +The function container must expose the declared OpenAI-compatible paths on its inference port. Use `inferenceUrl: "/"` for LLM functions unless the container needs a different base path. + +### Multi-Cluster View + +In a global deployment, DNS or a custom front door selects a regional +`llm.invocation.` endpoint. The LLM Gateway can use NATS for +cross-cluster usage-state chatter. LLM worker request streams still use the +request router and worker gateway path, not NATS worker streams. The +worker-gateway arrows show that routers can target local or remote worker +gateways when those workers are registered into the router mesh. + +![LLM multi-cluster invocation path](images/nvcf-llm-multicluster-invocation.svg) + +## Function Configuration + +Set `functionType` to `LLM` and define model routing metadata under `models[].llmConfig`. + +```json +{ + "name": "sample-llm-function", + "containerImage": "nvcr.io/example/openai-compatible:latest", + "inferenceUrl": "/", + "inferencePort": 8000, + "functionType": "LLM", + "models": [ + { + "name": "dummy-model", + "llmConfig": { + "uris": ["/v1/chat/completions", "/v1/responses", "/v1/embeddings"], + "routingMethod": "round_robin", + "tokenRateLimit": "1000-S" + } + } + ] +} +``` + +`llmConfig.uris` declares the OpenAI-compatible paths the model supports. Supported LLM paths are: + +| Path | Behavior | +| --- | --- | +| `/v1/chat/completions` | Supports streaming and non-streaming chat completion requests. | +| `/v1/responses` | Supports native Responses API requests. Streaming clients receive server-sent events (SSE). Non-streaming clients receive the terminal Responses JSON object. | +| `/v1/embeddings` | Supports embeddings requests with string or string array input. | + +`llmConfig.routingMethod` accepts `round_robin`, `power_of_two`, `groq_multiregion`, `pulsar`, or `random`. + +`llmConfig.tokenRateLimit` applies a per-model token limit. Use one or more comma-separated limits in `-` format, where `` is a positive integer and `` is one of `S` (seconds), `M` (minutes), `H` (hours), `D` (days), or `W` (weeks). A single limit is one token budget over one time window, such as `1000-S`. A combined limit is multiple token budgets over distinct time windows, such as `1000-S,5000-M,100000-H,500000-D,1000000-W`; do not repeat a unit in the same value. + +The same configuration can be provided with CLI flags: + +```bash +./nvcf-cli function create \ + --name "sample-llm-function" \ + --image "nvcr.io/example/openai-compatible:latest" \ + --inference-url "/" \ + --inference-port 8000 \ + --function-type LLM \ + --llm-model "name=dummy-model,uris=/v1/chat/completions|/v1/responses|/v1/embeddings,routingMethod=round_robin,tokenRateLimit=1000-S" +``` + +These per-model routing fields are mutable. Use `nvcf-cli function update --llm-model-update "name=,routingMethod=,tokenRateLimit="` or JSON `modelUpdates` to change them without recreating the function version. + +For request admission and rate limiting, the gateway uses request estimates until the upstream service returns usage data. Do not depend on gateway-side exact token counts. + +## Endpoint Behavior + +### Chat Completions + +Use `/v1/chat/completions` for OpenAI-compatible chat requests: + +```bash +curl -sS -X POST "http://${GATEWAY_ADDR}/v1/chat/completions" \ + -H "Host: llm.invocation.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer ${NVCF_API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/dummy-model", + "stream": true, + "messages": [ + { + "role": "user", + "content": "Write a one sentence summary of NVCF." + } + ] + }' +``` + +When `stream` is `true`, the gateway relays server-sent events. When `stream` is false or omitted, the gateway returns the final JSON response from the upstream service. + +### Responses + +Use `/v1/responses` for native Responses API requests: + +```bash +curl -sS -X POST "http://${GATEWAY_ADDR}/v1/responses" \ + -H "Host: llm.invocation.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer ${NVCF_API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/dummy-model", + "input": "Write a one sentence summary of NVCF.", + "stream": false + }' +``` + +The gateway requires the routed model to declare `/v1/responses` in `llmConfig.uris` when model specs are available. The gateway proxies native Responses requests upstream instead of converting them to chat completions. + +For upstream compatibility, the gateway sends native Responses requests to the router as streaming requests. If the client requested streaming, the gateway relays SSE. If the client requested a non-streaming response, the gateway consumes the upstream SSE stream and returns the terminal Responses JSON object. + +### Embeddings + +Use `/v1/embeddings` for OpenAI-compatible embeddings requests: + +```bash +curl -sS -X POST "http://${GATEWAY_ADDR}/v1/embeddings" \ + -H "Host: llm.invocation.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer ${NVCF_API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "/dummy-model", + "input": "NVCF embeddings check" + }' +``` + +The gateway accepts `input` as a string or an array of strings. Empty input is rejected. The request can include up to 2048 input entries. + +Embeddings requests do not use session stickiness. + +## Model Routing And Upstream Paths + +The client-facing `model` value must include a routing key and model name: + +```text +/ +``` + +The gateway uses `` to authorize the request and select the NVCF function deployment. It rewrites the upstream model to `` before forwarding the request. + +The configured `llmConfig.uris` must match the paths served by the container. For example, a model that declares `/v1/responses` must have a container backend that can answer `POST /v1/responses`. + +## Session Stickiness + +The LLM Gateway supports sticky routing for multi-turn OpenAI-compatible requests on `/v1/chat/completions` and `/v1/responses`. + +Sticky routing is not supported on `/v1/embeddings`. + +To keep later requests routed to the same backend, send the `x-multi-turn-session-id` response header value back as the `x-multi-turn-session-id` request header on the next request. + +The gateway chooses the sticky routing key in this order: + +| Endpoint | Precedence | +| --- | --- | +| `/v1/responses` | `prompt_cache_key`, `conversation.id`, `x-multi-turn-session-id`, input hash fallback | +| `/v1/chat/completions` | `x-multi-turn-session-id`, messages hash fallback | + +For Responses API follow-up calls, `previous_response_id` does not override the sticky routing key. Continue sending `prompt_cache_key`, `conversation.id`, or the returned `x-multi-turn-session-id` header when the next request needs the same backend affinity. + +Sticky routing only affects backend selection when the LLM request router is configured with a cache-affinity-aware routing method for the target model. Clients should only use `x-multi-turn-session-id`. The gateway derives and forwards the internal `x-cache-affinity-key`; clients should not send that header. + +## Operational Notes + +- The LLM invocation route is served by the `llm.invocation.` hostname. +- The LLM API Gateway chart and LLM request router chart are installed as self-managed stack components. +- The Gateway Routes chart creates the external HTTPRoute for LLM invocation. +- Token rate limits are evaluated per model when `llmConfig.tokenRateLimit` is set. +- The LLM request router and `pylon` worker sidecar images must be from a stack release that includes the endpoint support documented here. diff --git a/docs/v0.6.1-rc/lls-installation.md b/docs/v0.6.1-rc/lls-installation.md new file mode 100644 index 000000000..5abd5e78a --- /dev/null +++ b/docs/v0.6.1-rc/lls-installation.md @@ -0,0 +1,676 @@ +# LLS Installation + +This guide provides instructions for installing Low Latency Streaming (LLS) components for self-hosted NVCF deployments. LLS enables real-time streaming capabilities for simulation workloads. + + +LLS supports self-hosted Kubernetes deployments on AWS and Azure. AWS-specific +commands and resources in this guide are illustrative examples. + +This guide covers manual installation of LLS resources, image mirroring, and +load balancer resources. + + + +## Overview + +Low Latency Streaming (LLS) provides real-time streaming capabilities for self-hosted NVCF deployments. LLS consists of: + +- **Streaming Proxy (RP Container)** - Handle WebRTC UDP streaming connections + +Self-hosted mode uses your container registry for container images and customer-managed infrastructure. + +## Architecture + +### Request and Streaming Flow + +The following diagram illustrates the request and streaming flow for self-hosted NVCF LLS, showing how a browser application invokes a function and establishes direct streaming channels to worker nodes: + +![LLS Request and Streaming Flow Architecture](images/nvcf-lls-session.png) + +The architecture demonstrates a nine-step process: + +1. **Invoke function with user auth** - Browser application sends function invocation request with user authentication to the Partner Service +2. **Invoke function with API key** - Partner Service forwards the request to NVCF gRPC Proxy using an API key +3. **Validates the request** - NVCF gRPC Proxy validates the request with NVCF API +4. **Message added to queue** - Validated request is added to the message queue +5. **First available worker node picks message** - An available worker node retrieves the message from the queue +6. **WebSocket signaling channel** - Bidirectional WebSocket channel between Partner Service and NVCF gRPC Proxy for signaling +7. **Direct connection over QUIC** - Direct QUIC connection between NVCF gRPC Proxy and Worker Nodes +8. **WebSocket signaling channel** - Bidirectional WebSocket channel between Browser application and Partner Service +9. **Direct WebRTC Streaming channel** - Direct WebRTC streaming channel between Browser application and Streaming Proxy Container(STUN), which opens public routes to Worker Nodes + +### Self-Hosted Deployment Architecture + +The following diagram shows the detailed architecture of a self-hosted NVCF LLS deployment in AWS VPC with EKS: + +![Self-Hosted LLS AWS Architecture](images/lls-self-hosted-arch-streaming-proxy.png) + +Key components and data flows: + +**Control Plane Components (EKS):** + +- **Streaming Proxy Container** - Handles WebRTC streaming connections and opens routes to Worker Nodes +- **K8s Services** - Creates ClusterIP and NodePort services +- **KNS Service** - Fetches JWKs for authentication +- **NVCF Services** - Connects to customer-owned load balancers via signaling channels + +**GPU Nodes (EKS):** + +- **Worker Container** - Runs streaming applications (StreamSDK packaged) and receives streaming data + +**AWS VPC:** + +- **Network Load Balancer (Public Interface)** - Customer-owned, receives streaming and signaling data from browser applications + +**Data Flow Channels:** + +- **Red Lines (Streaming Channel)** - WebRTC streaming data path from Browser application through load balancers and Straming Proxy to Worker Containers +- **Blue Lines (Signaling Channel)** - WebSocket signaling data path connecting Browser application, Customer services, Load Balancers, NVCF Services, and Worker Containers + +## Prerequisites + +Before installing LLS, ensure: + +- Self-hosted NVCF control plane installed and running on a Kubernetes cluster (see [helmfile-installation](./helmfile-installation.md)) +- **kubectl** configured with access to your cluster +- **Helm v3.x** installed +- **NGC API Key** with access to the `nvcf-onprem` organization +- For AWS examples, AWS CLI configured with credentials that have permissions for EKS, ECR, EC2, ELBv2, and IAM operations + +**Configure AWS Credentials** + +For AWS examples, LLS installation requires AWS CLI access to create NLB resources, security groups, and manage EC2 instances. Verify your AWS credentials are configured: + +```bash +aws sts get-caller-identity +``` + +If this command fails, configure your AWS credentials before running the AWS examples using your organization's standard AWS authentication flow. + +**Set NGC API Key** + +Before proceeding, set your NGC API key as an environment variable: + +```bash +export NGC_API_KEY="nvapi-xxxxxxxxxxxxx" # Replace with your NGC API key +``` + + +**Configurable UDP Streaming Port** + +The UDP port for WebRTC streaming traffic defaults to 5004 and is exported as +`LLS_UDP_PORT` in [Step 2.1](./lls-installation.md). This variable is used throughout +the guide for the NLB security group rule, NLB listener, and Helm values configuration. + +To use a different port, change the `export LLS_UDP_PORT=` value in Step 2.1 and set +`env.lbPort` in your Helm values file (Step 3) to the same value. + + + +## Step 1: Mirror LLS Artifacts + +LLS requires specific container images and Helm charts to be available in your registry. + +### Required LLS Artifacts + +The following artifacts must be mirrored for LLS deployment: + +**Container Images:** + +- `streaming-proxy` - Streaming Proxy Container(RP Container) + +**Helm Charts:** + +- `gdn-streaming` - GDN Streaming Proxy Helm chart + +**Optional (for streaming workloads):** + +- Streaming application images (e.g., `usd-composer`) + +For detailed instructions on pulling these artifacts from NGC and pushing to your registry, see [self-hosted-image-mirroring](./image-mirroring.md). The [LLS-specific artifacts](./image-mirroring.md) section lists exactly what you need. + + +When mirroring to ECR, the repository path must match your Helm values configuration. Ensure your `registryName` in Step 4 includes the same repository path you used when mirroring the chart. + + + +## Step 2: Create Network Load Balancer + +LLS requires a Network Load Balancer (NLB) for streaming traffic. + +### Requirements + +- **Type**: Network Load Balancer +- **Scheme**: Internet-facing +- **Subnet**: Public subnet in the same availability zone as your GPU nodes + +### Security Group + +Create a security group with the following rules: + +**Inbound:** + +- Protocol: UDP, Port: 5004, Source: 0.0.0.0/0 (streaming traffic) + +**Outbound:** + +- Protocol: All, Destination: 0.0.0.0/0 + +### Target Group + +- **Target type**: Instance +- **Protocol**: UDP +- **Port**: 30504 +- **Health check protocol**: HTTP +- **Health check port**: 30800 +- **Health check path**: /v1/health + +### Listener + +- **Protocol**: UDP +- **Port**: 5004 +- **Default action**: Forward to target group + + +You must create the NLB in the same region **and availability zone** as your GPU node pool. + + + +### CLI Example + +The following commands create all required NLB resources. Copy and paste each section, replacing the placeholder values at the start. + +**2.1 Set Environment Variables** + +```bash +# Required: Set your cluster name and region +export CLUSTER_NAME="" +export REGION="" + +# UDP port for WebRTC streaming traffic (configurable — update if needed) +export LLS_UDP_PORT=5004 + +# Define resource names (based on cluster name) +export NLB_NAME="rp-public-${CLUSTER_NAME}" +export SG_NAME="${CLUSTER_NAME}-rp-sg" +export TG_NAME="${CLUSTER_NAME}-rp-tg" +``` + +**2.2 Get VPC and Public Subnet** + +```bash +# Get VPC ID from your EKS cluster +export VPC_ID=$(aws eks describe-cluster \ + --name "$CLUSTER_NAME" \ + --region "$REGION" \ + --query 'cluster.resourcesVpcConfig.vpcId' \ + --output text) +echo "VPC ID: $VPC_ID" + +# Set the availability zone where your GPU nodes are deployed +# This must match the availability zone used by the GPU node pool +export LLS_AZ="us-west-2a" # <-- Update to match your GPU node AZ + +# Get the public subnet in the LLS availability zone +export SUBNET=$(aws ec2 describe-subnets \ + --region "$REGION" \ + --filters \ + "Name=vpc-id,Values=${VPC_ID}" \ + "Name=map-public-ip-on-launch,Values=true" \ + "Name=availability-zone,Values=${LLS_AZ}" \ + --query 'Subnets[0].SubnetId' \ + --output text) +echo "Public Subnet ($LLS_AZ): $SUBNET" +``` + +**2.3 Create Security Group** + +```bash +# Create security group for NLB +export SG_ID=$(aws ec2 create-security-group \ + --group-name "$SG_NAME" \ + --description "Security group for LLS streaming NLB (UDP ${LLS_UDP_PORT})" \ + --vpc-id "$VPC_ID" \ + --region "$REGION" \ + --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=$SG_NAME},{Key=lls:cluster,Value=$CLUSTER_NAME},{Key=lls:managed-by,Value=customer},{Key=lls:purpose,Value=streaming-nlb},{Key=Project,Value=nvcf}]" \ + --query 'GroupId' \ + --output text) +echo "Security Group ID: $SG_ID" +``` + +**2.4 Configure Security Group Rules** + +```bash +# Add inbound rule: UDP ${LLS_UDP_PORT} from anywhere (streaming traffic) +aws ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --ip-permissions "IpProtocol=udp,FromPort=${LLS_UDP_PORT},ToPort=${LLS_UDP_PORT},IpRanges=[{CidrIp=0.0.0.0/0,Description='LLS streaming traffic'}]" \ + --region "$REGION" + +# Remove default egress rule and add explicit outbound rule +aws ec2 revoke-security-group-egress \ + --group-id "$SG_ID" \ + --ip-permissions 'IpProtocol=-1,IpRanges=[{CidrIp=0.0.0.0/0}]' \ + --region "$REGION" 2>/dev/null || true + +aws ec2 authorize-security-group-egress \ + --group-id "$SG_ID" \ + --ip-permissions 'IpProtocol=-1,IpRanges=[{CidrIp=0.0.0.0/0}]' \ + --region "$REGION" +``` + +**2.5 Create Network Load Balancer** + +Choose one of the following options: + +**Option A: Dynamic IP (simpler)** + +```bash +# Create NLB with AWS-assigned public IP (single AZ) +export NLB_ARN=$(aws elbv2 create-load-balancer \ + --name "$NLB_NAME" \ + --type network \ + --scheme internet-facing \ + --subnets "$SUBNET" \ + --security-groups "$SG_ID" \ + --region "$REGION" \ + --tags \ + Key=Name,Value="$NLB_NAME" \ + Key=lls:cluster,Value="$CLUSTER_NAME" \ + Key=lls:managed-by,Value="customer" \ + Key=lls:purpose,Value="streaming-traffic" \ + Key=Project,Value="nvcf" \ + --query 'LoadBalancers[0].LoadBalancerArn' \ + --output text) +echo "NLB ARN: $NLB_ARN" +``` + +**Option B: Static EIP (predictable IP)** + +Use this if you need a predictable ingress IP for firewall rules: + +```bash +# Allocate a static EIP +export EIP_ALLOC=$(aws ec2 allocate-address \ + --domain vpc \ + --region "$REGION" \ + --tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=${CLUSTER_NAME}-lls-eip},{Key=lls:cluster,Value=$CLUSTER_NAME}]" \ + --query 'AllocationId' \ + --output text) +echo "EIP Allocation ID: $EIP_ALLOC" + +# Create NLB with static EIP (single AZ) +export NLB_ARN=$(aws elbv2 create-load-balancer \ + --name "$NLB_NAME" \ + --type network \ + --scheme internet-facing \ + --subnet-mappings SubnetId="$SUBNET",AllocationId="$EIP_ALLOC" \ + --security-groups "$SG_ID" \ + --region "$REGION" \ + --tags \ + Key=Name,Value="$NLB_NAME" \ + Key=lls:cluster,Value="$CLUSTER_NAME" \ + Key=lls:managed-by,Value="customer" \ + Key=lls:purpose,Value="streaming-traffic" \ + Key=Project,Value="nvcf" \ + --query 'LoadBalancers[0].LoadBalancerArn' \ + --output text) +echo "NLB ARN: $NLB_ARN" +``` + +**2.6 Wait for NLB to Become Active** + +```bash +echo "Waiting for NLB to become active..." +aws elbv2 wait load-balancer-available \ + --load-balancer-arns "$NLB_ARN" \ + --region "$REGION" +echo "NLB is active" +``` + +**2.7 Create Target Group** + +```bash +export TG_ARN=$(aws elbv2 create-target-group \ + --name "$TG_NAME" \ + --protocol UDP \ + --port 30504 \ + --vpc-id "$VPC_ID" \ + --target-type ip \ + --health-check-enabled \ + --health-check-protocol HTTP \ + --health-check-port 30800 \ + --health-check-path /v1/health \ + --health-check-interval-seconds 30 \ + --health-check-timeout-seconds 6 \ + --healthy-threshold-count 5 \ + --unhealthy-threshold-count 2 \ + --matcher HttpCode=200-399 \ + --region "$REGION" \ + --tags \ + Key=Name,Value="$TG_NAME" \ + Key=lls:cluster,Value="$CLUSTER_NAME" \ + Key=lls:managed-by,Value="customer" \ + Key=lls:purpose,Value="streaming-targets" \ + Key=Project,Value="nvcf" \ + --query 'TargetGroups[0].TargetGroupArn' \ + --output text) +echo "Target Group ARN: $TG_ARN" +``` + +**2.8 Create Listener** + +```bash +export LISTENER_ARN=$(aws elbv2 create-listener \ + --load-balancer-arn "$NLB_ARN" \ + --protocol UDP \ + --port ${LLS_UDP_PORT} \ + --default-actions Type=forward,TargetGroupArn="$TG_ARN" \ + --region "$REGION" \ + --query 'Listeners[0].ListenerArn' \ + --output text) +echo "Listener ARN: $LISTENER_ARN" +``` + +**2.9 Verify and Save Outputs** + +```bash +# Get NLB DNS name +export NLB_DNS=$(aws elbv2 describe-load-balancers \ + --load-balancer-arns "$NLB_ARN" \ + --region "$REGION" \ + --query 'LoadBalancers[0].DNSName' \ + --output text) + +# Display summary +echo "" +echo "============================================" +echo "NLB Creation Complete" +echo "============================================" +echo "NLB Name: $NLB_NAME" +echo "NLB ARN: $NLB_ARN" +echo "NLB DNS: $NLB_DNS" +echo "Target Group ARN: $TG_ARN" +echo "Security Group: $SG_ID" +echo "============================================" +echo "" +echo "Save the Target Group ARN for Step 3 (Helm values):" +echo " $TG_ARN" +``` + + +**Save the Target Group ARN** — you will need it for the adding streaming proxy container nodes as targets to the target group [lls-step3-helm-values](./lls-installation.md). + + + +## Step 3: Create Helm Values File + +Create a `custom-values.yaml` file with your configuration. + +Use the load balancer DNS name and availability zone from Step 2: + +```yaml +env: + # UDP port for streaming traffic + lbPort: "5004" # UDP streaming port — must match LLS_UDP_PORT set in Step 2.1 + lbDns: ".elb..amazonaws.com" # load balancer dns name + +# Replica count +replicaCount: 1 + +# Availability Zone for streaming proxy container +availabilityZones: + enabled: true + zones: + - us-west-2a # Change to the actual availability zone of Load Balancer + +# Node Selection +nodeSelector: + node-type: compute + +# Image Configuration +image: + repository: /streaming-proxy + tag: 2.0.1 + pullPolicy: Always + +# Resource Limits +resources: + requests: + cpu: "5000m" + memory: "24Gi" + limits: + cpu: "5000m" + memory: "24Gi" +``` + + +- Replace all `` values with your actual configuration +- `env.lbPort` is the public UDP port for streaming traffic +- `env.lbDns` is the public load balancer dns name +- `availabilityZones.zones` is the availability zone for streaming proxy container which must be the same as the availability zone of the Load Balancer +- `nodeSelector.node-type` is the node type where streaming proxy container will be deployed +- `image.repository` is the streaming proxy container image repository +- `image.tag` is the streaming proxy container image tag +- `resources.requests.cpu` is the streaming proxy pod cpu request +- `resources.requests.memory` is the streaming proxy pod memory request +- `resources.limits.cpu` is the streaming proxy pod cpu limit +- `resources.limits.memory` is the streaming proxy pod memory limit + + + +## Step 4: Deploy LLS Operator + +**4.1 Deploy the LLS operator using Helm:** + +```bash +# Authenticate with ECR +aws ecr get-login-password --region $AWS_REGION | helm registry login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com + +# Set this to the repository prefix used when mirroring the gdn-streaming chart. +# Examples: nvcf, nvcf-self-hosted, or your cluster-specific prefix. +export LLS_HELM_REPOSITORY="" + +# Install, update to use latest artifact version and registry location +helm upgrade --install gdn-streaming \ + oci://$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$LLS_HELM_REPOSITORY/gdn-streaming \ + --version 2.0.1 \ + --namespace gdn-streaming \ + --create-namespace \ + -f custom-values.yaml +``` + +**4.2 Add RP Container Node IP and Port to Load Balancer's Target Group:** + +```bash +# Get the target group ARN from the Load balancer step and export as TG_ARN +for IP in $(kubectl get nodes -l node-type=compute -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'); do + aws elbv2 register-targets --target-group-arn "$TG_ARN" --targets Id="$IP",Port=30504 +done +``` + +**4.3 Add security rule to cluster node to accept streaming traffic on nodeport:** + +```bash + # # Get the security group from the Load balancer step and export as SG_ID + NODE_SG_ID=$(aws ec2 describe-security-groups \ + --filters "Name=group-name,Values=$CLUSTER_NAME-node-*" \ + --query 'SecurityGroups[0].GroupId' --output text) + + echo "Node Security Group ID: $NODE_SG_ID" + +# Add security rule to cluster node to accept streaming traffic on nodeport +aws ec2 authorize-security-group-ingress \ +--group-id "$NODE_SG_ID" \ +--protocol udp \ +--port 30504 \ +--source-group "$SG_ID" \ +--tag-specifications 'ResourceType=security-group-rule,Tags=[{Key=Name,Value=streaming-proxy-udp-30504}]' +``` + +## Step 6: Verify Deployment + +Verify the LLS components are running: + +1. **Check Streaming proxy pods** (should show `1/1 Running`): + + ```bash + kubectl get pods -n gdn-streaming + ``` + + Expected output: + + ```text + NAME READY STATUS RESTARTS AGE + streaming-proxy-xxxxx-xxxxx 1/1 Running 0 5m + ``` + +2. **Check container logs** (filter out verbose stack traces): + + ```bash + # show recent logs + kubectl logs -n gdn-streaming -l app=streaming-proxy --tail=50 + ``` + +## LLS Resource Summary + +The following table summarizes all AWS and Kubernetes resources created for LLS, who creates them, and how they are cleaned up: + +| Resource | Created By | Cleaned Up By | Notes | +| --- | --- | --- | --- | +| **Kubernetes Resources** - - - | | | | +| Namespace `gdn-streaming` | Helm install | Manual / Script | Contains all LLS K8s resources | +| Streaming Proxy Pods | Helm install | Manual / Script | Tagged `lls:managed-by=gdn-streaming` | +| **AWS Resources (Manual - Step 2)** - - - | | | | +| Network Load Balancer | Customer (CLI) | Cleanup script | Must delete before Target Group | +| NLB Target Group | Customer (CLI) | Cleanup script | Must delete after NLB | +| NLB Security Group | Customer (CLI) | Cleanup script | Must delete after NLB and node SG rule | +| Elastic IP (if static) | Customer (CLI) | Customer (CLI) | Optional, delete if created | +| **AWS Resources (Manual - Step 4)** - - - | | | | +| Node SG ingress rule (UDP 30504) | Customer (CLI) | Cleanup script | Must remove before NLB Security Group | + +### Step 1: Uninstall + +```bash +helm uninstall gdn-streaming -n gdn-streaming + +kubectl delete ns gdn-streaming +``` + +### Step 2: Delete Manual NLB Resources + +The NLB and its associated resources created in [lls-step2-nlb](./lls-installation.md) must be manually deleted. + +Save your NLB cleanup script as `lls-nlb-cleanup.sh`, then run: + +```bash +chmod +x lls-nlb-cleanup.sh +./lls-nlb-cleanup.sh + +# Example: +./lls-nlb-cleanup.sh my-cluster us-west-2 +``` + +The script performs the following steps in dependency order: + +1. Finds the NLB security group by tag +2. Removes the UDP 30504 ingress rule from the cluster node security group (added in Step 4.3 — must be removed before the NLB security group can be deleted) +3. Deletes the NLB and waits for deletion to complete +4. Deletes the target group (requires NLB to be fully deleted first) +5. Deletes the NLB security group (with retry logic) +6. Lists any Elastic IPs that may need manual release + + +**Safety:** The script identifies resources using the tags that were set during [lls-step2-nlb](./lls-installation.md). It will only delete resources with both `lls:cluster=` and `lls:managed-by=customer` tags, ensuring it won't accidentally delete other NLBs in your account. + +If the NLB security group deletion still fails with `DependencyViolation` after the script removes the node SG rule, the NLB may not have fully deleted yet. The script will automatically retry. If it still fails after several attempts, wait a few more minutes and run the script again. + + + +### Step 3: Final Verification + +Verify all LLS resources have been removed: + +```bash +export CLUSTER_NAME="" +export REGION="" + +echo "=== Kubernetes Resources ===" +kubectl get namespace gdn-streaming 2>/dev/null || echo "Namespace: deleted" +kubectl get pods -n gdn-streaming -o name 2>/dev/null | grep -q . && echo "Pods exist" || echo "Pods: deleted" + +echo "" +echo "=== AWS Resources (Manual NLB) ===" +aws elbv2 describe-load-balancers --region "$REGION" \ + --query "LoadBalancers[?contains(LoadBalancerName, '${CLUSTER_NAME}')].[LoadBalancerName]" \ + --output text | grep -q . && echo "NLB exists" || echo "NLB: deleted" +aws elbv2 describe-target-groups --region "$REGION" \ + --query "TargetGroups[?contains(TargetGroupName, '${CLUSTER_NAME}')].[TargetGroupName]" \ + --output text | grep -q . && echo "Target Groups exist" || echo "Target Groups: deleted" +aws ec2 describe-security-groups --region "$REGION" \ + --filters "Name=tag:lls:cluster,Values=${CLUSTER_NAME}" \ + --query 'SecurityGroups[*].GroupId' \ + --output text | grep -q . && echo "LLS Security Groups exist" || echo "LLS Security Groups: deleted" + +echo "" +echo "=== Cleanup verification complete ===" +``` + +## Resource Requirements + +### Streaming Proxy + +- **CPU**: 5000m (limit), 5000m (request) +- **Memory**: 24Gi (limit), 24Gi (request) +- Runs as a single pod + +### Network Load Balancer + +- **Type**: Network +- **Scheme**: Internet-facing +- **Protocol**: UDP +- **Port**: 5004 + +### Security Groups + +**NLB Security Group:** + +| Direction | Protocol | Port | Source/Destination | +| --- | --- | --- | --- | +| Inbound | UDP | 5004 | 0.0.0.0/0 (streaming clients) | +| Outbound | All | All | 0.0.0.0/0 | + +**Streaming Proxy Security Group:** + +| Direction | Protocol | Port | Source/Destination | +| --- | --- | --- | --- | +| Inbound | TCP | 30800 (NodePort) | NLB security group (health checks) | +| Inbound | UDP | 30504 (NodePort) | NLB security group (streaming traffic) | +| Inbound | UDP | 6004 | EKS Interal Node (streaming traffic) | +| Inbound | UDP | 3478 | EKS Internal Node (Stun request) | +| Inbound | TCP | 8000 | EKS Internal Node (API Request) | +| Outbound | All | All | 0.0.0.0/0 | + +## Liveness and Readiness Probes + +The streaming proxy pod has liveness and readiness probes configured to the /v1/health endpoint at port 8000. + +```json +{"failureThreshold":3,"httpGet":{"path":"/v1/health","port":8000,"scheme":"HTTP"},"initialDelaySeconds":15,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":5} +``` + +## Troubleshooting + +### Health check + +The health check is used to determine if the streaming proxy pod is healthy. The health check is configured to check the /v1/health endpoint at port 8000 of the streaming proxy pod. + +```bash +curl -X GET http://:8000/v1/health + +Expected output: + + { + "status": "OK" + } +``` diff --git a/docs/v0.6.1-rc/local-development.md b/docs/v0.6.1-rc/local-development.md new file mode 100644 index 000000000..5600bb8a0 --- /dev/null +++ b/docs/v0.6.1-rc/local-development.md @@ -0,0 +1,103 @@ +# Local Development (k3d) + +Run a full NVCF self-hosted stack on your laptop using +[k3d](https://k3d.io/) for development, testing, or demos. The canonical +local-k3d tooling lives at `tools/ncp-local-cluster/` in this repo. + + +This setup is for **local development only**. It uses fake GPUs, a single +Cassandra replica, and ephemeral storage. Do not use this for production +workloads. + + +Clone the public repository before using any local development flow: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Pick a flow + +Four canonical flows are documented, one per page. + +| Topology | Install path | Page | +|---|---|---| +| Single-cluster | CLI (`nvcf-cli self-hosted install`) | [Single-cluster CLI](./local-development/single-cluster-cli.md) | +| Single-cluster | Helmfile (`make install HELMFILE_ENV=...`) | [Single-cluster Helmfile](./local-development/single-cluster-helmfile.md) | +| Multi-cluster | CLI (`nvcf-cli self-hosted install`) | [Multi-cluster CLI](./local-development/multi-cluster-cli.md) | +| Multi-cluster | Helmfile (`make install HELMFILE_ENV=...`) | [Multi-cluster Helmfile](./local-development/multi-cluster-helmfile.md) | + +## Topologies + +**Single-cluster** brings up one k3d cluster named `ncp-local`. Control +plane and compute plane share the cluster. The fastest path for +function-lifecycle testing and basic install validation. + +**Multi-cluster** brings up `ncp-local-cp` plus `ncp-local-compute-N` +(N=1 by default). Control plane lives on the cp cluster; compute plane on +the compute cluster. Required when you need to exercise cross-cluster +registration, the OIDC/JWKS discovery flow, or the `.test` hostname +routing the cp Gateway exposes to compute workers. + +The two topologies are **mutually exclusive**: both claim host ports +8080/8443/4222. Destroy one before bringing up the other: + +```bash +# from single-cluster -> multi-cluster +make -C tools/ncp-local-cluster destroy + +# from multi-cluster -> single-cluster +make -C tools/ncp-local-cluster destroy-multicluster +``` + +## Install paths + +**CLI** drives the install through `nvcf-cli self-hosted install +--control-plane` (writes a control-plane profile YAML), `nvcf-cli init` +(mints the admin JWT against the live api-keys service), and +`nvcf-cli compute-plane register/install` (writes compute-plane values +and applies them). The CLI manages URL block selection (in-cluster vs +cross-cluster reachable) based on the kube contexts you pass. + +**Helmfile** drives the install through split Make targets: +`deploy/stacks/self-managed/Makefile` for control plane (`make template`, +`make install`) and `deploy/stacks/nvcf-compute-plane/Makefile` for +compute plane (`make register-cluster`, `make install`). The operator authors +topology-correct URLs into an environment file (different fixture per +topology) instead of relying on a CLI-managed profile. + +The two install paths intentionally diverge. See +`tests/bdd/AGENTS.md` (the "CLI vs Helmfile install paths" section) for +the rationale. + +## Prerequisites (common to all flows) + +- [Docker](https://www.docker.com/get-started) (running) +- [k3d](https://k3d.io/#installation) v5.x or later +- `kubectl` +- `helm` >= 3.12 +- `helmfile` >= 1.1.0, < 1.2.0 (Helmfile flows only) +- `helm-diff` plugin (Helmfile flows only): + `helm plugin install https://github.com/databus23/helm-diff` +- An **NGC API key** with access to the NVCF chart and image registry. +- `nvcf-cli` built from this repo: + + ```bash + go build -C src/clis/nvcf-cli -o ../../../nvcf-cli . + ``` + +Each flow page restates exactly the prerequisites that flow needs. + +## Cleanup + +The BDD suite ships destructive cleanup helpers reused for hand-driven +local dev: + +| Scope | Command | +|---|---| +| Stack-only (helm releases) on single-cluster | `tests/bdd/scripts/destroy-stack.sh single` | +| Stack-only on multi-cluster | `tests/bdd/scripts/destroy-stack.sh multi` | +| Whole single-cluster topology | `make -C tools/ncp-local-cluster destroy` | +| Whole multi-cluster topology | `make -C tools/ncp-local-cluster destroy-multicluster` | +| Every `ncp-local*` k3d cluster on the host | `make -C tools/ncp-local-cluster destroy-all-ncp-local` | diff --git a/docs/v0.6.1-rc/local-development/multi-cluster-cli.md b/docs/v0.6.1-rc/local-development/multi-cluster-cli.md new file mode 100644 index 000000000..e8a893be1 --- /dev/null +++ b/docs/v0.6.1-rc/local-development/multi-cluster-cli.md @@ -0,0 +1,331 @@ +# Multi-cluster Local Development with the CLI + +Install a NVCF self-hosted control plane on one local k3d cluster and a +separately registered compute plane on a second cluster, all using +`nvcf-cli`. Useful when you want to exercise the multi-cluster install and +registration paths before targeting real infrastructure. + + +This setup is for local development only. It uses fake GPUs, a single +Cassandra replica, and ephemeral storage. Do not use this for production +workloads. + + +Clone the public repository and run the remaining commands from its root: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Topology + +| k3d cluster | Role | kubectl context | +|---|---|---| +| `ncp-local-cp` | Control plane | `k3d-ncp-local-cp` | +| `ncp-local-compute-1` | Compute plane (first worker) | `k3d-ncp-local-compute-1` | + +The CLI writes `.localhost` URLs into the control-plane profile and +flows them through to the per-cluster register-values as-is. The NVCA +agent on the compute cluster uses those URLs at runtime to reach cp +services. The docker network shared between the two k3d clusters +(plus the install-time wiring `make build-and-deploy-multicluster` +sets up) is what makes the cross-cluster reach work. + +For users coming from the Helmfile install path: that flow is values-driven +and uses service-DNS endpoint values bridged by compute-cluster alias Services +and Endpoints. The CLI path writes a control-plane profile with localhost +URLs and does not depend on those Helmfile environment values. + +## Prerequisites + +Install the following tools: + +- [Docker](https://www.docker.com/get-started) (running) +- [k3d](https://k3d.io/#installation) v5.x or later +- `kubectl` +- `helm` >= 3.12 +- An NGC API key from [ngc.nvidia.com](https://ngc.nvidia.com) with + access to the NVCF chart and image registry. +- The NGC organization and team slugs that hold the chart and image + repository you have access to. `make build-and-deploy-multicluster` + reads these from `SAMPLE_NGC_ORG` / `SAMPLE_NGC_TEAM` during its + credential provider validation step; without them, the build target + fails and skips its final gateway-API setup. +- `nvcf-cli` built from this repo: + + ```bash + go build -C src/clis/nvcf-cli -o ../../../nvcf-cli . + ``` + +Export the env vars used by the cluster bootstrap and the install steps: + +```bash +export NGC_API_KEY="" +export SAMPLE_NGC_ORG="" +export SAMPLE_NGC_TEAM="" +``` + +## Step 1: Bring up the multi-cluster topology + +```bash +make -C tools/ncp-local-cluster build-and-deploy-multicluster +``` + +This creates `ncp-local-cp` plus `ncp-local-compute-1`, installs the fake +GPU operator and CSI SMB driver on the compute cluster, configures +compute-side control-plane service aliases, and validates Envoy Gateway on the +control-plane cluster. + + +The single-cluster (`ncp-local`) and multi-cluster +(`ncp-local-cp` + `ncp-local-compute-N`) topologies both claim host +ports 8080/8443/4222 and cannot coexist. The multi-cluster control plane also +claims host ports 9090 and 10081 for worker-facing API gRPC and the stack-owned +grpc-proxy TCP listener. If you already have the +single-cluster topology running: + +```bash +make -C tools/ncp-local-cluster destroy CLUSTER_NAME=ncp-local +``` + + + + +`build-and-deploy-multicluster` runs `setup-gateway-api`, +`check-gateway-api`, and `validate-gateway` on the control-plane cluster +as its final steps. If any earlier step fails (for example, credential +provider validation when `SAMPLE_NGC_ORG` / `SAMPLE_NGC_TEAM` are not +set), gateway setup is skipped. After fixing the underlying issue, +re-run just the gateway-API setup on the cp cluster: + +```bash +make -C tools/ncp-local-cluster setup-gateway-api CLUSTER_NAME=ncp-local-cp +make -C tools/ncp-local-cluster check-gateway-api CLUSTER_NAME=ncp-local-cp +``` + + + +## Step 2: Author the Helmfile environment files + +The CLI passes `--env local-bdd` to Helmfile when it renders each stack. +Create the environment files from the multi-cluster fixtures so Helmfile uses +the NGC organization and team that you can access: + +```bash +cp tests/bdd/fixtures/self-managed-local-bdd-multi.yaml \ + deploy/stacks/self-managed/environments/local-bdd.yaml +cp tests/bdd/fixtures/nvcf-compute-plane-local-bdd-multi.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml + +for file in \ + deploy/stacks/self-managed/environments/local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml; do + sed -i.bak \ + -e "s|REPLACE_WITH_SAMPLE_NGC_ORG|${SAMPLE_NGC_ORG}|g" \ + -e "s|REPLACE_WITH_SAMPLE_NGC_TEAM|${SAMPLE_NGC_TEAM}|g" \ + "$file" + rm "${file}.bak" +done +``` + +The control-plane profile and registration values produced later in this +workflow supply the compute-reachable endpoint values. + +## Step 3: Author the local secrets file + +```bash +cp deploy/stacks/self-managed/secrets/secrets.yaml.template \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml + +BASE64_CRED=$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n') +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${BASE64_CRED}|g" \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml +rm deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml.bak +``` + +## Step 4: Create the image pull secrets + +`nvcf-cli self-hosted install` renders helmfile manifests that reference +`imagePullSecrets: [{name: nvcr-pull-secret}]`. Create the secret in each +NVCF namespace on the control-plane cluster (`k3d-ncp-local-cp`) before +running install so pods can pull images from `nvcr.io`. Set the kubectl +context to the cp cluster first if you have not already: + +```bash +kubectl config use-context k3d-ncp-local-cp + +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system nvca-operator nvca-system nvcf-backend cert-manager; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +The loop is idempotent (uses `kubectl apply`). You must create the same pull +secret on the compute cluster before installing NVCA there. The explicit +`compute-plane install` command runs Helmfile and expects the referenced secret +to already exist. + +## Step 5: Install the control plane + +The install command needs both contexts so it knows which cluster gets each +plane: + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + --control-plane-context k3d-ncp-local-cp \ + --compute-plane-context k3d-ncp-local-compute-1 \ + --token DUMMY \ + install --control-plane \ + --cluster-name ncp-local-cp \ + --region us-west-1 \ + --nca-id nvcf-default +``` + + +`--token DUMMY` skips the install command's `check-cp` auth gate. The +install path itself never consumes the token. See the single-cluster CLI +page for the full explanation. + + +When this completes, a control-plane profile is written to +`deploy/stacks/self-managed/out/control-plane-profile.yaml`. It carries both +URL blocks: + +- `controlPlane.endpoints.inCluster.*` - resolves only inside the + control-plane cluster (for example `http://api.sis.svc.cluster.local:8080`). +- `controlPlane.endpoints.computeReachable.*` - the `.localhost` URLs + the CLI writes for cluster-external consumers. These flow through + to the register-values in Step 7 as-is; `compute-plane register` + does not rewrite them. + +`compute-plane register` picks the right block by inspecting +`--kube-context` against the cp context. + +## Step 6: Mint the admin JWT + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + init +``` + +## Step 7: Register the compute plane + +The `--kube-context` flag selects the compute cluster, which causes the CLI +to pick the `computeReachable` URL block from the profile and write those +URLs straight into the register-values file. The NVCA agent on the compute +cluster uses those URLs at runtime to reach cp services. + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + compute-plane register \ + --control-plane-profile deploy/stacks/self-managed/out/control-plane-profile.yaml \ + --cluster-name ncp-local-compute-1 \ + --kube-context k3d-ncp-local-compute-1 \ + --region us-west-1 \ + --output deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml +``` + +The output file's `selfManaged` block contains the `.localhost` +compute-reachable URLs, not the in-cluster service URLs. For the default local +topology, this is `http://sis.localhost:8080`, +`http://reval.localhost:8080`, and `nats://nats.localhost:4222`. + + +`nvcf-cli cluster register` (run internally during this step) auto-discovers +the target cluster's OIDC issuer and JWKS by running a probe Job in the +cluster identified by `--kube-context`. That identity is what ICMS validates +when the compute agent presents PSAT tokens at runtime. Always set +`--kube-context` to the COMPUTE cluster. + + +## Step 8: Install the compute plane + +Create the image pull secret in the compute namespaces first. These commands +target `k3d-ncp-local-compute-1` so the NVCA operator and agent pods can pull +from `nvcr.io` after Helmfile creates them: + +```bash +kubectl config use-context k3d-ncp-local-compute-1 + +for ns in nvca-operator nvca-system nvcf-backend; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + compute-plane install \ + --values deploy/stacks/nvcf-compute-plane/out/ncp-local-compute-1-register-values.yaml \ + --kube-context k3d-ncp-local-compute-1 \ + --cluster-name ncp-local-compute-1 +``` + +## Step 9: Verify + +The NVCFBackend resource is created on the compute cluster, not the +control-plane cluster. + +```bash +kubectl wait nvcfbackend ncp-local-compute-1 \ + -n nvca-operator \ + --context k3d-ncp-local-compute-1 \ + --for=jsonpath='{.status.agentStatus}'=healthy \ + --timeout=10m +``` + +Confirm the control-plane API is reachable (from the host, where +`api.localhost` resolves to 127.0.0.1): + +```bash +export NVCF_TOKEN=$(curl -s -X POST "http://api-keys.localhost:8080/v1/admin/keys" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])") + +curl -s "http://api.localhost:8080/v2/nvcf/functions" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" | python3 -m json.tool +``` + +## Teardown + +Remove the helm releases on both clusters but keep the topology (stack-only): + +```bash +tests/bdd/scripts/destroy-stack.sh multi +``` + +Or destroy the whole topology: + +```bash +make -C tools/ncp-local-cluster destroy-multicluster +``` diff --git a/docs/v0.6.1-rc/local-development/multi-cluster-helmfile.md b/docs/v0.6.1-rc/local-development/multi-cluster-helmfile.md new file mode 100644 index 000000000..6cffdc3d4 --- /dev/null +++ b/docs/v0.6.1-rc/local-development/multi-cluster-helmfile.md @@ -0,0 +1,272 @@ +# Multi-cluster Local Development with Helmfile + +Install the NVCF self-hosted control plane on one local k3d cluster and the +NVCA operator on a separately registered compute cluster, all driven by the +documented Helmfile workflow. + + +This setup is for local development only. It uses fake GPUs, a single +Cassandra replica, and ephemeral storage. Do not use this for production +workloads. + + +Clone the public repository and run the remaining commands from its root: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Topology + +| k3d cluster | Role | kubectl context | +|---|---|---| +| `ncp-local-cp` | Control plane | `k3d-ncp-local-cp` | +| `ncp-local-compute-1` | Compute plane (first worker) | `k3d-ncp-local-compute-1` | + +Cross-cluster traffic from the compute cluster uses the same service-DNS +hostnames as the local stack values. During topology bootstrap, +`tools/ncp-local-cluster/scripts/configure-control-plane-endpoints.sh` +creates compute-cluster alias Services and Endpoints for those names, and the +Endpoints point at the control-plane k3d load balancer: + +- `http://api.sis.svc.cluster.local:8080` +- `http://reval.nvcf.svc.cluster.local:8080` +- `nats://nats.nats-system.svc.cluster.local:4222` +- `http://ess-api.ess.svc.cluster.local:8080` +- `http://invocation.nvcf.svc.cluster.local:8080` +- `api.nvcf.svc.cluster.local:9090` + +## Prerequisites + +Install the following tools: + +- [Docker](https://www.docker.com/get-started) (running) +- [k3d](https://k3d.io/#installation) v5.x or later +- `kubectl` +- `helm` >= 3.12 +- `helmfile` >= 1.1.0, < 1.2.0 +- `helm-diff` plugin: `helm plugin install https://github.com/databus23/helm-diff` +- An NGC API key from [ngc.nvidia.com](https://ngc.nvidia.com) with + access to the NVCF chart and image registry. +- The NGC organization and team slugs that hold the chart/image repository + you have access to. +- `nvcf-cli` built from this repo. Steps 9 and 10 pass + `NVCF_CLI=$(pwd)/nvcf-cli` to the make targets, so the binary must + exist on disk before those steps run: + + ```bash + go build -C src/clis/nvcf-cli -o ../../../nvcf-cli . + ``` + +Export the env vars used below: + +```bash +export NGC_API_KEY="" +export SAMPLE_NGC_ORG="" +export SAMPLE_NGC_TEAM="" +``` + +## Step 1: Bring up the multi-cluster topology + +```bash +make -C tools/ncp-local-cluster build-and-deploy-multicluster +``` + + +The single-cluster (`ncp-local`) and multi-cluster +(`ncp-local-cp` + `ncp-local-compute-N`) topologies both claim host +ports 8080/8443/4222 and cannot coexist. The multi-cluster control plane also +claims host ports 9090 and 10081 for worker-facing API gRPC and the stack-owned +grpc-proxy TCP listener. If you already have the +single-cluster topology running: + +```bash +make -C tools/ncp-local-cluster destroy CLUSTER_NAME=ncp-local +``` + + + +## Step 2: Author the multi-cluster Helmfile environment files + +The values-driven Helmfile path has no control-plane profile; the operator +must author topology-correct URLs in the environment files. Use the +multi-cluster fixtures for this flow: + +```bash +cp tests/bdd/fixtures/self-managed-local-bdd-multi.yaml \ + deploy/stacks/self-managed/environments/local-bdd.yaml +cp tests/bdd/fixtures/nvcf-compute-plane-local-bdd-multi.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml +``` + +Substitute your NGC org and team: + +```bash +for file in \ + deploy/stacks/self-managed/environments/local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml; do + sed -i.bak \ + -e "s|REPLACE_WITH_SAMPLE_NGC_ORG|${SAMPLE_NGC_ORG}|g" \ + -e "s|REPLACE_WITH_SAMPLE_NGC_TEAM|${SAMPLE_NGC_TEAM}|g" \ + "$file" + rm "${file}.bak" +done +``` + + +The multi-cluster fixture intentionally uses service-DNS hostnames such as +`api.sis.svc.cluster.local` and `invocation.nvcf.svc.cluster.local`. In this +split topology those names resolve on the compute cluster because Step 1 +created alias Services and Endpoints that forward to the control-plane load +balancer. Do not replace these values with topology-specific `.test` +hostnames. + + +## Step 3: Author the secrets file + +```bash +cp deploy/stacks/self-managed/secrets/secrets.yaml.template \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml + +BASE64_CRED=$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n') +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${BASE64_CRED}|g" \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml +rm deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml.bak +``` + +## Step 4: Set kubectl context to the control-plane cluster + +Helmfile install runs against the ambient kubectl context. Switch to the +control-plane cluster so the install lands there: + +```bash +kubectl config use-context k3d-ncp-local-cp +``` + +## Step 5: Pre-create the image pull secret in NVCF namespaces (cp cluster) + +```bash +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system nvca-operator nvca-system nvcf-backend cert-manager; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +## Step 6: Install the control plane + +```bash +make -C deploy/stacks/self-managed install HELMFILE_ENV=local-bdd +``` + +The 18 standard helm releases land on `k3d-ncp-local-cp` (see the +single-cluster Helmfile page for the full release list). + +## Step 7: Switch kubectl context to the compute cluster (CRITICAL) + +```bash +kubectl config use-context k3d-ncp-local-compute-1 +``` + + +This single context switch is the most error-prone step in the multi-cluster +flow. The next step's `nvcf-cli cluster register` (run internally by +`make register-cluster`) auto-discovers the target cluster's OIDC issuer +and JWKS by running a probe Job in the CURRENT kubectl context. If you skip +the switch, the control-plane cluster's JWKS gets registered as the compute +cluster's identity, and the compute agent's PSAT tokens 401 against ICMS at +runtime. + + +## Step 8: Pre-create the image pull secret on the compute cluster + +```bash +for ns in nvca-operator nvca-system nvcf-backend; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +## Step 9: Register the compute cluster + +```bash +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME=ncp-local-compute-1 \ + NVCF_CLI=$(pwd)/nvcf-cli \ + NVCF_CLI_CONFIG=$(pwd)/tests/bdd/fixtures/nvcf-cli-local.yaml +``` + + +`make register-cluster` runs `nvcf-cli init` internally before +`cluster register`, so this flow does not need a separate `init` step. + + +The target writes the registration handoff file to +`deploy/stacks/nvcf-compute-plane/registration/ncp-local-compute-1-register-values.yaml`. +The `template`, `install`, and `apply` targets copy that file into +`deploy/stacks/nvcf-compute-plane/out/` before running Helmfile. + +## Step 10: Install the NVCA operator on the compute cluster + +```bash +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME=ncp-local-compute-1 \ + HELMFILE_ENV=local-bdd \ + NVCF_CLI=$(pwd)/nvcf-cli \ + NVCF_CLI_CONFIG=$(pwd)/tests/bdd/fixtures/nvcf-cli-local.yaml +``` + +This uses the compute-plane `local-bdd.yaml` file created in Step 2. + +## Step 11: Verify + +The NVCFBackend resource is created on the compute cluster, not the +control-plane cluster. Use the compute cluster context for all verification: + +```bash +kubectl rollout status deployment/nvca-operator \ + -n nvca-operator \ + --context k3d-ncp-local-compute-1 \ + --timeout=10m + +kubectl wait nvcfbackend ncp-local-compute-1 \ + -n nvca-operator \ + --context k3d-ncp-local-compute-1 \ + --for=jsonpath='{.status.agentStatus}'=healthy \ + --timeout=10m +``` + +Confirm the control-plane API is reachable (from the host): + +```bash +export NVCF_TOKEN=$(curl -s -X POST "http://api-keys.localhost:8080/v1/admin/keys" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])") + +curl -s "http://api.localhost:8080/v2/nvcf/functions" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" | python3 -m json.tool +``` + +## Teardown + +Remove the helm releases on both clusters but keep the topology (stack-only): + +```bash +tests/bdd/scripts/destroy-stack.sh multi +``` + +Or destroy the whole topology: + +```bash +make -C tools/ncp-local-cluster destroy-multicluster +``` diff --git a/docs/v0.6.1-rc/local-development/single-cluster-cli.md b/docs/v0.6.1-rc/local-development/single-cluster-cli.md new file mode 100644 index 000000000..45e5e2803 --- /dev/null +++ b/docs/v0.6.1-rc/local-development/single-cluster-cli.md @@ -0,0 +1,306 @@ +# Single-cluster Local Development with the CLI + +Install a complete NVCF self-hosted control plane and compute plane on a +single local k3d cluster using `nvcf-cli`. Useful for validating the install +and registration workflow before targeting real infrastructure. + + +This setup is for local development only. It uses fake GPUs, a single +Cassandra replica, and ephemeral storage. Do not use this for production +workloads. + + +Clone the public repository and run the remaining commands from its root: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Prerequisites + +Install the following tools: + +- [Docker](https://www.docker.com/get-started) (running) +- [k3d](https://k3d.io/#installation) v5.x or later +- `kubectl` +- `helm` >= 3.12 +- An NGC API key from [ngc.nvidia.com](https://ngc.nvidia.com) with + access to the NVCF chart and image registry. +- The NGC organization and team slugs that hold the chart and image + repository you have access to. `make build-and-deploy-cluster` reads + these from `SAMPLE_NGC_ORG` / `SAMPLE_NGC_TEAM` during its credential + provider validation step; without them, the build target fails and + skips its final gateway-API setup. +- `nvcf-cli` built from this repo: + + ```bash + go build -C src/clis/nvcf-cli -o ../../../nvcf-cli . + ``` + +Export the env vars used by the cluster bootstrap and the install steps: + +```bash +export NGC_API_KEY="" +export SAMPLE_NGC_ORG="" +export SAMPLE_NGC_TEAM="" +``` + +Create the Docker credential file that k3d mounts into the local nodes: + +```bash +mkdir -p tools/ncp-local-cluster/secrets +printf '{"auths":{"nvcr.io":{"auth":"%s"}}}\n' "$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n')" > tools/ncp-local-cluster/secrets/docker-config.json +``` + + +`docker-config.json` contains your NGC credential and is gitignored. Do not +commit or share this file. + + +## Step 1: Bring up the local k3d cluster + +The canonical single-cluster topology lives in `tools/ncp-local-cluster/`. + +```bash +make -C tools/ncp-local-cluster build-and-deploy-cluster +``` + +This creates a k3d cluster named `ncp-local`, installs the fake GPU operator, +the CSI SMB driver, Envoy Gateway, and validates the bootstrap end-to-end. + + +The single-cluster (`ncp-local`) and multi-cluster +(`ncp-local-cp` + `ncp-local-compute-N`) topologies both claim host +ports 8080/8443/4222 and cannot coexist. The multi-cluster control plane also +claims host ports 9090 and 10081 for worker-facing API gRPC and the stack-owned +grpc-proxy TCP listener. If you already have the +multi-cluster topology running, destroy it first: + +```bash +make -C tools/ncp-local-cluster destroy-multicluster +``` + + + + +`build-and-deploy-cluster` runs `setup-gateway-api`, `check-gateway-api`, +and `validate-gateway` as its final steps. If any earlier step fails (for +example, credential provider validation when `SAMPLE_NGC_ORG` / +`SAMPLE_NGC_TEAM` are not set), gateway setup is skipped. After fixing +the underlying issue, re-run just the gateway-API setup: + +```bash +make -C tools/ncp-local-cluster setup-gateway-api +make -C tools/ncp-local-cluster check-gateway-api +``` + + + +## Step 2: Author the Helmfile environment files + +The CLI passes `--env local-bdd` to Helmfile when it renders each stack. +Create the environment files from the single-cluster fixtures so Helmfile uses +the NGC organization and team that you can access: + +```bash +cp tests/bdd/fixtures/self-managed-local-bdd.yaml \ + deploy/stacks/self-managed/environments/local-bdd.yaml +cp tests/bdd/fixtures/nvcf-compute-plane-local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml + +for file in \ + deploy/stacks/self-managed/environments/local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml; do + sed -i.bak \ + -e "s|REPLACE_WITH_SAMPLE_NGC_ORG|${SAMPLE_NGC_ORG}|g" \ + -e "s|REPLACE_WITH_SAMPLE_NGC_TEAM|${SAMPLE_NGC_TEAM}|g" \ + "$file" + rm "${file}.bak" +done +``` + +## Step 3: Author the local secrets file + +`nvcf-cli self-hosted install --env local-bdd` reads NGC credentials from the +control-plane stack: + +- `deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml` (control plane) + +Author the file from its canonical template: + +```bash +cp deploy/stacks/self-managed/secrets/secrets.yaml.template \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml +``` + +Generate the base64 NGC dockerconfig credential and substitute it into the +file: + +```bash +BASE64_CRED=$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n') +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${BASE64_CRED}|g" \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml +rm deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml.bak +``` + + +`local-bdd-secrets.yaml` is gitignored. Keep your NGC key out of the working +tree. + + +## Step 4: Create the image pull secrets + +`nvcf-cli self-hosted install` renders helmfile manifests that reference +`imagePullSecrets: [{name: nvcr-pull-secret}]`. Create the secret in each +NVCF namespace before running install so pods can pull images from `nvcr.io`. +The loop is idempotent (uses `kubectl apply`): + +```bash +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system nvca-operator nvca-system nvcf-backend cert-manager; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +## Step 5: Install the control plane + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + --token DUMMY \ + install --control-plane \ + --cluster-name ncp-local \ + --region us-west-1 \ + --nca-id nvcf-default +``` + + +`--token DUMMY` is a gate-bypass, not a real credential. The install command's +`check-cp` phase normally requires a JWT, but the api-keys service that mints +that JWT does not exist yet on the first invocation. Pass `--token DUMMY` to +skip the gate; the install path itself never reads the token. + + +When this completes, a control-plane profile is written to +`deploy/stacks/self-managed/out/control-plane-profile.yaml`. + +## Step 6: Mint the admin JWT + +Now that the api-keys service is reachable, `nvcf-cli init` can mint a real +admin JWT: + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + init +``` + +The token is written to `~/.nvcf-cli.nvcf-cli-local.state`. Subsequent commands +read it from there; the token never appears in argv or per-command logs. + +## Step 7: Register the compute plane + +In single-cluster topology, compute and control plane share the same k3d +cluster (`ncp-local`). + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + compute-plane register \ + --control-plane-profile deploy/stacks/self-managed/out/control-plane-profile.yaml \ + --cluster-name ncp-local \ + --kube-context k3d-ncp-local \ + --region us-west-1 \ + --output deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml +``` + +This emits `out/ncp-local-register-values.yaml`. Because compute and control +plane share a cluster, the in-cluster service URLs (for example +`http://api.sis.svc.cluster.local:8080`) are directly reachable and are +selected automatically. + +## Step 8: Install the compute plane + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + compute-plane install \ + --values deploy/stacks/nvcf-compute-plane/out/ncp-local-register-values.yaml \ + --kube-context k3d-ncp-local \ + --cluster-name ncp-local +``` + +## Step 9: Verify + +Wait for the NVCA backend to become healthy: + +```bash +kubectl wait nvcfbackend ncp-local \ + -n nvca-operator \ + --for=jsonpath='{.status.agentStatus}'=healthy \ + --timeout=10m +``` + +Confirm the control-plane API is reachable: + +```bash +export NVCF_TOKEN=$(curl -s -X POST "http://api-keys.localhost:8080/v1/admin/keys" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])") + +curl -s "http://api.localhost:8080/v2/nvcf/functions" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" | python3 -m json.tool +# Expected: {"functions": []} +``` + +## Optional: Validate the profile + +The control-plane profile can be re-validated against the live cluster: + +```bash +./nvcf-cli \ + --config tests/bdd/fixtures/nvcf-cli-local.yaml \ + self-hosted \ + --control-plane-stack deploy/stacks/self-managed \ + --compute-plane-stack deploy/stacks/nvcf-compute-plane \ + --env local-bdd \ + --plain \ + control-plane profile validate \ + --file deploy/stacks/self-managed/out/control-plane-profile.yaml \ + --require in-cluster +``` + +## Teardown + +Remove the helm releases but keep the cluster (stack-only): + +```bash +tests/bdd/scripts/destroy-stack.sh single +``` + +Or destroy the whole cluster: + +```bash +make -C tools/ncp-local-cluster destroy +``` diff --git a/docs/v0.6.1-rc/local-development/single-cluster-helmfile.md b/docs/v0.6.1-rc/local-development/single-cluster-helmfile.md new file mode 100644 index 000000000..91691667f --- /dev/null +++ b/docs/v0.6.1-rc/local-development/single-cluster-helmfile.md @@ -0,0 +1,239 @@ +# Single-cluster Local Development with Helmfile + +Install the NVCF self-hosted control plane and the NVCA operator on a single +local k3d cluster using the documented Helmfile workflow. Useful when you want +to drive the install through the same Make targets used in production. + + +This setup is for local development only. It uses fake GPUs, a single +Cassandra replica, and ephemeral storage. Do not use this for production +workloads. + + +Clone the public repository and run the remaining commands from its root: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Prerequisites + +Install the following tools: + +- [Docker](https://www.docker.com/get-started) (running) +- [k3d](https://k3d.io/#installation) v5.x or later +- `kubectl` +- `helm` >= 3.12 +- `helmfile` >= 1.1.0, < 1.2.0 +- `helm-diff` plugin: `helm plugin install https://github.com/databus23/helm-diff` +- An NGC API key from [ngc.nvidia.com](https://ngc.nvidia.com) with + access to the NVCF chart and image registry. +- The NGC organization and team slugs that hold the chart/image repository + you have access to. +- `nvcf-cli` built from this repo. Steps 7 and 8 pass + `NVCF_CLI=$(pwd)/nvcf-cli` to the make targets, so the binary must + exist on disk before those steps run: + + ```bash + go build -C src/clis/nvcf-cli -o ../../../nvcf-cli . + ``` + +Export the env vars used below: + +```bash +export NGC_API_KEY="" +export SAMPLE_NGC_ORG="" +export SAMPLE_NGC_TEAM="" +``` + +## Step 1: Bring up the local k3d cluster + +```bash +make -C tools/ncp-local-cluster build-and-deploy-cluster +``` + + +The single-cluster (`ncp-local`) and multi-cluster +(`ncp-local-cp` + `ncp-local-compute-N`) topologies both claim host +ports 8080/8443/4222 and cannot coexist. The multi-cluster control plane also +claims host ports 9090 and 10081 for worker-facing API gRPC and the stack-owned +grpc-proxy TCP listener. If you already have the +multi-cluster topology running: + +```bash +make -C tools/ncp-local-cluster destroy-multicluster +``` + + + +## Step 2: Author the Helmfile environment file + +The single-cluster fixture +`tests/bdd/fixtures/self-managed-local-bdd.yaml` is the canonical +starting point. Its `nvcaOperator.selfManaged.*` URLs use in-cluster DNS +(for example `http://api.sis.svc.cluster.local:8080`), which is correct here +because compute and control plane share the cluster. + +```bash +cp tests/bdd/fixtures/self-managed-local-bdd.yaml \ + deploy/stacks/self-managed/environments/local-bdd.yaml +cp tests/bdd/fixtures/nvcf-compute-plane-local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml +``` + +Substitute your NGC org and team in for the placeholders: + +```bash +for file in \ + deploy/stacks/self-managed/environments/local-bdd.yaml \ + deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml; do + sed -i.bak \ + -e "s|REPLACE_WITH_SAMPLE_NGC_ORG|${SAMPLE_NGC_ORG}|g" \ + -e "s|REPLACE_WITH_SAMPLE_NGC_TEAM|${SAMPLE_NGC_TEAM}|g" \ + "$file" + rm "${file}.bak" +done +``` + +Across the two files, the substitutions update `global.helm.sources.repository` +and `global.image.repository`. The control-plane file also updates +`api.env.NVCF_SIDECARS_LLM_ROUTER_CLIENT_IMAGE`. Set +`global.imagePullSecrets[0].name` if your secret name differs from +`nvcr-pull-secret`. The compute-plane environment provides the NVCA endpoint +settings consumed by its Helmfile. + +## Step 3: Author the secrets file + +```bash +cp deploy/stacks/self-managed/secrets/secrets.yaml.template \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml + +BASE64_CRED=$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n') +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${BASE64_CRED}|g" \ + deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml +rm deploy/stacks/self-managed/secrets/local-bdd-secrets.yaml.bak +``` + + +The secrets file is gitignored. Keep your NGC key out of the working tree. + + +## Step 4: Pre-create the image pull secret in NVCF namespaces + +```bash +for ns in cassandra-system nats-system nvcf api-keys ess sis \ + vault-system nvca-operator nvca-system nvcf-backend cert-manager; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="${NGC_API_KEY}" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done +``` + +## Step 5: (Optional) Validate the rendered manifests + +```bash +make -C deploy/stacks/self-managed template HELMFILE_ENV=local-bdd +``` + +The command should exit 0 and its output must not contain `Error:`. + +## Step 6: Install the control plane + +```bash +make -C deploy/stacks/self-managed install HELMFILE_ENV=local-bdd +``` + +When this succeeds, the following helm releases are deployed: + +| Release | Namespace | +|---|---| +| `nats` | `nats-system` | +| `cert-manager` | `cert-manager` | +| `openbao-server` | `vault-system` | +| `cassandra` | `cassandra-system` | +| `api-keys` | `api-keys` | +| `sis` | `sis` | +| `api` | `nvcf` | +| `nvct-api` | `nvcf` | +| `invocation-service` | `nvcf` | +| `grpc-proxy` | `nvcf` | +| `ess-api` | `ess` | +| `notary-service` | `nvcf` | +| `admin-issuer-proxy` | `api-keys` | +| `reval` | `nvcf` | +| `nats-auth-callout-service` | `nats-system` | +| `ingress` | `envoy-gateway-system` | +| `llm-request-router` | `nvcf` | +| `llm-api-gateway` | `nvcf` | + +## Step 7: Register the cluster + +```bash +make -C deploy/stacks/nvcf-compute-plane register-cluster \ + CLUSTER_NAME=ncp-local \ + NVCF_CLI=$(pwd)/nvcf-cli \ + NVCF_CLI_CONFIG=$(pwd)/tests/bdd/fixtures/nvcf-cli-local.yaml +``` + + +`make register-cluster` runs `nvcf-cli init` internally before +`cluster register`, so the Helmfile flow does not need a separate `init` +step (unlike the CLI flow). + + +The target writes the registration handoff file to +`deploy/stacks/nvcf-compute-plane/registration/ncp-local-register-values.yaml`. +The `template`, `install`, and `apply` targets copy that file into +`deploy/stacks/nvcf-compute-plane/out/` before running Helmfile. + +## Step 8: Install the NVCA operator + +```bash +make -C deploy/stacks/nvcf-compute-plane install \ + CLUSTER_NAME=ncp-local \ + HELMFILE_ENV=local-bdd \ + NVCF_CLI=$(pwd)/nvcf-cli \ + NVCF_CLI_CONFIG=$(pwd)/tests/bdd/fixtures/nvcf-cli-local.yaml +``` + +## Step 9: Verify + +Wait for the NVCA operator to roll out and the backend to become healthy: + +```bash +kubectl rollout status deployment/nvca-operator -n nvca-operator --timeout=10m + +kubectl wait nvcfbackend ncp-local \ + -n nvca-operator \ + --for=jsonpath='{.status.agentStatus}'=healthy \ + --timeout=10m +``` + +Confirm the control-plane API is reachable: + +```bash +export NVCF_TOKEN=$(curl -s -X POST "http://api-keys.localhost:8080/v1/admin/keys" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])") + +curl -s "http://api.localhost:8080/v2/nvcf/functions" \ + -H "Authorization: Bearer ${NVCF_TOKEN}" | python3 -m json.tool +``` + +## Teardown + +Remove the helm releases but keep the cluster (stack-only): + +```bash +tests/bdd/scripts/destroy-stack.sh single +``` + +Or destroy the whole cluster: + +```bash +make -C tools/ncp-local-cluster destroy +``` diff --git a/docs/v0.6.1-rc/manifest.md b/docs/v0.6.1-rc/manifest.md new file mode 100644 index 000000000..d79536d24 --- /dev/null +++ b/docs/v0.6.1-rc/manifest.md @@ -0,0 +1,170 @@ +# Artifact Manifest + +This section provides a comprehensive list of all components required for NVIDIA Cloud Functions (NVCF) Self-Hosted deployment for basic inference. Additional components are needed for Low Latency Streaming (Simulation). + +## Artifacts Overview + +The following inventories list the artifacts for an inference-only self-hosted +NVCF deployment. Artifacts are grouped by deployment plane and type. + + +Artifact version compatibility + +Newer artifact versions might be available. NVCF self-managed stack and +compute-plane stack releases are QA-qualified as umbrella releases with the +specific versions shown on this page. Use these versions together. NVIDIA +cannot guarantee compatibility when you substitute other artifact versions. + + + +## Prepare Helm charts for Helmfile + +The self-managed Helmfile bundles currently expect NVCF charts in an OCI +registry. Chart distributions that start with `https://` are Helm repository +charts. Before deploying with Helmfile, copy each required repository chart +version into the OCI registry configured by `global.helm.sources` in the +Helmfile bundles. + +The following example copies one public NVCF chart into an OCI registry: + +```bash +export CHART_NAME="helm-nvcf-api" +export CHART_VERSION="1.22.5" +export TARGET_REGISTRY="" +export TARGET_REPOSITORY="" + +helm repo add nvcf https://helm.ngc.nvidia.com/nvidia/nvcf +helm repo update +helm pull "nvcf/${CHART_NAME}" --version "${CHART_VERSION}" + +helm registry login "${TARGET_REGISTRY}" +helm push "${CHART_NAME}-${CHART_VERSION}.tgz" \ + "oci://${TARGET_REGISTRY}/${TARGET_REPOSITORY}" +``` + +Repeat this process for every required chart with an `https://` distribution. +Copy required charts with an `nvcr.io` distribution into the same target +repository so Helmfile can resolve all NVCF charts from one source. Configure +the stack environment with that OCI location: + +```yaml +global: + helm: + sources: + registry: "" + repository: "" +``` + +See [Image Mirroring](./image-mirroring.md) for additional registry examples. + + +Some supporting components such as the GPU Operator, OpenBao, NATS, Cassandra, etc. can alternatively be pulled directly from public NGC Catalog or other public opensource repositories if desired. + + + +The following tables list the complete artifact inventory. + +{/* docs-version-sync:BEGIN manifest-artifact-registry-paths */} + +### Control plane Helm charts + +| Artifact | Version | Required | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | --- | +| `helm-admin-token-issuer-proxy` | `1.4.3` | Optional | Deploys the admin token issuer proxy used by the reference architecture. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-admin-token-issuer-proxy:1.4.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/admin-token-issuer-proxy) | +| `helm-nvcf-api` | `1.23.6` | Required | Deploys the NVCF API service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-api:1.23.6` | | +| `helm-nvcf-api-keys` | `1.6.0` | Required | Deploys the API key management service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-api-keys:1.6.0` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/api-keys-colocated) | +| `helm-nvcf-cassandra` | `0.15.5` | Required | Deploys Cassandra and its initialization jobs. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-cassandra:0.15.5` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/cassandra) / [Upstream](https://github.com/bitnami/charts/tree/main/bitnami/cassandra) | +| `helm-nvcf-cert-manager` | `0.1.0` | Required | Deploys the NVCF cert-manager configuration. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-cert-manager:0.1.0` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/cert-manager) / [Upstream](https://github.com/cert-manager/cert-manager) | +| `helm-nvcf-ess-api` | `1.6.1` | Required | Deploys the Encrypted Secrets Service API. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-ess-api:1.6.1` | | +| `helm-nvcf-grpc-proxy` | `1.6.7` | Required | Deploys the gRPC proxy service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-grpc-proxy:1.6.7` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/grpc-proxy) | +| `helm-nvcf-invocation-service` | `1.5.5` | Required | Deploys the HTTP invocation service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-invocation-service:1.5.5` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/http-invocation) | +| `helm-nvcf-llm-api-gateway` | `1.2.0` | Optional | Deploys the OpenAI-compatible LLM API gateway. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-llm-api-gateway:1.2.0` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/llm-api-gateway) | +| `helm-nvcf-llm-request-router` | `1.6.3` | Optional | Deploys the LLM request router. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-llm-request-router:1.6.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/llm-request-router) | +| `helm-nvcf-nats` | `0.7.1` | Required | Deploys NATS messaging for the control plane. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-nats:0.7.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/nats) / [Upstream](https://github.com/nats-io/k8s) | +| `helm-nvcf-nats-auth-callout-service` | `1.1.3` | Required | Deploys the NATS authorization callout service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-nats-auth-callout-service:1.1.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/nats-auth-callout) | +| `helm-nvcf-notary-service` | `1.4.2` | Required | Deploys the notary service for signing and validation. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-notary-service:1.4.2` | | +| `helm-nvcf-nvct-api` | `1.4.3` | Required | Deploys the NVCF tenant API service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-nvct-api:1.4.3` | | +| `helm-nvcf-openbao-server` | `0.30.23` | Required | Deploys OpenBao secret management. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-openbao-server:0.30.23` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/openbao) / [Upstream](https://github.com/openbao/openbao-helm) | +| `helm-nvcf-rate-limiter` | `1.0.3` | Optional | Deploys request rate limiting for supported invocation paths. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-rate-limiter:1.0.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/ratelimiter) | +| `helm-nvcf-sis` | `1.18.3` | Required | Deploys the Spot Instance Service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-sis:1.18.3` | | +| `helm-nvcf-state-metrics` | `1.0.1` | Optional | Deploys NVCF state metrics for observability. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-state-metrics:1.0.1` | | +| `helm-nvcf-vanity-gateway` | `0.1.0-nvcf-10204.1` | Optional | Deploys the optional vanity hostname gateway. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvcf-vanity-gateway:0.1.0-nvcf-10204.1` | | +| `helm-reval` | `1.3.8` | Required | Deploys the function revalidation service. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-reval:1.3.8` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/helm-reval) | +| `nvcf-example-dashboards` | `1.6.0` | Optional | Deploys example Grafana dashboards for NVCF telemetry. | `https://helm.ngc.nvidia.com/nvidia/nvcf/nvcf-example-dashboards:1.6.0` | | +| `nvcf-gateway-routes` | `1.14.0` | Optional | Deploys Gateway API routes for the reference architecture. | `https://helm.ngc.nvidia.com/nvidia/nvcf/nvcf-gateway-routes:1.14.0` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/gateway-routes) | +| `nvcf-observability-reference-stack` | `1.10.0` | Optional | Deploys a reference observability backend for evaluation. | `https://helm.ngc.nvidia.com/nvidia/nvcf/nvcf-observability-reference-stack:1.10.0` | | + +### Control plane services and images + +| Artifact | Version | Required | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | --- | +| `admin-token-issuer-proxy` | `1.0.2` | Optional | Proxies admin token requests for the reference architecture. | `nvcr.io/nvidia/nvcf/admin-token-issuer-proxy:1.0.2` | | +| `alpine-k8s` | `1.36.1` | Required | Provides Kubernetes command-line utilities for deployment jobs. | `nvcr.io/0833294136851237/selfhosted-ga/alpine-k8s:1.36.1-ea` | | +| `cert-manager-cainjector` | `v1.20.2` | Required | Injects certificate authority data into Kubernetes resources. | `nvcr.io/nvidia/nvcf/cert-manager-cainjector:v1.20.2` | [Upstream](https://github.com/cert-manager/cert-manager) | +| `cert-manager-controller` | `v1.20.2` | Required | Reconciles certificates and issuers for the control plane. | `nvcr.io/nvidia/nvcf/cert-manager-controller:v1.20.2` | [Upstream](https://github.com/cert-manager/cert-manager) | +| `cert-manager-startupapicheck` | `v1.20.2` | Required | Verifies that the cert-manager API is ready. | `nvcr.io/nvidia/nvcf/cert-manager-startupapicheck:v1.20.2` | [Upstream](https://github.com/cert-manager/cert-manager) | +| `cert-manager-webhook` | `v1.20.2` | Required | Validates and converts cert-manager API resources. | `nvcr.io/nvidia/nvcf/cert-manager-webhook:v1.20.2` | [Upstream](https://github.com/cert-manager/cert-manager) | +| `ess-api` | `v0.57.26` | Required | Provides encrypted application secrets to NVCF workloads. | `nvcr.io/nvidia/nvcf/ess-api:v0.57.26` | | +| `llm-api-gateway` | `0.8.3` | Optional | Exposes OpenAI-compatible APIs for LLM functions. | `nvcr.io/0833294136851237/selfhosted-ga/llm-api-gateway:0.8.3-ea` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/invocation-plane-services/llm-api-gateway) | +| `llm-request-router` | `0.3.0` | Optional | Routes LLM requests to eligible worker instances. | `nvcr.io/0833294136851237/selfhosted-ga/stargate:0.3.0-ea` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/libraries/rust/stargate) | +| `nats-box` | `0.19.7-nonroot` | Required | Provides NATS administration and diagnostic utilities. | `nvcr.io/nvidia/nvcf/nats-box:0.19.7-nonroot` | [Upstream](https://github.com/nats-io/nats-box) | +| `nats-server` | `2.11.17-alpine3.22` | Required | Provides messaging for function deployment and invocation. | `nvcr.io/nvidia/nvcf/nats-server:2.11.17-alpine3.22` | [Upstream](https://github.com/nats-io/nats-server) | +| `nats-server-config-reloader` | `0.23.0` | Required | Reloads NATS server configuration when mounted settings change. | `nvcr.io/0833294136851237/selfhosted-ga/nats-server-config-reloader:0.23.0-ea` | [Upstream](https://github.com/nats-io/k8s) | +| `notary-service` | `1.8.1` | Required | Signs and validates functions and cluster nodes. | `nvcr.io/nvidia/nvcf/notary-service:1.8.1` | | +| `nvcf-api-keys-service` | `1.5.0` | Required | Creates and manages NVCF API keys. | `nvcr.io/nvidia/nvcf/nvcf-api-keys-service:1.5.0` | | +| `nvcf-grpc-proxy` | `1.29.1` | Required | Proxies bidirectional gRPC traffic between the control and compute planes. | `nvcr.io/nvidia/nvcf/nvcf-grpc-proxy:1.29.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/invocation-plane-services/grpc-proxy) | +| `nvcf-invocation-service` | `0.8.5` | Required | Routes stateless HTTP function invocation requests. | `nvcr.io/nvidia/nvcf/nvcf-invocation-service:0.8.5` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/invocation-plane-services/http-invocation) | +| `nvcf-nats-auth-callout-service` | `0.5.10` | Required | Authorizes NATS clients for NVCF services and workloads. | `nvcr.io/nvidia/nvcf/nvcf-nats-auth-callout-service:0.5.10` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/control-plane-services/nats-auth-callout) | +| `nvcf-openbao` | `2.5.4-nv-1.3.0` | Required | Stores and manages control-plane secrets. | `nvcr.io/0833294136851237/selfhosted-ga/nvcf-openbao:2.5.4-nv-1.3.0-ea` | [Upstream](https://github.com/openbao/openbao) | +| `nvcf-openbao-migrations` | `0.16.1` | Required | Applies the OpenBao configuration required by NVCF. | `nvcr.io/nvidia/nvcf/nvcf-openbao-migrations:0.16.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/migrations/openbao) | +| `nvcf-service-oss` | `1.9.0-hotfix.1` | Required | Provides the primary NVCF control-plane API. | `nvcr.io/nvidia/nvcf/nvcf-service-oss:1.9.0-hotfix.1` | | +| `nvct-service-oss` | `1.5.9-hotfix.1` | Required | Provides tenant-scoped NVCF control-plane operations. | `nvcr.io/nvidia/nvcf/nvct-service-oss:1.5.9-hotfix.1` | | +| `oss-vault-k8s` | `1.7.4` | Required | Integrates Kubernetes workloads with OpenBao secrets. | `nvcr.io/nvidia/nvcf/oss-vault-k8s:1.7.4` | | +| `reval-server` | `0.2.2` | Required | Revalidates function state in the background. | `nvcr.io/nvidia/nvcf/reval-server:0.2.2` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/control-plane-services/helm-reval) | +| `spot` | `1.563.1-hotfix.1` | Required | Manages deployments, clusters, and function instances. | `nvcr.io/nvidia/nvcf/spot:1.563.1-hotfix.1` | | + +### Compute plane Helm charts + +| Artifact | Version | Required | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | --- | +| `csi-driver-smb` | `supported` | Optional | Provides SMB persistent volumes for supported deployments. | `https://raw.githubusercontent.com/kubernetes-csi/csi-driver-smb/master/charts` | [Upstream](https://github.com/kubernetes-csi/csi-driver-smb) | +| `ebs-csi-driver` | `supported` | Optional | Provides Amazon EBS persistent volumes for EKS clusters. | `https://kubernetes-sigs.github.io/aws-ebs-csi-driver` | [Upstream](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) | +| `gpu-operator` | `supported` | Required | Manages NVIDIA GPU software on Kubernetes nodes. | `https://helm.ngc.nvidia.com/nvidia` | [Upstream](https://github.com/NVIDIA/gpu-operator) | +| `helm-nvca-operator` | `1.12.7` | Required | Deploys the NVCA operator and compute-plane integration. | `https://helm.ngc.nvidia.com/nvidia/nvcf/helm-nvca-operator:1.12.7` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/nvca-operator) | +| `nvcf-container-cache` | `0.25.22` | Optional | Deploys container image caching on GPU cluster nodes. | `https://helm.ngc.nvidia.com/nvidia/nvcf/nvcf-container-cache:0.25.22` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/container-cache) | + +### Compute plane services and images + +| Artifact | Version | Required | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | --- | +| `ess-agent` | `1.3.1` | Required | Injects encrypted application secrets into function workloads. | `nvcr.io/nvidia/nvcf/ess-agent:1.3.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/ess-agent) | +| `gpu-operator-validator` | `supported` | Required | Validates GPU Operator components on GPU nodes. | `https://catalog.ngc.nvidia.com/orgs/nvidia/teams/cloud-native/containers/gpu-operator-validator` | [Upstream](https://github.com/NVIDIA/gpu-operator) | +| `k8s-device-plugin` | `supported` | Required | Advertises NVIDIA GPU resources to Kubernetes. | `https://catalog.ngc.nvidia.com/orgs/nvidia/teams/k8s/containers/device-plugin` | [Upstream](https://github.com/NVIDIA/k8s-device-plugin) | +| `nvca` | `3.0.3` | Required | Registers GPU clusters and orchestrates deployments in-cluster. | `nvcr.io/nvidia/nvcf/nvca:3.0.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/nvca) | +| `nvca-operator` | `3.0.3` | Required | Reconciles NVCA resources and compute-plane configuration. | `nvcr.io/nvidia/nvcf/nvca-operator:3.0.3` | | +| `nvcf-container-cache` | `v1.1.36` | Optional | Caches container image layers on GPU cluster nodes. | `nvcr.io/nvidia/nvcf/nvcf-container-cache:v1.1.36` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/helm/container-cache) | +| `nvcf-image-credential-helper` | `0.10.2` | Required | Resolves container image credentials for function workloads. | `nvcr.io/nvidia/nvcf/nvcf-image-credential-helper:0.10.2` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/image-credential-helper) | +| `nvcf-proxy-tls-certs` | `v1.2.10` | Optional | Configures TLS trust for the optional container cache proxy. | `nvcr.io/nvidia/nvcf/nvcf-proxy-tls-certs:v1.2.10` | | +| `nvcf_worker_init` | `1.0.1` | Required | Prepares function resources before the user container starts. | `nvcr.io/nvidia/nvcf/nvcf_worker_init:1.0.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/worker-init) | +| `nvcf_worker_llm_credentials` | `1.0.1` | Optional | Maintains a current NVCF worker token for LLM function workloads. | `nvcr.io/nvidia/nvcf/nvcf_worker_llm_credentials:1.0.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/worker-llm-credentials) | +| `nvcf_worker_utils` | `1.0.1` | Required | Proxies NATS traffic between function containers and the control plane. | `nvcr.io/nvidia/nvcf/nvcf_worker_utils:1.0.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/compute-plane-services/worker-utils) | +| `pylon` | `0.3.1` | Optional | Connects LLM worker pods to the LLM request router. | `nvcr.io/nvidia/nvcf/pylon:0.3.1` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/libraries/rust/stargate) | + +### EA-only CVE-impacted artifacts + +These Early Access artifacts have known CVE impact. Use only the QA-qualified versions listed for this EA stack. + +| Artifact | Version | Required | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | --- | +| `bitnami-cassandra` | `5.0.6-nv-1` | Required | Stores NVCF account, function, cluster, and service state during Early Access. | `nvcr.io/0833294136851237/selfhosted-ga/bitnami-cassandra:5.0.6-nv-1-ea` | [Upstream](https://github.com/bitnami/containers/tree/main/bitnami/cassandra) | +| `nvcf-cassandra-migrations` | `0.8.1` | Required | Applies the Cassandra schemas required by Early Access NVCF services. | `nvcr.io/0833294136851237/selfhosted-ga/nvcf-cassandra-migrations:0.8.1-ea` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/migrations/cassandra) | + +### Tools and deployment resources + +| Artifact | Version | Description | Distribution | Source code | +| --- | --- | --- | --- | --- | +| `nvcf-cli` | `1.10.3` | Manages functions, deployments, and clusters from the command line. | `nvcr.io/nvidia/nvcf/nvcf-cli:1.10.3` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/src/clis/nvcf-cli) | +| `nvcf-compute-plane-stack` | `1.0.6` | Provides the Helmfile bundle for compute-plane deployment. | `nvcr.io/nvidia/nvcf/nvcf-compute-plane-stack:1.0.6` | | +| `nvcf-self-managed-stack` | `0.6.0` | Provides the Helmfile bundle for control-plane deployment. | `nvcr.io/nvidia/nvcf/nvcf-self-managed-stack:0.6.0` | [GitHub](https://github.com/NVIDIA/nvcf/tree/main/deploy/stacks/self-managed) | + +{/* docs-version-sync:END manifest-artifact-registry-paths */} diff --git a/docs/v0.6.1-rc/metrics/cassandra/metrics.md b/docs/v0.6.1-rc/metrics/cassandra/metrics.md new file mode 100644 index 000000000..e2836faf3 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/cassandra/metrics.md @@ -0,0 +1,11 @@ +# C\* Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------------------------ | ----------- | ------------------------------------- | ---------------------------------------------------- | ----------------------- | ------------------------- | ----------------------------------- | +| cassandra_client_authentication_failures_total | Counter | TBD | Client auth failures | | host | | +| cassandra_client_request_latency_seconds | Histogram | TBD | Client requests types and histogram of latency | seconds | host, operation, quantile | | +| cassandra_client_request_timeouts_total | Counter | TBD | Client request timeouts by type | | host, operation | | +| cassandra_endpoint_connection_timeouts_total | Counter | TBD | C\* endpoint connection timeouts | | endpoint, host | | +| cassandra_jvm_gc_estimated_collection_duration_seconds_total | Gauge | TBD | Time spent in garbage collection | seconds | cassandra_cluster, host | | +| spring_data_repository_invocations_seconds_sum | Counter | nvcf-invocation-service:41337/metrics | Number of seconds for each C\* invocation, by method | Seconds | method, state | namespace="\" | +| spring_data_repository_invocations_seconds_count | Counter | nvcf-invocation-service:41337/metrics | Count of invocations for C\* by method | | method, state | namespace="\" | diff --git a/docs/v0.6.1-rc/metrics/ess/metrics.md b/docs/v0.6.1-rc/metrics/ess/metrics.md new file mode 100644 index 000000000..0462cda46 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/ess/metrics.md @@ -0,0 +1,6 @@ +# ESS Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------ | ----------- | ------------------------------------- | -------------------------------------- | ----------------------- | ------------------------------------------------------------------- | ----------------------------------- | +| ess_templates_rendered_total | Counter | ess-api:9464/metrics | Total number of ESS templates rendered | | function_id, function_version_id, status | | +| http_client_request_duration_seconds_count | Gauge | nvcf-invocation-service:41337/metrics | HTTP Request Duration | seconds | http_request_method, http_response_status_code, http_route, service | server_address="ess.ngc.nvidia.com" | diff --git a/docs/v0.6.1-rc/metrics/grpc-proxy/metrics.md b/docs/v0.6.1-rc/metrics/grpc-proxy/metrics.md new file mode 100644 index 000000000..2af89249a --- /dev/null +++ b/docs/v0.6.1-rc/metrics/grpc-proxy/metrics.md @@ -0,0 +1,37 @@ +# gRPC Proxy Metrics + +The NVCF gRPC proxy emits Prometheus metrics on Service `grpc` (and the headless variant `grpc-headless`) in namespace `nvcf`, port `metrics` (10083), path `/metrics`. The exposition format is Prometheus text (via `prometheus/client_golang`). + +The metric families below cover client connection health, the NATS pipe to workers, and gRPC worker session attachment. + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| -------------------------------------------------------- | ----------- | ---------------- | ------------------------------------------------------------------------------------------------------ | ----------------------- | ------------------------------ | ---------------------------------------------------- | +| nvcf_grpc_proxy_service_active_connections_total | Gauge | grpc:10083/metrics | Active client TCP connections to the gRPC proxy | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_active_http_requests_total | Gauge | grpc:10083/metrics | Active client HTTP requests in-flight | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_in_bytes | Gauge | grpc:10083/metrics | Bytes received from the NATS connection | bytes | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_in_msgs | Gauge | grpc:10083/metrics | Messages received from the NATS connection | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_out_bytes | Gauge | grpc:10083/metrics | Bytes sent to the NATS connection | bytes | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_out_msgs | Gauge | grpc:10083/metrics | Messages sent to the NATS connection | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_error_total | Counter | grpc:10083/metrics | Errors observed on the NATS connection | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_reconnect_total | Counter | grpc:10083/metrics | NATS reconnect attempts | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_reconnects | Gauge | grpc:10083/metrics | Current reconnect attempt count | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_nats_lame_duck_total | Counter | grpc:10083/metrics | NATS lame-duck messages observed | | | namespace="nvcf" | +| nvcf_grpc_proxy_service_session_init_seconds_bucket | Histogram | grpc:10083/metrics | Time spent initializing a gRPC worker session. Fires only on gRPC worker attach to port 10086. | seconds | is_reconnect, le | namespace="nvcf" | +| http_server_request_duration_seconds_bucket | Histogram | grpc:10083/metrics | RED metric: HTTP server request duration on the gRPC proxy. | seconds | http_request_method, http_response_status_code, http_route, le | namespace="nvcf" | +| http_server_request_body_size_bytes_bucket | Histogram | grpc:10083/metrics | HTTP server request body size | bytes | http_request_method, http_route, le | namespace="nvcf" | +| http_server_response_body_size_bytes_bucket | Histogram | grpc:10083/metrics | HTTP server response body size | bytes | http_request_method, http_route, le | namespace="nvcf" | +| rpc_client_duration_milliseconds_bucket | Histogram | grpc:10083/metrics | OpenTelemetry gRPC client RPC duration (per-RPC outcome) | milliseconds | rpc_service, rpc_method, rpc_grpc_status_code, le | namespace="nvcf" | + +## Notes + +- `nvcf_grpc_proxy_service_session_init_seconds_bucket` is the SLI for **gRPC** inference function health. HTTP inference functions invoked through the regular HTTP invocation gateway bypass the gRPC proxy entirely and do not register session-init samples here. +- The `nvcf_grpc_proxy_service_nats_*` family is a useful proxy signal for "is the gRPC proxy → NATS pipe healthy" (in/out bytes and message deltas) and "are NATS upstreams stable" (reconnect and error counters). +- Per-RPC outcomes (success vs. error per call) are covered by the OpenTelemetry `rpc_client_*` family; aggregate proxy-side errors are covered by `nvcf_grpc_proxy_service_nats_error_total`. +- Standard Go runtime metrics (`go_*`) and process metrics (`process_*`) are also exposed on the same endpoint and follow upstream conventions. + +## Reproducing locally + +```bash +kubectl port-forward -n nvcf svc/grpc 10083:10083 +curl http://127.0.0.1:10083/metrics | grep -E '^nvcf_grpc_proxy_' +``` diff --git a/docs/v0.6.1-rc/metrics/init-container/metrics.md b/docs/v0.6.1-rc/metrics/init-container/metrics.md new file mode 100644 index 000000000..c3b675252 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/init-container/metrics.md @@ -0,0 +1,7 @@ +# Init Container Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------- | ----------- | ------------------------------------------ | -------------------------------------------- | ----------------------- | ------------------------------------------------ | ----------------------------------- | +| kube_pod_container_status_restarts_total | Counter | prometheus-kube-state-metrics:8080/metrics | Total number of restarts for init containers | | function_id, function_version_id, namespace, pod | container="init" | +| kube_pod_container_status_terminated_reason | Gauge | prometheus-kube-state-metrics:8080/metrics | Reason an init container terminated | | namespace, pod, reason | container="init" | +| kube_pod_container_status_waiting_reason | Gauge | prometheus-kube-state-metrics:8080/metrics | Reason an init container is waiting to start | | namespace, pod, reason | container="init" | diff --git a/docs/v0.6.1-rc/metrics/invocation-service/metrics.md b/docs/v0.6.1-rc/metrics/invocation-service/metrics.md new file mode 100644 index 000000000..99c4d22c2 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/invocation-service/metrics.md @@ -0,0 +1,12 @@ +# Invocation Service Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ----------------------------------------- | ----------- | ------------------------------------- | ------------------------------------------------- | ----------------------- | ------------------ | ----------------------------------------------------------------------------------------------- | +| axum_http_requests_total | Counter | nvcf-invocation-service:41337/metrics | Inovcation Service http requests count | | status | exported_endpoint=~"^/v2/nvcf.\*\|^/health\$", namespace="astro-tenant-nvcf-invocation-service" | +| axum_http_requests_duration_seconds_count | Counter | nvcf-invocation-service:41337/metrics | Invocation Service http request counts | | status | exported_endpoint=~"^/v2/nvcf.\*\|^/health\$", namespace="astro-tenant-nvcf-invocation-service" | +| axum_http_requests_duration_seconds_sum | Counter | nvcf-invocation-service:41337/metrics | Invocation Service durations of http requests | seconds | status | exported_endpoint=~"^/v2/nvcf.\*\|^/health\$", namespace="astro-tenant-nvcf-invocation-service" | +| app_invocation_error | Counter | nvcf-invocation-service:41337/metrics | Invocation Service invocation errors | | http_status_code | namespace=""astro-tenant-nvcf-invocation-service | +| container_cpu_usage_seconds_total | Counter | nvcf-invocation-service:41337/metrics | Container cpu usage (used for uptime calculation) | | container | container="nvcf-invocation-service" | +| container_memory_usage_bytes | Gauge | nvcf-invocation-service:41337/metrics | Container memory usage | bytes | container | container="nvcf-invocation-service" | +| aws_requests_status | Gauge | AWS Cloud Watch | AWS request status | | aws_status_code | namespace="astro-tenant-nvcf-invocation-service", service="nvcf-invocation-service" | +| nats_jetstream_publish | Counter | Pushed from Synadia | NATS Stream creation count | | namespace | namespace="astro-tenant-nvcf-invocation-service" | diff --git a/docs/v0.6.1-rc/metrics/llm-api-gateway/metrics.md b/docs/v0.6.1-rc/metrics/llm-api-gateway/metrics.md new file mode 100644 index 000000000..49dcb8f3e --- /dev/null +++ b/docs/v0.6.1-rc/metrics/llm-api-gateway/metrics.md @@ -0,0 +1,42 @@ +# LLM API Gateway Metrics + +The LLM API Gateway serves Prometheus metrics from +`llm-api-gateway:9464/metrics` when +`llmApiGateway.metrics.enabled` is `true`. + +The self-managed stack maps `global.observability.metrics.enabled` to this chart +value. The gateway metrics use the `llm_api_gateway_` service prefix and must not +emit legacy service-prefixed metric names. + +## Label Boundaries + +Use bounded labels only. Do not add request IDs, session IDs, function IDs, +organization IDs, project IDs, raw URLs, raw prompts, authorization values, or +other unbounded request fields as metric labels. + +## Metrics + +| Metric name | Type | Source endpoint | Labels | Notes | +| --- | --- | --- | --- | --- | +| `llm_api_gateway_http_requests_total` | Counter | `llm-api-gateway:9464/metrics` | `method`, `route`, `status` | Total inbound HTTP requests. `route` is the templated route, not the raw URL path. | +| `llm_api_gateway_http_request_duration_seconds` | Histogram | `llm-api-gateway:9464/metrics` | `method`, `route`, `status` | Inbound HTTP request duration in seconds. | +| `llm_api_gateway_http_active_requests` | Gauge | `llm-api-gateway:9464/metrics` | `method`, `route` | Current in-flight inbound HTTP requests. | +| `llm_api_gateway_upstream_requests_total` | Counter | `llm-api-gateway:9464/metrics` | `upstream`, `result`, `status` | Total outbound upstream requests. `upstream` is a bounded service name such as `llm-request-router`. | +| `llm_api_gateway_upstream_request_duration_seconds` | Histogram | `llm-api-gateway:9464/metrics` | `upstream`, `result`, `status` | Outbound upstream request duration in seconds. | +| `llm_api_gateway_llm_tokens_total` | Counter | `llm-api-gateway:9464/metrics` | `endpoint`, `token_type`, `stream` | LLM token counts reported by upstream providers. `token_type` is a bounded enum such as `prompt`, `completion`, or `total`. | +| `llm_api_gateway_provider_time_seconds` | Histogram | `llm-api-gateway:9464/metrics` | `endpoint`, `phase`, `stream` | Provider-reported timing phases in seconds. | +| `llm_api_gateway_stream_first_token_seconds` | Histogram | `llm-api-gateway:9464/metrics` | `endpoint` | Time from stream request start to first token in seconds. | +| `llm_api_gateway_stream_duration_seconds` | Histogram | `llm-api-gateway:9464/metrics` | `endpoint`, `status` | Total stream duration in seconds. | +| `llm_api_gateway_pubsub_publish_failures_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of messages that failed to publish. | +| `llm_api_gateway_pubsub_consume_failures_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of messages that failed to consume. | +| `llm_api_gateway_pubsub_consume_duration_seconds` | Histogram | `llm-api-gateway:9464/metrics` | None | Time to consume a message in seconds. | +| `llm_api_gateway_rate_limit_event_replication_lag_seconds` | Histogram | `llm-api-gateway:9464/metrics` | None | Lag between rate limit event creation and processing in seconds. | +| `llm_api_gateway_rate_limit_events_received_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of rate limit events received from the sync transport. | +| `llm_api_gateway_rate_limit_events_dropped_total` | Counter | `llm-api-gateway:9464/metrics` | `reason` | Number of received rate limit events dropped. `reason` is a bounded enum such as `same_cluster`, `old_message`, or `remote_apply_disabled`. | +| `llm_api_gateway_rate_limit_events_applied_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of rate limit events applied to the local limiter. | +| `llm_api_gateway_rate_limit_events_failed_apply_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of rate limit events that failed to apply locally. | +| `llm_api_gateway_rate_limit_events_dry_run_would_apply_total` | Counter | `llm-api-gateway:9464/metrics` | None | Number of rate limit events that would apply when remote application is disabled. | +| `llm_api_gateway_rate_limit_synchronizer_publish_duration_seconds` | Histogram | `llm-api-gateway:9464/metrics` | None | Time to publish a rate limit event in seconds. | +| `llm_api_gateway_rate_limit_synchronizer_queue_wait_seconds` | Histogram | `llm-api-gateway:9464/metrics` | None | Time spent queueing a rate limit event in seconds. | +| `llm_api_gateway_rate_limit_synchronizer_queue_length` | Gauge | `llm-api-gateway:9464/metrics` | None | Current rate limit synchronizer queue length. | +| `llm_api_gateway_rate_limit_synchronizer_events_dropped_total` | Counter | `llm-api-gateway:9464/metrics` | `reason` | Number of rate limit events dropped before publishing. `reason` is a bounded enum such as `old_message`. | diff --git a/docs/v0.6.1-rc/metrics/llm-function-invocation-path.md b/docs/v0.6.1-rc/metrics/llm-function-invocation-path.md new file mode 100644 index 000000000..50a75d2a5 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/llm-function-invocation-path.md @@ -0,0 +1,97 @@ +# LLM Function Invocation Metrics Report + +This report covers the metrics available on the LLM function invocation path: +the LLM API Gateway, the LLM Request Router, and the Stargate client sidecar in +LLM function pods. + +## Scrape points + +| Component | Endpoint | Service name | Metric prefix | +| --- | --- | --- | --- | +| LLM API Gateway | `llm-api-gateway:9464/metrics` | `llm-api-gateway` | `llm_api_gateway_` | +| Rate limit sync worker | `:9464/metrics` when deployed with `METRICS_PORT=9464` | `llm-api-gateway-rate-limit-sync-worker` | `llm_api_gateway_` | +| LLM Request Router | `llm-request-router:9090/metrics` | `llm-request-router` | `llm_request_router_` | +| Stargate client sidecar | `:9089/metrics` by default | `stargate-client` | `stargate_client_` | + +The request-router chart passes `--metrics-prefix=llm_request_router_`. +Upstream Stargate still defaults to `stargate_` when run outside the NVCF chart. + +## LLM API Gateway + +| Metric | Labels | +| --- | --- | +| `llm_api_gateway_http_requests_total` | `method`, `route`, `status` | +| `llm_api_gateway_http_request_duration_seconds` | `method`, `route`, `status` | +| `llm_api_gateway_http_active_requests` | `method`, `route` | +| `llm_api_gateway_upstream_requests_total` | `upstream`, `result`, `status` | +| `llm_api_gateway_upstream_request_duration_seconds` | `upstream`, `result`, `status` | +| `llm_api_gateway_llm_tokens_total` | `endpoint`, `token_type`, `stream` | +| `llm_api_gateway_provider_time_seconds` | `endpoint`, `phase`, `stream` | +| `llm_api_gateway_stream_first_token_seconds` | `endpoint` | +| `llm_api_gateway_stream_duration_seconds` | `endpoint`, `status` | +| `llm_api_gateway_pubsub_publish_failures_total` | None | +| `llm_api_gateway_pubsub_consume_failures_total` | None | +| `llm_api_gateway_pubsub_consume_duration_seconds` | None | +| `llm_api_gateway_rate_limit_event_replication_lag_seconds` | None | +| `llm_api_gateway_rate_limit_events_received_total` | None | +| `llm_api_gateway_rate_limit_events_dropped_total` | `reason` | +| `llm_api_gateway_rate_limit_events_applied_total` | None | +| `llm_api_gateway_rate_limit_events_failed_apply_total` | None | +| `llm_api_gateway_rate_limit_events_dry_run_would_apply_total` | None | +| `llm_api_gateway_rate_limit_synchronizer_publish_duration_seconds` | None | +| `llm_api_gateway_rate_limit_synchronizer_queue_wait_seconds` | None | +| `llm_api_gateway_rate_limit_synchronizer_queue_length` | None | +| `llm_api_gateway_rate_limit_synchronizer_events_dropped_total` | `reason` | + +The sync worker reuses the same telemetry package and emits the rate limit +synchronizer and Pub/Sub metrics under the worker service name. + +## LLM Request Router + +| Metric | Labels | +| --- | --- | +| `llm_request_router_requests_total` | `routing_key`, `model`, `inference_server_id`, `status` | +| `llm_request_router_proxy_attempts_total` | `routing_key`, `model`, `inference_server_id`, `result` | +| `llm_request_router_proxy_retries_total` | `routing_key`, `model`, `reason` | +| `llm_request_router_proxy_retry_exhausted_total` | `routing_key`, `model`, `reason` | +| `llm_request_router_quic_connection_evictions_total` | `inference_server_id`, `reason` | +| `llm_request_router_quic_hot_path_reconnect_total` | `inference_server_id`, `result` | +| `llm_request_router_proxy_replay_buffer_bytes` | `model` | +| `llm_request_router_proxy_duration_seconds` | `routing_key`, `model`, `inference_server_id` | +| `llm_request_router_routing_duration_seconds` | `routing_key`, `model` | +| `llm_request_router_active_inference_servers` | `routing_key`, `model` | + +## Stargate Client Sidecar + +| Metric | Labels | +| --- | --- | +| `target_info` | `service_version`, `service_name`, `commit` | +| `stargate_client_requests_inflight` | `model` | +| `stargate_client_requests_state` | `model`, `state` | +| `stargate_client_requests_state_input_tokens` | `model`, `state` | +| `stargate_client_requests_total` | `model`, `routing_key`, `status` | +| `stargate_client_request_time_to_response_headers_seconds` | `model`, `routing_key` | +| `stargate_client_request_time_to_first_output_seconds` | `model`, `routing_key` | +| `stargate_client_request_time_to_first_token_seconds` | `model`, `routing_key` | +| `stargate_client_request_duration_seconds` | `model`, `routing_key`, `status` | +| `stargate_client_request_input_tokens_total` | `model`, `routing_key`, `status` | +| `stargate_client_request_output_tokens_total` | `model`, `routing_key`, `status` | +| `stargate_client_request_input_tokens` | `model`, `routing_key`, `status` | +| `stargate_client_request_output_tokens` | `model`, `routing_key`, `status` | +| `stargate_client_registration_stream_connected` | `router` | +| `stargate_client_reverse_tunnel_connected` | `router` | +| `stargate_client_model_input_tps` | `model` | +| `stargate_client_model_output_tps` | `model` | +| `stargate_client_model_max_input_tps` | `model` | +| `stargate_client_model_max_output_tps` | `model` | +| `stargate_client_model_queue_size` | `model` | +| `stargate_client_model_queued_input_tokens` | `model` | +| `stargate_client_model_kv_cache_capacity_tokens` | `model` | +| `stargate_client_model_kv_cache_used_tokens` | `model` | +| `stargate_client_model_kv_cache_free_tokens` | `model` | +| `stargate_client_model_advertised_status` | `router`, `model`, `status` | +| `stargate_client_retryable_responses_total` | `inference_server_id`, `reason`, `status` | +| `stargate_client_nonretryable_failures_total` | `inference_server_id`, `reason` | + +Keep request IDs, session IDs, function IDs, organization IDs, project IDs, +authorization values, raw prompts, and raw URLs out of metric labels. diff --git a/docs/v0.6.1-rc/metrics/llm-request-router/metrics.md b/docs/v0.6.1-rc/metrics/llm-request-router/metrics.md new file mode 100644 index 000000000..3cc6f0d5c --- /dev/null +++ b/docs/v0.6.1-rc/metrics/llm-request-router/metrics.md @@ -0,0 +1,35 @@ +# LLM Request Router Metrics + +The LLM Request Router serves Prometheus metrics from +`llm-request-router:9090/metrics` when +`llmRequestRouter.metrics.enabled` is `true`. + +The self-managed stack maps `global.observability.metrics.enabled` to this chart +value. The request-router chart runs Stargate with +`--metrics-prefix=llm_request_router_`, so deployed metric names use the +`llm_request_router_` prefix instead of the upstream default `stargate_` prefix. +The chart also sets the trace service name with +`--otel-service-name=llm-request-router`. + +## Label Boundaries + +Use bounded labels only. Keep `routing_key`, `model`, `inference_server_id`, +`status`, `result`, and `reason` to bounded service dimensions. Do not add +request IDs, session IDs, function IDs, organization IDs, project IDs, raw URLs, +raw prompts, authorization values, or other unbounded request fields as metric +labels. + +## Metrics + +| Metric name | Type | Source endpoint | Labels | Notes | +| --- | --- | --- | --- | --- | +| `llm_request_router_requests_total` | Counter | `llm-request-router:9090/metrics` | `routing_key`, `model`, `inference_server_id`, `status` | Total proxied requests by selected backend and status. | +| `llm_request_router_proxy_attempts_total` | Counter | `llm-request-router:9090/metrics` | `routing_key`, `model`, `inference_server_id`, `result` | Upstream proxy attempts by selected backend and result. | +| `llm_request_router_proxy_retries_total` | Counter | `llm-request-router:9090/metrics` | `routing_key`, `model`, `reason` | Total proxy retries by retry reason. | +| `llm_request_router_proxy_retry_exhausted_total` | Counter | `llm-request-router:9090/metrics` | `routing_key`, `model`, `reason` | Total requests that exhausted retry options. | +| `llm_request_router_quic_connection_evictions_total` | Counter | `llm-request-router:9090/metrics` | `inference_server_id`, `reason` | Total QUIC pool evictions by backend and reason. | +| `llm_request_router_quic_hot_path_reconnect_total` | Counter | `llm-request-router:9090/metrics` | `inference_server_id`, `result` | Direct QUIC reconnect attempts from the proxy hot path. | +| `llm_request_router_proxy_replay_buffer_bytes` | Histogram | `llm-request-router:9090/metrics` | `model` | Proxied request replay buffer size in bytes. | +| `llm_request_router_proxy_duration_seconds` | Histogram | `llm-request-router:9090/metrics` | `routing_key`, `model`, `inference_server_id` | Time to first byte from upstream in seconds. | +| `llm_request_router_routing_duration_seconds` | Histogram | `llm-request-router:9090/metrics` | `routing_key`, `model` | Load-balancer decision time in seconds. | +| `llm_request_router_active_inference_servers` | Gauge | `llm-request-router:9090/metrics` | `routing_key`, `model` | Currently routable inference servers for a routing target. | diff --git a/docs/v0.6.1-rc/metrics/metrics-index.md b/docs/v0.6.1-rc/metrics/metrics-index.md new file mode 100644 index 000000000..66cb8056c --- /dev/null +++ b/docs/v0.6.1-rc/metrics/metrics-index.md @@ -0,0 +1,34 @@ +# Metrics Overview + +Per-service metrics reference for the NVCF self-hosted control plane. Each linked page lists metric names, types, sources, descriptions, and the labels and filters that make the metric useful in queries and dashboards. + +## Control plane services + +- [NVCF API](./nvcf-api/metrics.md): request rates, response status codes, and log event counts for the NVCF API service. +- [Invocation Service](./invocation-service/metrics.md): HTTP request counts, durations, and invocation error metrics for the invocation path. +- [ESS](./ess/metrics.md): template rendering counters and HTTP client metrics for the Encrypted Secrets Service. +- [gRPC Proxy](./grpc-proxy/metrics.md): client connection counts, NATS pipe health, gRPC worker session-attach latency, and HTTP RED metrics for the gRPC proxy. +- [State Metrics Service](./state-metrics/metrics.md): per-function instance count, stage durations, request latency, and function metadata. +- [SIS/Spot](./sis-spot/metrics.md): HTTP client metrics for the Spot Instance Service. +- [Function Autoscaler](../autoscaling/observability.md): OpenTelemetry metrics emitted by the function autoscaler service. + +## LLM services + +- [LLM API Gateway](./llm-api-gateway/metrics.md): request and routing metrics for the LLM API gateway. +- [LLM Function Invocation Metrics Report](./llm-function-invocation-path.md): end-to-end LLM invocation path report. +- [LLM Request Router](./llm-request-router/metrics.md): request router metrics for LLM traffic. + +## Per-function containers + +- [Init Container](./init-container/metrics.md): restart counts and termination reasons for function init containers. +- [Utils Container](./utils-container/metrics.md): restart counts, termination reasons, and worker service response metrics for function utils containers. + +## Datastores + +- [Cassandra](./cassandra/metrics.md): client request latency, timeouts, authentication failures, and endpoint connection metrics. +- [Vault/OpenBao](./vault-openbao/metrics.md): pointer to upstream OpenBao telemetry documentation. + +## See also + +- [Observability](../observability.md) for logging, tracing, and overall observability configuration. +- [Example Dashboards](../example-dashboards.md) for reference Grafana dashboards. diff --git a/docs/v0.6.1-rc/metrics/nvcf-api/metrics.md b/docs/v0.6.1-rc/metrics/nvcf-api/metrics.md new file mode 100644 index 000000000..3057b65d2 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/nvcf-api/metrics.md @@ -0,0 +1,6 @@ +# NVCF API Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------ | ----------- | ---------------------------------------- | ---------------------------------------------- | ----------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | +| logback_events_total | Counter | nvcf-api: nvcf-api-service:10083/metrics | Log event level counts | | level | service="strap-api" | +| http_server_request_duration_seconds_count | Counter | nvcf-api: nvcf-api-service:10083/metrics | Response codes for various endpoints, nvcf api | | http_request_method, http_response_status_code, http_route, method | server_address="api.nvcf.nvidia.com", job="strap-api", namespace="gdn-strap-api-fp" | diff --git a/docs/v0.6.1-rc/metrics/sis-spot/metrics.md b/docs/v0.6.1-rc/metrics/sis-spot/metrics.md new file mode 100644 index 000000000..b92d3eaad --- /dev/null +++ b/docs/v0.6.1-rc/metrics/sis-spot/metrics.md @@ -0,0 +1,5 @@ +# SIS/SPOT Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------ | ----------- | ---------------------------------- | -------------------------------- | ----------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- | +| http_client_request_duration_seconds_count | Counter | spot-instance-service:9464/metrics | http_response codes with timings | | http_request_method, http_response_status_code, http_route, service | server_address="spot.gdn.nvidia.com", namespace="gdn-spot-api-fp" | diff --git a/docs/v0.6.1-rc/metrics/state-metrics/metrics.md b/docs/v0.6.1-rc/metrics/state-metrics/metrics.md new file mode 100644 index 000000000..60cae207d --- /dev/null +++ b/docs/v0.6.1-rc/metrics/state-metrics/metrics.md @@ -0,0 +1,13 @@ +# State Metrics Service Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------ | ----------- | ------------------------------------------ | ------------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| container_cpu_usage_seconds_total | Counter | prometheus-kube-state-metrics:8080/metrics | Container cpu usage (used for uptime calculation) | | container , function_id, function_version_id, namespace | namespace="astro-tenant-nvcf-state-metrics-service" | +| state_metrics_stage_duration_seconds | Gauge | nvcf-state-metrics-service:9464/metrics | Duration of various stages in the state metrics service | seconds | stage | namespace="astro-tenant-nvcf-state-metrics-service" | +| http_client_request_duration_seconds_count | Counter | nvcf-state-metrics-service:9464/metrics | http_response codes with timings | | http_request_method, http_response_status_code, http_route, service | service="nvcf-state-metrics-service" | +| nvcf_function_info | Gauge | nvcf-state-metrics-service:9464/metrics | Various function information | | container_image, endpoint, function_id, function_version_id, helm_chart, name, secrets | | +| nvcf_function_status | Gauge | nvcf-state-metrics-service:9464/metrics | Various function status | | account_display_name, account_name, function_id, function_version_id, name, nca_id, status, version | | +| nvcf_function_instances_current | Gauge | nvcf-state-metrics-service:9464/metrics | Currently deployed functions | | function_version_id, nca_id, version | | +| nvcf_function_queue_depth | Gauge | nvcf-state-metrics-service:9464/metrics | Queue metrics for functions | | account_display_name, account_name, function_id, function_version_id, name, nca_id, version | | +| nvcf_function_instances_current | Gauge | nvcf-state-metrics-service:9464/metrics | Number of instances for a function | | account_display_name, account_name, function_id, function_version_id, name, nca_id, version | | +| function_request_latency_bucket | Histogram | nvcf-state-metrics-service:9464/metrics | Invocation latency histogram | | function_id, function_version_id, le | | diff --git a/docs/v0.6.1-rc/metrics/utils-container/metrics.md b/docs/v0.6.1-rc/metrics/utils-container/metrics.md new file mode 100644 index 000000000..ac2634cc0 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/utils-container/metrics.md @@ -0,0 +1,8 @@ +# Utils Container Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------- | ----------- | ------------------------------------------- | ------------------------------ | ----------------------- | ------------------------------------------------ | ----------------------------------- | +| kube_pod_container_status_restarts_total | Counter | prometheus-kube-state-metrics:8080/metrics | Pod restart count | | function_id, function_version_id, namespace, pod | container="utils" | +| kube_pod_container_status_terminated_reason | Gauge | prometheus-kube-state-metrics:8080/metrics | Reason pod was terminated | | namespace, pod, reason | container="utils" | +| kube_pod_container_status_waiting_reason | Gauge | prometheus-kube-state-metrics:8080/metrics | Reason pod is waiting to start | | namespace, pod | container="utils" | +| nvcf_worker_service_response_total | Gauge | prometheus-agent-metrics-utils:8010/metrics | | | | | diff --git a/docs/v0.6.1-rc/metrics/vault-openbao/metrics.md b/docs/v0.6.1-rc/metrics/vault-openbao/metrics.md new file mode 100644 index 000000000..0ced56d89 --- /dev/null +++ b/docs/v0.6.1-rc/metrics/vault-openbao/metrics.md @@ -0,0 +1,5 @@ +# Vault (implemented as OpenBao) Metrics + +| Metric name | Metric type | Source | Description | Unit (where applicable) | Interesting Labels | Required Filters (where applicable) | +| ------------------------------------------------------------------------------------------------- | ----------- | ------ | ----------- | ----------------------- | ------------------ | ----------------------------------- | +| For open telemetry metrics available, see [https://openbao.org/docs/internals/telemetry/metrics/](https://openbao.org/docs/internals/telemetry/metrics/) | | | | | | | diff --git a/docs/v0.6.1-rc/observability.md b/docs/v0.6.1-rc/observability.md new file mode 100644 index 000000000..0a6520801 --- /dev/null +++ b/docs/v0.6.1-rc/observability.md @@ -0,0 +1,414 @@ +# Observability Configuration + +This page provides guidance on configuring observability for self-hosted NVCF control-plane, including metrics, logging, and tracing. + +## Find the answer to your question + +Common operator questions and where to look on this page or in linked references. + +| Question | Where to look | +|----------|---------------| +| How do I see application-level NVCF stats (number of functions, queue depth, request latency)? | [State Metrics Service metrics](./metrics/state-metrics/metrics.md). The page documents per-function instance count, queue depth, and request latency, plus other function-level signals. | +| How do I debug a single request end-to-end? | Combine the per-hop signals: enable tracing per [Tracing Configuration](#tracing-configuration), correlate with the [Metrics Overview](./metrics/metrics-index.md) for each service in the request path, and tail the matching service logs. A consolidated hop-by-hop walkthrough is in development. | +| Where are per-service metrics? | [Metrics Overview](./metrics/metrics-index.md). | +| Where are gRPC proxy metrics? | [gRPC Proxy metrics](./metrics/grpc-proxy/metrics.md). The page documents client connection counts, NATS pipe health, gRPC worker session-attach latency, and HTTP RED metrics. | +| How do I add custom spans or metrics in a Kit application? | Use the OpenTelemetry API directly, the OmniTrace helper, the Carbonite static metrics API, or the `omni::observability::IMeter` interface. Refer to the Omniverse Kit and Carbonite documentation for details. | +| Where are reference dashboards? | [Example dashboards](./example-dashboards.md) and the [Dashboards](#dashboards) section below. | + +## Overview + +Self-hosted NVCF control-plane observability enables users to monitor the health and performance of their NVCF deployment. The observability solution is designed to be: + +- **Cloud-agnostic**: Works in any Kubernetes environment (cloud provider, on-premises, or air-gapped) +- **Offline-capable**: Fully functional in isolated networks without external dependencies +- **Bring-Your-Own (BYO)**: Integrates with your existing observability platforms +- **No vendor lock-in**: Uses open standards (Prometheus, OpenTelemetry, OTLP) + +The observability solution currently provides: + +- [Metrics Collection]: Prometheus-compatible metrics from all control-plane services +- [Logging]: Logs emitted to stdout/stderr for easy collection +- [Tracing]: Distributed tracing via OTLP to your collector +- [Dashboards]: Reference Grafana dashboards for key metrics + + +**Looking for a quick start?** If you want to quickly deploy example observability components +to explore metrics, logs, and dashboards, see [self-hosted-example-dashboards](./example-dashboards.md). + +The example deployments are designed for development and testing only, and are not suitable +for production use. For production deployments, follow the guidance on this page to integrate +with your own observability infrastructure. + + + +## Early Access Phase + +NVCF self-hosted observability is currently in Early Access (EA). During EA, NVCF provides interfaces and documentation for you to integrate with your own observability backend: + +**What's Provided:** + +- Documented metrics for critical control-plane services +- Example scrape targets for prometheus-operator ServiceMonitor configuration +- Metrics exposed via Prometheus-compatible endpoints +- Logs emitted to stdout/stderr for easy collection +- Configuration and deployment documentation +- Example dashboards for key metrics + +**Your Responsibility:** + +- Deploy and manage your own observability backend (Prometheus, Grafana, Loki, Elasticsearch, etc.) +- Configure metrics scraping from control-plane services +- Deploy log collectors (e.g., Fluentd, Promtail, OTel Collector) to aggregate logs +- Set up your preferred visualization and alerting tools + +## Control-Plane Services + +The following control-plane services expose metrics and logs for monitoring: + +**Core NVCF Services:** + +- **NVCF API**: Main API for function management and invocation +- **Invocation Service**: Handles function invocation requests +- **SPOT Instance Service (SIS)**: Manages worker pod and cluster state +- **State Metrics Service**: Aggregates and exports NVCF-specific metrics + +**Supporting Services:** + +- **Cassandra (C\*)**: Primary database for control-plane state +- **OpenBao/Vault**: Secret management and S2S authentication +- **Encrypted Secrets Service (ESS)**: Function and account secrets +- **NATS Core**: Pub/sub messaging +- **NATS JetStream**: Persistent messaging + +**Worker Pod Components:** + +- **Utils Container**: Proxy to NATS from user applications +- **Init Container**: Setup and resource loading +- **Inference Container**: Inference workload + +## Architecture + +### Metrics Collection + +All control-plane services expose Prometheus-compatible metrics endpoints. You can scrape these metrics using: + +- **Prometheus Operator**: Create ServiceMonitor resources based on the provided scrape targets +- **Prometheus**: Configure scrape targets manually +- **OpenTelemetry Collector**: Use the Prometheus receiver + +**Metrics Documentation:** + +Detailed metrics documentation is available for each service, including metric names, +types, labels, and descriptions. See the per-service metrics reference under the +`Metrics` section. + +### Logging + +**Log Format:** + +- All services emit logs to stdout/stderr (standard for Kubernetes) +- Sensitive data redaction must be configured by the log collector + +**Log Collection:** + +You can collect logs using any Kubernetes-compatible log aggregator: + +- Fluentd or Fluent Bit +- Promtail (for Loki) +- Filebeat (for Elasticsearch) +- OpenTelemetry Collector (filelog receiver) + +**System Logs:** + +System logs are available at standard UNIX locations and from the systemd journal. + +### Tracing (Available in GA) + +Distributed tracing support via OpenTelemetry Protocol (OTLP) is planned for a future release: + +- Key flows will be instrumented with OpenTelemetry SDK +- Traces will be exportable via OTLP (HTTP or gRPC) +- Configurable sampling strategies +- Support for any OTLP-compatible backend (Jaeger, Tempo, Zipkin, etc.) +- Tracing is configurable via Helm values under `global.observability.tracing` + +## Configuration + +You configure observability by integrating with your own backend: + +### Metrics Scraping + +Metrics export is opt-in and disabled by default. Enable it in your Helmfile +environment before configuring scrape targets: + +```yaml +global: + observability: + metrics: + enabled: true +``` + +Use Prometheus Operator with the provided ServiceMonitor examples: + +```yaml +# Example ServiceMonitor for NVCF API +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: nvcf-api + namespace: nvcf +spec: + selector: + matchLabels: + app: nvcf-api + endpoints: + # Endpoint created based on the scrape target in the + # per-service metrics documentation + - port: metrics + interval: 30s + path: /metrics +``` + +Or configure Prometheus scrape targets manually in your prometheus.yml. + +#### Application-level NVCF stats + +The State Metrics Service exposes per-function signals you can query in +Prometheus. The following PromQL examples cover the three most common +operator questions. Metric names and labels are sourced from +[State Metrics Service metrics](./metrics/state-metrics/metrics.md). + +Number of registered functions: + +```promql +# nvcf_function_info is emitted per function with descriptive labels. +# Dedupe by function_id so multiple label series do not inflate the count. +count(count by (function_id) (nvcf_function_info)) +``` + +Queue depth per function: + +```promql +# nvcf_function_queue_depth is a gauge keyed by function_id. +sum by (function_id, name) (nvcf_function_queue_depth) +``` + +Function request latency (p50 and p95) over a 5 minute window: + +```promql +# p50 +histogram_quantile( + 0.50, + sum by (le, function_id) (rate(function_request_latency_bucket[5m])) +) + +# p95 +histogram_quantile( + 0.95, + sum by (le, function_id) (rate(function_request_latency_bucket[5m])) +) +``` + +### Log Collection + +Deploy a log collector as a DaemonSet to ship logs to your backend: + +```bash +# Example: Deploy Promtail for Loki +kubectl apply -f promtail-daemonset.yaml + +# Example: Deploy Fluentd or Fluent Bit +kubectl apply -f fluentd-daemonset.yaml +``` + +Configure your log collector to: + +- Tail logs from all namespaces +- Add metadata labels (pod name, namespace, service) +- Forward to your log aggregation backend (Loki, Elasticsearch, etc.) + +### Tracing Configuration + +Enable distributed tracing by setting Helm values under +`global.observability.tracing`. The control-plane exports traces via OTLP +to your own OTLP-compatible collector. Set `collectorEndpoint`, +`collectorPort`, and `collectorProtocol` to match your collector's address. +`collectorProtocol` is the endpoint URI scheme expected by the stack, not the +OTLP transport. + +Helm overrides example: + +```yaml +global: + observability: + tracing: + enabled: true + collectorEndpoint: "otel-collector-gateway-collector.observability.svc.cluster.local" + collectorPort: 4317 + collectorProtocol: http +``` + +Configuration fields: + +- `enabled`: Set to `true` to enable OTLP trace export from control-plane + services. +- `collectorEndpoint`: DNS name or address of your OTLP collector (e.g., + OpenTelemetry Collector, Jaeger collector). Use a Kubernetes service DNS name + such as `..svc.cluster.local` when the collector runs + in-cluster. +- `collectorPort`: Port on which the collector accepts OTLP traffic (e.g., + 4317 for gRPC, 4318 for HTTP depending on your collector setup). +- `collectorProtocol`: URI scheme used to build the collector endpoint + (`http` or `https`). This value does not select the OTLP transport. + +Ensure your collector is deployed and reachable from the NVCF control-plane +namespace, and that it forwards traces to your backend (Jaeger, Tempo, Zipkin, +or another OTLP-compatible system). + +## Dashboards + +Reference Grafana dashboards are provided for control-plane services showing critical metrics for key services: + +- ESS (Encrypted Secrets Service) + +- Cassandra + +- Vault + +- Invocation Service + +- NVCF API + +- SIS (SPOT Instance Service) + +- Worker Pods (Utils Container, Init Container, Inference Container) + + - Note: Worker Pods are deployed in the backend cluster, not the control-plane + cluster, but their configuration is globally controlled as part of the control-plane + +- State Metrics Service + +**Dashboard Location:** + +Dashboards are provided in native Grafana JSON format for [file-provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards). + +Load dashboards into Grafana by placing them in `/etc/grafana/provisioning/dashboards/` on startup. + +Published dashboards will be available in the +[NVCF examples](https://github.com/NVIDIA/nvcf/tree/main/examples) public GitHub repository. + +## Troubleshooting + +For troubleshooting common observability issues: + +**Metrics not appearing:** + +1. Verify the service is exposing metrics: + + ```bash + # Port-forward to the service metrics port + kubectl port-forward -n nvcf svc/nvcf-api 8080:8080 + + # In another terminal, curl the metrics endpoint + curl http://localhost:8080/metrics + ``` + +2. Check ServiceMonitor or scrape configuration: + + ```bash + # Verify ServiceMonitor exists + kubectl get servicemonitor -n nvcf + + # Check ServiceMonitor details + kubectl describe servicemonitor nvcf-api -n nvcf + ``` + +3. Verify network policies allow scraping: + + ```bash + # List network policies that might block traffic + kubectl get networkpolicy -n nvcf + + # Test connectivity from Prometheus namespace + kubectl run -n --rm -it debug \ + --image=curlimages/curl --restart=Never -- \ + curl http://nvcf-api.nvcf.svc.cluster.local:8080/metrics + ``` + +4. Check service logs for errors: + + ```bash + # Check for metrics-related errors + kubectl logs -n nvcf deployment/nvcf-api | grep -i metric + ``` + +**Logs not being collected:** + +1. Verify log collector DaemonSet is running: + + ```bash + # Check DaemonSet status (e.g., for Fluentd/Fluent Bit) + # Note: Namespaces may be different depending on the log collector deployment + kubectl get daemonset -n logging + kubectl get pods -n logging -l app=fluent-bit + ``` + +2. Check collector can access pod logs: + + ```bash + # Verify log collector has proper volume mounts + kubectl describe daemonset fluent-bit -n logging | grep -A5 Mounts + + # Check collector logs for errors + kubectl logs -n logging -l app=fluent-bit --tail=50 + ``` + +3. Verify log backend is reachable: + + ```bash + # Test connectivity to log backend (e.g. Loki) + kubectl run -n logging --rm -it debug \ + --image=curlimages/curl --restart=Never -- \ + curl -v http://loki.logging.svc.cluster.local:3100/ready + ``` + +4. Check for log redaction or filtering rules: + + ```bash + # Review collector configuration + kubectl get configmap fluent-bit-config -n logging -o yaml + + # Check if logs are being dropped + kubectl logs -n logging -l app=fluent-bit | grep -i "drop\|filter" + ``` + +## Security + +**Metrics Endpoints:** + +- Metrics endpoints should be accessed over HTTP in-cluster only + + - Any external access should be SSL/TLS or mTLS secured with a reverse proxy or other ingress controller, or + - Aggregated locally and exposed via a secured otel-collector + +- All sensitive log data should be redacted by the log collector (currently, this is the responsibility of the log collector, not the service) + + - Example implementation by OTEL Collector: [Log Redaction](https://opentelemetry.io/docs/languages/dotnet/logs/redaction/) + +- User-provided observability backend should be properly secured with RBAC, TLS/SSL, and other security best practices. + +## Related Documentation + +- [OpenTelemetry documentation](https://opentelemetry.io/docs/) +- [Prometheus documentation](https://prometheus.io/docs/) + +## Version Compatibility + +NVCF self-hosted control-plane observability is compatible with: + +- Supported versions are the latest Kubernetes minor release and the two prior minor releases (N-2). See official Kubernetes docs for current supported [versions](https://kubernetes.io/releases/version-skew-policy/#supported-versions). +- Any Prometheus-compatible metrics collection system +- Any log aggregation system that can collect from Kubernetes stdout/stderr or read + from the filesystem (depending on K8s cluster configuration) + +For the latest compatibility information, see the release notes. diff --git a/docs/v0.6.1-rc/optional-enhancements-gpu.md b/docs/v0.6.1-rc/optional-enhancements-gpu.md new file mode 100644 index 000000000..ae577b122 --- /dev/null +++ b/docs/v0.6.1-rc/optional-enhancements-gpu.md @@ -0,0 +1,3 @@ +# Optional Enhancements (GPU) + +This page is superseded. See [optional-enhancements](./optional-enhancements.md). diff --git a/docs/v0.6.1-rc/optional-enhancements-standalone.md b/docs/v0.6.1-rc/optional-enhancements-standalone.md new file mode 100644 index 000000000..228db0de9 --- /dev/null +++ b/docs/v0.6.1-rc/optional-enhancements-standalone.md @@ -0,0 +1,3 @@ +# Optional Enhancements (Standalone) + +This page is superseded. See [optional-enhancements](./optional-enhancements.md). diff --git a/docs/v0.6.1-rc/optional-enhancements.md b/docs/v0.6.1-rc/optional-enhancements.md new file mode 100644 index 000000000..fae4f9ff3 --- /dev/null +++ b/docs/v0.6.1-rc/optional-enhancements.md @@ -0,0 +1,45 @@ +# Optional Enhancements + +NVCF supports several optional components that can enhance your deployment's +performance, routing, and GPU cluster capabilities. Each component has its own +installation and configuration guide. + +## Low-Latency Streaming + +- [LLS Installation](lls-installation.md) - Required for streaming Cloud Functions using WebRTC + +## LLM Functions + +- [LLM Function Enablement](./llm-function-enablement.md) - Required for `functionType: "LLM"` functions using the LLM invocation gateway + +## NVCF Caches + +- [container-cache](./cluster-management/container-cache.md) - Accelerates container image pulls by caching layers locally +- [gxcache](./cluster-management/gxcache.md) - Shader caching for simulation and rendering workloads + +## Vanity Gateway + +Vanity Gateway is an optional HTTP gateway service for deployments that need +customer-facing hostnames or path mappings in front of the standard NVCF API and +invocation routes. It is available only in stack packages that include the +Vanity Gateway addon. Older packages do not contain the `vanity-gateway` release +or route values. + +When the addon is present and enabled, it is deployed as the `vanity-gateway` +service and exposed through the Gateway API route `vanity.` by default. +Enable it only when you need a vanity routing layer. Standard API, API Keys, +invocation, LLM invocation, and gRPC routes do not require it. See +[Gateway Routing](./gateway-routing.md#vanity-gateway-optional) for routing and +verification details. + +## Physical Simulation Caches + +For an overview refer to [self-hosted-caches](./caches.md) + +- [Derived Data Cache Service](https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/) - Derived Data Cache Service +- [USD Content Cache](https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/) - USD Content Cache + + +These enhancements are supported for single-cluster Control Plane and GPU-only (BYOC) clusters. + + diff --git a/docs/v0.6.1-rc/quickstart.md b/docs/v0.6.1-rc/quickstart.md new file mode 100644 index 000000000..42e49ba6b --- /dev/null +++ b/docs/v0.6.1-rc/quickstart.md @@ -0,0 +1,352 @@ +# Quickstart: Local k3d Installation + +Install a self-hosted NVCF stack on one local k3d cluster, register that +cluster with the control plane, and confirm that the local deployment is +healthy. + +This quickstart uses a single k3d cluster named `ncp-local`, fake GPUs, and +local route hostnames. It is for local development and validation only. For a +remote deployment, or for separate control-plane and GPU clusters, use +[Helmfile Installation](./helmfile-installation.md) and +[Self-Managed Clusters](./cluster-management/self-managed.md). + +Run the commands from the NVCF repository root unless a step says otherwise. +The `nvcf-cli self-hosted up` command runs on your workstation. It does not run +inside Kubernetes. + +Clone the public repository before you start: + +```bash +git clone https://github.com/nvidia/nvcf.git +cd nvcf +``` + +## Prerequisites + +Before you start, install and prepare: + +- Docker running on your workstation +- `k3d` v5.x or later +- `kubectl` +- `helm` >= 3.14 +- `helmfile` >= 1.0. Use `helmfile` >= 1.5.0 with Helm 4. +- `helm-diff` plugin +- `nvcf-cli` on your `PATH`. See [Installation](./cli.md#installation) to + build it from the repository or download it from NGC. +- An NGC API key with access to the NVCF chart and image registry +- The NGC organization and team slugs for that registry access + +Use these install helpers if you do not already have the local tools. + + +Install Docker from the +[Docker installation guide](https://docs.docker.com/get-started/get-docker/). +After Docker starts, verify that the CLI can reach it: + +```bash +docker version +``` + + + +On macOS with Homebrew: + +```bash +brew install k3d kubectl helm helmfile + +k3d version +kubectl version --client +helm version +helmfile version +``` + +For other systems, use the official installation guides: +[k3d](https://k3d.io/stable/#installation), +[kubectl](https://kubernetes.io/docs/tasks/tools/), +[Helm](https://helm.sh/docs/intro/install/), and +[Helmfile](https://helmfile.readthedocs.io/en/stable/). + + + +Install the Helm plugin used by Helmfile: + +```bash +if helm version --short | grep -q '^v4'; then + helm plugin install https://github.com/databus23/helm-diff --verify=false +else + helm plugin install https://github.com/databus23/helm-diff +fi + +helm plugin list +``` + +See the [helm-diff installation instructions](https://github.com/databus23/helm-diff#install) +for offline or Helm 4 installation options. + + + +Run these commands from the NVCF repository root: + +```bash +./setup.sh +bazel build //src/clis/nvcf-cli:nvcf-cli + +mkdir -p "${HOME}/.local/bin" +install -m 0755 \ + bazel-bin/src/clis/nvcf-cli/nvcf-cli_/nvcf-cli \ + "${HOME}/.local/bin/nvcf-cli" + +export PATH="${HOME}/.local/bin:${PATH}" +nvcf-cli version +``` + +For the packaged CLI release, see [Installation](./cli.md#download-from-ngc). + + +`self-hosted up` defaults to `--env local` and supports only the single local +k3d layout. It requires a current `k3d-*` kube context. + +## Step 1: Create the local k3d cluster + +Export the registry credentials used by the local cluster bootstrap: + +```bash +export NGC_API_KEY="" +export SAMPLE_NGC_ORG="" +export SAMPLE_NGC_TEAM="" +``` + +Create the single-cluster local topology: + +```bash +make -C tools/ncp-local-cluster build-and-deploy-cluster +kubectl config use-context k3d-ncp-local +kubectl config current-context +``` + +Expected output: + +```text +k3d-ncp-local +``` + + +This creates a k3d cluster named `ncp-local`, installs the fake GPU operator, +the CSI SMB driver, and Envoy Gateway, and makes the local kube context +available to `kubectl` and `nvcf-cli`. + + +## Step 2: Prepare local routing + +Add these entries to `/etc/hosts` on the workstation running the CLI if they do +not already resolve: + +```text +127.0.0.1 api.localhost +127.0.0.1 api-keys.localhost +127.0.0.1 sis.localhost +127.0.0.1 invocation.localhost +``` + +Use the local CLI configuration that points commands at the local routes: + +```bash +export NVCF_CLI_CONFIG=deploy/stacks/self-managed/nvcf-cli-local.yaml +``` + +Log in to the NGC container registry: + +```bash +helm registry login nvcr.io -u '$oauthtoken' -p "${NGC_API_KEY}" +``` + + +The local hostnames route CLI traffic through the local Envoy Gateway. The +local CLI configuration keeps later function commands on those local routes +instead of the hosted NVCF endpoints. + + +## Step 3: Create the local stack secrets file + +Create the local secrets files used by the control-plane and compute-plane stacks: + +```bash +cp deploy/stacks/self-managed/secrets/secrets.yaml.template \ + deploy/stacks/self-managed/secrets/local-secrets.yaml + +BASE64_CRED="$(printf '%s' "\$oauthtoken:${NGC_API_KEY}" | base64 | tr -d '\n')" +sed -i.bak "s|REPLACE_WITH_BASE64_DOCKER_CREDENTIAL|${BASE64_CRED}|g" \ + deploy/stacks/self-managed/secrets/local-secrets.yaml +rm deploy/stacks/self-managed/secrets/local-secrets.yaml.bak +``` + + +`local-secrets.yaml` is gitignored. Keep your NGC key out of committed files. + + +## Step 4: Run the install + +Check local tools and Kubernetes access: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted check --pre +``` + +Run the local install: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted up \ + --control-plane-stack=deploy/stacks/self-managed \ + --compute-plane-stack=deploy/stacks/nvcf-compute-plane \ + --env=local \ + --cluster-name=ncp-local \ + --nca-id=nvcf-default \ + --region=us-west-1 \ + --icms-url=http://sis.localhost:8080 \ + --refresh-token +``` + +Expected result: the final screen reports a successful install, a registered +cluster, and a healthy backend. + + +The command installs the control plane, mints CLI authentication, registers +`ncp-local`, installs the compute-plane components, and waits for the +`NVCFBackend` health check to report healthy. + + +## Step 5: Verify the install + +Run the full self-hosted health checks: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted check --all \ + --cluster-name=ncp-local +``` + +Inspect the local Kubernetes resources: + +```bash +kubectl get pods -A +kubectl get httproute -A +kubectl get nvcfbackends -A +``` + +Expected result: the CLI checks do not report failed checks, and the +`ncp-local` `NVCFBackend` reports healthy. + +## Step 6: Deploy and invoke a sample function + +Create a function from the `load_tester_supreme` sample image in the registry +organization and team you exported earlier: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" function create \ + --name quickstart-load-tester-supreme \ + --image "nvcr.io/${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM}/load_tester_supreme:0.0.8" \ + --inference-url /echo \ + --inference-port 8000 \ + --health-uri /health \ + --health-port 8000 \ + --health-timeout PT30S +``` + +Deploy the function to the local fake GPU backend: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" function deploy create \ + --gpu H100 \ + --instance-type NCP.GPU.H100_8x \ + --backend ncp-local \ + --regions us-west-1 \ + --min-instances 1 \ + --max-instances 1 \ + --timeout 900 +``` + +Generate an API key for invocation: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" api-key generate \ + --description quickstart-load-tester-supreme \ + --scopes invoke_function,list_functions,queue_details,list_functions_details +``` + +Invoke the sample function: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" function invoke \ + --request-body '{"message":"quickstart-echo","repeats":1}' \ + --timeout 120 \ + --poll-duration 5 +``` + +Expected result: the invocation response contains `quickstart-echo`. + +For other local fake GPU configurations, choose a `--gpu` and +`--instance-type` that match the discovered node labels and GPU count. + +## Clean up + +Remove the sample function deployment and function: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" function deploy remove +nvcf-cli --config "${NVCF_CLI_CONFIG}" function delete +``` + +Remove the compute-plane components: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted uninstall \ + --compute-plane \ + --cluster-name ncp-local \ + --compute-plane-stack=deploy/stacks/nvcf-compute-plane +``` + +Remove the control plane: + +```bash +nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted uninstall \ + --control-plane \ + --control-plane-stack=deploy/stacks/self-managed +``` + +Destroy the local k3d cluster: + +```bash +make -C tools/ncp-local-cluster destroy +``` + +## Troubleshooting + +If the quickstart fails, start with [Troubleshooting](./troubleshooting.md). + +Common local k3d issues: + +- `sis.localhost` must resolve from the workstation running `nvcf-cli`. +- `kubectl config current-context` must print `k3d-ncp-local`. +- `lookup api-keys.nvcf.nvidia.com: no such host` means the CLI is using the + hosted default endpoint. Run commands with + `--config "${NVCF_CLI_CONFIG}"` from this quickstart. +- `node inotify limits below NVCA minimums` means the local k3d nodes need + higher Linux `fs.inotify` limits. This does not change your macOS shell + limits. From the repository root, apply + `tools/ncp-local-cluster/apps/node-tuning/node-tuning.yaml`, wait for the + `node-tuning` DaemonSet in the `kube-system` namespace, then rerun + `nvcf-cli --config "${NVCF_CLI_CONFIG}" self-hosted check --pre`: + + ```bash + kubectl apply -f tools/ncp-local-cluster/apps/node-tuning/node-tuning.yaml + kubectl -n kube-system rollout status ds/node-tuning --timeout=5m + ``` + + For non-local clusters, see + [Node inotify limits](./cluster-management/self-managed.md#node-inotify-limits). + +## See Also + +- [Local Development](./local-development.md) for local k3d variants and cleanup commands. +- [Helmfile Installation](./helmfile-installation.md) for remote or manual control-plane installs. +- [Self-Managed Clusters](./cluster-management/self-managed.md) for registering GPU clusters outside the local quickstart. +- `src/clis/nvcf-cli/examples/` in this repository for sample CLI input files. diff --git a/docs/v0.6.1-rc/registry-allowlist.md b/docs/v0.6.1-rc/registry-allowlist.md new file mode 100644 index 000000000..762241362 --- /dev/null +++ b/docs/v0.6.1-rc/registry-allowlist.md @@ -0,0 +1,109 @@ +# Allowlisting an Unsupported Registry + +This guide is for customers deploying the `helm-nvcf-api` chart who need to use a container registry that is not in the NVCF API's built-in recognized list. Examples include `ghcr.io`, an internal corporate registry, or any third-party registry that NVCF does not yet natively recognize. + +The procedure registers the registry hostname with the NVCF API as a custom registry. Custom registries are not subject to artifact or credential validation, so function creation no longer fails with `Missing CONTAINER registry for hostname ''`. The same steps work for any registry hostname. Only the `HOSTNAME` value and the registry key (`GHCR`, `INTERNAL`, `MYREG`, etc.) change. + + +Container functions only. This guide covers allowlisting registries used for container function images. Allowlisting registries for Helm-chart functions is not supported today. Support is planned for a later release. + + + +## When to use this + +Use this only if all of the following are true: + +- Your function image is hosted on a registry hostname that NVCF does not natively validate. You are seeing `Missing CONTAINER registry for hostname ''` on function create. +- Your cluster (kubelet or the worker init container) can already pull from that registry. +- You accept that the NVCF API will not pre-validate that the image exists at function-create time. Any pull failure surfaces later as `ErrImagePull` on the workload pod. + +If the registry you need is in the supported list (ECR, NGC, ACR, VolcEngine, Artifactory, Harbor), follow the standard registry guidance instead. See [Working with Third-Party Registries](./third-party-registries.md). + +## Step 1: Apply the Helm env vars + +Pick a short uppercase key for your registry. The key is used only as a config map key. The example below uses `GHCR` for `ghcr.io`. The two env vars register the hostname under the `container` artifact type as a custom registry. + +```bash +# Customize for your environment. +KUBE_CONTEXT="" +CHART_VERSION="" +ORG_PATH="" + +# Customize for the registry you want to allowlist. +REG_KEY="GHCR" +REG_HOSTNAME="ghcr.io" +REG_NAME="GitHub Container Registry" + +helm upgrade api "oci://nvcr.io/${ORG_PATH}/helm-nvcf-api" \ + --version "${CHART_VERSION}" --namespace nvcf \ + --kube-context "${KUBE_CONTEXT}" \ + --reuse-values \ + --set-string "api.env.NVCF_REGISTRIES_RECOGNIZED_CONTAINER_${REG_KEY}_NAME=${REG_NAME}" \ + --set-string "api.env.NVCF_REGISTRIES_RECOGNIZED_CONTAINER_${REG_KEY}_HOSTNAME=${REG_HOSTNAME}" +``` + +`NAME` and `HOSTNAME` are both required by the schema. + +## Step 2: Wait for the rollout + +The NVCF API container is distroless, so a rollout is required for env changes to take effect. After each `helm upgrade`, force the new pod to schedule and wait for it to become ready: + +```bash +kubectl delete pod -n nvcf \ + --kube-context "${KUBE_CONTEXT}" \ + -l app.kubernetes.io/name=helm-nvcf-api \ + --field-selector=status.phase=Running + +kubectl rollout status deployment/nvcf-api -n nvcf \ + --kube-context "${KUBE_CONTEXT}" --timeout=120s +``` + +## Step 3: Verify the env vars are applied + +Use `helm get values` because the API container has no shell: + +```bash +helm get values api -n nvcf --kube-context "${KUBE_CONTEXT}" \ + | grep -A1 "${REG_KEY}" +``` + +You should see both env vars (`NAME` and `HOSTNAME`) under `api.env`. + +## Step 4: Test that function creation now succeeds + +Use the nvcf-cli to create a function pointing at an image on the new registry: + +```bash +IMAGE_PATH="/:" + +nvcf-cli function create \ + --name "registry-allowlist-test-$(date +%s)" \ + --image "${REG_HOSTNAME}/${IMAGE_PATH}" \ + --inference-url "/health" --inference-port 8080 \ + --health-uri "/health" --health-timeout PT30S +``` + +Expected: a 2xx response with a function id and version id. Before this change, the same call returned `400 Missing CONTAINER registry for hostname '${REG_HOSTNAME}'`. + +The create call succeeds even if you have not registered a credential for `${REG_HOSTNAME}` in the NVCF credential store. The workload pod will fail later with `ErrImagePull` if the kubelet cannot pull the image; pulling is the cluster's responsibility, not NVCF's. + +## Rolling back + +To remove the registry, delete the entries from the chart's persisted values: + +```bash +helm get values api -n nvcf --kube-context "${KUBE_CONTEXT}" -o yaml > values.yaml +# Edit values.yaml to remove the api.env.NVCF_REGISTRIES_RECOGNIZED_CONTAINER_${REG_KEY}_* keys +helm upgrade api "oci://nvcr.io/${ORG_PATH}/helm-nvcf-api" \ + --version "${CHART_VERSION}" --namespace nvcf \ + --kube-context "${KUBE_CONTEXT}" \ + -f values.yaml +``` + +Then repeat Step 2 (force rollout) and Step 3 (verify the env vars are gone). After rollback, function create against the registry returns the original `400 Missing CONTAINER registry for hostname` error. + +## Important notes + +This procedure does not pull the image. The cluster is responsible for that. The kubelet (or the worker init container) must already have a way to authenticate to the registry. Common paths are an auto-injected image pull secret on the workload namespace (for example via Kyverno), or a kubelet integration provided by the cloud (for example ECR, GCR, and ACR on CSP-managed clusters where node IAM lets the kubelet pull without an explicit secret). + +The `REG_KEY` you choose is a Spring Boot map key. Use anything short and uppercase that is not already in use by a built-in registry (`DOCKER`, `NGC`, `ECR`, `ECR_PUBLIC`, `VOLCENGINE`, `ACR`). diff --git a/docs/v0.6.1-rc/release-notes/0.5.0-to-0.6.0-upgrade.md b/docs/v0.6.1-rc/release-notes/0.5.0-to-0.6.0-upgrade.md new file mode 100644 index 000000000..1fb591320 --- /dev/null +++ b/docs/v0.6.1-rc/release-notes/0.5.0-to-0.6.0-upgrade.md @@ -0,0 +1,473 @@ +# 0.5.0 to 0.6.0 Upgrade + +The upgrade from 0.5.0 to 0.6.0 crosses two scheduled data migrations and one +required Spot Instance Service cleanup boundary. Run Stops 1 and 2, sync the full 0.6.0 stack +while the Spot Instance Service migration runtime can restore the global NATS streams, and then +run Stop 3. + + +These steps assume 0.5.0 is already deployed and healthy, that you have downloaded the 0.6.0 stack, and that you run `make` from the extracted `nvcf-self-managed-stack/` directory with your environment set. + + +```bash +export HELMFILE_ENV= # for example eks-cds-qa +``` + +## Prerequisite: upgrade to 0.5.1 or later first + + +If you pull NVCF images from a private registry (for example NGC `nvcr.io`) and rely on `global.imagePullSecrets` to deliver the pull secret, move to 0.5.1 or later before you start this procedure. Release 0.5.0 does not propagate `global.imagePullSecrets` to the chart pods, so private migration and sidecar images (for example `nvcf-cassandra-migrations` and `nats-box`) fail to pull with `ImagePullBackOff` / `403`, and a clean 0.5.0 install never becomes healthy. 0.5.1 adds that propagation. If you already run 0.5.1 or later, skip this section. + + +Moving from 0.5.0 to 0.5.1 is a normal patch sync, not a migration stop. Download the 0.5.1 stack and sync it with your existing environment: + +```bash +# Create the pull secret in every NVCF namespace (private registry only). +# The 0.5.1 charts reference this secret by name; without it there is nothing to propagate. +kubectl create namespace cert-manager \ + --dry-run=client -o yaml | kubectl apply -f - + +for ns in cassandra-system nats-system nvcf api-keys ess sis vault-system nvca-operator nvca-system nvcf-backend cert-manager; do + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server= --docker-username='' --docker-password='' \ + -n "$ns" --dry-run=client -o yaml | kubectl apply -f - +done + +# Confirm the env references the secret so every chart inherits it. +# environments/.yaml should contain: +# global: +# imagePullSecrets: +# - name: nvcr-pull-secret + +# Sync the full 0.5.1 stack so every chart re-renders with imagePullSecrets set. +make install HELMFILE_ENV="$HELMFILE_ENV" + +# Confirm the control plane is healthy before starting the 0.6.0 upgrade. +# This should print nothing; any output is a pod that is not yet Running/Completed. +kubectl get pods -A | grep -vE 'Running|Completed|READY' +``` + +## Prerequisite: install nvcf-cli 1.8.3 or later + +Use `nvcf-cli` 1.8.3 or later when you re-register GPU clusters after the control-plane upgrade. Earlier versions return the existing cluster identity without refreshing its OIDC issuer and JSON Web Key Set (JWKS). A cluster upgraded from 0.5.x can therefore retain an empty JWKS and the NVCA agent cannot authenticate. + +Confirm the CLI version before starting the upgrade: + +```bash +nvcf-cli version +``` + +## Prepare the 0.6.0 configuration + +Return to the extracted 0.6.0 stack before running any 0.6.0 sync command. Reconcile the site-specific settings from the 0.5.1 deployment into the 0.6.0 configuration files: + +- `environments/$HELMFILE_ENV.yaml` +- `secrets/$HELMFILE_ENV-secrets.yaml` + +Start from the files or templates included with 0.6.0 and carry forward the required values from 0.5.1. Do not replace the 0.6.0 files wholesale because the available settings can change between releases. Preserve the existing registry, storage, endpoint, credential, and secret values unless this procedure explicitly instructs you to change them. + +Confirm that both files exist before continuing: + +```bash +for file in \ + "environments/${HELMFILE_ENV}.yaml" \ + "secrets/${HELMFILE_ENV}-secrets.yaml"; do + [ -f "$file" ] || { echo "missing required file: $file"; exit 1; } +done +``` + +### Enable OpenBao issuer discovery for CSP clusters + +Cloud service provider (CSP) clusters such as Amazon EKS use an external OpenID Connect (OIDC) issuer for Kubernetes service account tokens. Enable issuer discovery in `environments/$HELMFILE_ENV.yaml` so the OpenBao migration configures the external issuer's JWKS URL: + +```yaml +openbao: + migrations: + issuerDiscovery: + enabled: true +``` + + +Do not continue with the 0.6.0 sync on a CSP cluster while issuer discovery is disabled. The OpenBao migration otherwise falls back to a Kubernetes API server public key that cannot validate tokens signed by an external CSP issuer. New vault-agent logins then fail and prevent service pods from starting. + + +### Enable NATS Gateway routing + +NVCA connects to NATS through the external Gateway on TCP port `4222`. A +Gateway created with an earlier version of the quickstart might not have the +required listener. Set the Gateway name and namespace used by your environment, +label the route-owning namespace, and add the listener if it is missing: + +```bash +export NATS_GATEWAY_NAME=nvcf-gateway +export NATS_GATEWAY_NAMESPACE=envoy-gateway + +kubectl label namespace "$NATS_GATEWAY_NAMESPACE" nvcf/platform=true --overwrite + +if [ "$(kubectl -n "$NATS_GATEWAY_NAMESPACE" get gateway "$NATS_GATEWAY_NAME" \ + -o jsonpath='{.spec.listeners[?(@.name=="nats")].name}')" != "nats" ]; then + kubectl -n "$NATS_GATEWAY_NAMESPACE" patch gateway "$NATS_GATEWAY_NAME" \ + --type=json \ + -p='[{"op":"add","path":"/spec/listeners/-","value":{"name":"nats","protocol":"TCP","port":4222,"allowedRoutes":{"namespaces":{"from":"Selector","selector":{"matchLabels":{"nvcf/platform":"true"}}}}}}]' +fi +``` + +Enable the NATS route in `environments/$HELMFILE_ENV.yaml`. Use the same +Gateway name and namespace as the preceding command: + +```yaml +ingress: + gatewayApi: + routes: + nats: + enabled: true + gateways: + nats: + name: nvcf-gateway + namespace: envoy-gateway +``` + +Sync the ingress release so it creates the NATS `TCPRoute`: + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" HELMFILE_SELECTOR=name=ingress +``` + +Confirm that the listener and route exist and that the Envoy service exposes +port `4222` before continuing: + +```bash +kubectl -n "$NATS_GATEWAY_NAMESPACE" get gateway "$NATS_GATEWAY_NAME" \ + -o jsonpath='{.spec.listeners[?(@.name=="nats")].port}{"\n"}' +kubectl -n "$NATS_GATEWAY_NAMESPACE" get tcproute nats +kubectl get service -n envoy-gateway-system \ + -l "gateway.envoyproxy.io/owning-gateway-name=$NATS_GATEWAY_NAME" \ + -o jsonpath='{.items[0].spec.ports[?(@.port==4222)].port}{"\n"}' +``` + +Both port checks must print `4222`, and the `nats` `TCPRoute` must exist. Do not +continue to the NVCA refresh if any check fails. + +## Stop 1: Cassandra schema migration + + +Keep the Cassandra storage settings unchanged during this upgrade. `global.storageClass` and `global.storageSize` in the upgrade environment must produce the same values as the original installation. These settings render into the Cassandra StatefulSet `volumeClaimTemplates`, which Kubernetes treats as immutable. Changing either value, including introducing a value that was previously unset, causes the Cassandra Helm upgrade to fail. + +Do not use this upgrade procedure to resize Cassandra storage or change its storage class. + + +Before entering the maintenance window, record the storage class and size from the live Cassandra StatefulSet: + +```bash +kubectl -n cassandra-system get statefulset cassandra \ + -o jsonpath='{range .spec.volumeClaimTemplates[*]}{.metadata.name}{": storageClass="}{.spec.storageClassName}{", size="}{.spec.resources.requests.storage}{"\n"}{end}' +``` + +Confirm that the effective `global.storageClass` and `global.storageSize` settings for `HELMFILE_ENV` match the live values. If they differ, stop and restore the settings from the original installation before continuing. + +Quiesce the control plane so no service writes to the database while the migration runs: + +```bash +for ns in nvcf api-keys ess sis; do + kubectl -n "$ns" scale deployment --all --replicas=0 +done +``` + +Sync only the Cassandra release from the 0.6.0 stack. This runs the schema migration: + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" HELMFILE_SELECTOR=name=cassandra +``` + +The migration runs as a Helm `post-upgrade` hook, so this command blocks until the migration Job completes and fails if it does not. A non-zero exit means the migration did not finish; resolve it before continuing. Confirm the migration Job succeeded: + +```bash +kubectl get jobs -n cassandra-system +# the *-migrations Job should report COMPLETIONS 1/1 +``` + +## Stop 2: Spot Instance Service auth-client-id migration + +Stop 2 must reach Spot Instance Service runtime `1.561.1`. This is the released version of the +patched Spot Instance Service runtime that enables the auth-client-id migration task for the +self-hosted Spring profile. The task copies each existing cluster row's +`ssa_client_id` into `auth_client_id`. This runtime also enables the global NATS +stream validation task for the self-hosted Spring profile. + + +Do not deploy Spot Instance Service runtime `1.562.2` or later until this migration is complete. Those versions remove the migration task and expect `auth_client_id` to already be populated. + + +Temporarily override the Spot Instance Service runtime image in +`environments/$HELMFILE_ENV.yaml`: + +```yaml +sis: + image: + tag: 1.561.1 +``` + +Sync only the Spot Instance Service release from the 0.6.0 stack. The `make` target loads the +selected site environment and its matching secrets file: + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" HELMFILE_SELECTOR=name=sis +``` + +Confirm that the Spot Instance Service deployment uses runtime `1.561.1`: + +```bash +kubectl -n sis get deployment spot-instance-service \ + -o jsonpath='{.spec.template.spec.containers[*].image}{"\n"}' +``` + +Scale Spot Instance Service up and wait for it to become ready. Keep the other control-plane deployments stopped while the migration runs: + +```bash +kubectl -n sis scale deployment/spot-instance-service --replicas=1 +kubectl -n sis rollout status deployment/spot-instance-service --timeout=10m +``` + +Enable the auth-client-id migration task on Spot Instance Service: + + +Spot Instance Service already runs with the self-hosted Spring profile set by its chart. Do not +change `SPRING_PROFILES_ACTIVE`. The command below only adds the migration-task +variables and leaves the existing profile untouched. + + +```bash +kubectl set env deployment/spot-instance-service -n sis \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_ENABLED=true \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_LOCK_TTL_IN_SECONDS=60 \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_INITIAL_DELAY_DURATION=PT30S \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_SCHEDULE_DURATION=PT1M +kubectl -n sis rollout status deployment/spot-instance-service --timeout=10m +``` + +Wait at least two minutes for the scheduled task to run: + +```bash +sleep 120 +``` + +Inspect the cluster rows: + +```bash +CASSANDRA_PASSWORD=$(kubectl get secret -n cassandra-system cassandra \ + -o jsonpath='{.data.cassandra-password}' | base64 --decode) +kubectl exec -n cassandra-system cassandra-0 -- \ + cqlsh -u cassandra -p "$CASSANDRA_PASSWORD" \ + -e "SELECT cluster_id, ssa_client_id, auth_client_id FROM sis_api.cluster_by_cluster_id;" +``` + +For every row with a non-empty `ssa_client_id`, `auth_client_id` must contain the same value. Do not continue if any eligible row has an empty or mismatched `auth_client_id`. + +### Enable Spot Instance Service NATS reconnect + +The Spot Instance Service chart included with 0.6.0 does not enable NATS reconnect. Enable it on +the live ConfigMap before the full-stack sync rolls NATS, then restart Spot Instance Service so +the pod reads the new value: + +```bash +kubectl patch configmap spot-instance-service-env -n sis --type merge \ + -p '{"data":{"NATS_RECONNECT_ALLOWED":"true"}}' +kubectl rollout restart deployment/spot-instance-service -n sis +kubectl rollout status deployment/spot-instance-service -n sis --timeout=10m +``` + +Confirm that reconnect is enabled: + +```bash +test "$(kubectl -n sis get configmap spot-instance-service-env \ + -o jsonpath='{.data.NATS_RECONNECT_ALLOWED}')" = "true" +``` + +Do not continue if the command returns a non-zero exit code. A later Spot Instance Service chart +release manages this setting directly, but the 0.6.0 upgrade requires this +one-time patch. + +## Sync the full 0.6.0 stack + +Keep the `sis.image.tag: 1.561.1` override in place and sync the full +0.6.0 stack. This upgrades NATS while Spot Instance Service can recreate the global JetStream +streams if the NATS upgrade removes them: + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" +``` + +Confirm that Spot Instance Service remains ready after the full sync: + +```bash +kubectl -n sis rollout status deployment/spot-instance-service --timeout=10m +``` + +Confirm that the full-stack sync preserved the reconnect setting: + +```bash +test "$(kubectl -n sis get configmap spot-instance-service-env \ + -o jsonpath='{.data.NATS_RECONNECT_ALLOWED}')" = "true" || { + echo "Spot Instance Service NATS reconnect is not enabled; reapply the preceding patch" >&2 + exit 1 +} +``` + +The Spot Instance Service validation task runs every three minutes. During the NATS roll, Spot Instance Service +may log `A JetStream context can't be established during close` while its NATS +client reconnects. This error is transient during reconnection. Wait up to ten +minutes for both global streams to exist: + +```bash +NATS_BOX_POD="$(kubectl -n nats-system get pod \ + -l app.kubernetes.io/component=nats-box \ + -o jsonpath='{.items[0].metadata.name}')" + +for attempt in 0 1 2 3 4 5 6 7 8 9 10; do + if kubectl -n nats-system exec "$NATS_BOX_POD" -- \ + nats --context default stream info TerminateNvcaStream >/dev/null && \ + kubectl -n nats-system exec "$NATS_BOX_POD" -- \ + nats --context default stream info CreateNvcaFunctionTaskStream >/dev/null; then + break + fi + + if [ "$attempt" -eq 10 ]; then + echo "required global NATS streams were not recreated" >&2 + exit 1 + fi + sleep 60 +done + +for stream in TerminateNvcaStream CreateNvcaFunctionTaskStream; do + kubectl -n nats-system exec "$NATS_BOX_POD" -- \ + nats --context default stream info "$stream" +done +``` + +Do not continue unless both stream information commands succeed. + +If the streams are still absent after ten minutes, restart Spot Instance Service to reestablish +its JetStream context, wait for the rollout, and then rerun the preceding +stream check: + +```bash +kubectl rollout restart deployment/spot-instance-service -n sis +kubectl rollout status deployment/spot-instance-service -n sis --timeout=10m +``` + +## Stop 3: upgrade Spot Instance Service to the cleanup runtime + +Remove the temporary `sis.image.tag` override from +`environments/$HELMFILE_ENV.yaml`. Then sync the Spot Instance Service release again. The 0.6.0 +Spot Instance Service chart deploys runtime `1.563.1`. Helm preserves the migration-task +environment variables because Stop 2 added them directly to the live +Deployment. Remove them explicitly after the sync. + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" HELMFILE_SELECTOR=name=sis +kubectl set env deployment/spot-instance-service -n sis \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_ENABLED- \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_LOCK_TTL_IN_SECONDS- \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_INITIAL_DELAY_DURATION- \ + NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_SCHEDULE_DURATION- +kubectl -n sis rollout status deployment/spot-instance-service --timeout=10m +``` + +Confirm that the cleanup-runtime sync preserved the reconnect setting: + +```bash +test "$(kubectl -n sis get configmap spot-instance-service-env \ + -o jsonpath='{.data.NATS_RECONNECT_ALLOWED}')" = "true" || { + echo "Spot Instance Service NATS reconnect is not enabled; reapply the preceding patch" >&2 + exit 1 +} +``` + +Confirm that Spot Instance Service now uses runtime `1.563.1`: + +```bash +kubectl -n sis get deployment spot-instance-service \ + -o jsonpath='{.spec.template.spec.containers[*].image}{"\n"}' +``` + +Confirm that the temporary migration-task variables are no longer present. The +output must not include variables whose names start with +`NVCA_AUTH_CLIENT_ID_MIGRATION_TASK_`: + +```bash +kubectl set env deployment/spot-instance-service -n sis --list +``` + + +Spot Instance Service runtime `1.563.1` does not run periodic global NATS stream validation under +the self-hosted Spring profile. If either global stream is lost after Stop 3, +temporarily restore the `1.561.1` image override, sync Spot Instance Service, repeat the +stream checks above, remove the override, and sync Spot Instance Service back to `1.563.1`. Do not +refresh the NVCA operator while either stream is missing. + + +## Refresh the GPU cluster registration and NVCA operator + +After the control plane is on 0.6.0, use the compute-plane stack bundle shipped for the 0.6.0 release to re-register each GPU cluster and reinstall the NVCA operator so the worker layer reconnects to the upgraded control plane with the migrated `nvca` auth-client identity. `nvcf-default` is the default cluster name; substitute your own where it applies. See [Self-Managed Clusters](../cluster-management/self-managed.md) for the full registration reference. + +Download and extract the `nvcf-compute-plane-stack` bundle listed in the 0.6.0 artifact manifest. Run the remaining commands in this section from the extracted compute-plane stack directory. + + +Do not delete the existing cluster registration. `nvcf-cli` 1.8.3 or later refreshes the OIDC issuer and JWKS while preserving the existing `clusterID` and `clusterGroupID`. Deleting the registration creates a new cluster identity and can orphan existing function deployments. + + +Re-register the cluster with the upgraded control plane (idempotent): + +```bash +make register-cluster HELMFILE_ENV="$HELMFILE_ENV" CLUSTER_NAME=nvcf-default +``` + + +`make register-cluster` defaults to local development values (`CLUSTER_REGION=us-west-1`, `ICMS_URL=http://sis.localhost:8080`) and a sibling CLI checkout. On EKS or any other CSP, pass your real region, the control-plane ingress address, and your CLI build and config so registration reaches the right ICMS and writes correct values: + +Set the ICMS URL and Host header in the CLI configuration. Replace both placeholders with resolved values. Static YAML does not expand shell variables such as `$GATEWAY_ADDR`. + +```yaml +icms_url: "http://" +icms_host: "sis." +``` + +```bash +make register-cluster HELMFILE_ENV="$HELMFILE_ENV" CLUSTER_NAME=nvcf-default \ + CLUSTER_REGION= ICMS_URL=http:// \ + NVCF_CLI= NVCF_CLI_CONFIG= +``` + + +Install the compute-plane stack from the refreshed registration values, then wait for the NVCA operator to roll out: + +```bash +make install HELMFILE_ENV="$HELMFILE_ENV" CLUSTER_NAME=nvcf-default +kubectl -n nvca-operator rollout status deployment/nvca-operator --timeout=10m +``` + +Confirm the backend reconnects and reports healthy: + +```bash +kubectl wait nvcfbackend nvcf-default -n nvca-operator \ + --for=jsonpath='{.status.agentStatus}'=healthy --timeout=10m +``` + +### Recover an NVCA agent that returns HTTP 401 + +If an earlier registration attempt left the cluster JWKS empty, the NVCA agent can repeatedly fail to register with ICMS and return HTTP 401. Rotate the JWKS for the existing cluster instead of deleting its registration: + +```bash +nvcf-cli --config cluster rotate \ + --cluster-id \ + --kubeconfig \ + --icms-url http:// \ + --force +``` + +The command discovers the current OIDC issuer and JWKS from the GPU cluster and updates the existing ICMS row. It preserves the cluster identity. Confirm that the backend becomes healthy after the agent retries: + +```bash +kubectl wait nvcfbackend nvcf-default -n nvca-operator \ + --for=jsonpath='{.status.agentStatus}'=healthy --timeout=10m +``` diff --git a/docs/v0.6.1-rc/release-notes/0.6.0.md b/docs/v0.6.1-rc/release-notes/0.6.0.md new file mode 100644 index 000000000..f2ab855d4 --- /dev/null +++ b/docs/v0.6.1-rc/release-notes/0.6.0.md @@ -0,0 +1,115 @@ +# NVCF Self-Hosted 0.6.0 Release Notes + +NVCF Self-Hosted 0.6.0 is a stable release. It adds asynchronous task +workloads, multi-cluster control-plane management, OpenAI-compatible LLM +routing, single-mode Multi-Instance GPU (MIG) support, and control-plane +observability. It also adds Early Access support for optional NVIDIA Dynamo and +Grove operator integration. + +## Highlights + +| Feature | Status | Summary | +| --- | --- | --- | +| NVIDIA Cloud Tasks (NVCT) | GA | Run asynchronous GPU jobs to completion without keeping an online endpoint. | +| Multi-cluster GPU management | GA | Register and manage multiple GPU clusters from one NVCF control plane. | +| Dynamo operator integration | Early Access, optional | Install optional NVIDIA Dynamo and Grove operators on GPU clusters. | +| LLM API Gateway | GA | Route OpenAI-compatible chat completions, Responses, and embeddings by model. | +| Single-mode MIG | GA | Expose single-strategy MIG profiles as NVCF instance types. | +| Control-plane observability | GA | Export metrics, logs, and optional traces from control-plane services. | + +## NVIDIA Cloud Tasks (NVCT) + +NVIDIA Cloud Tasks (NVCT) adds an asynchronous workload type for +run-to-completion GPU jobs. Tasks are the batch counterpart to Cloud Functions: +functions stay online to serve invocation requests, while tasks run to +completion and release GPU capacity when they finish. + +Use NVCT for batch inference, fine-tuning, evaluation, solvers, data +preparation, and other GPU workloads that do not require an always-on endpoint. + +## Multi-Cluster GPU Management + +A single NVCF control plane can now register and manage multiple GPU clusters. +Each cluster keeps its own identity and configuration, so operators can expand +across regions, cloud providers, or on-premises environments without running a +separate NVCF control plane for each cluster. + +After registration, workloads can deploy to the available clusters through the +same control-plane API. + +## Dynamo Operator Integration + +NVCF can integrate with optional NVIDIA Dynamo and Grove operators installed on +each GPU cluster. KAI Scheduler is a separate prerequisite for using these +components. + +This integration is Early Access and optional. + +## LLM API Gateway + +The LLM API Gateway provides an OpenAI-compatible entry point for LLM workloads +on NVCF. It reads the target function and model from the request's `model` +field, routes chat completions, Responses, and embeddings to the matching model +backend, and applies per-model token rate limits. + +When cache-aware routing is enabled, the gateway can keep multi-turn requests on +the same backend for better cache locality. + +## Single-Mode MIG + +NVCF can detect GPUs configured with the single MIG strategy and expose each MIG +profile as an NVCF instance type. Workloads can target an isolated GPU slice +instead of a full GPU, improving capacity use on supported nodes. + +## Control-Plane Observability + +Control-plane observability is configured through one Helmfile configuration +block. NVCF can export Prometheus-compatible metrics, control-plane logs, and +optional OpenTelemetry Protocol (OTLP) traces to an existing observability +backend, including in air-gapped environments. + +## Upgrade Notes + +For an existing 0.5.x deployment, follow the +[0.5.0 to 0.6.0 Upgrade](./0.5.0-to-0.6.0-upgrade.md) procedure. It covers the +required migration stops, full stack sync, and GPU cluster re-registration +steps. + +Customers with GPU clusters running NVIDIA Cluster Agent (NVCA) versions older +than 2.51.0 must upgrade those clusters to NVCA 2.51.0 before moving to NVCA +3.x. NVCA 2.51.0 includes fixes for workload lifecycle management and GPU +capacity accounting. Skipping this version can cause incorrect GPU resource +accounting, untracked workload pods, or capacity mismatches between the GPU +cluster and the control plane. + +| Current NVCA version | Action | +| --- | --- | +| 2.50.5 or older | Upgrade to 2.51.0 first, verify cluster health, then upgrade to 3.0 or later. | +| 2.51.0 | Upgrade directly to 3.0 or later. | + +Check the current NVCA version: + +```bash +kubectl get nvcfbackend -n nvca-operator +``` + +The `VERSION` column shows the deployed NVCA version. + +Upgrade the NVCA operator to 2.51.0 before continuing to 3.x: + +```bash +helm upgrade nvca-operator -n nvca-operator \ + --reuse-values \ + --wait \ + "${CHART_URL}" \ + --username='$oauthtoken' \ + --password="$(helm get values -n nvca-operator nvca-operator -o json | jq -r '.ngcConfig.serviceKey')" \ + --set helmManaged.nvcaVersion="2.51.0" +``` + +After the upgrade, verify that the cluster is healthy before proceeding: + +```bash +kubectl get nvcfbackend -n nvca-operator +# Verify VERSION shows 2.51.0 and HEALTH shows healthy. +``` diff --git a/docs/v0.6.1-rc/release-notes/index.md b/docs/v0.6.1-rc/release-notes/index.md new file mode 100644 index 000000000..bea1ef89a --- /dev/null +++ b/docs/v0.6.1-rc/release-notes/index.md @@ -0,0 +1,6 @@ +# Release Notes + +Changes, fixes, and upgrade procedures for each self-managed NVCF release. + +- [0.6.0](./0.6.0.md): Stable 0.6.0 feature release and upgrade notes. +- [0.5.0 to 0.6.0 Upgrade](./0.5.0-to-0.6.0-upgrade.md): Upgrade procedure from 0.5.0 to 0.6.0. diff --git a/docs/v0.6.1-rc/runbooks/caches.md b/docs/v0.6.1-rc/runbooks/caches.md new file mode 100644 index 000000000..de9d7e82d --- /dev/null +++ b/docs/v0.6.1-rc/runbooks/caches.md @@ -0,0 +1,54 @@ +# Caches Runbook + +## UCC & DDCS + +Caching is a critical component of the rendering pipeline. However, being that these +services are caches, data loss is tolerated by the rendering pipeline. Offline pods +or missing data will result in reduced simulation performance, i.e. increased load times. + +Each DDCS replica/pod is a partition of the data set. Partial data loss in DDCS +will trigger re-computation for some or all of the data set. + +UCC replicas serve as a pull-through cache for USD assets and are populated on-demand +during scene load time. Each replica will contain a distinct +copy of the source content. Data loss of a pod means traffic serviced by that peer +may see reduced performance as the cache is rebuilt from requests to the source. + +### Cache Co-locality + +For maximum performance it is recommended that caches are deployed as close to the +pods they service as possible. We recommend placing one cache per availability zone +to reduce network transit times between the caches and the render nodes. It is also +recommended to keep all render nodes that are participating in a single simulation +in the same availability zone to further reduce network latency. + +## Kubernetes Pod Health Monitoring with Prometheus + +Monitoring Kubernetes with Prometheus is essential to detect signs of unhealthy +pods before they impact your application. Below are just a few Prometheus metrics +that can be monitored for issues. + +| Metric | Signal | +| --- | --- | +| `kube_pod_status_ready` | Shows if a pod is ready or not to accept traffic. Pods in non-ready states for extended periods of time (e.g. more than 5min) indicate an unresolved problem. | +| `kube_pod_container_status_restarts_total` | Counts total restarts per container. High or increasing values indicate crashes or instability. Cache pods are long running processes and do not restart unless they encounter fatal errors. | +| `kube_pod_container_status_waiting_reason` | Waiting reasons such as `CrashLoopBackOff`, indicate persistent failures and restarts in the cache applications. | +| `kube_pod_status_phase` | When pod status is not `Running` for an extended period, this indicates a persistent failure state. | +| `container_cpu_usage_seconds_total` | Although more difficult to monitor, cache pods that consume large amounts of CPU cycles without traffic may indicate compaction issues. | + +## Removing Unhealthy Pods and PVCs + +Cache pods maintain a persistent data set. When cache services are in an unhealthy +state, (due to crashes, restarts etc.) it is recommended to delete the pod(s) and +storage to attempt to return the service to normal operation. + +1. Delete the PVCs for the Pod *before* restarting it: + + `kubectl delete pvc -n ` + +2. Delete the Pod: + + `kubectl delete pod -n ` + +Monitor the offending pod(s) and ensure that Kubernetes correctly re-creates and +attaches new PVCs to recreated Cache pod(s). diff --git a/docs/v0.6.1-rc/runbooks/control-plane-key-rotation-mek.md b/docs/v0.6.1-rc/runbooks/control-plane-key-rotation-mek.md new file mode 100644 index 000000000..9b8871a16 --- /dev/null +++ b/docs/v0.6.1-rc/runbooks/control-plane-key-rotation-mek.md @@ -0,0 +1,215 @@ +# MEK (Master Encryption Key) Rotation + +## Overview + +The **Master Encryption Key (MEK)** is an AES-256-GCM key stored in OpenBAO that wraps all Namespace Encryption Keys (NEKs). The MEK is shared across NVCF services in the control plane -- ESS is its primary consumer. + +Rotate the MEK on a regular schedule (for example, every 90 days) or when required by your security policy. + +## Prerequisites + +- `kubectl` configured for your NVCF control plane cluster +- Access to OpenBAO pods in the `vault-system` namespace +- The OpenBAO root token (stored in the `openbao-server-root-token` secret in `vault-system`) +- A tool to generate UUIDs (`uuidgen` or equivalent) +- `base64`, `python3`, or `jq` for JSON manipulation +- A maintenance window; MEK rotation can briefly affect availability + +## Where the MEK is Stored + +The MEK is stored in the `services/all/kv/` KV v2 secret engine in OpenBAO, at the path: + +`encryption/keys/stored_data` + +This path contains four fields: + +| Field | Description | +| --- | --- | +| `keys` | Base64-encoded JSON object containing an array of MEK keys. Each key is a JWK with fields: `kty` (`oct`), `use` (`enc`), `kid` (UUID), `k` (base64url-encoded 256-bit key), `alg` (`A256GCM`). | +| `current_kid` | The `kid` of the active MEK used for encryption. Must match the first key in the `keys` array. | +| `jwe_mapping` | JSON string mapping the active key ID, e.g. `{"payload_jwe_kid":""}`. | +| `private_jwks` | A separate key set used by other NVCF services. Update this field alongside `keys` during rotation. | + +## Inspecting Current State + +**Retrieve the OpenBAO root token:** + +```bash +VAULT_TOKEN=$(kubectl get secret -n vault-system openbao-server-root-token \ + -o jsonpath='{.data.root_token}' | base64 -d) +``` + +**Read the current MEK data:** + +```bash +kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get \ + services/all/kv/encryption/keys/stored_data" +``` + +**Decode the keys array to see the current MEK(s):** + +```bash +kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get \ + -field=keys services/all/kv/encryption/keys/stored_data" \ + | base64 -d | python3 -m json.tool +``` + +You should see output like: + +```json +{ + "keys": [ + { + "kty": "oct", + "use": "enc", + "kid": "fa85bab2-fccd-11f0-b875-82c3b2df389e", + "k": "", + "alg": "A256GCM" + } + ] +} +``` + +## MEK Rotation Procedure + + +Do not remove the old MEK from the keys array. ESS needs the old MEK to decrypt existing NEKs during the transition. + + + +1. **Read the current stored_data** and save it as a backup: + + ```bash + VAULT_TOKEN=$(kubectl get secret -n vault-system openbao-server-root-token \ + -o jsonpath='{.data.root_token}' | base64 -d) + + kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get -format=json \ + services/all/kv/encryption/keys/stored_data" > mek_backup.json + ``` + +2. **Generate a new MEK key ID and key material:** + + ```bash + NEW_KID=$(python3 -c "from uuid import uuid1; kid = str(uuid1()); print(kid)") + NEW_KEY=$(python3 -c "import secrets, base64; \ + key = secrets.token_bytes(32); \ + print(base64.urlsafe_b64encode(key).rstrip(b'=').decode())") + echo "New kid: $NEW_KID" + echo "New key: $NEW_KEY" + ``` + +3. **Build the updated keys JSON** with the new key as the first element: + + ```bash + # Extract current keys + CURRENT_KEYS_B64=$(kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get \ + -field=keys services/all/kv/encryption/keys/stored_data") + + # Build new keys array (new key first, then existing keys) + UPDATED_KEYS_B64=$(echo "$CURRENT_KEYS_B64" | base64 -d | python3 -c " + import sys, json, base64 + data = json.load(sys.stdin) + new_key = { + 'kty': 'oct', + 'use': 'enc', + 'kid': '$NEW_KID', + 'k': '$NEW_KEY', + 'alg': 'A256GCM' + } + data['keys'].insert(0, new_key) + print(base64.b64encode(json.dumps(data).encode()).decode()) + ") + ``` + +4. **Write the updated values back to OpenBAO:** + + ```bash + NEW_JWE_MAPPING="{\"payload_jwe_kid\":\"$NEW_KID\"}" + + kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv put \ + services/all/kv/encryption/keys/stored_data \ + keys='$UPDATED_KEYS_B64' \ + current_kid='$NEW_KID' \ + jwe_mapping='$NEW_JWE_MAPPING' \ + private_jwks='$UPDATED_KEYS_B64'" + ``` + +5. **Verify the update:** + + ```bash + kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get \ + -field=current_kid services/all/kv/encryption/keys/stored_data" + ``` + + The output should show your new key ID (`$NEW_KID`). + +## Verification + +After completing MEK rotation: + +- OpenBAO shows the updated `current_kid`: + + ```bash + kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv get \ + -field=current_kid services/all/kv/encryption/keys/stored_data" + ``` + +- ESS pods are `Running` and `Ready` with no MEK/decryption errors: + + ```bash + kubectl get pods -n ess + kubectl logs -n ess -l app.kubernetes.io/name=helm-nvcf-ess-api \ + -c helm-nvcf-ess-api --tail=200 | grep -i error + ``` + +- Secrets can still be read and written through the NVCF API. + +## MEK Propagation Grace Period + +After writing the new MEK to OpenBAO, ESS does **not** start using it immediately. Each ESS pod refreshes its secrets from OpenBAO via the `vault-agent` sidecar container roughly every **24 hours**. Because different pods refresh at different times, there is a default grace period of **48 hours** before the new MEK is actively used for encryption. + +This grace period prevents a race condition: if pod A picks up the new MEK and uses it to write encrypted data to the database before pod B has refreshed, pod B would be unable to decrypt that data because it still only has the old MEK loaded. + + +- Do **not** remove the old MEK from the `keys` array as ESS needs the old MEK to decrypt existing NEKs during the transition. +- During the grace period the old MEK remains the active encryption key; the new MEK is present in the key set but not yet used for writes. +- After the grace period, ESS switches to the new MEK for all new encryption operations while retaining the old MEK for decrypting previously encrypted data. + + + +## Rollback + +If the rotation causes issues: + +1. Restore the previous `keys`, `current_kid`, `jwe_mapping`, and `private_jwks` from the backup (`mek_backup.json`) taken in Step 1: + + ```bash + # Extract original values from backup + ORIG_KEYS=$(python3 -c "import json; d=json.load(open('mek_backup.json')); print(d['data']['data']['keys'])") + ORIG_KID=$(python3 -c "import json; d=json.load(open('mek_backup.json')); print(d['data']['data']['current_kid'])") + ORIG_JWE=$(python3 -c "import json; d=json.load(open('mek_backup.json')); print(d['data']['data']['jwe_mapping'])") + ORIG_PRIV=$(python3 -c "import json; d=json.load(open('mek_backup.json')); print(d['data']['data']['private_jwks'])") + + kubectl exec -n vault-system openbao-server-0 -c openbao -- \ + sh -c "VAULT_TOKEN=$VAULT_TOKEN bao kv put \ + services/all/kv/encryption/keys/stored_data \ + keys='$ORIG_KEYS' \ + current_kid='$ORIG_KID' \ + jwe_mapping='$ORIG_JWE' \ + private_jwks='$ORIG_PRIV'" + ``` + +2. Restart ESS to pick up the restored MEK: + + ```bash + kubectl rollout restart deployment -n ess ess-api-helm-nvcf-ess-api-deployment + ``` + +3. Do not remove the old key from the keys array until the new key has been verified and ESS is healthy. diff --git a/docs/v0.6.1-rc/runbooks/index.md b/docs/v0.6.1-rc/runbooks/index.md new file mode 100644 index 000000000..9fb4905a4 --- /dev/null +++ b/docs/v0.6.1-rc/runbooks/index.md @@ -0,0 +1,10 @@ +--- +--- + +# Runbooks + +A service runbook is a concise, structured document that outlines the standard operating procedures for running, troubleshooting, and maintaining various services. + +Its purpose is to give engineers (especially on-call or incident responders) the exact steps needed to quickly understand the service, handle common problems, and recover from failures without having to dig through large amounts of documentation. + +- [Control Plane Key Rotation](./control-plane-key-rotation-mek.md) diff --git a/docs/v0.6.1-rc/runbooks/overview.md b/docs/v0.6.1-rc/runbooks/overview.md new file mode 100644 index 000000000..37d18bcd6 --- /dev/null +++ b/docs/v0.6.1-rc/runbooks/overview.md @@ -0,0 +1,8 @@ +--- +--- + +# Runbooks + +A service runbook is a concise, structured document that outlines the standard operating procedures for running, troubleshooting, and maintaining various services. + +Its purpose is to give engineers (especially on-call or incident responders) the exact steps needed to quickly understand the service, handle common problems, and recover from failures — without having to dig through large amounts of documentation. diff --git a/docs/v0.6.1-rc/samples/configs/cp-env-eks-example.yaml b/docs/v0.6.1-rc/samples/configs/cp-env-eks-example.yaml new file mode 100644 index 000000000..c88ea7da9 --- /dev/null +++ b/docs/v0.6.1-rc/samples/configs/cp-env-eks-example.yaml @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +global: + + # Domain for external access (used by Gateway API HTTPRoutes) + domain: "GATEWAY_ADDR" # Replace with ELB domain + + # ============================================================================= + # Helm Chart Sources Configuration + # ============================================================================= + # Configure the OCI registry where NVCF Helm charts are stored. + # This must point to a registry containing the NVCF chart packages. + # ============================================================================= + helm: + sources: + registry: .dkr.ecr..amazonaws.com + repository: + # NGC Example: + # registry: nvcr.io + # repository: YOUR_ORG/YOUR_TEAM # e.g. 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: + + # ============================================================================= + # Container Image Registry Configuration + # ============================================================================= + # Configure the container registry where NVCF service images are stored. + # These images are pulled by Kubernetes when deploying the NVCF stack. + # ============================================================================= + image: + registry: .dkr.ecr..amazonaws.com + repository: + # NGC Example: + # registry: nvcr.io + # repository: YOUR_ORG/YOUR_TEAM # e.g. 123456789102/YOUR_TEAM + # ECR Example: + # registry: .dkr.ecr..amazonaws.com + # repository: + + nodeSelectors: + enabled: true # Set true when using dedicated node labels for NVCF workloads + vault: + key: nvcf.nvidia.com/workload + value: vault + cassandra: + key: nvcf.nvidia.com/workload + value: cassandra + controlplane: + key: nvcf.nvidia.com/workload + value: control-plane + + storageClass: "gp3" # Customize to your storage class + storageSize: "10Gi" # Customize to your storage size + + # ============================================================================= + # Observability Configuration + # ============================================================================= + # Enable distributed tracing via OTLP (dsiabled by default). + # This must point to an OTLP-compatible collector. + # ============================================================================= + observability: + tracing: + enabled: false + collectorEndpoint: "" + collectorPort: 4317 + collectorProtocol: http + # Example: + # enabled: true + # collectorEndpoint: + # collectorPort: + # collectorProtocol: + +fakeGpuOperator: + enabled: false # If deploying locally with no GPUs, true + ubuntu: + imageName: alpine-k8s + tag: 1.30.12 + +accounts: # Default NVCF account configuration + limits: + maxFunctions: 10 + maxTasks: 10 # Note: Tasks (NVCT) are not currently supported for EA + maxTelemetries: 10 # Note: BYOO is not currently supported for EA + maxRegistryCreds: 10 + +# These static global values are processed in the values template +nats: + enabled: true + +cassandra: + enabled: true + +openbao: + enabled: true + migrations: + issuerDiscovery: + enabled: true # Recommended true for EKS - discovers OIDC issuer automatically + +# Ingress Gateway Configuration +ingress: + gatewayApi: + enabled: true + controllerNamespace: "envoy-gateway-system" # must be set by the environment + routes: + nvcfApi: + routeAnnotations: {} + apiKeys: + routeAnnotations: {} + invocation: + routeAnnotations: {} + grpc: + routeAnnotations: {} + gateways: + shared: + name: "nvcf-gateway" # must be set by the environment + namespace: "envoy-gateway" # must be set by the environment + listenerName: http + grpc: + name: "nvcf-gateway" # must be set by the environment + namespace: "envoy-gateway" # must be set by the environment + listenerName: tcp diff --git a/docs/v0.6.1-rc/samples/configs/cp-example-secrets.yaml b/docs/v0.6.1-rc/samples/configs/cp-example-secrets.yaml new file mode 100644 index 000000000..cd722128a --- /dev/null +++ b/docs/v0.6.1-rc/samples/configs/cp-example-secrets.yaml @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Required structure for any environment secrets. +# This is the minimal set of values to provide. + +# Notes: +# Cassandra: +# The password should match the value set in the cassandra keyspace migrations +# +# API: +# The value for the registry will be used in three places, as it is +# expected the same registry is used as a single source for all images. +# openbao.migrations.env[1].value +# api.accountBootstrap.registryCredentials[0].secret.value +# api.accountBootstrap.registryCredentials[1].secret.value + +openbao: + migrations: + env: + # Stored in OpenBao shared secrets (written by migration job) + - name: DEFAULT_CASSANDRA_PASSWORD + value: "ch@ng3m3" + # Stored in OpenBao KV for nvcf-api (written by migration job) + - name: NVCF_API_SIDECARS_IMAGE_PULL_SECRET + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + - name: ADMIN_CLIENT_ID + value: ncp # <- keep this value + +api: + accountBootstrap: + registryCredentials: + - registryHostname: nvcr.io # ECR: .dkr.ecr..amazonaws.com + secret: + name: nvcr-containers # ECR: ecr-containers + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + artifactTypes: ["CONTAINER"] + tags: [] + description: "NGC Container registry" + - registryHostname: helm.ngc.nvidia.com # ECR: .dkr.ecr..amazonaws.com + secret: + name: nvcr-helmcharts # ECR: ecr-helmcharts + value: REPLACE_WITH_BASE64_DOCKER_CREDENTIAL # Replace with base64 credentials (ex. NGC / ECR / etc.) for your registry, refer to Working with Third-Party Registries. + artifactTypes: ["HELM"] + tags: [] + description: "NGC Helm registry" diff --git a/docs/v0.6.1-rc/samples/configs/local-dev-env.yaml b/docs/v0.6.1-rc/samples/configs/local-dev-env.yaml new file mode 100644 index 000000000..c5c66794e --- /dev/null +++ b/docs/v0.6.1-rc/samples/configs/local-dev-env.yaml @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# NVCF Self-Hosted Local Development Environment +# For use with k3d clusters. See the Local Development guide for setup instructions. +# +# Save this file as environments/.yaml in your nvcf-self-managed-stack directory. +# Create a matching secrets/-secrets.yaml file with your registry credentials. +# Deploy with: HELMFILE_ENV= helmfile sync + +global: + # Domain for local access (routes use .localhost TLD) + domain: "localhost" + + # Helm chart registry (where helmfile pulls OCI charts from) + helm: + sources: + registry: nvcr.io + repository: nvidia/nvcf + + # Container image registry (where Kubernetes pulls images from) + image: + registry: nvcr.io + repository: nvidia/nvcf + + # Pull secret created by create-nvcr-pull-secrets.sh (run once before deploying) + imagePullSecrets: + - name: nvcr-pull-secret + + # Disable node selectors for local development (pods schedule on any node) + nodeSelectors: + enabled: false + + # k3d uses the local-path StorageClass by default + storageClass: local-path + storageSize: 2Gi + + observability: + tracing: + enabled: false + collectorEndpoint: "" + collectorPort: 4317 + collectorProtocol: http + +# Single Cassandra replica for local development +cassandra: + enabled: true + replicaCount: 1 + jvm: + # Fast startup options -- only safe with a single replica. + # Do NOT use these settings with multiple replicas. + extraOpts: "-Dcassandra.superuser_setup_delay_ms=100 -Dcassandra.gossip_settle_min_wait_ms=1000" + +nats: + enabled: true + +openbao: + enabled: true + migrations: + issuerDiscovery: + enabled: true + +# Gateway configuration matching the local k3d setup script +ingress: + gatewayApi: + enabled: true + controllerNamespace: envoy-gateway-system + routes: + nvcfApi: + routeAnnotations: {} + apiKeys: + routeAnnotations: {} + invocation: + routeAnnotations: {} + grpc: + routeAnnotations: {} + gateways: + shared: + name: shared-gw + namespace: envoy-gateway-system + listenerName: http + grpc: + name: grpc-gw + namespace: envoy-gateway-system + listenerName: tcp diff --git a/docs/v0.6.1-rc/samples/scripts/create-nvcr-pull-secrets.sh b/docs/v0.6.1-rc/samples/scripts/create-nvcr-pull-secrets.sh new file mode 100644 index 000000000..62c461d40 --- /dev/null +++ b/docs/v0.6.1-rc/samples/scripts/create-nvcr-pull-secrets.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +export NGC_API_KEY="" + +for ns in cassandra-system nats-system nvcf api-keys ess sis vault-system nvca-operator nvca-system nvcf-backend; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - +done + +for ns in cassandra-system nats-system nvcf api-keys ess sis vault-system nvca-operator nvca-system nvcf-backend; do + kubectl create secret docker-registry nvcr-pull-secret \ + --docker-server=nvcr.io \ + --docker-username='$oauthtoken' \ + --docker-password="$NGC_API_KEY" \ + --namespace="$ns" \ + --dry-run=client -o yaml | kubectl apply -f - +done diff --git a/docs/v0.6.1-rc/samples/scripts/force-cleanup-nvcf.sh b/docs/v0.6.1-rc/samples/scripts/force-cleanup-nvcf.sh new file mode 100644 index 000000000..58f0bd502 --- /dev/null +++ b/docs/v0.6.1-rc/samples/scripts/force-cleanup-nvcf.sh @@ -0,0 +1,238 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# ============================================================================= +# force-cleanup-nvcf.sh - NVCA Component Removal Script +# ============================================================================= +# This script forcefully removes all NVCA components from a cluster. +# Use this as a LAST RESORT when normal cleanup methods fail due to: +# - Stuck finalizers on namespaces or custom resources +# - Orphaned resources blocking deletion +# - Partial deployments that need complete removal +# +# WARNING: This script will FORCEFULLY remove all NVCA resources, including +# removing finalizers which bypasses normal cleanup procedures. +# +# Usage: ./force-cleanup-nvcf.sh [--dry-run] +# ============================================================================= + +set -euo pipefail + +# --- Configuration --- +# NVCA-related namespaces +NVCA_NAMESPACES=( + "nvcf-backend" + "nvca-system" + "nvca-operator" +) + +# CRDs created by NVCA components +NVCA_CRDS=( + "nvcfbackends.nvcf.nvidia.io" +) + +# --- Parse Arguments --- +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + echo "Usage: $0 [--dry-run]" + echo "" + echo "Options:" + echo " --dry-run Show what would be deleted without making changes" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +echo "==============================================" +echo "NVCA Force Cleanup Script" +echo "==============================================" +if $DRY_RUN; then + echo "MODE: DRY-RUN (no changes will be made)" +fi +echo "" + +# --- Step 1: Show and delete function pods in nvcf-backend --- +echo ">>> Step 1: Checking for function pods in nvcf-backend namespace..." +if kubectl get namespace nvcf-backend >/dev/null 2>&1; then + pods=$(kubectl get pods -n nvcf-backend -o name 2>/dev/null || true) + if [[ -n "$pods" ]]; then + echo " Found the following pods that will be deleted:" + kubectl get pods -n nvcf-backend -o wide 2>/dev/null || true + echo "" + if ! $DRY_RUN; then + echo " Deleting all pods in nvcf-backend..." + kubectl delete pods -n nvcf-backend --all --force --grace-period=0 2>/dev/null || true + else + echo "[DRY-RUN] Would delete all pods in nvcf-backend namespace" + fi + else + echo " No pods found in nvcf-backend namespace" + fi +else + echo " nvcf-backend namespace not found, skipping..." +fi +echo "" + +# --- Step 2: Delete NVCFBackend Custom Resources --- +echo ">>> Step 2: Deleting NVCFBackend custom resources..." +if kubectl get crd nvcfbackends.nvcf.nvidia.io >/dev/null 2>&1; then + echo " Found NVCFBackend CRD, deleting all instances..." + if ! $DRY_RUN; then + kubectl delete nvcfbackends -A --all --wait=false 2>/dev/null || true + echo " Waiting 15 seconds for operator cleanup..." + sleep 15 + else + echo "[DRY-RUN] Would delete all NVCFBackends and wait for cleanup" + fi +else + echo " NVCFBackend CRD not found, skipping..." +fi +echo "" + +# --- Step 3: Delete Helm Releases --- +echo ">>> Step 3: Deleting Helm releases in NVCA namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + releases=$(helm list -n "$ns" -q 2>/dev/null || true) + if [[ -n "$releases" ]]; then + for release in $releases; do + echo " Deleting Helm release: $release (namespace: $ns)" + if ! $DRY_RUN; then + helm delete -n "$ns" "$release" --wait=false 2>/dev/null || true + fi + done + fi + fi +done +echo "" + +# --- Step 4: Force-delete stuck NVCFBackend resources (remove finalizers) --- +echo ">>> Step 4: Removing finalizers from stuck NVCFBackend resources..." +if kubectl get crd nvcfbackends.nvcf.nvidia.io >/dev/null 2>&1; then + nvcfbackends=$(kubectl get nvcfbackends -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null || true) + if [[ -n "$nvcfbackends" ]]; then + while IFS= read -r backend; do + if [[ -n "$backend" ]]; then + ns=$(echo "$backend" | cut -d'/' -f1) + name=$(echo "$backend" | cut -d'/' -f2) + echo " Removing finalizers from NVCFBackend: $name (namespace: $ns)" + if ! $DRY_RUN; then + kubectl patch nvcfbackend "$name" -n "$ns" -p '{"metadata":{"finalizers":[]}}' --type=merge 2>/dev/null || true + kubectl delete nvcfbackend "$name" -n "$ns" --wait=false 2>/dev/null || true + fi + fi + done <<< "$nvcfbackends" + else + echo " No stuck NVCFBackend resources found" + fi +else + echo " NVCFBackend CRD not found, skipping..." +fi +echo "" + +# --- Step 5: Delete Namespaces --- +echo ">>> Step 5: Deleting NVCA namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + echo " Deleting namespace: $ns" + if ! $DRY_RUN; then + kubectl delete namespace "$ns" --wait=false 2>/dev/null || true + fi + fi +done +echo " Waiting 10 seconds for namespace deletion..." +if ! $DRY_RUN; then + sleep 10 +fi +echo "" + +# --- Step 6: Force-remove finalizers from stuck namespaces --- +echo ">>> Step 6: Removing finalizers from stuck namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + phase=$(kubectl get namespace "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || true) + if [[ "$phase" == "Terminating" ]]; then + echo " Namespace $ns is stuck in Terminating, removing finalizers..." + if ! $DRY_RUN; then + # First, try to remove finalizers from all resources in the namespace + for resource_type in deployments statefulsets daemonsets replicasets pods services configmaps secrets serviceaccounts roles rolebindings; do + kubectl get "$resource_type" -n "$ns" -o name 2>/dev/null | while read -r resource; do + kubectl patch "$resource" -n "$ns" -p '{"metadata":{"finalizers":[]}}' --type=merge 2>/dev/null || true + done + done + + # Remove namespace finalizers using the API + kubectl get namespace "$ns" -o json | \ + jq '.spec.finalizers = []' | \ + kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f - 2>/dev/null || true + fi + fi +done +echo "" + +# --- Step 7: Delete CRDs --- +echo ">>> Step 7: Deleting NVCA CRDs..." +for crd in "${NVCA_CRDS[@]}"; do + if kubectl get crd "$crd" >/dev/null 2>&1; then + echo " Deleting CRD: $crd" + if ! $DRY_RUN; then + kubectl delete crd "$crd" --wait=false 2>/dev/null || true + fi + fi +done +echo "" + +# --- Step 8: Verification --- +echo ">>> Step 8: Verification..." +echo "" +echo "Remaining NVCA namespaces:" +remaining_ns=0 +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + phase=$(kubectl get namespace "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown") + echo " - $ns (status: $phase)" + remaining_ns=$((remaining_ns + 1)) + fi +done +if [[ $remaining_ns -eq 0 ]]; then + echo " None - all namespaces removed successfully" +fi + +echo "" +echo "Remaining NVCA CRDs:" +remaining_crds=0 +for crd in "${NVCA_CRDS[@]}"; do + if kubectl get crd "$crd" >/dev/null 2>&1; then + echo " - $crd" + remaining_crds=$((remaining_crds + 1)) + fi +done +if [[ $remaining_crds -eq 0 ]]; then + echo " None - all CRDs removed successfully" +fi + +echo "" +echo "==============================================" +if $DRY_RUN; then + echo "DRY-RUN complete. No changes were made." +else + if [[ $remaining_ns -eq 0 ]] && [[ $remaining_crds -eq 0 ]]; then + echo "Cleanup complete! All NVCA resources have been removed." + else + echo "Cleanup finished with some resources remaining." + echo "You may need to run this script again or investigate manually." + fi +fi +echo "==============================================" diff --git a/docs/v0.6.1-rc/streaming-functions.md b/docs/v0.6.1-rc/streaming-functions.md new file mode 100644 index 000000000..12c0bb193 --- /dev/null +++ b/docs/v0.6.1-rc/streaming-functions.md @@ -0,0 +1,448 @@ +# Low Latency Streaming (LLS/WebRTC) Functions + +Cloud Functions supports the ability to stream video, audio, and other data using WebRTC. + +![Low Latency Streaming Diagram](images/low-latency-streaming.png) + +For complete examples of LLS streaming functions, contact your NVIDIA representative for access to sample function containers. + +## Cloud Provider Network Requirements + +WebRTC streaming uses UDP NodePort services to carry media traffic between +the browser client and the streaming application. Cloud providers apply +network security rules at multiple layers. If any layer blocks UDP inbound +traffic on the NodePort range (30000-32767), streaming sessions fail even +though the function shows ACTIVE. + +### Azure (AKS) + +AKS attaches two Network Security Groups (NSGs) to every node: one on the +subnet and one on the NIC in the managed resource group +(`MC___`). Both NSGs must allow inbound UDP +traffic on the NodePort range. The subnet NSG alone is not sufficient. + +After `helmfile sync` completes, add the NIC NSG rule: + +```bash +# Identify the managed resource group +MC_RG="MC_${RESOURCE_GROUP}_${CLUSTER_NAME}_${LOCATION}" + +# Add the UDP rule to every NIC NSG in the managed resource group. +# Multi-pool clusters have one NSG per pool; streaming pods may +# schedule on any pool, so all NSGs need the rule. +for NIC_NSG in $(az network nsg list -g "$MC_RG" --query "[].name" -o tsv); do + echo "Adding UDP rule to NSG: $NIC_NSG" + az network nsg rule create -g "$MC_RG" --nsg-name "$NIC_NSG" \ + -n allow-udp-nodeports-webrtc --priority 510 \ + --direction Inbound --access Allow --protocol Udp \ + --source-address-prefix Internet --source-port-range "*" \ + --destination-address-prefix "*" --destination-port-range "30000-32767" +done +``` + +Verify the rules are present: + +```bash +# Subnet NSG (your own, created during cluster setup) +az network nsg rule list -g "$RESOURCE_GROUP" --nsg-name "$SUBNET_NSG" \ + --query "[?protocol=='Udp']" -o table + +# NIC NSGs (AKS-managed, one per node pool) +MC_RG="MC_${RESOURCE_GROUP}_${CLUSTER_NAME}_${LOCATION}" +for NIC_NSG in $(az network nsg list -g "$MC_RG" --query "[].name" -o tsv); do + echo "=== $NIC_NSG ===" + az network nsg rule list -g "$MC_RG" --nsg-name "$NIC_NSG" \ + --query "[?protocol=='Udp']" -o table +done +``` + +If the NIC NSG rule is missing, WebRTC clients receive +`NVST_R_GENERIC_ERROR` while the function remains ACTIVE. See +[streaming-troubleshooting](./troubleshooting.md#webrtc-streaming-fails-after-function-shows-active) +for diagnosis steps. + +### AWS (EKS) + +For LLS streaming on EKS, security group and NLB configuration is covered +in [LLS Installation](./lls-installation.md#security-group). + +## Building the Streaming Server Application + +The streaming application needs to be packaged inside a container and should be leveraging the StreamSDK. The streaming application needs to follow the below guidelines: + +1. Expose an HTTP server at port `CONTROL_SERVER_PORT` with following 2 endpoints: + + 1. **Health endpoint:** This endpoint should return 200 HTTP status code only when the streaming application container is ready to start streaming a session. If the streaming application container doesn't want to serve any more streaming sessions of current container deployment this endpoint should return HTTP status code 500. + + ```yaml + Request: + Endpoint: + GET /v1/streaming/ready + + Responses: + Status code: + 200 OK + 500 Internal Server Error + ``` + + 2. **STUN creds endpoint**: This endpoint should accept the access details and credentials for STUN server and keep it cached in the memory of the streaming application. When the streaming requests comes, the streaming application can use these access details and credentials to communicate with STUN server and request for opening of ports for streaming. + + ```yaml + Request: + Endpoint: + POST /v1/streaming/creds + Headers: + Content-Type: application/json + Request Body: + { + "stunIp": "", + "stunPort": , + "username": "", + "password": "" + } + Responses: + Status code: + 200 OK + ``` + +2. Expose a server at port `STREAMING_SERVER_PORT` to accepting WebSocket connection + + 1. An endpoint `STREAMING_START_ENDPOINT` should be exposed by this server + +3. Post websocket connection establishment guidelines: + + 1. When the browser client requests for opening port for specific protocols (e.g. WebRTC), the streaming application needs to request STUN server to open port. This port should be in the range of 47998 and 48020 which would be referred as `STREAMING_PORT_BINDING_RANGE` in this doc. + +4. Containerization guidelines: + + 1. The container should make sure that the `CONTROL_SERVER_PORT`, `STREAMING_SERVER_PORT` and `STREAMING_PORT_BINDING_RANGE` are exposed by the container and accessible from outside the container. + 2. If multiple sessions one after another needs to be supported with a fresh start of container, then exit the container after a streaming session ends. + +## Creating the LLS Streaming Function + +When creating the function, ensure `functionType` is set to `STREAMING`: + +```bash +curl -s -X POST "http://${GATEWAY_ADDR}/v2/nvcf/functions" \ + -H "Host: api.${GATEWAY_ADDR}" \ + -H "Authorization: Bearer $NVCF_TOKEN" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "name": "'$STREAMING_FUNCTION_NAME'", + "inferenceUrl": "/sign_in", + "inferencePort": '$STREAMING_SERVER_PORT', + "health": { + "protocol": "HTTP", + "uri": "/v1/streaming/ready", + "port": '$CONTROL_SERVER_PORT', + "timeout": "PT10S", + "expectedStatusCode": 200 + }, + "containerImage": "'$STREAMING_CONTAINER_IMAGE'", + "apiBodyFormat": "CUSTOM", + "description": "'$STREAMING_FUNCTION_NAME'", + "functionType": "STREAMING" + } + }' +``` + +## Connecting to a streaming function with a client + +### Intermediary Proxy + +An intermediary proxy service needs to be deployed in order to facilitate the connection to the streaming function. + +The intermediate proxy serves to handle authentication and the headers that are required for NVCF, +and also to align the connection behavior with NVCF that the browser can't handle on its own, +or the browser behavior is unpredictable. + +**Proxy Responsibilities** + +The intermediary proxy performs the following functionalities: + +1. Authenticate the user token coming from the browser to the intermediary proxy +2. Authorize the user to have access to specific streaming function +3. Once the user is authenticated and authorized, modify the websocket connection coming in to append the required NVCF headers (`NVCF_API_KEY` and `STREAMING_FUNCTION_ID`) +4. Forward the websocket connection request to NVCF + +**Technical Implementation Guidance** + +**nvcf-function-id Header** + +NVCF requires this header to be present to identify the function that needs to be reached. Browser does not have the mechanism to set any kind of headers in case of WebSocket connections other than Sec-Websocket-Protocol, so the intermediate proxy can serve to either add the nvcf-function-id header on its own, or to parse Sec-Websocket-Protocol if the browser added it there and get the function id from there. + +See [http-request add-header documentation in HAProxy](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/proxying-essentials/http-rewrites/#add-a-header). + +**Authentication** + +The role of intermediate proxy is to add the required server authentication (e.g. `http-request set-header Authorization "Bearer NVCF_BEARER_TOKEN"`). + +**Connection Keepalive** + +NVCF controls the session lifetime based on the TCP connection lifetime to the function and the type of disconnection that happens. The intermediate proxy helps to keep the connection with the browser alive. + +**Resume Support** + +NVCF returns the cookie with nvcf-request-id, but given the browser may reject the cookie since it is not from the same domain, the intermediate proxy helps to align this. + +**CORS Headers** + +For browsers to allow traffic with NVCF, the intermediate proxy needs to add the relevant CORS headers to responses from NVCF: + +- `access-control-expose-headers: *` +- `access-control-allow-headers: *` +- `access-control-allow-origin: *` + +For guidance on implementing this in HAProxy, see [http-response set-header documentation](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/proxying-essentials/http-rewrites/#add-a-header-1). + +**Example HAProxy Dockerfile** + +Below is an all-in-one Dockerfile sample for setting up an HAProxy intermediary proxy with optional TLS/SSL support: + + +This example focuses on NVCF integration. In production, you should also implement user authentication and authorization to control access to your streaming function. + +For certain applications, TLS/SSL support is required. The proxy can be configured to use self-signed certificates for development and testing purposes by setting `PROXY_SSL_INSECURE=true`. + +Update `NVCF_SERVER` to point to your gateway address. See [gateway-routing](./gateway-routing.md) for details. + + + +```dockerfile +FROM haproxy:3.2 + +# Switch to root user for package installation +USER root + +# Install necessary tools +RUN apt-get update && apt-get install -y \ + bash \ + gettext-base \ + lua5.3 \ + openssl \ + && rm -rf /var/lib/apt/lists/* + +# Create directory for configuration and certificates +RUN mkdir -p /usr/local/etc/haproxy/lua \ + && mkdir -p /usr/local/etc/haproxy/certs \ + && chown -R haproxy:haproxy /usr/local/etc/haproxy + +# Create certificate generation script +COPY < server.pem +chown haproxy:haproxy server.key server.crt server.pem +chmod 600 server.key server.pem +chmod 644 server.crt +EOF + +RUN chmod +x /usr/local/bin/generate-cert.sh + +# Create the HAProxy configuration template file +COPY --chown=haproxy:haproxy < /usr/local/etc/haproxy/haproxy.cfg + +# Function to handle signals and forward them to HAProxy +handle_signal() { + echo "Received signal, shutting down HAProxy..." + if [ -n "\$HAPROXY_PID" ]; then + kill -TERM "\$HAPROXY_PID" 2>/dev/null + wait "\$HAPROXY_PID" + fi + exit 0 +} + +# Set up signal handlers +trap handle_signal SIGTERM SIGINT + +# Start HAProxy in background and capture PID +echo "Starting HAProxy..." +haproxy -f /usr/local/etc/haproxy/haproxy.cfg & +HAPROXY_PID=\$! + +# Wait for HAProxy process +wait "\$HAPROXY_PID" +EOF + +RUN chmod +x /entrypoint.sh + +# Switch back to haproxy user +USER haproxy + +# Set the entrypoint +ENTRYPOINT ["/entrypoint.sh"] +``` + +**Environment Variables** + +The following environment variables control proxy behavior: + +| Variable | Required | Default | Description | +| --- | --- | --- | --- | +| `NVCF_API_KEY` | Yes | \- | Your NVCF API Key | +| `STREAMING_FUNCTION_ID` | Yes | \- | Your NVCF streaming function ID | +| `STREAMING_FUNCTION_VERSION_ID` | No | \- | Specific version of your function (optional) | +| `NVCF_SERVER` | No | GATEWAY_ADDR | NVCF server endpoint (your gateway address) | +| `PROXY_PORT` | No | 49100 | Port for the proxy to listen on | +| `PROXY_SSL_INSECURE` | No | false | Enable SSL with self-signed certificate (set to "true" to enable) | + +**Usage Examples** + +**1. HTTP Mode (Default)** + +Standard configuration without SSL: + +```bash +export STREAMING_FUNCTION_ID=your-function-id +export NVCF_API_KEY=your-nvcf-api-key +export PROXY_PORT=49100 + +docker build -t nvcf-haproxy-proxy . + +docker run --rm -it \ + -p 127.0.0.1:${PROXY_PORT}:${PROXY_PORT}/tcp \ + -e PROXY_PORT="$PROXY_PORT" \ + -e NVCF_API_KEY="$NVCF_API_KEY" \ + -e STREAMING_FUNCTION_ID="$STREAMING_FUNCTION_ID" \ + nvcf-haproxy-proxy +``` + +**2. HTTPS Mode with Self-Signed Certificate** + +Configuration with SSL enabled using a self-signed certificate: + +```bash +export STREAMING_FUNCTION_ID=your-function-id +export NVCF_API_KEY=your-nvcf-api-key +export PROXY_SSL_INSECURE=true +export PROXY_PORT=48322 + +docker build -t nvcf-haproxy-proxy . + +docker run --rm -it \ + -p 127.0.0.1:${PROXY_PORT}:${PROXY_PORT}/tcp \ + -e PROXY_PORT=${PROXY_PORT} \ + -e PROXY_SSL_INSECURE=${PROXY_SSL_INSECURE} \ + -e NVCF_API_KEY="$NVCF_API_KEY" \ + -e STREAMING_FUNCTION_ID="$STREAMING_FUNCTION_ID" \ + nvcf-haproxy-proxy +``` + + +Since this configuration uses self-signed certificates for development and testing, you will need to configure your client to accept untrusted certificates. In production environments, you should use proper CA-signed certificates. + + + +### Web Browser Client + +Using the proxy, a browser client can be used to connect to the stream. +The browser client needs to be developed by the customer leveraging the `raganrok` dev branch 0.0.1503 version. Please ensure that the flags are set: + +```javascript +const configData: RagnarokConfigData = { + overrideData: "disableworkerws=true" +} + +ConfigureRagnarokSettings(configData); +``` diff --git a/docs/v0.6.1-rc/task-creation.md b/docs/v0.6.1-rc/task-creation.md new file mode 100644 index 000000000..3dd3c95a9 --- /dev/null +++ b/docs/v0.6.1-rc/task-creation.md @@ -0,0 +1,44 @@ +# Task Creation + +NVIDIA Cloud Tasks (NVCT) runs GPU-backed batch jobs. A task runs to +completion on a reserved GPU instance and optionally uploads results to a +model registry location when it finishes. + +Tasks can be created in one of two ways: + +1. Container image + - Runs any container that executes a workload and exits. + - The container receives GPU access and any secrets or environment variables + you configure. + - See [Container-Based Task Creation](./container-tasks.md). + +2. Helm chart + - Orchestrates multi-container workloads using a Helm chart. + - Suitable for complex jobs that require multiple coordinated services. + - See [Helm-Based Task Creation](./helm-tasks.md). + +## Differences from functions + +Tasks and functions both run containers on GPU instances, but they serve +different purposes: + +- Functions are long-running inference services that handle repeated invocation + requests. They stay deployed until explicitly removed. +- Tasks are one-shot batch jobs. A task starts, runs its workload, and exits. + The lifecycle ends when the container exits or a timeout is reached. + +## Result handling + +Result upload to a model registry is not supported on self-hosted NVCF in this +release. + +`resultHandlingStrategy` defaults to `UPLOAD` when it is omitted, not `NONE`. +Because `UPLOAD` requires registry secrets and a `resultsLocation`, a task +created without those is rejected with a missing-secrets error. On self-hosted, +set `resultHandlingStrategy` to `NONE` so the task runs without registry upload. + +## Authentication + +Task commands require their own API key separate from the function API key. +Run `nvcf-cli api-key generate` after `nvcf-cli init` to mint both keys in +one step. See [CLI](./cli.md#generate-api-keys) for details. diff --git a/docs/v0.6.1-rc/third-party-registries.md b/docs/v0.6.1-rc/third-party-registries.md new file mode 100644 index 000000000..4a11200c9 --- /dev/null +++ b/docs/v0.6.1-rc/third-party-registries.md @@ -0,0 +1,505 @@ +# Working with Third-Party Registries + +This guide covers how to configure and manage registry credentials for function container images and Helm charts in self-hosted NVCF deployments. + +## Overview + +In NVCF, **third-party registries** refer to container registries used for hosting: + +- **Function container images** - The containers that run your inference workloads +- **Helm charts** - Used for deploying helm chart functions + +When a function is created or deployed, these credentials are used by different components: + +1. **NVCF API** - Stores and manages registry credentials, validates that images exist during function creation. See [self-hosted-api](./api.md) for the full API specification. +2. **NVCA (Cluster Agent)** - Renders Helm charts or pod specs for container functions and handles deployment lifecycle. Generates image pull credentials based on the registry type. +3. **Worker init container** - Responsible for pulling the function container images during deployment. + +### Supported Registries + +NVCF supports the following container registries: + +- **Amazon ECR** (Elastic Container Registry) +- **NVIDIA NGC** (nvcr.io) +- **Azure ACR** (Azure Container Registry) +- **VolcEngine CR** (Volcano Engine Container Registry) +- **JFrog Artifactory** +- **Harbor** + +### Registry Credential Format + +All registry credentials in NVCF use a base64-encoded `username:password` format: + +```bash +echo -n "username:password" | base64 +``` + +The specific username and password values depend on your registry type. See the sections below for registry-specific instructions. + +## Bootstrap vs. Runtime Credentials + +### Understanding Initial Bootstrap + +When you first deploy the NVCF control plane, registry credentials are configured in the `secrets/-secrets.yaml` file under `api.accountBootstrap.registryCredentials`. Example using ECR: + +```yaml +api: + accountBootstrap: + registryCredentials: + - registryHostname: nvcr.io + secret: + name: nvcr-containers + value: + artifactTypes: ["CONTAINER"] + description: "NGC Container registry" + - registryHostname: .dkr.ecr..amazonaws.com + secret: + name: ecr-containers + value: + artifactTypes: ["CONTAINER"] + description: "ECR Container registry" +``` + +Example using Volcano Engine Container Registry: + +```yaml +api: + accountBootstrap: + registryCredentials: + - registryHostname: .cr.volces.com + secret: + name: vcr-containers + value: + artifactTypes: ["CONTAINER", "HELM"] + description: "Volcano Engine Container and Helm Registry" +``` + +These bootstrap credentials are loaded during the initial `helmfile sync` deployment and stored in the NVCF backend. + + +The `registryHostname` must be the **registry base URL only** (e.g., `779846807323.dkr.ecr.us-west-2.amazonaws.com`), not including repository paths. + + + +### Managing Credentials After Deployment + +After initial deployment, registry credentials can be managed dynamically using the **NVCF API** or **CLI** without redeploying the control plane: + +- **Add** new registry credentials +- **List** existing credentials +- **Update** credentials (e.g., rotate secrets) +- **Delete** unused credentials + +This is the recommended approach for credential rotation and adding or modifying registries post-deployment of the control plane. + +### Credential Propagation Delay + +Registry credential changes do not take effect for task creation immediately. Task processing caches each account's registry credentials with a time-to-live of about 5 minutes (`nvct.nvcf.cache-ttl`, default `PT5M` in the self-managed stack). After you add, update, or delete a credential, allow up to about 5 minutes for the change to apply to new tasks. The API and `nvcf-cli registry-credential list` and `get` reflect the change immediately, but task validation can keep using the previous value until the cached entry expires. + + +After you rotate or delete a credential, allow up to the cache TTL (about 5 minutes) for task creation to start using the new value. To apply the change immediately, restart the task service: + +```bash +kubectl -n nvcf rollout restart deployment/nvct-api +``` + + + +## Adding AWS ECR Registry Credentials + +AWS ECR requires **permanent IAM credentials** (Access Key ID + Secret Access Key). Temporary SSO/STS credentials will not work. + + +**Why SSO credentials don't work:** AWS SSO and assumed role credentials include a session token that must be passed alongside the access key and secret. The NVCF registry credential format (`ACCESS_KEY_ID:SECRET_ACCESS_KEY`) does not support session tokens, so temporary credentials will fail with `UnrecognizedClientException`. + + + +NVCF supports both **ECR Private** and **ECR Public** registries: + +- **ECR Private**: `.dkr.ecr..amazonaws.com` +- **ECR Public**: `public.ecr.aws` + +### Step 1: Create an IAM User + +```bash +# Create IAM user for NVCF +aws iam create-user --user-name nvcf-ecr-service-account +``` + +### Step 2: Create and Attach ECR Policy + +Create a least privilege IAM policy based on your ECR type. + +**For Private ECR:** + +```bash +# Set your AWS account ID and region +AWS_ACCOUNT_ID="" +AWS_REGION="" +REPO_PREFIX="" + +# Create the policy +``` + + +**About REPO_PREFIX:** ECR repository names can include path-like prefixes for organization. The `REPO_PREFIX` scopes the IAM policy to only allow access to repositories matching that prefix. + +**Examples:** + +- If your repositories are named `nvcf/echo-server`, `nvcf/triton-server`, etc., set `REPO_PREFIX="nvcf"` +- If your repositories are named `my-cluster/nvcf-api`, `my-cluster/nvcf-sis`, etc., set `REPO_PREFIX="my-cluster"` +- To allow access to all repositories in the account, set `REPO_PREFIX="*"` (less secure) + +The resulting IAM resource ARN `arn:aws:ecr:::repository//*` will match all repositories starting with that prefix. + + + +```bash +aws iam create-policy \ + --policy-name NVCFECRPrivateAccess \ + --policy-document '{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ecr:GetAuthorizationToken", + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchCheckLayerAvailability", + "ecr:DescribeImages" + ], + "Resource": "arn:aws:ecr:'"${AWS_REGION}"':'"${AWS_ACCOUNT_ID}"':repository/'"${REPO_PREFIX}"'/*" + } + ] + }' + +# Attach the policy to the user +aws iam attach-user-policy \ + --user-name nvcf-ecr-service-account \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPrivateAccess +``` + +**For Public ECR:** + + +**About REPO_PREFIX for Public ECR:** ECR Public uses aliases instead of account-based paths. When you create a public repository, you choose an alias (e.g., `nvidia`, `my-company`). Images are then referenced as `public.ecr.aws//:`. + +Set `REPO_PREFIX` to your ECR Public alias to scope the policy to your repositories. + + + +```bash +# Set your AWS account ID and ECR Public alias +AWS_ACCOUNT_ID="" +REPO_PREFIX="" + +# Create the policy +aws iam create-policy \ + --policy-name NVCFECRPublicAccess \ + --policy-document '{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr-public:GetAuthorizationToken", + "sts:GetServiceBearerToken" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "ecr-public:DescribeImages", + "Resource": "arn:aws:ecr-public::'"${AWS_ACCOUNT_ID}"':repository/'"${REPO_PREFIX}"'/*" + } + ] + }' + +# Attach the policy to the user +aws iam attach-user-policy \ + --user-name nvcf-ecr-service-account \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPublicAccess +``` + +### Step 3: Create Access Keys + +```bash +# Create access keys and save the output securely +aws iam create-access-key --user-name nvcf-ecr-service-account +``` + +Example response: + +```json +{ + "AccessKey": { + "UserName": "nvcf-ecr-service-account", + "AccessKeyId": "", + "SecretAccessKey": "", + "Status": "Active" + } +} +``` + + +**Save these credentials immediately!** The secret access key is only shown once. Store it securely (e.g., in a password manager or secrets vault). + + + +### Step 4: Add ECR Credentials via CLI + +Generate the base64-encoded credential and add it using the CLI. + +**For Private ECR:** + +```bash +# Generate base64 credential (format: ACCESS_KEY_ID:SECRET_ACCESS_KEY) +ACCESS_KEY_ID="" +SECRET_ACCESS_KEY="" + +ECR_SECRET=$(echo -n "${ACCESS_KEY_ID}:${SECRET_ACCESS_KEY}" | base64 -w 0) + +# Add the registry credential for Private ECR +./nvcf-cli registry-credential add \ + --hostname .dkr.ecr..amazonaws.com \ + --secret "${ECR_SECRET}" \ + --artifact-type CONTAINER \ + --description "ECR Private Registry" +``` + +**For Public ECR:** + +```bash +# Generate base64 credential (format: ACCESS_KEY_ID:SECRET_ACCESS_KEY) +ACCESS_KEY_ID="" +SECRET_ACCESS_KEY="" + +ECR_SECRET=$(echo -n "${ACCESS_KEY_ID}:${SECRET_ACCESS_KEY}" | base64 -w 0) + +# Add the registry credential for Public ECR +./nvcf-cli registry-credential add \ + --hostname public.ecr.aws \ + --secret "${ECR_SECRET}" \ + --artifact-type CONTAINER \ + --description "ECR Public Registry" +``` + +### Cleanup IAM Resources (if needed) + +To remove the IAM user and associated resources: + +```bash +AWS_ACCOUNT_ID="" + +# List and delete access keys +aws iam list-access-keys --user-name nvcf-ecr-service-account +aws iam delete-access-key --user-name nvcf-ecr-service-account --access-key-id + +# Detach and delete policy (for private ECR) +aws iam detach-user-policy \ + --user-name nvcf-ecr-service-account \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPrivateAccess +aws iam delete-policy \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPrivateAccess + +# Detach and delete policy (for public ECR) +aws iam detach-user-policy \ + --user-name nvcf-ecr-service-account \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPublicAccess +aws iam delete-policy \ + --policy-arn arn:aws:iam::${AWS_ACCOUNT_ID}:policy/NVCFECRPublicAccess + +# Delete user +aws iam delete-user --user-name nvcf-ecr-service-account +``` + +## Adding NGC Registry Credentials + +NGC (NVIDIA GPU Cloud) uses API keys for authentication. + +### Step 1: Generate NGC API Key + +1. Navigate to [https://ngc.nvidia.com/setup/api-key](https://ngc.nvidia.com/setup/api-key) to generate a new API key or use an existing one if you have one saved. +2. Copy the key (format: `nvapi-xxxxxxxxxxxx`) + +### Step 2: Add NGC Credentials via CLI + +For NGC, the username is always `$oauthtoken`: + +```bash +# Generate base64 credential (format: $oauthtoken:NGC_API_KEY) +NGC_API_KEY="" + +NGC_SECRET=$(echo -n '$oauthtoken:'"${NGC_API_KEY}" | base64 -w 0) + +# Add the registry credential for containers +./nvcf-cli registry-credential add \ + --hostname nvcr.io \ + --secret "${NGC_SECRET}" \ + --artifact-type CONTAINER \ + --description "NGC Container Registry" + +# Add the registry credential for Helm charts (if needed) +./nvcf-cli registry-credential add \ + --hostname helm.ngc.nvidia.com \ + --secret "${NGC_SECRET}" \ + --artifact-type HELM \ + --description "NGC Helm Registry" +``` + +## Adding Volcano Engine Container Registry Credentials + +Volcano Engine Container Registry (VCR) uses access keys for authentication. + +### Step 1: Get Volcano Engine Access Key + +1. Login to the Volcano Engine Console +2. Go to the **Access Key** management page: [https://console.volcengine.com/iam/keymanage](https://console.volcengine.com/iam/keymanage). +3. Copy the **Access Key ID** and **Secret Access Key**. (Click on **Create Access Key** if you don't have one already.) + +### Step 2: Add VCR Credentials via CLI + +```bash +# Generate base64 credential (format: ACCESS_KEY_ID:SECRET_ACCESS_KEY) +ACCESS_KEY_ID="" +SECRET_ACCESS_KEY="" + +REGISTRY_NAME="" +VCR_SECRET=$(echo -n "${ACCESS_KEY_ID}:${SECRET_ACCESS_KEY}" | base64 -w 0) + +# Add the registry credential for containers and Helm charts +./nvcf-cli registry-credential add \ + --hostname ${REGISTRY_NAME}.cr.volces.com \ + --secret "${VCR_SECRET}" \ + --artifact-type CONTAINER,HELM \ + --description "Volcano Engine Container and Helm Registry" +``` + +## Listing Registry Credentials + +To view all configured registry credentials: + +```bash +./nvcf-cli registry-credential list +``` + +## Deleting Registry Credentials + +To remove a registry credential: + +```bash +# Delete by credential ID +./nvcf-cli registry-credential delete +``` + +## How NVCF Matches Registries to Images + +When you create or deploy a function, NVCF automatically matches the `containerImage` path to the appropriate registry credentials based on the **hostname**. + +**Example: ECR Private** + +```json +{ + "name": "my-function", + "containerImage": "779846807323.dkr.ecr.us-west-2.amazonaws.com/my-repo/my-image:v1.0" +} +``` + +NVCF extracts the hostname `779846807323.dkr.ecr.us-west-2.amazonaws.com` and looks for a matching registry credential. + +**Example: ECR Public** + +```json +{ + "name": "my-function", + "containerImage": "public.ecr.aws/my-alias/my-image:v1.0" +} +``` + +NVCF extracts the hostname `public.ecr.aws` and looks for a matching registry credential. + +If credentials are found, they are used to: + +1. Validate the image exists (during function creation) +2. Pull the image (during function deployment) + +## Troubleshooting + +### Incorrect registryHostname Format + +The `registryHostname` in your credentials must **exactly match** the hostname portion of your container image path. Do not include repository paths in the hostname. + +**ECR Private:** + +- Correct: `779846807323.dkr.ecr.us-west-2.amazonaws.com` +- Incorrect: `779846807323.dkr.ecr.us-west-2.amazonaws.com/my-repo` + +**ECR Public:** + +- Correct: `public.ecr.aws` +- Incorrect: `public.ecr.aws/my-alias` + +**NGC:** + +- Correct: `nvcr.io` +- Incorrect: `nvcr.io/nvidia/pytorch` + +### UnrecognizedClientException + +**Error:** + +```json +{ + "detail": "{\"__type\":\"UnrecognizedClientException\",\"message\":\"The security token included in the request is invalid.\"}" +} +``` + +**Cause:** This AWS-specific error indicates a malformed or invalid security token. Common causes include: + +- Using temporary AWS credentials (SSO, STS assumed role) that include a session token, which NVCF's credential format does not support +- Incorrectly formatted credentials (e.g., wrong base64 encoding, missing or extra characters) +- Expired or revoked access keys + +**Solution:** + +1. Verify you are using permanent IAM user credentials (Access Key ID + Secret Access Key), not temporary credentials +2. Re-generate the base64-encoded credential ensuring the format is exactly `ACCESS_KEY_ID:SECRET_ACCESS_KEY` with no trailing newlines etc. +3. If using SSO/assumed roles, create a dedicated IAM user instead. See [ecr-registry-setup](./third-party-registries.md) + +### Registry Not Found + +**Error:** + +```text +No registry credential found for hostname: example.dkr.ecr.us-west-2.amazonaws.com +``` + +**Cause:** The hostname in your `containerImage` doesn't match any configured registry credential. + +**Solution:** + +1. List your credentials with `./nvcf-cli registry-credential list` +2. Verify the hostname matches exactly (no trailing slashes, no repository paths) +3. Add the missing registry credential if needed + +### Authentication Failed + +**Error:** + +```text +authentication required / unauthorized +``` + +**Cause:** The credentials are malformed or the password/key is incorrect. + +**Solution:** + +1. Verify the credential format is `username:password` base64-encoded +2. For ECR: Use `ACCESS_KEY_ID:SECRET_ACCESS_KEY` +3. For NGC: Use `$oauthtoken:NGC_API_KEY` +4. Re-add the credential with correct values diff --git a/docs/v0.6.1-rc/troubleshooting.md b/docs/v0.6.1-rc/troubleshooting.md new file mode 100644 index 000000000..8e8763f72 --- /dev/null +++ b/docs/v0.6.1-rc/troubleshooting.md @@ -0,0 +1,1082 @@ +# Troubleshooting / FAQ + +This appendix contains common issues, tips, and clarifications learned from deploying self-hosted NVCF. + +## Configuration Issues and Best Practices + +### Incorrect Base64 Docker Credentials Format + +**Symptom:** + +- API migration job fails during installation +- Error appears as a "timeout" in the logs +- Re-running `helmfile sync` or `helmfile apply` appears to succeed but the deployment doesn't work properly +- Functions fail to deploy or pull images + +**Root Cause:** + +The base64-encoded Docker credential in `secrets.yaml` was incorrectly formatted. A common mistake is encoding **only the NGC API key** instead of the full **basic auth credential** in the format `$oauthtoken:API_KEY`. + +**Incorrect (will fail):** + +```bash +# ❌ WRONG - Only encoding the API key +echo -n 'nvapi-1234567890abcdef' | base64 +# Results in: bnZhcGktMTIzNDU2Nzg5MGFiY2RlZg== +``` + +**Correct:** + +```bash +# ✅ CORRECT - Encoding the full credential in basic auth format +echo -n '$oauthtoken:nvapi-1234567890abcdef' | base64 +# Results in: JG9hdXRodG9rZW46bnZhcGktMTIzNDU2Nzg5MGFiY2RlZg== +``` + +**How to Diagnose:** + +1. Check the migration job logs specifically: + + ```bash + kubectl logs -n nvcf job/nvcf-api-migration -c migration + ``` + +2. If you don't see detailed errors, add debug output to migration scripts: + + ```bash + # Add set -x to the migration script for verbose output + kubectl edit configmap -n nvcf nvcf-api-migration-scripts + # Add 'set -x' at the top of the script + ``` + +3. Fix your secrets.yaml with correct base64 credential, then follow the [clean-install-procedure](./troubleshooting.md). + +**How to Prevent:** + +1. **Always use the correct format**: Encode `$oauthtoken:YOUR_API_KEY`, not just the API key + +2. **Verify before deploying**: Decode your base64 string to verify it's correct: + + ```bash + # Verify your encoded credential + echo 'YOUR_BASE64_STRING' | base64 -d + # Should output: $oauthtoken:nvapi-1234567890abcdef + ``` + +3. **Test NGC authentication**: Before deploying, test that your credential works: + + ```bash + # Test NGC login with your credential + echo 'YOUR_BASE64_STRING' | base64 -d | IFS=: read username password + docker login nvcr.io -u "$username" -p "$password" + ``` + +### Registry Credential Change Not Taking Effect + +Task creation fails with a `Missing registry credential for hostname` error, or keeps using a previous value, shortly after you add, update, or delete a registry credential, even though `nvcf-cli registry-credential list` and `get` already show the new value. + +This is expected propagation delay, not a failure. Task processing caches each account's registry credentials for about 5 minutes (`nvct.nvcf.cache-ttl`, default `PT5M`), and picks up the change once that cached copy refreshes. + +- Wait up to about 5 minutes and retry the task. +- To apply the change immediately, restart the task service: + + ```bash + kubectl -n nvcf rollout restart deployment/nvct-api + ``` + +## Identifying Deployment Issues + +Use these commands to diagnose deployment problems. For phase-by-phase monitoring during installation, see the Deployment Progression section in [helmfile-installation](./helmfile-installation.md). + +**Find Stuck Deployments:** + +```bash +# Pods stuck in pending state +kubectl get pods -A | grep Pending + +# Pods with image pull issues +kubectl get pods -A | grep -E "ImagePullBackOff|ErrImagePull" + +# Pods in crash loops +kubectl get pods -A | grep CrashLoopBackOff + +# Check recent events for errors +kubectl get events --sort-by='.lastTimestamp' -A | grep -i error | tail -10 +``` + +**Resource Check:** + +```bash +# Check node resources +kubectl top nodes + +# Check pod resource usage +kubectl top pods -A | grep -E "nvcf|nats|cassandra|openbao" +``` + +## Installation & Deployment Issues + +### Account Bootstrap Job Failures + +**Symptom:** + +- `helmfile sync` hangs or fails during the services phase +- Events show `BackoffLimitExceeded` for `nvcf-api-account-bootstrap` +- Bootstrap pod shows `CrashLoopBackOff` or `Error` status + +**Diagnosis:** + +1. Watch events in real-time (run this as soon as helmfile reaches services phase): + + ```bash + kubectl get events -n nvcf -w + ``` + +2. Check the bootstrap job logs: + + ```bash + kubectl logs job/nvcf-api-account-bootstrap -n nvcf + ``` + +3. Check the NVCF API logs for detailed error messages: + + ```bash + kubectl logs -n nvcf -l app.kubernetes.io/name=nvcf-api --tail=100 + ``` + + +The bootstrap job auto-deletes after ~5 minutes (`ttlSecondsAfterFinished: 300`). Monitor events to catch failures in real-time. + + + + +**Enable debug logging for the bootstrap job.** The account bootstrap script supports +a `DEBUG` environment variable that enables verbose output. To enable it before +redeploying, patch the bootstrap secret: + +```bash +kubectl patch secret nvcf-api-account-bootstrap-secret -n nvcf \ + -p '{"stringData":{"DEBUG":"true"}}' +``` + +Then follow the "Recovering from Services Failures" steps in [helmfile-installation](./helmfile-installation.md) +to redeploy. The next bootstrap job run will include detailed debug logs visible via +`kubectl logs job/nvcf-api-account-bootstrap -n nvcf`. + +To disable debug logging afterward: + +```bash +kubectl patch secret nvcf-api-account-bootstrap-secret -n nvcf \ + -p '{"stringData":{"DEBUG":"false"}}' +``` + + + +**Common Causes:** + +- **Invalid registry credentials format** - See [Incorrect Base64 Docker Credentials Format] +- **Wrong registry hostname** - Hostname in secrets doesn't match actual registry (e.g., using `nvcr.io` but credentials are for ECR) +- **Missing \`\`\$oauthtoken\`\` prefix** - NGC credentials must be in format `$oauthtoken:API_KEY` + +**Solution:** + +Fix your `secrets/-secrets.yaml` file, then follow the "Recovering from Services Failures" steps in [helmfile-installation](./helmfile-installation.md) to preserve your dependencies. + +### Pods Stuck in ImagePullBackOff + +**Symptoms:** Pods cannot pull container images + +**Solutions:** + +1. Verify registry credentials: + + ```bash + # Check secret exists + kubectl get secret -n nvcf nvcf-image-pull-secret + + # Verify credential is valid + kubectl get secret -n nvcf nvcf-image-pull-secret -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d + ``` + +2. Verify images exist in your registry: + + ```bash + # For ECR (replace with your repository name) + aws ecr describe-images --repository-name --region + + # For NGC (if using) + ngc registry image list nvidia/nvcf/* + ``` + +3. Check network connectivity from cluster to registry + +### Pods Stuck in Pending + +**Symptoms:** Pods remain in Pending state + +**Solutions:** + +1. Check cluster resources: + + ```bash + kubectl describe node + ``` + +2. Verify storage class exists: + + ```bash + kubectl get storageclass + ``` + +3. Check node selectors: + + ```bash + # View pod events + kubectl describe pod -n + + # Check node labels + kubectl get nodes --show-labels + ``` + +### Helm Release in Failed State After First Install + +**Symptom:** + +- First `helmfile sync` fails partway through +- Re-running `helmfile sync` or `helmfile apply` appears to succeed but things don't work +- Migrations or initialization jobs weren't executed + +**Root Cause:** + +When a Helm installation fails, the release remains in a failed state. Subsequent commands run `helm upgrade` instead of `helm install`, which **skips initialization hooks** (migrations, account bootstrap, etc.). + +**Solution:** + +Fix the underlying issue (credentials, config, etc.), then follow the appropriate recovery procedure in [helmfile-installation](./helmfile-installation.md): + +- **If only services failed** (dependencies are healthy): Use the "Recovering from Services Failures" steps to preserve your dependencies +- **If dependencies are also broken**: Follow the "Uninstalling" section in [helmfile-installation](./helmfile-installation.md) + +### NVCA Operator Fails: nvcfbackends CRD Not Found + +**Symptom:** + +NVCA Operator installation fails with CRD not found error: + +```text +Error: customresourcedefinitions.apiextensions.k8s.io "nvcfbackends.nvcf.nvidia.io" not found +``` + +**Root Cause:** + +A race condition occurs where Helm validates CRD references before the CRD is created by the operator's installation hooks. This can happen during first install or when reinstalling after the CRD was deleted. + +**Solution:** + +Two changes are required in `helmfile.d/03-worker.yaml.gotmpl`: + +1. **Add \`\`disableValidation: true\`\`** to the nvca-operator release to disable OpenAPI validation: + +```yaml +wait: true +waitForJobs: true +disableValidation: true # Add this line +labels: + release-group: workers +``` + +2. **Remove \`\`--dry-run=server\`\`** from the `helmDefaults.diffArgs` section. This prevents server-side validation during the diff phase, which fails when the CRD doesn't exist: + +```yaml +helmDefaults: + createNamespace: true + devel: true + timeout: 900 + wait: true + waitForJobs: true + # Note: --dry-run=server removed for worker releases to avoid CRD validation failures + # when reinstalling nvca-operator after CRD deletion +``` + +Then run `./force-cleanup-nvcf.sh` followed by `HELMFILE_ENV= helmfile sync`. + +## Accessing OpenBao Secrets (CLI) + +NVCF stores most service credentials, signing keys, and internal passwords in +[OpenBao](https://openbao.org/) (a Vault-compatible secrets manager) running in the +`vault-system` namespace. Use the `bao` CLI inside the OpenBao pod to inspect or +manage these secrets. + + +For the full `bao` CLI reference, see the +[OpenBao CLI documentation](https://openbao.org/docs/commands/). +The KV secrets engine commands are documented at +[OpenBao KV commands](https://openbao.org/docs/commands/kv/). + + + +### Retrieve the Root Token + +The OpenBao root token is stored in a Kubernetes secret created during initialization: + +```bash +# Retrieve the root token +export BAO_ROOT_TOKEN=$(kubectl get secret openbao-server-root-token \ + -n vault-system -o jsonpath='{.data.root_token}' | base64 -d) +``` + + +The root token grants unrestricted access to all secrets in OpenBao. Treat it as a +highly sensitive credential and avoid storing it in shell history or logs. + + + +### List Secrets Engines + +To see all mounted secrets engines (each NVCF service has its own path): + +```bash +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao secrets list +``` + +Example output (abbreviated): + +```text +Path Type Description +---- ---- ----------- +services/all/kv/ kv n/a +services/api-keys-api/jwt/ vault-plugin-secrets-jwt n/a +services/api-keys-api/kv/ kv n/a +services/ess-api/jwt/ vault-plugin-secrets-jwt n/a +services/ess-api/kv/ kv n/a +services/invocation-api/jwt/ vault-plugin-secrets-jwt n/a +services/invocation-api/kv/ kv n/a +services/nvcf-api/jwt/ vault-plugin-secrets-jwt n/a +services/nvcf-api/kv/ kv n/a +services/nvcf-notary/kv/ kv n/a +services/sis-api/jwt/ vault-plugin-secrets-jwt n/a +services/sis-api/kv/ kv n/a +... +``` + +### List and Read Secrets + +Browse secrets under a specific engine path: + +```bash +# List top-level keys under a secrets engine +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao kv list services/nvcf-api/kv + +# List keys in a subdirectory (paths ending in / are directories) +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao kv list services/nvcf-api/kv/cassandra + +# Read a specific secret +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao kv get services/nvcf-api/kv/cassandra/creds +``` + + +Use `bao kv get -format=json ` for machine-readable output, or +`bao kv get -field= ` to extract a single field. + + + +### Run Arbitrary `bao` Commands + +You can run any `bao` subcommand by exec-ing into the pod with the root token: + +```bash +# General pattern +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao [args] + +# Examples: +# Check server status +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao status + +# List auth methods +kubectl exec -it openbao-server-0 -c openbao -n vault-system -- \ + env BAO_TOKEN=$BAO_ROOT_TOKEN \ + bao auth list +``` + +## Debugging Techniques + +### Enabling Verbose Logging + +To get more detailed logs from specific components: + +**For Migration Jobs:** + +```bash +# Edit the migration script configmap +kubectl edit configmap -n nvcf nvcf-api-migration-scripts + +# Add to the top of the script: +set -x # Enable command tracing +set -e # Exit on error +``` + +**Example For API Service:** + +```bash +# Set log level via environment variable +kubectl set env -n nvcf deployment/nvcf-api LOG_LEVEL=debug +``` + +### Inspecting Failed Pods + +```bash +# Get pod status with more details +kubectl get pods -n nvcf -o wide + +# Describe a problematic pod +kubectl describe pod -n nvcf + +# View logs (current) +kubectl logs -n nvcf + +# View logs (previous if pod restarted) +kubectl logs -n nvcf --previous + +# Follow logs in real-time +kubectl logs -n nvcf -f + +# Logs for all containers in a pod +kubectl logs -n nvcf --all-containers +``` + +### Checking Events + +Kubernetes events often contain valuable debugging information: + +```bash +# Get recent events for a namespace +kubectl get events -n nvcf --sort-by='.lastTimestamp' + +# Get events for a specific pod +kubectl get events -n nvcf --field-selector involvedObject.name= + +# Watch events in real-time +kubectl get events -n nvcf --watch +``` + +## Recovery Procedures + +For detailed recovery steps, see the **Recovering from Partial Deployments** section in [helmfile-installation](./helmfile-installation.md). This section provides quick reference for common scenarios. + +### Choosing a Recovery Strategy + +| Failure Scenario | Recovery Strategy | Reference | +| --- | --- | --- | +| **Dependencies failed** (Cassandra, NATS, OpenBao) | Redeploy individual dependency | See `Redeploy Stuck Dependencies`_ below | +| **Services failed** (API, api-keys, etc.) but dependencies OK | Partial recovery (preserve dependencies) | See "Recovering from Services Failures" in [helmfile-installation](./helmfile-installation.md) | +| **Everything broken** or uncertain state | Full uninstall and reinstall | See "Uninstalling" in [helmfile-installation](./helmfile-installation.md) | + + +**Do not attempt to fix failed services by re-running** `helmfile sync` **or** `helmfile apply`. Helm will skip initialization hooks (migrations, account bootstrap) on upgrade, resulting in a deployment that appears successful but doesn't function correctly. + + + +### Redeploy Stuck Dependencies + +Dependency services (Cassandra, NATS, OpenBao) can be safely redeployed without affecting other components: + +```bash +# Redeploy only Cassandra +HELMFILE_ENV= helmfile --selector name=cassandra apply + +# Redeploy all dependencies +HELMFILE_ENV= helmfile --selector release-group=dependencies apply +``` + +### Reinstalling NVCA Operator Only + +If only NVCA needs reinstalling (and NVCF services are working): + +```bash +./force-cleanup-nvcf.sh +HELMFILE_ENV= helmfile --selector release-group=workers sync +``` + +If NVCF services are also broken, follow the "Recovering from Services Failures" steps in [helmfile-installation](./helmfile-installation.md). + +### NVCA Force Cleanup Script + +If `helmfile destroy` hangs on NVCA cleanup (typically when functions are still deployed in `nvcf-backend`), use the force cleanup script in a new terminal. See [force-cleanup-script](./troubleshooting.md) for the full script and usage instructions. + +```bash +./force-cleanup-nvcf.sh --dry-run # Preview +./force-cleanup-nvcf.sh # Execute +``` + +## Database Issues + +### Cassandra Pods OOM During Initialization + +Symptoms: + +- Cassandra pods restart with `OOMKilled` or exit code `137`. +- The `cassandra-migrations` job fails with a consistency-level error such as `Cannot achieve consistency level ALL`. +- The install does not continue to OpenBao or NVCF services. + +Diagnosis: + +Check Cassandra pod restarts and the previous container state: + +```bash +kubectl -n cassandra-system get pods +kubectl -n cassandra-system describe pod cassandra-0 | grep -E "OOMKilled|Exit Code: 137|Last State" +``` + +Check the migration job log for the first failed keyspace: + +```bash +kubectl -n cassandra-system logs job/cassandra-helm-nvcf-cassandra-migrations +``` + +Root cause: + +The Cassandra resource preset is too small for first boot, commit-log replay, or migration startup. The `small` preset is not recommended for cloud installs, and environments that still OOM on `xlarge` should move to `2xlarge`. + +Solution: + +Increase the preset in your environment file, then resync Cassandra: + +```yaml +cassandra: + resourcesPreset: "2xlarge" +``` + +```bash +HELMFILE_ENV= helmfile --selector name=cassandra sync +``` + +If Cassandra was interrupted during migration, also check for dirty migration state before rerunning the full install. + +### Cassandra Migrations Fail After Dirty Migration State + +Symptoms: + +- The `cassandra-migrations` job fails repeatedly after Cassandra restarts during a previous migration attempt. +- Logs show an error similar to `no migration found for version 0`, or another migration error that does not identify the failed keyspace state. +- A row in a `schema_migrations.` table has `dirty=true`. + +Diagnosis: + +The migration bookkeeping tables live in the `schema_migrations` keyspace, with one table per application keyspace. Check the keyspace named in the migration logs: + +```bash +CPASS=$(kubectl -n cassandra-system get secret cassandra \ + -o jsonpath='{.data.cassandra-password}' | base64 -d) + +kubectl -n cassandra-system exec cassandra-0 -c cassandra -- \ + /opt/bitnami/cassandra/bin/cqlsh -u cassandra -p "$CPASS" localhost \ + -e "SELECT version, dirty FROM schema_migrations.;" +``` + +Root cause: + +`golang-migrate` marks a keyspace dirty when a migration attempt is interrupted. Cassandra DDL statements may already have committed, but the migration marker remains dirty and blocks the next run. + +Solution: + +First verify whether the failed migration's DDL was applied or needs manual reconciliation. After the schema matches the dirty version, clear the dirty flag and rerun the migration job: + +```bash +kubectl -n cassandra-system exec cassandra-0 -c cassandra -- \ + /opt/bitnami/cassandra/bin/cqlsh -u cassandra -p "$CPASS" localhost \ + -e "UPDATE schema_migrations. SET dirty = false WHERE version = ;" + +HELMFILE_ENV= helmfile --selector name=cassandra sync +``` + +If more than one keyspace is dirty, repeat the diagnosis and reconciliation for each affected `schema_migrations.` table. + +### Cassandra Migration Stuck Due to Missing ConfigMap + +**Symptoms:** + +Cassandra pods are running but migration job is stuck. + +```bash +kubectl get pods -n cassandra-system + +... +pod/cassandra-initialize-cluster-qp4ft 0/1 ContainerCreating # Stuck in ContainerCreating state perpetually +``` + +**Diagnosis:** + +Check all Cassandra resources including ConfigMaps: + +```bash +kubectl -n cassandra-system get all,secrets,sa,cm +``` + +Expected output should show **3 ConfigMaps**: + +```text +NAME DATA AGE +configmap/cassandra-init-cql 1 5d19h # This one may be missing +configmap/cassandra-init-script 1 100s # This one may be missing +configmap/kube-root-ca.crt 1 8d +``` + +If you only see 2 ConfigMaps (missing `cassandra-migrations`), this is a race condition during deployment. + +**Root Cause:** + +A race condition can occur where the Cassandra migration job starts before all ConfigMaps are created, causing the deployment to hang. + +**Solution:** + +Force a sync to recreate missing resources: + +```bash +# Use helmfile sync instead of apply to force resource recreation +HELMFILE_ENV="" \ +helmfile --environment default --selector name=cassandra sync +``` + + +The `sync` command differs from `apply` in that it will recreate resources if needed, which resolves the ConfigMap race condition. + + + +**Alternative Solution:** + +If the above doesn't work, you can safely redeploy Cassandra (it's a dependency without complex initialization hooks): + +```bash +# Delete the stuck migration job first +kubectl delete job -n cassandra-system cassandra-migrations + +# Then redeploy Cassandra +HELMFILE_ENV= helmfile --selector name=cassandra apply +``` + +## Streaming Issues + +### WebRTC Streaming Fails After Function Shows Active + +**Symptom:** + +- A streaming function deploys and shows ACTIVE +- WebRTC clients fail to connect with `NVST_R_GENERIC_ERROR` +- No errors in the function pod logs + +**Root Cause:** + +UDP traffic on the Kubernetes NodePort range (30000-32767) is blocked by a +cloud-provider network security rule. The function health checks pass over +TCP, so the function appears healthy, but the UDP media path is unreachable. + +On Azure (AKS), AKS attaches a second NSG to node NICs in the managed +resource group (`MC___`). Even if the subnet NSG +allows UDP, the NIC NSG blocks it by default. + +**Diagnosis:** + +1. Confirm the function is ACTIVE and pods are running: + + ```bash + kubectl get pods -n nvcf-backend -l nvcf-function-name= + ``` + +2. On Azure, check whether the NIC NSG has a UDP allow rule: + + ```bash + MC_RG="MC_${RESOURCE_GROUP}_${CLUSTER_NAME}_${LOCATION}" + for NIC_NSG in $(az network nsg list -g "$MC_RG" --query "[].name" -o tsv); do + echo "=== $NIC_NSG ===" + az network nsg rule list -g "$MC_RG" --nsg-name "$NIC_NSG" \ + --query "[?protocol=='Udp']" -o table + done + ``` + + If any NSG is missing a UDP rule, add it to all of them: + + ```bash + for NIC_NSG in $(az network nsg list -g "$MC_RG" --query "[].name" -o tsv); do + az network nsg rule create -g "$MC_RG" --nsg-name "$NIC_NSG" \ + -n allow-udp-nodeports-webrtc --priority 510 \ + --direction Inbound --access Allow --protocol Udp \ + --source-address-prefix Internet --source-port-range "*" \ + --destination-address-prefix "*" --destination-port-range "30000-32767" + done + ``` + +See [Cloud Provider Network Requirements](./streaming-functions.md#cloud-provider-network-requirements) +for the full CSP networking checklist. + +### gRPC Session Resumption Fails + +#### Symptom + +A streaming client receives gRPC NotFound (`grpc-status: 5`) with "no existing +session found" when reconnecting to a function that was previously working. The +function shows active in the control plane. The HTTP status remains 200 for +gRPC responses. The proxy also clears the `nvcf-request-id` cookie so the next +retry starts a fresh session. + +#### Root cause + +The client is sending a stale `nvcf-reqid` header or `nvcf-request-id` cookie +from a previous session that no longer exists. The gRPC proxy looks up the +request ID, finds no matching worker session, and returns gRPC NotFound. + +Common causes of stale request IDs: + +- The worker pod was evicted or restarted between requests. +- The session timed out because the connection was idle too long. +- The client is reusing a request ID from a different function or function + version. + +#### Diagnosis + +Check whether the client is sending the `nvcf-reqid` header or +`nvcf-request-id` cookie: + +```bash +# Look for session-not-found errors in gRPC proxy logs. +kubectl logs -n nvcf -l app.kubernetes.io/name=grpc-proxy --tail=200 \ + | grep -E "no existing session found|worker connection not found" +``` + +If the logs show "no existing session found for request id", the client is +referencing an expired session. + +#### Resolution + +Remove the stale request ID from the client and reconnect without it. The proxy +creates a new session and returns a fresh request ID. Update the client to +handle gRPC NotFound by discarding the stored request ID and retrying without +it. + +See [Session Resumption](./grpc-function-invocation.md#session-resumption) for +the full request ID lifecycle. + +## Getting Help + +When requesting support, provide: + +1. **Environment details:** + + ```bash + kubectl version + helm version + kubectl get nodes -o wide + ``` + +2. **Deployment configuration:** + + - Environment file (sanitized) + - Secrets file structure (sanitized - no actual secrets!) + +3. **Relevant logs:** + + ```bash + kubectl logs -n > pod-logs.txt + ``` + +4. **Events:** + + ```bash + kubectl get events -n --sort-by='.lastTimestamp' > events.txt + ``` + +5. **Resource status:** + + ```bash + kubectl get all -n -o wide > resources.txt + ``` + +## Appendix: NVCA Force Cleanup Script + +This script forcefully removes all NVCA components from a cluster. Use it when `helmfile destroy` hangs on NVCA cleanup, typically because functions are still deployed in `nvcf-backend`. + + +This script bypasses normal cleanup procedures by removing finalizers. Always try `helmfile destroy` first. + + + +``` +#!/bin/bash +# ============================================================================= +# force-cleanup-nvcf.sh - NVCA Component Removal Script +# ============================================================================= +# This script forcefully removes all NVCA components from a cluster. +# Use this as a LAST RESORT when normal cleanup methods fail due to: +# - Stuck finalizers on namespaces or custom resources +# - Orphaned resources blocking deletion +# - Partial deployments that need complete removal +# +# WARNING: This script will FORCEFULLY remove all NVCA resources, including +# removing finalizers which bypasses normal cleanup procedures. +# +# Usage: ./force-cleanup-nvcf.sh [--dry-run] +# ============================================================================= + +set -euo pipefail + +# --- Configuration --- +# NVCA-related namespaces +NVCA_NAMESPACES=( + "nvcf-backend" + "nvca-system" + "nvca-operator" +) + +# CRDs created by NVCA components +NVCA_CRDS=( + "nvcfbackends.nvcf.nvidia.io" +) + +# --- Parse Arguments --- +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + echo "Usage: $0 [--dry-run]" + echo "" + echo "Options:" + echo " --dry-run Show what would be deleted without making changes" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +echo "==============================================" +echo "NVCA Force Cleanup Script" +echo "==============================================" +if $DRY_RUN; then + echo "MODE: DRY-RUN (no changes will be made)" +fi +echo "" + +# --- Step 1: Show and delete function pods in nvcf-backend --- +echo ">>> Step 1: Checking for function pods in nvcf-backend namespace..." +if kubectl get namespace nvcf-backend >/dev/null 2>&1; then + pods=$(kubectl get pods -n nvcf-backend -o name 2>/dev/null || true) + if [[ -n "$pods" ]]; then + echo " Found the following pods that will be deleted:" + kubectl get pods -n nvcf-backend -o wide 2>/dev/null || true + echo "" + if ! $DRY_RUN; then + echo " Deleting all pods in nvcf-backend..." + kubectl delete pods -n nvcf-backend --all --force --grace-period=0 2>/dev/null || true + else + echo "[DRY-RUN] Would delete all pods in nvcf-backend namespace" + fi + else + echo " No pods found in nvcf-backend namespace" + fi +else + echo " nvcf-backend namespace not found, skipping..." +fi +echo "" + +# --- Step 2: Delete NVCFBackend Custom Resources --- +echo ">>> Step 2: Deleting NVCFBackend custom resources..." +if kubectl get crd nvcfbackends.nvcf.nvidia.io >/dev/null 2>&1; then + echo " Found NVCFBackend CRD, deleting all instances..." + if ! $DRY_RUN; then + kubectl delete nvcfbackends -A --all --wait=false 2>/dev/null || true + echo " Waiting 15 seconds for operator cleanup..." + sleep 15 + else + echo "[DRY-RUN] Would delete all NVCFBackends and wait for cleanup" + fi +else + echo " NVCFBackend CRD not found, skipping..." +fi +echo "" + +# --- Step 3: Delete Helm Releases --- +echo ">>> Step 3: Deleting Helm releases in NVCA namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + releases=$(helm list -n "$ns" -q 2>/dev/null || true) + if [[ -n "$releases" ]]; then + for release in $releases; do + echo " Deleting Helm release: $release (namespace: $ns)" + if ! $DRY_RUN; then + helm delete -n "$ns" "$release" --wait=false 2>/dev/null || true + fi + done + fi + fi +done +echo "" + +# --- Step 4: Force-delete stuck NVCFBackend resources (remove finalizers) --- +echo ">>> Step 4: Removing finalizers from stuck NVCFBackend resources..." +if kubectl get crd nvcfbackends.nvcf.nvidia.io >/dev/null 2>&1; then + nvcfbackends=$(kubectl get nvcfbackends -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null || true) + if [[ -n "$nvcfbackends" ]]; then + while IFS= read -r backend; do + if [[ -n "$backend" ]]; then + ns=$(echo "$backend" | cut -d'/' -f1) + name=$(echo "$backend" | cut -d'/' -f2) + echo " Removing finalizers from NVCFBackend: $name (namespace: $ns)" + if ! $DRY_RUN; then + kubectl patch nvcfbackend "$name" -n "$ns" -p '{"metadata":{"finalizers":[]}}' --type=merge 2>/dev/null || true + kubectl delete nvcfbackend "$name" -n "$ns" --wait=false 2>/dev/null || true + fi + fi + done <<< "$nvcfbackends" + else + echo " No stuck NVCFBackend resources found" + fi +else + echo " NVCFBackend CRD not found, skipping..." +fi +echo "" + +# --- Step 5: Delete Namespaces --- +echo ">>> Step 5: Deleting NVCA namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + echo " Deleting namespace: $ns" + if ! $DRY_RUN; then + kubectl delete namespace "$ns" --wait=false 2>/dev/null || true + fi + fi +done +echo " Waiting 10 seconds for namespace deletion..." +if ! $DRY_RUN; then + sleep 10 +fi +echo "" + +# --- Step 6: Force-remove finalizers from stuck namespaces --- +echo ">>> Step 6: Removing finalizers from stuck namespaces..." +for ns in "${NVCA_NAMESPACES[@]}"; do + phase=$(kubectl get namespace "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || true) + if [[ "$phase" == "Terminating" ]]; then + echo " Namespace $ns is stuck in Terminating, removing finalizers..." + if ! $DRY_RUN; then + # First, try to remove finalizers from all resources in the namespace + for resource_type in deployments statefulsets daemonsets replicasets pods services configmaps secrets serviceaccounts roles rolebindings; do + kubectl get "$resource_type" -n "$ns" -o name 2>/dev/null | while read -r resource; do + kubectl patch "$resource" -n "$ns" -p '{"metadata":{"finalizers":[]}}' --type=merge 2>/dev/null || true + done + done + + # Remove namespace finalizers using the API + kubectl get namespace "$ns" -o json | \ + jq '.spec.finalizers = []' | \ + kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f - 2>/dev/null || true + fi + fi +done +echo "" + +# --- Step 7: Delete CRDs --- +echo ">>> Step 7: Deleting NVCA CRDs..." +for crd in "${NVCA_CRDS[@]}"; do + if kubectl get crd "$crd" >/dev/null 2>&1; then + echo " Deleting CRD: $crd" + if ! $DRY_RUN; then + kubectl delete crd "$crd" --wait=false 2>/dev/null || true + fi + fi +done +echo "" + +# --- Step 8: Verification --- +echo ">>> Step 8: Verification..." +echo "" +echo "Remaining NVCA namespaces:" +remaining_ns=0 +for ns in "${NVCA_NAMESPACES[@]}"; do + if kubectl get namespace "$ns" >/dev/null 2>&1; then + phase=$(kubectl get namespace "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown") + echo " - $ns (status: $phase)" + remaining_ns=$((remaining_ns + 1)) + fi +done +if [[ $remaining_ns -eq 0 ]]; then + echo " None - all namespaces removed successfully" +fi + +echo "" +echo "Remaining NVCA CRDs:" +remaining_crds=0 +for crd in "${NVCA_CRDS[@]}"; do + if kubectl get crd "$crd" >/dev/null 2>&1; then + echo " - $crd" + remaining_crds=$((remaining_crds + 1)) + fi +done +if [[ $remaining_crds -eq 0 ]]; then + echo " None - all CRDs removed successfully" +fi + +echo "" +echo "==============================================" +if $DRY_RUN; then + echo "DRY-RUN complete. No changes were made." +else + if [[ $remaining_ns -eq 0 ]] && [[ $remaining_crds -eq 0 ]]; then + echo "Cleanup complete! All NVCA resources have been removed." + else + echo "Cleanup finished with some resources remaining." + echo "You may need to run this script again or investigate manually." + fi +fi +echo "==============================================" + +``` + +[force-cleanup-nvcf.sh](https://raw.githubusercontent.com/NVIDIA/nvcf/main/docs/v0.6.0/samples/scripts/force-cleanup-nvcf.sh) + +**Usage:** + +1. Download or copy the script to your working directory + +2. Make executable: `chmod +x force-cleanup-nvcf.sh` + +3. Preview what will be deleted: + + ```bash + ./force-cleanup-nvcf.sh --dry-run + ``` + +4. Run the cleanup: + + ```bash + ./force-cleanup-nvcf.sh + ``` + +**What the script does:** + +1. Lists and force-deletes all function pods in `nvcf-backend` namespace +2. Deletes all NVCFBackend custom resources +3. Deletes Helm releases in NVCA namespaces +4. Removes finalizers from stuck NVCFBackend resources +5. Deletes the NVCA namespaces (`nvcf-backend`, `nvca-system`, `nvca-operator`) +6. Removes finalizers from namespaces stuck in Terminating state +7. Deletes the NVCFBackend CRD +8. Verifies cleanup completion diff --git a/fern/docs.yml b/fern/docs.yml index 7eeed3fb4..a38e25a91 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -29,6 +29,9 @@ versions: - display-name: dev path: versions/dev.yml slug: dev +- display-name: "0.6.1-rc" + path: versions/v0.6.1-rc.yml + slug: "v0.6.1-rc" - display-name: "0.6.0" path: versions/v0.6.0.yml slug: "v0.6.0" diff --git a/fern/versions/v0.6.1-rc.yml b/fern/versions/v0.6.1-rc.yml new file mode 100644 index 000000000..c49f8c8d6 --- /dev/null +++ b/fern/versions/v0.6.1-rc.yml @@ -0,0 +1,297 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +navigation: + - page: Overview + path: ../../docs/v0.6.1-rc/index.md + - page: Quickstart + path: ../../docs/v0.6.1-rc/quickstart.md + + - section: Release Notes + skip-slug: true + contents: + - page: Release Notes Overview + path: ../../docs/v0.6.1-rc/release-notes/index.md + - page: "0.6.0 Release Notes" + path: ../../docs/v0.6.1-rc/release-notes/0.6.0.md + - page: "0.5.0 to 0.6.0 Upgrade" + path: ../../docs/v0.6.1-rc/release-notes/0.5.0-to-0.6.0-upgrade.md + + - section: Before You Deploy + skip-slug: true + contents: + - page: Infrastructure Sizing + path: ../../docs/v0.6.1-rc/infrastructure-sizing.md + - page: Manifest + path: ../../docs/v0.6.1-rc/manifest.md + + - section: Deployment + skip-slug: true + contents: + - page: Installation Overview + path: ../../docs/v0.6.1-rc/installation.md + - page: Image Mirroring + path: ../../docs/v0.6.1-rc/image-mirroring.md + - page: Helmfile Installation + path: ../../docs/v0.6.1-rc/helmfile-installation.md + - page: gRPC Invocation Enablement + path: ../../docs/v0.6.1-rc/grpc-invocation-enablement.md + - page: CSP End-to-End Example + path: ../../docs/v0.6.1-rc/csp-end-to-end-example-installation.md + + - section: GPU Cluster Setup + skip-slug: true + contents: + - page: GPU Cluster Setup + path: ../../docs/v0.6.1-rc/cluster-management/index.md + - page: Self-Managed Clusters + path: ../../docs/v0.6.1-rc/cluster-management/self-managed.md + + - section: Configuration + skip-slug: true + contents: + - page: Optional Enhancements + path: ../../docs/v0.6.1-rc/optional-enhancements.md + - page: LLM Function Enablement + path: ../../docs/v0.6.1-rc/llm-function-enablement.md + - page: Gateway Routing + path: ../../docs/v0.6.1-rc/gateway-routing.md + - page: Third-Party Registries + path: ../../docs/v0.6.1-rc/third-party-registries.md + - page: Registry Allowlist + path: ../../docs/v0.6.1-rc/registry-allowlist.md + - page: Cluster Configuration + path: ../../docs/v0.6.1-rc/cluster-management/configuration.md + - page: KAI Scheduler + path: ../../docs/v0.6.1-rc/cluster-management/kai-scheduler.md + - section: Low Latency Streaming + skip-slug: true + contents: + - page: LLS Installation + path: ../../docs/v0.6.1-rc/lls-installation.md + - section: NVCF Caches + skip-slug: true + contents: + - page: Container Cache + path: ../../docs/v0.6.1-rc/cluster-management/container-cache.md + - page: GXCache + path: ../../docs/v0.6.1-rc/cluster-management/gxcache.md + - section: Physical Simulation Caches + skip-slug: true + contents: + - page: Simulation Caches + path: ../../docs/v0.6.1-rc/caches.md + - section: DDCS + skip-slug: true + contents: + - link: DDCS + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/ + - link: DDCS Configuration + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/configure.html + - link: DDCS Deployment + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/deploy.html + - link: DDCS TLS + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/tls.html + - section: UCC + skip-slug: true + contents: + - link: UCC + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/ + - link: UCC Configuration + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/configure.html + - link: UCC Deployment + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/deploy.html + - link: UCC TLS + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/tls.html + + - section: Using Cloud Functions + skip-slug: true + contents: + - page: API + path: ../../docs/v0.6.1-rc/api.md + - page: Function Creation + path: ../../docs/v0.6.1-rc/function-creation.md + - page: LLM Gateway + path: ../../docs/v0.6.1-rc/llm-gateway.md + - page: Generic HTTP Function Invocation + path: ../../docs/v0.6.1-rc/generic-http-function-invocation.md + - page: gRPC Function Invocation + path: ../../docs/v0.6.1-rc/grpc-function-invocation.md + - page: Container Functions + path: ../../docs/v0.6.1-rc/container-functions.md + - page: Helm Functions + path: ../../docs/v0.6.1-rc/helm-functions.md + - page: Streaming Functions + path: ../../docs/v0.6.1-rc/streaming-functions.md + - page: Configure Autoscaling + path: ../../docs/v0.6.1-rc/configure-autoscaling.md + - page: CLI + path: ../../docs/v0.6.1-rc/cli.md + - page: Task Creation + path: ../../docs/v0.6.1-rc/task-creation.md + - page: Container-Based Task Creation + path: ../../docs/v0.6.1-rc/container-tasks.md + - page: Helm-Based Task Creation + path: ../../docs/v0.6.1-rc/helm-tasks.md + + - section: Function Autoscaling + skip-slug: true + contents: + - page: Function Autoscaling Overview + path: ../../docs/v0.6.1-rc/autoscaling/index.md + - page: Architecture + path: ../../docs/v0.6.1-rc/autoscaling/architecture.md + - page: Operations + path: ../../docs/v0.6.1-rc/autoscaling/operations.md + - page: Observability + path: ../../docs/v0.6.1-rc/autoscaling/observability.md + slug: autoscaler-observability + + - section: Observability + skip-slug: true + contents: + - page: Observability + path: ../../docs/v0.6.1-rc/observability.md + - page: Example Dashboards + path: ../../docs/v0.6.1-rc/example-dashboards.md + - section: Metrics + skip-slug: true + contents: + - page: Metrics Overview + path: ../../docs/v0.6.1-rc/metrics/metrics-index.md + - page: Cassandra + path: ../../docs/v0.6.1-rc/metrics/cassandra/metrics.md + - page: ESS + path: ../../docs/v0.6.1-rc/metrics/ess/metrics.md + - page: Init Container + path: ../../docs/v0.6.1-rc/metrics/init-container/metrics.md + - page: Invocation Service + path: ../../docs/v0.6.1-rc/metrics/invocation-service/metrics.md + - page: LLM API Gateway + path: ../../docs/v0.6.1-rc/metrics/llm-api-gateway/metrics.md + - page: LLM Function Invocation Metrics Report + path: ../../docs/v0.6.1-rc/metrics/llm-function-invocation-path.md + - page: LLM Request Router + path: ../../docs/v0.6.1-rc/metrics/llm-request-router/metrics.md + - page: NVCF API + path: ../../docs/v0.6.1-rc/metrics/nvcf-api/metrics.md + - page: SIS/Spot + path: ../../docs/v0.6.1-rc/metrics/sis-spot/metrics.md + - page: State Metrics + path: ../../docs/v0.6.1-rc/metrics/state-metrics/metrics.md + - page: Utils Container + path: ../../docs/v0.6.1-rc/metrics/utils-container/metrics.md + - page: Vault/OpenBao + path: ../../docs/v0.6.1-rc/metrics/vault-openbao/metrics.md + + - section: Operations + skip-slug: true + contents: + - page: Control Plane Operations + path: ../../docs/v0.6.1-rc/control-plane-operations.md + - page: Cluster Monitoring + path: ../../docs/v0.6.1-rc/cluster-management/monitoring.md + - page: Troubleshooting + path: ../../docs/v0.6.1-rc/troubleshooting.md + + - section: Runbooks + skip-slug: true + contents: + - page: Runbooks + path: ../../docs/v0.6.1-rc/runbooks/index.md + - page: Key Rotation + path: ../../docs/v0.6.1-rc/runbooks/control-plane-key-rotation-mek.md + - section: Cache Runbooks + skip-slug: true + contents: + - page: Caches Overview + path: ../../docs/v0.6.1-rc/runbooks/caches.md + - link: DDCS Cache Misses + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/runbooks/cache-misses.html + - link: DDCS Client Sharding + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/runbooks/client-sharding.html + - link: DDCS Disk Exhaustion + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/runbooks/disk-exhaustion.html + - link: DDCS Network Bottlenecks + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/runbooks/network-bottlenecks.html + - link: DDCS RocksDB Corruption + href: https://docs.omniverse.nvidia.com/ovcaches/ddcs/5.0/runbooks/rocksdb-corruption.html + - link: UCC Connection Saturation + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/runbooks/connection-saturation.html + - link: UCC Data Disk Bandwidth + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/runbooks/data-disk-bandwidth.html + - link: UCC Metadata Cache Undersizing + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/runbooks/metadata-cache-undersizing.html + - link: UCC Network Bandwidth Saturation + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/runbooks/network-bandwidth-saturation.html + - link: UCC Upstream S3 Connections + href: https://docs.omniverse.nvidia.com/ovcaches/ucc/3.0/runbooks/upstream-s3-connections.html + + - section: Reference + skip-slug: true + contents: + - page: Cluster Reference + path: ../../docs/v0.6.1-rc/cluster-management/reference.md + - page: gRPC Load Testing + path: ../../docs/v0.6.1-rc/grpc-load-testing.md + - page: gRPC Load Test SLI Guide + path: ../../docs/v0.6.1-rc/grpc-load-test-sli-guide.md + - page: HTTP Load Testing + path: ../../docs/v0.6.1-rc/http-load-testing.md + - page: HTTP Load Test SLI Guide + path: ../../docs/v0.6.1-rc/http-load-test-sli-guide.md + - page: HTTP Soak Testing + path: ../../docs/v0.6.1-rc/http-soak-testing.md + + - section: Development + skip-slug: true + contents: + - page: Architecture Overview + path: ../../docs/dev/architecture.md + - section: Local Development + skip-slug: true + contents: + - page: Local Development + path: ../../docs/v0.6.1-rc/local-development.md + - page: Single-cluster (CLI) + path: ../../docs/v0.6.1-rc/local-development/single-cluster-cli.md + - page: Single-cluster (Helmfile) + path: ../../docs/v0.6.1-rc/local-development/single-cluster-helmfile.md + - page: Multi-cluster (CLI) + path: ../../docs/v0.6.1-rc/local-development/multi-cluster-cli.md + - page: Multi-cluster (Helmfile) + path: ../../docs/v0.6.1-rc/local-development/multi-cluster-helmfile.md + - page: Fake GPU Operator + path: ../../docs/v0.6.1-rc/fake-gpu-operator.md + - page: Release Process + path: ../../docs/dev/release-process.md + + - section: Managed (Legacy) + skip-slug: true + contents: + - section: Cluster Management + skip-slug: true + contents: + - page: NGC-Managed Clusters + path: ../../docs/ngc-managed/cluster-management/ngc-managed.md + - page: Helm-Managed Clusters + path: ../../docs/ngc-managed/cluster-management/helm-managed.md + - page: Configuration + path: ../../docs/ngc-managed/cluster-management/configuration.md + - page: Monitoring + path: ../../docs/ngc-managed/cluster-management/monitoring.md + - page: Reference + path: ../../docs/ngc-managed/cluster-management/reference.md + - page: Container Cache + path: ../../docs/ngc-managed/cluster-management/container-cache.md + - page: GXCache + path: ../../docs/ngc-managed/cluster-management/gxcache.md + - page: KAI Scheduler + path: ../../docs/ngc-managed/cluster-management/kai-scheduler.md + - page: Function Lifecycle + path: ../../docs/ngc-managed/function-lifecycle.md + - page: Service Keys + path: ../../docs/ngc-managed/service-keys.md + - page: Observability + slug: managed-observability + path: ../../docs/ngc-managed/observability.md