Install Rafay Controller on OpenShift (Air-Gapped)¶
This guide covers the prerequisites and steps required to install the Rafay Self-Hosted Airgapped Controller on an existing, operational OpenShift cluster using radm. It assumes the OpenShift cluster is already installed and operational, and is scoped exclusively to the Rafay controller installation.
Prerequisites¶
OpenShift cluster requirements¶
| Requirement | Details |
|---|---|
| OpenShift version | 4.x (tested on 4.19.x, Kubernetes 1.32+) |
| Access level | cluster-admin kubeconfig |
| Node configuration (HA) | 3 control-plane nodes minimum (compact), or 3 control-plane + 3 workers (recommended for production) |
| Node configuration (POC / non-HA) | Single-Node OpenShift (SNO) possible; 3-node compact recommended |
| Node OS | RHCOS (immutable root filesystem — all host paths must be under /var; see OpenShift-specific overrides below) |
| Root disk per node | 1000 GB SSD minimum |
| Ingress | Support for Services of type LoadBalancer (cloud LB, or MetalLB on bare-metal OpenShift) |
| Resources (CPU / memory) | See Node sizing below |
Install host requirements¶
| Item | Details |
|---|---|
| OS | Linux, with network access to the OpenShift API server and the registry |
| Tools required | oc or kubectl, helm 3.x, jq (for the pull-secret merge) |
| Binary | radm matching the bundle version |
| Bundle | Rafay airgap tarball downloaded and extracted (see Controller installation → Download and extract the bundle) |
| Disk space | Minimum 200 GB free total: 100 GB for the downloaded + extracted bundle, and a minimum of 100 GB free under the unarchive path (default /tmp) to unarchive image tarballs |
Set the working directory for all commands used in this guide:
cd /path/to/rafay-airgapped-controller-<version>-openshift
Image registry (air-gapped)¶
A container registry reachable by the cluster is required (JFrog Artifactory, Harbor, Nexus, ECR, etc.) — this is what the Rafay images are pushed to and pulled from in the air-gapped install. Before running any radm install phase, the registry must be trusted by OpenShift and the Rafay images must be pushed to it (covered in Controller installation → Push images to the registry).
1. TLS trust
- Registry with a public CA certificate (e.g., DigiCert on JFrog Cloud) — no extra CA step needed.
- Registry with a private/self-signed CA — add the CA to OpenShift via
image.config.openshift.io/clusterspec.additionalTrustedCA, or configureadditionalTrustBundleat cluster install time.
2. Registry authentication — merge into the global pull secret
export KUBECONFIG=/path/to/kubeconfig
REG=myregistry.example.com
REG_USER='<username>'
REG_PASS='<password-or-token>'
# Extract current pull secret
oc get secret/pull-secret -n openshift-config \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d > /tmp/pull.json
# Merge new registry credentials
AUTH=$(printf '%s:%s' "$REG_USER" "$REG_PASS" | base64 -w0)
jq --arg reg "$REG" --arg auth "$AUTH" \
'.auths[$reg]={auth:$auth}' /tmp/pull.json \
> /tmp/pull-merged.json
# Apply merged pull secret
oc set data secret/pull-secret -n openshift-config \
--from-file=.dockerconfigjson=/tmp/pull-merged.json
rm -f /tmp/pull.json /tmp/pull-merged.json
# Wait for the Machine Config Operator to roll credentials to all nodes
oc get mcp # wait until UPDATED=True, UPDATING=False
oc get nodes
Storage¶
The Rafay controller requires Kubernetes StorageClass resources for PVCs: one ReadWriteOnce (RWO) and one ReadWriteMany (RWX) class. Storage must be deployed on the cluster before installation — radm does not deploy storage on OpenShift. Any CSI-compliant backend can be used; install it by following the storage vendor's public documentation, for example:
| Storage option | Public documentation | Typical classes (RWO / RWX) |
|---|---|---|
| Longhorn | https://longhorn.io/docs/ (OCP install guide) | longhorn / longhorn (RWX via share-manager) |
| OpenEBS | https://openebs.io/docs/ | openebs-hostpath / openebs-kernel-nfs |
| OpenShift Data Foundation (Ceph) | Red Hat ODF documentation | ocs-storagecluster-ceph-rbd / ocs-storagecluster-cephfs |
These classes are referenced in config.yaml during configuration (Controller installation → Configure config.yaml):
spec:
storage:
external:
enabled: true # true = storage is pre-provisioned on the cluster (this guide).
# radm skips its storage phase and uses the classes below.
storageClass:
readWriteOnce: "<your-rwo-sc>"
readWriteOnceReplicated: "<your-rwo-sc>"
readWriteMany: "<your-rwx-sc>" # required when deployment.ha: true
DNS and ingress¶
Rafay exposes all traffic via the Istio ingress gateway (a Service of type LoadBalancer in istio-system). DNS records for the controller domain must point to the EXTERNAL-IP of that service. The IP is assigned during the dependency install phase, so prepare the DNS zone in advance; the records are created after that phase completes (Controller installation → Configure DNS with the Istio external IP).
A wildcard DNS record is recommended for the controller domain (replace rafay.example.com with your chosen domain):
*.rafay.example.com
If a wildcard is not possible, create the individual records below:
api.<rafay.example.com>
console.<rafay.example.com>
fluentd-aggr.<rafay.example.com>
ops-console.<rafay.example.com>
grafana.<rafay.example.com>
kibana.<rafay.example.com>
*.cdrelay.<rafay.example.com>
*.core-connector.<rafay.example.com>
*.core.<rafay.example.com>
*.infrarelay.<rafay.example.com>
*.kubeapi-proxy.<rafay.example.com>
*.user.<rafay.example.com>
Node sizing¶
The cluster must provide the following total allocatable capacity across schedulable nodes for the selected controller size (spec.deployment.size in config.yaml):
| Size | HA mode | CPU (minimum) | Memory (minimum) | Root disk per node |
|---|---|---|---|---|
| POC | Non-HA only | 32 CPU | 64 GB | 1000 GB SSD |
| S | HA only | 32 CPU | 64 GB | 1000 GB SSD |
| M | HA only | 48 CPU | 96 GB | 1000 GB SSD |
| L | HA only | 96 CPU | 192 GB | 1000 GB SSD |
| XL | HA only | 128 CPU | 192 GB | 1000 GB SSD |
Controller installation¶
The following installs the Rafay Self-Hosted Airgapped Controller: images are pulled from the release bundle and pushed to your own registry, and the cluster pulls exclusively from that registry — no images are fetched from the public internet during install.
All commands are run from the install host, from the bundle directory.
1. Download and extract the bundle¶
sudo mkdir -p /opt/rafay && cd /opt/rafay
wget https://rafay-airgap-controller.s3.us-west-2.amazonaws.com/3.1/rafay-airgapped-controller-<version>-openshift.tar.gz
# Verify the md5sum of the downloaded package against the value
# provided with the release
md5sum rafay-airgapped-controller-<version>-openshift.tar.gz
# Untar the package
tar -xf rafay-airgapped-controller-<version>-openshift.tar.gz
cd rafay-airgapped-controller-<version>-openshift
# Install the radm binary and copy the config template
sudo cp ./radm /usr/bin/
cp config.yaml-airgap-tmpl config.yaml
vi config.yaml
2. Configure config.yaml¶
Set the minimum required fields:
apiVersion: radm.k8s.io/v1beta1
kind: InitConfiguration
metadata:
name: RafayAirGapController
spec:
blueprintVersion: v3
# External registry --- all chart images pull from here
external-registry-endpoint: "myregistry.example.com/rafay-docker/rafay"
deployment:
ha: true # true for S/M/L/XL; false only for POC
size: "S" # POC | S | M | L | XL
kube-config: "/path/to/kubeconfig" # OpenShift kubeconfig --- radm targets
# the existing cluster (radm init is NOT used)
storage:
external:
enabled: true
storageClass:
readWriteOnce: "<your-rwo-sc>"
readWriteOnceReplicated: "<your-rwo-sc>"
readWriteMany: "<your-rwx-sc>"
metrics-server:
external: true # OpenShift already provides metrics-server
repo:
archive-directory: /path/to/rafay-airgapped-controller-<version>-openshift
unarchive-path: /tmp # needs minimum 100 GB free
rafay-registry:
registry-subpath: "rafay-docker" # repository key/path in your registry;
# images live at <endpoint>/<subpath>/rafay/<image>
app-config:
generate-self-signed-certs: true
partner:
star-domain: rafay.example.com # your controller base domain
super-user:
user: [email protected]
password: <base64-encoded-password>
Warning: Path alignment is critical.
external-registry-endpointmust exactly match whereradm registry sync(see Push images to the registry) pushes the images:<endpoint>/<registry-subpath>/rafay/<image>:<tag>. Any mismatch causesErrImagePullor "manifest unknown" errors.
OpenShift-specific overrides¶
RHCOS has a read-only root filesystem, so hostPath volumes under /data or /storage will fail. Always use /var/... paths. Add these OpenShift-specific overrides under override-config:
override-config:
localprovisioner.basePath: "/var/openebs/local"
core-registry-path: "/var/registry"
etcd-path: "/var/etcd"
global.external_lb: "false"
global.enable_hosted_dns_server: "false"
Export your kubeconfig before starting the install phases below:
cd /path/to/rafay-airgapped-controller-<version>-openshift
export KUBECONFIG=/path/to/kubeconfig # or pass --kubeconfig on each command
3. Push images to the registry (radm registry sync)¶
Push the bundle image archives — downloaded in step 1, with no external network calls — into the registry configured in config.yaml. The --archive flags list the image tarballs from the bundle directory; --stage is the staging directory used to unarchive them (minimum 100 GB free).
JFrog / generic Docker registry:
./radm registry sync jfrog \
--config config.yaml \
--endpoint myregistry.example.com \
--user "<your-registry-user>" \
--password "<your-registry-token-or-api-key>" \
--archive rafay-infra-images.tar.gz \
--archive rafay-dep-images-v3.tar.gz \
--archive rafay-core-images.tar.gz \
--archive rafay-istio-1.27.2-images.tar.gz \
--stage /tmp
AWS ECR:
./radm registry sync ecr \
--config config.yaml \
--endpoint <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com \
--ecr-region <REGION> \
--archive rafay-infra-images.tar.gz \
--archive rafay-dep-images-v3.tar.gz \
--archive rafay-core-images.tar.gz \
--archive rafay-istio-1.27.2-images.tar.gz \
--stage /tmp
Verify the sync:
oc run registry-pull-test --restart=Never \
--image=myregistry.example.com/rafay-docker/rafay/<any-image>:<tag> \
--command -- sleep 30
oc get pod registry-pull-test # expect: Running
oc delete pod registry-pull-test
4. Install dependencies (radm dependency)¶
Installs the platform charts: Istio, cert-manager, kube-prometheus-stack, postgres, kafka, and other platform dependencies — all pulled from your registry.
sudo ./radm dependency \
--config config.yaml \
--kubeconfig $KUBECONFIG
On OpenShift, this phase automatically:
- Detects OpenShift via the API (
security.openshift.io) - Grants the
anyuidandprivilegedSCCs to the Rafay namespaces - Skips
haproxy-ingressandmetrics-server(provided by the platform) - Skips uploading images to the local registry when
external-registry-endpointis set
Verify:
oc get pods -n istio-system
oc get pods -n cert-manager
oc get svc istio-ingressgateway -n istio-system # note the EXTERNAL-IP for DNS (see "Configure DNS with the Istio external IP" below)
5. Install application — Rafay core (radm application)¶
Installs the Rafay controller components: Nexus, Dgraph, Kafka, paas-api, dashboard, and all core services. Allow approximately 20–30 minutes for all pods to become ready; pods may briefly restart during database migrations.
sudo ./radm application \
--config config.yaml \
--kubeconfig $KUBECONFIG
Verify:
oc get pods -n rafay-core
oc get pods -n rafay-core | grep -E 'nexus|dgraph|paas-api|dashboard'
helm list -n rafay-core
6. Configure DNS with the Istio external IP¶
Get the external IP assigned to the Istio ingress gateway, add it to the DNS records for the controller domain (see Prerequisites → DNS and ingress), and verify DNS resolution before proceeding.
# 1. Get the Istio gateway external IP
oc get svc istio-ingressgateway -n istio-system \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# 2. Add/point the DNS records to this IP:
# *.rafay.example.com → <EXTERNAL-IP>
# (or the individual records listed under "Prerequisites → DNS and ingress")
# 3. Verify DNS resolves to the external IP
dig +short console.rafay.example.com
dig +short ops-console.rafay.example.com
# Both must return the Istio gateway EXTERNAL-IP before continuing
7. Upload cluster provisioning artifacts (radm cluster)¶
Required only if you plan to provision managed clusters from the Rafay console. Uploads cluster images and binary assets to the controller. DNS (see Configure DNS with the Istio external IP) must resolve before this step, since assets upload to ops-console.<star-domain>.
sudo ./radm cluster \
--config config.yaml \
--kubeconfig $KUBECONFIG \
--fqdn rafay.example.com
# Replace rafay.example.com with your app-config.partner.star-domain value
Validation¶
Confirm the controller installed correctly and is functioning end-to-end.
1. Pod health¶
oc get pods -n rafay-core
oc get pods -n istio-system
# All critical pods should be Running or Completed (jobs)
oc get pods -A | grep -vE 'Running|Completed'
2. Helm releases¶
helm list -n rafay-core
# all releases should show STATUS: deployed
3. Ingress and DNS¶
# Get the Istio gateway external IP
oc get svc istio-ingressgateway -n istio-system -o wide
# DNS should resolve to that IP
dig +short console.rafay.example.com
dig +short ops-console.rafay.example.com
4. Image provenance (air-gapped check)¶
Confirm the running pods are pulling exclusively from your private registry and not from any public source:
oc get pods -n rafay-core -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -u
# every image reference should be prefixed with your registry endpoint
# (<endpoint>/<registry-subpath>/rafay/...)
5. Access the Rafay console¶
Open the console in a browser via the Istio ingress:
https://console.<star-domain>
Use the Sign Up link to create the first organization for the controller and register a new account, then log in with the newly registered account. A successful login confirms the API, database, and UI layers are all functioning.
6. Cluster provisioning readiness (if applicable)¶
If Upload cluster provisioning artifacts was run, confirm the provisioning assets are reachable:
https://ops-console.<star-domain>