Before you begin, make sure your workstation and cluster meet the Kubernetes Requirements. Then install NVIDIA Minerva in this order. Each section ends with a check: confirm it before you move on.
-
Unpack the release.
-
Import the images into your registry.
-
Create the namespaces and pull secrets.
-
Create the certificates, trust bundle, and license.
-
Install the Keycloak Operator.
-
Install the NVIDIA Minerva operator.
-
Prepare the external addresses.
-
Configure the custom resources.
-
Apply the custom resources.
When you finish, continue with Validate and Sign In.
The commands on this page are the simplest way to do each task. If you have your own tooling for registries, secrets, or manifests, use it. What matters is the result.
Confirm that you're on the right cluster before each section. KUBECONFIG is set per shell, so an exported variable is lost when you reconnect.
kubectl config current-context
Cluster-scoped objects such as CRDs apply successfully to the wrong cluster. Namespaced objects then fail with namespaces "minerva-*" not found, which almost always means you're on the wrong cluster, not that you missed a step.
Unpack the Release
-
Compare the archive's SHA-256 digest with the one delivered with it, either as a
.sha256file beside the archive or on the download page:sha256sum <archive-file> -
Unpack the archive and go to its directory. Whatever the archive is called, it unpacks to
minerva-k8s-<version>-<arch>/:tar xzf <archive-file> cd minerva-k8s-<version>-<arch> -
Read
MANIFEST.txt. It names the platform release, the operator version, and the CPU architecture the images were built for. The archive runs only on that architecture. On any other, the import and installation seem to succeed, and then every pod fails withexec format error. -
Confirm that the directory contains the files listed in Release archive contents.
Import the Images into Your Registry
images/images.tar.gz is a standard docker-save archive. images/images.txt lists each image by the path it must have under your registry. Lines beginning with # are comments. Use whatever you normally use to populate your registry: a load-and-push from a workstation, skopeo, or your registry's import or replication feature.
Note: Load images/images.tar.gz once, and then tag and push each image from there. Don't read the archive with skopeo copy docker-archive: once per image. That extracts the whole archive for every image, and it fails when your temporary directory is smaller than the unpacked image set.
-
Import every image under one registry path: the value you chose for
${MINERVA_REGISTRY_URL}. -
Keep each image's path and tag exactly as listed in
images.txt. Flat names stay flat, and vendor paths stay nested. For example, with the registry pathregistry.example.com/minerva,strimzi/operator:0.47.0becomesregistry.example.com/minerva/strimzi/operator:0.47.0, andapi:1.1.0becomesregistry.example.com/minerva/api:1.1.0. -
Confirm that every entry in
images.txtresolves from your registry at its expected path. Fix any missing image now. Otherwise, it shows up during the installation asImagePullBackOff.
Docker 25 or later may report that only a single-platform image was loaded or pushed. For a single-architecture archive, that's expected.
Create the Namespaces and Pull Secrets
-
Create four namespaces:
minerva-system,minerva-infra,minerva-app, andminerva-identity. -
In each namespace, create an image-pull Secret for your registry named exactly
minerva-registry, of typekubernetes.io/dockerconfigjson. Pull secrets don't cross namespaces, so you need all four. Use whatever you normally use to manage secrets, such askubectl create secret docker-registry, a secrets operator, or GitOps. Use a credential that doesn't expire, or arrange its refresh. -
Confirm that
kubectl get ns | grep minervalists all four namespaces, and that each one has aminerva-registrySecret of typekubernetes.io/dockerconfigjson.
Create the Certificates, Trust Bundle, and License
The platform's front door uses the UI certificate, stored in the Secret minerva-app-nginx-tls in minerva-app. Besides your UI hostname, it must carry the in-cluster names shown below. A public CA doesn't issue in-cluster names, so use a private issuer or bring your own certificate. Sign-in does not need a certificate of its own: browsers reach the platform's login service through the same front door, under /identity on your UI hostname.
-
Request the certificate. With cert-manager:
kubectl apply -f - <<'YAML' apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: minerva-app-nginx-tls namespace: minerva-app spec: secretName: minerva-app-nginx-tls commonName: <ui-hostname> privateKey: { algorithm: RSA, size: 2048 } usages: ["server auth"] dnsNames: - <ui-hostname> - nginx.minerva-app - nginx.minerva-app.svc - nginx.minerva-app.svc.cluster.local - minerva-app-nginx - minerva-app-nginx.minerva-app - minerva-app-nginx.minerva-app.svc - minerva-app-nginx.minerva-app.svc.cluster.local - localhost issuerRef: { name: <issuer-name>, kind: <issuer-kind>, group: cert-manager.io } YAMLTo bring your own certificate instead, create the same Secret with
kubectl -n minerva-app create secret tls minerva-app-nginx-tls --cert=<file> --key=<file>. The certificate must carry the same subject alternative names. -
If you used cert-manager, wait until the certificate is issued. Until then, reading the Secret fails with
Could not read certificate.kubectl -n minerva-app wait --for=condition=Ready \ certificate/minerva-app-nginx-tls --timeout=120s -
Check the UI certificate. The issuer must be your CA, and the subject alternative names must include your UI hostname:
kubectl -n minerva-app get secret minerva-app-nginx-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer -dates -ext subjectAltName -
Create the trust bundle. Platform pods trust your CA through a Secret named
ca-certs, with the keyroot-ca.crt, that holds the PEM chain that signed the UI certificate above. Create it inminerva-app,minerva-infra, andminerva-identity. If you plan to federate login to an identity provider whose certificate is privately signed, add that CA too. -
In each of the three namespaces, confirm that the bundle holds a real certificate. An empty file still creates the key and only fails later, inside the pods.
kubectl -n minerva-app get secret ca-certs -o jsonpath='{.data.root-ca\.crt}' | base64 -d | openssl x509 -noout -subject -enddateThe command prints your CA's subject and expiry date.
unable to load certificatemeans the bundle is empty or isn't a PEM certificate. -
Create the license ConfigMap, so the license service finds the license when it first starts. To get a license file, see Get a License. The ConfigMap must be named
minerva-license, with the license in the keylicense.lic:kubectl -n minerva-app create configmap minerva-license --from-file=license.lic=<license-file>
You'll confirm the license state after the installation, in Validate and Sign In. The trust bundle is a point-in-time copy: when your CA rotates, re-create it and restart the workloads that use it.
Install the Keycloak Operator
The application tier waits until identity is ready, so it never starts without Keycloak. NVIDIA Minerva creates the Keycloak resources, and the upstream Keycloak Operator, which you install, runs them. The Keycloak Operator has no Helm chart.
-
Get these five files from the Keycloak project's
keycloak-k8s-resourcesrepository, at tag 26.7.5, directorykubernetes/, and put them in one directory:kubernetes.yml keycloaks.k8s.keycloak.org-v1.yml keycloakrealmimports.k8s.keycloak.org-v1.yml keycloakoidcclients.k8s.keycloak.org-v1.yml keycloaksamlclients.k8s.keycloak.org-v1.ymlAll four CRDs are required: the Keycloak Operator fails to start if any is missing. Its two images,
keycloak/keycloak-operator:26.7.5andkeycloak/keycloak:26.7.5, are already in your registry. -
In the same directory, save the following overlay as
kustomization.yaml, with your registry path filled in. It changes the namespace, the two image references, and the pull secret, and leaves the upstream files untouched.
-
Apply the overlay with server-side apply. Two of the CRDs are too large for a client-side apply.
kubectl apply --server-side -k <that-directory>/ -
Confirm that all four CRDs exist, that the operator rolls out, and that its image tag is
26.7.5:kubectl get crd | grep k8s.keycloak.org # all four CRDs kubectl -n minerva-identity rollout status deploy/keycloak-operator --timeout=120s kubectl -n minerva-identity get deploy keycloak-operator \ -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
Only one Keycloak controller may watch minerva-identity.
Install the NVIDIA Minerva Operator
-
Apply the chart's CRDs with server-side apply, because they're large. Then install the chart with
crds.install: false:mkdir -p work/chart && tar -xzf charts/minerva-operator-<chart-version>.tgz -C work/chart kubectl apply --server-side -f work/chart/minerva-operator/files/crds/ helm upgrade --install minerva-operator charts/minerva-operator-<chart-version>.tgz \ --namespace minerva-system --wait --timeout 10m \ -f - <<YAML image: registry: "<registry>/<path>" repository: minerva-operator tag: "<operator-version>" imagePullSecrets: - name: minerva-registry replicaCount: 2 webhook: enabled: true replicas: 2 failurePolicy: Fail podDisruptionBudget: minAvailable: 1 crds: install: false certManager: enabled: true issuer: create: true YAMLOn a single-node cluster, set
replicaCount: 1andwebhook.replicas: 1. The webhook replicas can't be scheduled on the same node. -
Confirm that the controller and webhook pods are running and ready, and that the three NVIDIA Minerva CRDs exist:
kubectl -n minerva-system get pods # controller and webhook Running and Ready kubectl get crd | grep minerva.nvidia.com # minervainfras, minervaidentities, minervaapps
The chart's own issuer serves only the operator's webhook and metrics certificates, not your UI certificate. Two events of the form BadConfig: Certificate will be issued with an empty Issuer DN come from it at this point, and they need no action. The commands are the same in Helm 3 and Helm 4.
Prepare the External Addresses
Two Services in minerva-app need LoadBalancer addresses. Each accepts traffic only from the networks in its loadBalancerSourceRanges.
|
Service |
Ports |
Used by |
|---|---|---|
|
UI front door (nginx) |
443/TCP |
Browsers |
|
Devices load balancer (telemetry) |
4317/TCP, 4318/TCP, 9091/TCP, 4319/TCP, 514/UDP, and 57402/TCP when WJH is on |
Devices and telemetry senders |
-
Set up how your load-balancing solution assigns the addresses. The reference custom resources name a MetalLB pool
minerva-collectors. MetalLB doesn't fall back when a named pool is missing, so either create a pool with that name or change the annotation in the application file. On Amazon EKS, use your AWS Load Balancer Controller annotations, and confirm that your controller version supports a Service that mixes TCP and UDP. -
(Optional) To publish the UI through your existing gateway instead, route HTTPS to backend port 443, and preserve the browser's
Hostheader and theX-Forwarded-*headers. Sign-in builds its redirect addresses from them. Then setspec.nginx.service.type: ClusterIPin the application file and remove its LoadBalancer-only settings. The telemetry endpoint carries gRPC and UDP, so it can't go behind an HTTP ingress.
You can assign the addresses later. The installation still completes, and the Services show <pending>. Until every LoadBalancer Service has an address, the application's ExternalAccessReady condition stays False.
Configure the Custom Resources
-
Copy the three files from
examples/to your working directory. -
Replace the placeholders one file at a time, using Values to Decide Before You Start. Don't use one search-and-replace across all three files.
-
Set the fields in the following table. Leave everything else as delivered: the inline comments explain each fixed setting, and several that look redundant aren't.
|
File |
Field |
Set to |
|---|---|---|
|
All three |
|
Your registry path |
|
Infrastructure and application |
|
Your StorageClass |
|
Infrastructure and application |
|
The platform version from |
|
Infrastructure |
|
|
|
Identity |
|
Your UI hostname, the same value as |
|
Application |
|
Your display names |
|
Application |
|
Your UI hostname |
|
Application |
Both |
Your client networks, and your device networks |
|
Application |
The LoadBalancer |
What your load balancer needs |
|
Application |
|
Only if you publish the UI through your gateway |
Warning: spec.version is required. If it's unset, the components don't fall back to the release version. Each one uses a built-in default tag that may not exist in your registry, and the failure shows up later as a job that can't pull its image, not as an error when you apply. For the same reason, spec.mysql.imageRepository: "mysql" is required and must not be removed.
-
In the application file, set the collector options under
spec.otelCollector. Most installations set only the device networks and the load-balancer annotations, both under thegateway-pushinstance. Devices reach the platform only throughgateway-push, by way of the Devices load balancer (theminerva-app-otel-data-lbDeployment and Service). For the settings, see What to set. For the optional collectors, see Optional collectors.
-
If you turned on WJH, set
wjh.secrets.dialoutTLSto exactly one of:-
secretRef.name: an existingkubernetes.io/tlsSecret inminerva-appwith the keystls.crtandtls.key. The Secret must exist before you apply. -
issuer: cert-manager issues and renews the certificate. Name your issuer inissuerRef.name.issuerRef.kinddefaults toIssuer, which must then exist inminerva-app; setkind: ClusterIssuerfor a cluster-scoped issuer. Put the address the switches dial inipAddressesordnsNames. Until the first certificate is issued,gateway-pushcan't start, and the application'sOtelCollectorReadycondition staysFalsewith the cert-manager reason in its message. If a later renewal fails, the current certificate keeps serving.
Either way, the certificate must name the exact IP address or DNS name the switches dial, normally the Devices load balancer address, so keep that address stable by pinning it with your load balancer. The certificate must also chain to a CA your switches trust: a switch rejects an unknown CA during the TLS handshake. If the switches' gNMI servers require authentication, also set
wjh.secrets.gnmiCreds.nameto a Secret with the keysusernameandpassword. -
-
Confirm that no placeholder is left. Every line this command prints must be a comment:
grep -n 'MINERVA_' customer-minerva*.yaml
Apply the Custom Resources
-
Apply the infrastructure, identity, and application resources in that order, waiting for each one:
kubectl apply --server-side -f customer-minervainfra.yaml kubectl -n minerva-infra wait --for=condition=Ready minervainfra/minerva-infra --timeout=30m kubectl apply --server-side -f customer-minervaidentity.yaml kubectl wait --for=condition=Ready minervaidentity/cluster-identity --timeout=20m kubectl apply --server-side -f customer-minervaapp.yaml kubectl -n minerva-app wait --for=condition=Ready minervaapp/minerva-app --timeout=30mAllow about 40 minutes on a cluster sized as in Capacity. In a reference run, the infrastructure took 19 minutes, identity 3, and the application 16. The operator resolves the order itself, so applying all three at once also works; applying them one at a time makes a failure easier to trace. While it settles, the following are normal:
-
KeycloakCRDsNotRegisteredon the identity resource, for about a minute. -
waiting for MinervaIdentity Ready=Trueon the application. The application is waiting for identity to be ready. -
One or two restarts of two Druid pods.
To watch progress:
kubectl get minervainfra,minervaidentity,minervaapp -A kubectl get pods -n minerva-infra kubectl get pods -n minerva-app -
-
Point your UI hostname at the address of the UI Service, or at your gateway. It's the only address to publish. Sign-in doesn't have a hostname, route, or Ingress of its own; it runs under
/identityon your UI hostname. -
Confirm that the collectors are ready:
kubectl -n minerva-app get statefulset/otel-collector-gateway-push-collector \ deployment/otel-collector-processor-collector deployment/otel-collector-internal-collector \ deployment/minerva-app-otel-data-lb service/minerva-app-otel-data-lbEvery workload must show all replicas ready, and the Service
minerva-app-otel-data-lbmust show an external address. It shows<pending>until your load balancer assigns one. If you turned on an optional collector, its workload must be ready too. Point your devices at the Service's address. If you enabled WJH, readiness alone doesn't prove that the switches trust the certificate or that their subscriptions work. Confirm that when you configure the switches.
Last updated: