OSUS Direct vs Replicated OpenShift
Choose between direct and replicated OSUS graph data modes in OpenShift. Configure UpdateService for connected and disconnected environments.
π‘ Quick Answer: OSUS (OpenShift Update Service) serves the upgrade graph that tells CVO which versions are available and safe. Direct mode β OSUS fetches graph data live from
api.openshift.com(connected clusters). Replicated mode β OSUS serves graph data from a local container image containing a pre-built graph (disconnected/air-gapped). Choose replicated for air-gapped, direct for connected, and a pull-through cache for hybrid.
The Problem
OpenShiftβs Cluster Version Operator (CVO) needs an upgrade graph to determine:
- Which versions exist
- Which upgrade paths are safe (tested)
- Which versions are blocked (CVEs, regressions)
- Conditional update risks
In connected clusters, CVO queries api.openshift.com directly. But in disconnected environments:
- No internet access to
api.openshift.com - Stale graph data means missing safe upgrade paths
- No visibility into blocked versions or conditional updates
- CVO shows βNo updates availableβ even when release images are mirrored
OSUS solves this β but you must choose how it gets its graph data.
The Solution
Mode Comparison
| Aspect | Direct | Replicated |
|---|---|---|
| Graph source | Live from api.openshift.com | Local graph-data container image |
| Internet required | Yes (OSUS β internet) | No |
| Freshness | Always current | Stale until image updated |
| Use case | Connected/hybrid | Fully disconnected |
| Graph updates | Automatic | Manual (mirror new image) |
| Setup complexity | Simple | More steps (mirror graph image) |
Architecture
graph TD
subgraph Direct Mode
CVO1[CVO] --> |queries| OSUS1[OSUS]
OSUS1 --> |fetches live| API[api.openshift.com<br/>Cincinnati graph]
end
subgraph Replicated Mode
CVO2[CVO] --> |queries| OSUS2[OSUS]
OSUS2 --> |reads| GDI[graph-data<br/>Container Image<br/>in Mirror Registry]
end
style API fill:#2196F3,color:white
style GDI fill:#FF9800,color:white
style OSUS1 fill:#EE0000,color:white
style OSUS2 fill:#EE0000,color:whiteInstall OSUS Operator
# Same for both modes
cat <<EOF | oc apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: cincinnati-operator
namespace: openshift-update-service
spec:
channel: v1
installPlanApproval: Automatic
name: cincinnati-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
oc get csv -n openshift-update-service | grep update-serviceDirect Mode (Connected)
apiVersion: updateservice.operator.openshift.io/v1
kind: UpdateService
metadata:
name: update-service
namespace: openshift-update-service
spec:
replicas: 1
releases: quay.io/openshift-release-dev/ocp-release
graphDataImage: "" # Empty = direct mode (fetch from upstream)In direct mode, OSUS acts as a local Cincinnati server that proxies graph requests to api.openshift.com. Benefits:
- Caches graph data locally
- Faster CVO queries (LAN vs internet)
- Single egress point for graph data (firewall-friendly)
- Still needs outbound HTTPS to
api.openshift.com
Replicated Mode (Disconnected)
Step 1: Mirror the Graph Data Image
# On a connected host, mirror the graph data image
# This image contains the Cincinnati graph database
# Using oc-mirror (recommended)
cat > imageset-config.yaml <<EOF
apiVersion: mirror.openshift.io/v2alpha1
kind: ImageSetConfiguration
mirror:
additionalImages:
- name: registry.redhat.io/openshift-update-service/graph-data:latest
EOF
oc mirror --config imageset-config.yaml \
docker://registry.example.com:8443
# Or using skopeo directly
skopeo copy --all \
docker://registry.redhat.io/openshift-update-service/graph-data:latest \
docker://registry.example.com:8443/openshift-update-service/graph-data:latestStep 2: Create UpdateService with Graph Data Image
apiVersion: updateservice.operator.openshift.io/v1
kind: UpdateService
metadata:
name: update-service
namespace: openshift-update-service
spec:
replicas: 2 # HA for production
releases: registry.example.com:8443/openshift-release-dev/ocp-release
graphDataImage: registry.example.com:8443/openshift-update-service/graph-data:latestThe key difference: graphDataImage is set β OSUS reads graph data from this container image instead of fetching from the internet.
Step 3: Trust the Mirror Registry CA
# Create ConfigMap with mirror registry CA
oc create configmap update-service-registry-ca \
--from-file=updateservice-registry=ca-bundle.crt \
-n openshift-update-service
# Note: The key MUST be "updateservice-registry"
# If registry URL has a port, use ".." separator:
# --from-file=registry.example.com..8443=ca-bundle.crtStep 4: Point CVO to OSUS
# Get the OSUS route
OSUS_ROUTE=$(oc get updateservice update-service -n openshift-update-service \
-o jsonpath='{.status.policyEngineURI}')
echo $OSUS_ROUTE
# https://update-service-policy-engine-route-openshift-update-service.apps.cluster.example.com
# Patch CVO to use OSUS
oc patch clusterversion version \
--type merge \
--patch "{\"spec\":{\"upstream\":\"${OSUS_ROUTE}/api/upgrades_info/v1/graph\"}}"
# Verify
oc get clusterversion version -o jsonpath='{.spec.upstream}'Hybrid Mode (Pull-Through Cache)
For environments with intermittent connectivity:
# Use direct mode but cache through a pull-through registry
apiVersion: updateservice.operator.openshift.io/v1
kind: UpdateService
metadata:
name: update-service
namespace: openshift-update-service
spec:
replicas: 1
releases: registry.example.com:8443/openshift-release-dev/ocp-release
graphDataImage: "" # Direct mode# Configure firewall to allow OSUS pod β api.openshift.com:443
# OSUS fetches graph live but release images come from mirrorThis gives you fresh graph data (direct) with mirrored release images β useful when the control plane has limited internet but worker nodes are air-gapped.
Keeping Replicated Graph Data Fresh
The graph-data image is a point-in-time snapshot. It goes stale as Red Hat publishes new versions and blocks old ones.
# Schedule regular graph data updates (e.g., weekly cron)
# On connected jump host:
#!/bin/bash
# update-graph-data.sh β run weekly
# Pull latest graph data
skopeo copy --all \
docker://registry.redhat.io/openshift-update-service/graph-data:latest \
docker://registry.example.com:8443/openshift-update-service/graph-data:latest
echo "Graph data updated at $(date)"
# OSUS will pick up the new image on next reconcile
# Or force restart:
# oc rollout restart deployment/update-service -n openshift-update-serviceVerify OSUS Is Working
# Check UpdateService status
oc get updateservice -n openshift-update-service
# NAME AGE POLICY ENGINE URI
# update-service 30d https://...
# Query the graph directly
curl -s "${OSUS_ROUTE}/api/upgrades_info/v1/graph?channel=stable-4.18&arch=amd64" | \
jq '.nodes | length'
# 42 β number of versions in graph
# Check available upgrades via CVO
oc adm upgrade
# Cluster version is 4.18.12
# Upgradeable=True
# VERSION IMAGE
# 4.18.15 quay.io/openshift-release-dev/...
# Check OSUS pod logs
oc logs -n openshift-update-service -l app=update-service --tail=50Graph Data Contents
The graph-data image contains:
/var/lib/cincinnati/graph-data/
βββ channels/
β βββ stable-4.17.yaml
β βββ stable-4.18.yaml
β βββ fast-4.18.yaml
β βββ candidate-4.18.yaml
β βββ eus-4.18.yaml
βββ blocked-edges/
β βββ 4.17.8-blocked.yaml # Known bad versions
β βββ 4.18.3-blocked.yaml
βββ conditional-edges/
βββ 4.18.10-conditional.yaml # "Update with caution" advisoriesCommon Issues
βNo updates availableβ in disconnected cluster
Graph data image is stale or not mirrored. Update the graph-data image and restart OSUS. Also verify CVO spec.upstream points to OSUS route, not api.openshift.com.
OSUS pod CrashLooping β βfailed to pull graph-data imageβ
Mirror registry unreachable or CA not trusted. Check the CA ConfigMap key name (must be updateservice-registry) and ensure OSUS namespace has the imagePullSecret.
CVO shows versions that arenβt mirrored
Graph data includes ALL versions but only mirrored release images are installable. Use oc-mirror to mirror the specific versions you need, not just the graph.
Graph shows blocked upgrade path
This is correct behavior β Red Hat blocked that path due to a known issue. Check the conditional edge details or use oc adm upgrade --include-not-recommended to see blocked versions.
policyEngineURI is empty in UpdateService status
OSUS route not created yet. Check if the OSUS pods are Running and the Route exists: oc get routes -n openshift-update-service.
Best Practices
- Replicated for air-gapped, direct for connected β donβt over-engineer
- Update graph-data image weekly in disconnected β stale graphs miss safe paths and blocks
- Use
policyEngineURIfrom status β donβt manually construct the OSUS route URL - HA:
replicas: 2for production β OSUS downtime blocks upgrade visibility - CA ConfigMap key naming matters β must be
updateservice-registry(with..for ports) - Mirror graph-data WITH release images β graph without images = visible but not installable
- Test OSUS with
curlbefore patching CVO β verify graph returns data
Key Takeaways
- Direct mode: OSUS fetches graph from
api.openshift.comlive (connected clusters) - Replicated mode: OSUS reads from a mirrored
graph-datacontainer image (disconnected) graphDataImagefield in UpdateService CR is the switch: empty = direct, set = replicated- Replicated graph data goes stale β schedule weekly updates of the graph-data image
- CVO must be patched to point
spec.upstreamat the OSUSpolicyEngineURI - CA trust is the #1 failure point β key naming in the ConfigMap must be exact
- The graph shows available paths; actual upgrade requires mirrored release images

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
