⌘K
0 / 22

What is OpenShift?

Enterprise Kubernetes with built-in security, automation, and a complete developer experience.

Documentation baseline: This guide is verified against OpenShift Container Platform 4.22 (Kubernetes 1.35) as of 11 July 2026. Product add-ons follow their own compatibility and support lifecycles. Confirm release-specific procedures in the official OCP 4.22 documentation before applying them to a cluster.

OpenShift at a Glance

Red Hat OpenShift Container Platform (OCP) is an enterprise Kubernetes distribution. It extends upstream Kubernetes with automated operations, consistent security, and developer tooling. Deploy and manage containerized applications at scale — on-premises, cloud, or edge.

OpenShift = Kubernetes + Enterprise Platform: Core OCP includes the Kubernetes control plane, RHCOS for control-plane nodes, CRI-O, OVN-Kubernetes, cluster Operators, OAuth, the web console, ingress, the internal registry Operator, and platform monitoring. Pipelines, GitOps, logging, Virtualization, ACM, ACS, ODF, Quay, and cert-manager are layered products or Operators: entitlement varies by edition and they are not all installed by default.

Architecture Overview

OpenShift Container Platform Architecture
Kubernetes-native OpenShift adds (not in upstream K8s) OpenShift replaces K8s default CONTROL PLANE — 3x Master Nodes (RHCOS) API Server :6443 REST gateway etcd (x3) Raft consensus store Controller Manager Reconciliation loops Scheduler Pod to Node binding OAuth / Console Identity + Web UI OCP adds CLUSTER OPERATORS (OCP adds): CVO | Ingress | Network | DNS | Registry | Monitoring | Console | MCO | Auth | Storage WORKER NODES — Nx scalable (RHCOS / RHEL) CRI-O Replaces containerd kubelet Node agent OVN-Kubernetes Replaces kube-proxy CSI Plugins Storage drivers Application Pods Deployments, StatefulSets, Jobs PLATFORM SERVICES (OCP adds): HAProxy Router | Internal Registry | Prometheus | Alertmanager | Console UI | CoreDNS OPTIONAL LAYERED PRODUCTS — Entitlement and Installation Vary ACM — Multi-Cluster ACS — Security ODF — Storage Virtualization — Run VMs RHCOS — Immutable OS OCP adds: ostree atomic updates | MCO managed Operator Lifecycle Manager OCP adds: catalogs | installation | dependency resolution

Version Alignment — OCP, RHCOS, CRI-O & Kubernetes

Core OpenShift versions are tightly coupled with RHCOS, CRI-O, and Kubernetes. They are tested, qualified, and shipped together as a matched set. The Cluster Version Operator coordinates core platform updates, while layered Operators such as GitOps, Pipelines, ACM, ACS, ODF, and Quay have separate releases and compatibility matrices. RHEL compute nodes also retain a separate operating-system maintenance lifecycle.

OCP Core Version Alignment — Platform Components Move Together
Version Mapping (each OCP release pins exact versions) OCP RHCOS CRI-O Kubernetes RHCOS Base 4.22 422.98.x 1.35.x 1.35 RHEL 9.8 4.20 EUS 420.96.x 1.33.x 1.33 RHEL 9.6 4.18 EUS 418.94.x 1.31.x 1.31 RHEL 9.4 How It Works — One Core Platform Update, Coordinated by CVO OpenShift 4.x CVO orchestrates RHCOS MCO applies OS update CRI-O Bundled in RHCOS Kubernetes API/kubelet version RHCOS Base RHEL packages + kernel Key Rules: • CRI-O major.minor matches Kubernetes major.minor (for example, 1.35 in OCP 4.22) • RHCOS build numbers vary by OCP z-stream; verify the installed release image • Layered Operators and RHEL workers have separate update lifecycles

RHCOS — Red Hat Enterprise Linux CoreOS

RHCOS is OpenShift's controlled-immutable, container-optimized operating system. It is required for bootstrap and control-plane machines and is the default for compute machines. Supported user-provisioned installations can instead use supported RHEL compute nodes, whose operating-system lifecycle is managed separately.

Manage RHCOS declaratively: The Machine Config Operator (MCO) applies supported host configuration such as CRI-O and kubelet settings, registries, files, systemd units, kernel arguments, and SSH keys. Administrators still own capacity, health, change review, upgrade readiness, troubleshooting, and recovery; routine package-by-package administration and direct SSH changes are discouraged.

High-Level Deployment Architecture

Typical OpenShift Cluster — Load Balancers, Node Roles & Network Layers
EXTERNAL API Load Balancer api.cluster.domain :6443 DevOps / Admin / CLI Apps Load Balancer *.apps.cluster.domain :443/:80 Application Traffic (Routes) OPENSHIFT CLUSTER master-0 master-1 master-2 worker + infra router/mon/reg worker + infra router/mon/reg Control Plane (etcd, API, scheduler) Infra + App workloads (co-located) Machine Network Storage (FC / iSCSI) worker-0 worker-1 worker-2 worker-3 worker-4 Application Worker Nodes Machine Network Storage (FC / iSCSI) Control Plane Worker + Infra Worker Node

OpenShift vs Kubernetes

CapabilityKubernetes (Upstream)OpenShift (OCP)
Operating SystemSelected and managed by the cluster operatorRHCOS required for control plane; RHCOS or supported RHEL for eligible compute nodes
InstallationCommunity and vendor-specific tools such as kubeadmInstaller-provisioned, user-provisioned, Assisted, and Agent-based workflows
UpgradesMechanism and support depend on the chosen distributionRecommended update graph coordinated by the Cluster Version Operator
NetworkingCNI implementation selected separatelyOVN-Kubernetes is the supported default
IngressIngress API; controller selected separatelyIngress Operator, HAProxy router, Kubernetes Ingress, and OpenShift Route
RegistryRegistry selected separatelyIntegrated image registry Operator; production storage still requires configuration
SecurityRBAC and Pod Security Admission primitivesRBAC, SCC, OAuth, and optional install-time FIPS mode
MonitoringMonitoring stack selected separatelyCore platform Prometheus and Alertmanager deployed by default
CI/CD & GitOpsTools selected separatelySupported Pipelines (Tekton) and GitOps (Argo CD) Operators; installed separately
OperatorsOperator frameworks and catalogs are optionalOLM and OperatorHub integration; individual Operators are installed separately
ConsoleDashboard or vendor UI selected separatelyIntegrated, RBAC-aware web console with unified perspectives in OCP 4.19+
SupportVaries by Kubernetes distribution and providerRed Hat subscription support with terms determined by the selected offering

OpenShift Product Editions

Red Hat offers four self-managed editions that build upon each other. The diagram describes subscription entitlement, not what is automatically deployed: layered capabilities still require their Operators and custom resources to be installed and configured.

Red Hat OpenShift Editions — Feature Entitlements
* Example entitlements — verify with Red Hat for current details OVE Virtualization Engine OKE Kubernetes Engine OCP Container Platform OPP Platform Plus INCLUDED CAPABILITIES OpenShift Virtualization (KubeVirt) Container Workloads Pipelines / Serverless / Service Mesh / GitOps ACM (Multi-cluster Management) ACS (Advanced Cluster Security) / Quay SUBSCRIPTION MODEL Core-pair (2 cores / 4 vCPUs) Bare-metal node (socket-pair) Standard (8×5) / Premium (24×7)

Support Tiers

FeatureStandardPremium
Support Hours8×5 (business hours)24×7
Sev 1/2 ResponseBusiness hours onlyAround the clock
EUS (Extended Update Support)Optional add-onIncluded
Infra node subscription: Nodes labeled node-role.kubernetes.io/infra running only platform workloads (router, registry, monitoring) do not count toward subscription. However, on large bare-metal servers it is common to co-locate infra and app workloads on the same worker nodes — in that case the node does count toward subscription since it also runs application containers.

Installation Methods

IPI (Installer-Provisioned)
Fully automated: provisions infrastructure + deploys cluster. Best for cloud (AWS, Azure, GCP, vSphere).
UPI (User-Provisioned)
You provision infrastructure, installer deploys cluster. For on-prem and custom requirements.
Assisted Installer
Web-based wizard with agent on bare-metal. Ideal for bare-metal and edge deployments.
SNO (Single Node)
All-in-one: control plane + worker on a single node. For edge, lab, and dev environments.

OpenShift Virtualization

OpenShift Virtualization lets you run traditional virtual machines alongside containers on the same cluster. Based on the KubeVirt project, it wraps QEMU/KVM in Kubernetes-native CRDs.

OpenShift Virtualization — VMs as First-Class Citizens on Kubernetes
VirtualMachine Persistent VM definition run strategy, template, disks VirtualMachineInstance Running VM instance like Pod for containers virt-launcher Pod Wraps QEMU/KVM 1 pod = 1 VM Guest OS RHEL, Windows, CentOS, Ubuntu... Full OS with its own kernel qemu-guest-agent for integration KEY CAPABILITIES Live Migration Snapshots Hot-plug CPU/Disk VM Templates Import from VMware VMs Share the Same Infrastructure as Containers Networking (OVN) Same pod network + Services Storage (PVCs) VM disks = PersistentVolumes Security (RBAC/SCC) Same auth + access control Monitoring (Prometheus) VM metrics + alerts built-in
When to use OpenShift Virtualization: Legacy apps that can't be containerized, Windows workloads (Windows Server 2019/2022 supported), gradual VMware migration (MTV — Migration Toolkit for Virtualization imports VMs from vSphere), or running VMs and containers side-by-side during modernization.

See the OpenShift Virtualization section for the full KubeVirt architecture, VirtualMachine/DataVolume YAML, live migration mechanics, and MTV migration details.

Nodes & Node Types

Understanding the physical and logical building blocks of an OpenShift cluster.

What is a Node?

A node is a physical or virtual machine that participates in the cluster. Every Linux node runs kubelet, CRI-O, and cluster networking components. RHCOS nodes also run the Machine Config Daemon; supported RHEL compute nodes use a separate operating-system management workflow.

Node Architecture

OpenShift Node Types & Their Roles
BOOTSTRAP Temporary (install only) Bootstraps etcd + API Removed after install CONTROL PLANE (x3 min) kube-apiserver — REST API gateway etcd — distributed key-value store kube-controller-manager kube-scheduler — pod placement openshift-apiserver + oauth-server OS: RHCOS only | Not schedulable by default INFRASTRUCTURE (x2-3) Ingress Controller (HAProxy) Internal Registry Monitoring (Prometheus + Alertmanager) Logging (Loki / EFK) Service Mesh (optional) Label: node-role.../infra | No sub cost WORKER / COMPUTE (xN) Application pods (user workloads) DaemonSets (node-exporter, fluentd) Managed by MachineSet / Autoscaler OS: RHCOS or supported RHEL | Schedulable CORE NODE SERVICES kubelet | CRI-O | OVN-Kubernetes | CSI node plugin | MCD on RHCOS nodes | workload DaemonSets MACHINE API Machine → MachineSet → MachineAutoscaler → ClusterAutoscaler | MachineHealthCheck

Node Comparison

PropertyControl PlaneInfrastructureWorker
PurposeCluster brain — runs API Server, etcd, Controller Manager, Scheduler. Manages all cluster state and decisions.Platform services — runs Ingress Controller (HAProxy), Registry, Prometheus, Alertmanager, Logging. Keeps platform workloads off worker nodes.Application workloads — runs user pods, deployments, jobs. Where your actual business applications run.
OSRHCOS onlyUsually RHCOS; supported RHEL is possible for eligible compute nodesRHCOS or supported RHEL on eligible user-provisioned compute nodes
SchedulableNo (default)Yes (restricted by taints)Yes
Min Count3 (HA) / 1 (SNO)2-3 recommended2+ (prod)
SubscriptionIncludedFree if only platform workloadsCharged

Inside Every Node — Component Deep Dive

OpenShift nodes run a common set of Kubernetes and platform services, with some host-management components specific to RHCOS. Understanding what each does is essential for troubleshooting and architecture decisions.

Node Internals — Components Running on Every Worker Node
OpenShift Node (RHCOS) Each layer depends on the one below it PODS (user + platform DaemonSets on every node) App Pod 10.128.2.15:8080 App Pod 10.128.2.22:3000 App Pod 10.128.2.31:9090 . . . node-exporter DS — metrics vector DS — logs CSI Node DS — volumes ovnkube-node DS — networking dns-default DS — CoreDNS MCD DS — OS config tuned DS SYSTEMD SERVICES (on every node) kubelet Primary node agent — manages all pods on this node Pod lifecycle | Probes | Volume mount | Status → API Server Port :10250 | Watches API Server for pod assignments manages all pods CRI-O Container runtime (OCI). Not Docker. Pull image | Create container | Start/Stop | cgroups CRI gRPC socket | Uses runc/crun | Versioned with K8s gRPC runs containers Machine Config Operator (MCO) Delivers OS config to nodes MCD pod applies configs Reboot on kernel/systemd changes NETWORKING STACK OVN-Kubernetes + Open vSwitch (CNI) Pod networking | Service LB (no kube-proxy) | Geneve tunnels (UDP 6081) NetworkPolicy via ACLs | EgressIP | Multus | br-int bridge per node ovnkube-node DaemonSet programs OVS flow rules CoreDNS (dns-default) Cluster DNS — DaemonSet, one per node <svc>.<ns>.svc.cluster.local → ClusterIP Port 53 UDP/TCP | Managed by DNS Operator OPERATING SYSTEM RHCOS (Red Hat Enterprise Linux CoreOS) Immutable | ostree atomic updates | SELinux enforcing | FIPS | Managed by MCO Linux Kernel cgroups v2 | namespaces | seccomp | capabilities Connections to Control Plane kubelet → API Server :6443 (watches pods, reports node status) ovnkube-node → ovnkube-master (network config)    MCD → API Server (watches MachineConfig)    CoreDNS → upstream DNS

kubelet — The Node Agent

The kubelet is the primary agent on every node. It registers the node with the API server, watches for pod assignments, and ensures containers are running as expected.

# Check kubelet status on a node
oc debug node/<node-name> -- chroot /host systemctl status kubelet

# View kubelet logs
oc debug node/<node-name> -- chroot /host journalctl -u kubelet --no-pager -n 50

# Check node conditions
oc describe node <node-name> | grep -A5 Conditions

CRI-O — Container Runtime

CRI-O is the lightweight, OCI-compliant container runtime used by OpenShift. It implements the Kubernetes Container Runtime Interface (CRI) and completely replaces Docker.

# Check CRI-O status
oc debug node/<node-name> -- chroot /host systemctl status crio

# List containers on a node via crictl
oc debug node/<node-name> -- chroot /host crictl ps

# Inspect a container
oc debug node/<node-name> -- chroot /host crictl inspect <container-id>

OVN-Kubernetes — Software-Defined Networking

OVN-Kubernetes is the default CNI plugin since OpenShift 4.12. It provides pod networking, service load balancing, and network policy enforcement using Open Virtual Network (OVN) on top of Open vSwitch (OVS).

CoreDNS — Cluster DNS

CoreDNS is the cluster DNS server providing service discovery. Every pod is configured to use CoreDNS for name resolution.

Machine Config Daemon (MCD)

The MCD is the node-level agent of the Machine Config Operator (MCO). It runs on RHCOS nodes and handles supported OS-level configuration delivery. RHEL compute nodes are maintained outside this RHCOS/MCO lifecycle.

Node Tuning Operator

The Node Tuning Operator manages node-level performance tuning via Tuned profiles. A tuned DaemonSet pod runs on every node applying sysctl parameters, hugepage allocation, CPU governor settings, and NUMA pinning.

CSI Node Plugin

Each storage provider runs a CSI Node Plugin as a DaemonSet on every node. It handles the last-mile operations of making storage available to pods.

Monitoring & Logging Agents

Every node runs DaemonSet pods for observability:

Taints, Tolerations & Affinity

How the scheduler decides which node runs each pod — and how you control placement.

Scheduling Decision Flow

Pod Scheduling — Filter → Score → Bind
Pending Pod In scheduling queue Filter: Taints Pod has toleration? No match = node excluded Filter: Affinity nodeSelector match? requiredDuring... = hard Filter: Resources CPU/memory available? requests vs allocatable Score (0-100) preferredDuring... weight Highest score wins Bind to Node TAINT EFFECTS NoSchedule — hard block, existing pods stay PreferNoSchedule — soft, scheduler tries to avoid NoExecute — hard block + evicts existing pods AFFINITY TYPES nodeSelector (simple) nodeAffinity (advanced) podAffinity (co-locate) podAntiAffinity (spread)

Taints & Tolerations

A taint on a node repels pods. A toleration on a pod allows it to schedule on a tainted node. Taints are key=value:effect.

# Add taint to a node
oc adm taint nodes worker-1 dedicated=infra:NoSchedule

# Pod toleration to match
spec:
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "infra"
      effect: "NoSchedule"

# Remove taint
oc adm taint nodes worker-1 dedicated=infra:NoSchedule-

Example: Toleration Matching

Node has taint dedicated=gpu:NoSchedule. Only pods with matching toleration can schedule there.

Taint & Toleration — Which Pods Land on a Tainted Node?
worker-gpu-01 Taint: dedicated=gpu:NoSchedule ML Training Pod tolerations: key: dedicated value: gpu effect: NoSchedule ✓ Scheduled Inference Pod tolerations: key: dedicated operator: Exists (matches any value) ✓ Scheduled nvidia.com/gpu: 4 NVIDIA A100 GPU × 4 (node resource) node-exporter DaemonSet — auto-tolerates ✓ always runs ovnkube-node DaemonSet — auto-tolerates ✓ always runs PODS IN SCHEDULING QUEUE ML Training Pod tolerations: - key: dedicated, value: gpu   effect: NoSchedule ✓ toleration matches taint Web Frontend Pod no tolerations defined Stays Pending (no matching node) ✗ rejected — no toleration × Batch Job Pod tolerations: - key: dedicated, value: database   effect: NoSchedule ✗ wrong value (database ≠ gpu) × Toleration matches → scheduled No match / wrong value → rejected

Node Affinity

# Hard requirement: only schedule in us-east zones
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: [us-east-1a, us-east-1b]

Infra Node Setup Example

Common design workshop task: dedicate nodes for platform services (router, registry, monitoring) to avoid subscription costs and resource contention.

# 1. Label workers as infra
oc label node worker-3 node-role.kubernetes.io/infra=""
oc label node worker-4 node-role.kubernetes.io/infra=""

# 2. Taint infra nodes (prevent user workloads)
oc adm taint nodes worker-3 worker-4 node-role.kubernetes.io/infra=reserved:NoSchedule
oc adm taint nodes worker-3 worker-4 node-role.kubernetes.io/infra=reserved:NoExecute

# 3. Move Ingress Controller to infra nodes
oc patch ingresscontroller/default -n openshift-ingress-operator --type=merge -p \
  '{"spec":{"nodePlacement":{"nodeSelector":{"matchLabels":{"node-role.kubernetes.io/infra":""}},"tolerations":[{"key":"node-role.kubernetes.io/infra","value":"reserved","effect":"NoSchedule"},{"key":"node-role.kubernetes.io/infra","value":"reserved","effect":"NoExecute"}]}}}'

# 4. Move Monitoring to infra nodes (edit ConfigMap)
oc -n openshift-monitoring edit cm cluster-monitoring-config
# Add nodeSelector + tolerations under prometheusK8s, alertmanagerMain

# 5. Move Registry to infra nodes
oc patch configs.imageregistry.operator.openshift.io/cluster --type=merge -p \
  '{"spec":{"nodeSelector":{"node-role.kubernetes.io/infra":""},"tolerations":[{"key":"node-role.kubernetes.io/infra","value":"reserved","effect":"NoSchedule"},{"key":"node-role.kubernetes.io/infra","value":"reserved","effect":"NoExecute"}]}}'
Cost saving: Infra-labeled nodes running only platform workloads (router, registry, monitoring, logging) do not count toward OpenShift subscription. This can significantly reduce licensing costs for large clusters.

Core Resources

The fundamental Kubernetes and OpenShift objects that compose every application deployment.

Container Image Lifecycle

Before a container can run in a Pod, its image must be built and pushed to a registry. Here is how source code becomes a running container.

From Dockerfile to Running Container
Source Code app.py, go.mod... + Dockerfile or Containerfile Build podman build -t Executes Dockerfile steps: FROM, COPY, RUN, EXPOSE Container Image OCI format, immutable Layer 3: app code Layer 2: deps Layer 1: base OS Tag podman tag registry.example.com/ my-app:v1.2.0 Image Registry podman push Quay, Docker Hub, OCP internal registry, or private registry Pod (CRI-O pulls) image: registry.example.com/ my-app:v1.2.0 kubelet triggers CRI-O to pull image
# Example Dockerfile
FROM registry.access.redhat.com/ubi9/python-311:latest
COPY . /app
RUN  pip install -r /app/requirements.txt
EXPOSE 8080
CMD  ["python", "/app/main.py"]

# Build, tag, push
podman build -t my-app:v1.0 .
podman tag my-app:v1.0 registry.example.com/team/my-app:v1.0
podman push registry.example.com/team/my-app:v1.0

How Resources Wire Together

Each resource references others via specific YAML fields. Understanding these connections is key to building and debugging applications.

Resource Wiring — How Objects Reference Each Other
Deployment spec.replicas: 3 spec.template ↓ creates Pod spec.containers[].image spec.volumes[] spec.containers[].env[] labels: {app: my-app} ConfigMap data: {config.yaml: ...} non-sensitive config envFrom / volume Secret data: {password: base64} RBAC-protected volume mount PVC spec.resources: 10Gi storageClassName: gp3 volumes[].pvc Service spec.selector: {app: my-app} ClusterIP 172.30.1.42 selector matches labels Route spec.to: {kind: Service} host: app.apps.cluster.com spec.to Resource Wiring Cheat Sheet Deployment .spec.template → creates Pods with embedded spec .spec.replicas → how many Pod copies to run Pod .spec.containers[].image → which container image .spec.containers[].envFrom → ConfigMap or Secret .spec.volumes[] → PVC, ConfigMap, Secret, emptyDir Service .spec.selector → matches Pod labels (e.g. app: my-app) .spec.ports[].targetPort → container port number Route .spec.to.name → references Service by name .spec.tls.termination → edge | passthrough | reencrypt PVC .spec.storageClassName → which CSI driver provisions storage

Resource Quick Reference

ResourceScopePurpose
ContainerPodRunning process from an image
ImageRegistryImmutable binary artifact (OCI format)
PodNamespaceSmallest deployable unit; wraps 1+ containers
DeploymentNamespaceManages ReplicaSets with rolling updates and rollback
ReplicaSetNamespaceEnsures N pod replicas are running (managed by Deployment)
DaemonSetNamespaceRuns one pod per matching node
StatefulSetNamespaceOrdered pods with stable network IDs and per-pod PVCs
ConfigMapNamespaceStores non-sensitive config as key-value pairs
SecretNamespaceStores sensitive data (tokens, passwords)
ServiceNamespaceStable VIP + DNS name for pod access
RouteNamespaceExternal hostname with TLS termination
Namespace / ProjectClusterLogical isolation; Project adds default RBAC
Project vs Namespace: An OpenShift Project is a Kubernetes Namespace with additional annotations and automatic RBAC (admin/edit/view roles for the creator). Use oc new-project instead of kubectl create namespace.

Pod Anatomy — How a Pod Uses Resources

A Pod consumes configuration, secrets, and storage by referencing other Kubernetes objects. Compare this to how a traditional VM gets its config.

Traditional VM vs OpenShift Pod — How Applications Get Config, Secrets & Storage
Traditional VM Virtual Machine full OS + app process Config Files /etc/app/config.yaml Passwords /etc/app/db-pass (plaintext) Local Disk / SAN LUN /data — manually provisioned, VMDK/RAW OpenShift Pod Pod my-app Container nginx:1.25 :8080 ConfigMap app-config LOG_LEVEL=info, DB_HOST=... env vars or volume Secret db-credentials base64 encoded, RBAC-protected mounted /var/secrets/ PVC data-vol (10Gi RWO) PV (auto-provisioned) StorageClass → CSI → Backend Service ClusterIP 172.30.1.42 Route myapp.apps.cluster.com

ConfigMaps & Secrets

Both decouple configuration from the container image (shown wired into the Pod above). A ConfigMap holds non-sensitive key-value data; a Secret holds sensitive data and is RBAC-protected. A pod consumes either as environment variables or as files in a mounted volume.

# ConfigMap — non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: info
  app.properties: |
    timeout=30
    retries=3
---
# Secret — sensitive data (stringData is auto base64-encoded on apply)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: appuser
  password: s3cr3t-p@ss
# Consume inside a container — three patterns
env:
  - name: LOG_LEVEL          # single key as env var
    valueFrom:
      configMapKeyRef: { name: app-config, key: LOG_LEVEL }
envFrom:
  - secretRef: { name: db-credentials }   # every key as env vars
volumeMounts:
  - name: config-vol            # mount as files
    mountPath: /etc/app

Secret Types

TypePurpose
OpaqueDefault — arbitrary user-defined key-value data
kubernetes.io/dockerconfigjsonRegistry pull credentials (used by imagePullSecrets)
kubernetes.io/tlsTLS cert + key (tls.crt, tls.key) for Routes/Ingress
kubernetes.io/basic-authUsername + password for Git/source secrets
kubernetes.io/service-account-tokenAuto-generated token for a ServiceAccount
Secrets are base64-encoded, not encrypted. By default they sit in plaintext in etcd. Enable etcd encryption at rest for real protection. Both ConfigMaps and Secrets have a 1 MiB size limit; set immutable: true on stable ones to reduce API-server load and prevent accidental edits.

Deployment In Depth

A Deployment is the primary controller for stateless workloads. It creates and manages ReplicaSets, which in turn create Pods. When you update a Deployment (e.g., new image), it creates a new ReplicaSet and gradually shifts pods from old to new — this is a rolling update.

Deployment → ReplicaSet → Pod Relationship
Deployment strategy: RollingUpdate | replicas: 3 revisionHistoryLimit: 10 ReplicaSet (v3) Active — 3 replicas ReplicaSet (v2) Old — 0 replicas (kept for rollback) Pod (v3-abc) Pod (v3-def) Pod (v3-ghi) Rolling Update Flow 1. New RS created (0 replicas) 2. New RS scaled up by maxSurge 3. Old RS scaled down 4. Repeat until new RS = desired 5. Old RS kept (0 replicas) Rollback: oc rollout undo deployment/<name> DeploymentConfig (OCP-specific, deprecated since 4.14) Similar to Deployment but uses ReplicationControllers. Supports lifecycle hooks & custom strategies. Migrate to Deployment for new workloads.
# Create a deployment
oc create deployment nginx --image=nginx:1.25 --replicas=3

# Update image (triggers rolling update)
oc set image deployment/nginx nginx=nginx:1.26

# Check rollout status
oc rollout status deployment/nginx

# Rollback to previous version
oc rollout undo deployment/nginx

# View revision history
oc rollout history deployment/nginx

Namespaces & Projects

Logical isolation boundaries — how OpenShift organizes and secures multi-tenant workloads.

Namespace vs Project

A Namespace is the Kubernetes-native scope for namespaced resources. An OpenShift Project is an alternative, user-facing representation of a Namespace that can carry OpenShift display annotations and is exposed through the Project API. The important distinction is the creation workflow, not two independent isolation objects.

Creation workflow: oc new-project submits a ProjectRequest. The cluster's project request template can add annotations, quotas, policies, and a RoleBinding that grants the requester admin. kubectl create namespace creates the underlying Namespace directly and does not run that request template or automatically grant the caller project administration.

Project Anatomy

What Lives Inside a Project
Project: my-app (namespace/my-app) WORKLOADS Deployment replicas: 3 Pod 10.128.2.11 Pod 10.128.4.22 Pod 10.128.6.33 Service ClusterIP 172.30.1.42 CONFIGURATION ConfigMap app-config Secret db-credentials Route *.apps.cluster.com PersistentVolumeClaim data-vol (10Gi RWO) GOVERNANCE ResourceQuota cpu: 8, memory: 16Gi, pods: 20 LimitRange default: 500m cpu, 512Mi mem NetworkPolicy deny-all + allow frontend RBAC SCOPED TO PROJECT admin full control + RBAC edit create/update resources view read-only access ServiceAccount default (auto-created)

Default System Namespaces

NamespacePurpose
openshift-apiserverOpenShift API Server pods
openshift-etcdetcd cluster members
openshift-ingressIngress Controller (HAProxy) pods
openshift-monitoringPrometheus, Alertmanager, Thanos
openshift-loggingLoki, Vector, logging stack
openshift-operatorsAllNamespaces operator installs
openshift-image-registryInternal container image registry
kube-systemKubernetes core components
defaultDefault namespace for ad-hoc resources

Governance Resources

ResourceQuota
Limits total resource consumption per namespace: max CPU, memory, pod count, services, PVCs. Prevents any single team from consuming all cluster capacity.
LimitRange
Sets default and min/max per-container resource requests/limits. Auto-injects defaults when containers omit resource specs. Prevents unbounded container resource usage.
🔒
NetworkPolicy
Controls pod-to-pod traffic at namespace level. Without a policy all traffic is allowed. First policy enables isolation. Enforced by OVN-Kubernetes ACLs.

Multi-Tenancy Model

OpenShift provides soft multi-tenancy via namespace isolation. Each team gets their own Project with:

Not hard isolation: Namespaces share the same kernel, control plane, and nodes. For true workload isolation (e.g., PCI compliance), use dedicated node pools with taints/tolerations or separate clusters.

Common Commands

# Create a new project (with default RBAC)
oc new-project my-app --display-name="My Application"

# Switch to a project
oc project my-app

# List all projects you have access to
oc get projects

# Admin: create project for another user
oc adm new-project team-backend --admin=alice

# Set resource quota
oc create quota compute --hard=cpu=8,memory=16Gi,pods=20 -n my-app

# View current resource usage vs quota
oc describe quota -n my-app

# Delete a project (WARNING: deletes all resources inside)
oc delete project my-app

Pod Lifecycle

Understanding how pods are created, scheduled, run, and terminated in OpenShift.

What is a Pod?

A pod is the smallest deployable unit in Kubernetes/OpenShift. It wraps one or more containers that share the same network namespace (IP address), storage volumes, and lifecycle. Most pods run a single application container.

Pod Lifecycle Flow

Pod Lifecycle — From Creation to Termination
POD PHASES Pending Waiting for scheduler Running Containers active Succeeded Failed Unknown STARTUP SEQUENCE Schedule Filter + Score Pull Image IfNotPresent Init Containers Sequential Start Containers Parallel Probes Active Startup → Liveness + Readiness HEALTH PROBES startupProbe Blocks other probes until success. For slow-starting apps. livenessProbe Detects deadlocks. Failure = container restart. readinessProbe Controls traffic. Failure = removed from endpoints. GRACEFUL TERMINATION preStop hook SIGTERM Grace Period (30s) SIGKILL Removed

Resource Management

Requests vs Limits: requests guarantee a minimum amount of CPU/memory. limits set the maximum. If a container exceeds its memory limit, it gets OOMKilled. If it exceeds CPU limit, it gets throttled.
resources:
  requests:
    cpu: "250m"     # 0.25 CPU cores guaranteed
    memory: "256Mi"  # 256 MiB guaranteed
  limits:
    cpu: "1000m"   # Max 1 CPU core (throttled beyond)
    memory: "512Mi"  # Max 512 MiB (OOMKilled beyond)

Quality of Service (QoS) Classes

The kubelet derives a QoS class for every pod from its requests and limits — you don’t set it directly. QoS decides eviction order when a node runs low on memory: lower-priority pods are killed first to protect critical ones.

QoS ClassConditionEviction priorityTypical use
Guaranteedlimits == requests for CPU and memory on every containerEvicted lastDatabases, critical stateful workloads
BurstableAt least one container has a request set, but not GuaranteedEvicted middle (those most over their requests first)Most general-purpose apps
BestEffortNo requests or limits set anywhereEvicted firstBatch jobs, non-critical workloads
Under node memory pressure the kubelet evicts in order: BestEffort → Burstable (those exceeding requests) → Guaranteed last. Set requests == limits for anything that must not be evicted. CPU pressure throttles rather than evicts.

Health Probes

OpenShift uses three probe types (shown in the lifecycle diagram above) to manage container health and traffic. Each can use an httpGet, tcpSocket, exec, or grpc handler.

ProbeQuestion it answersOn failure
startupProbeHas the app finished starting?Restart container; disables liveness/readiness until it passes (for slow-starting apps)
livenessProbeIs the container still alive (not deadlocked)?Restart the container
readinessProbeCan it serve traffic right now?Remove from Service endpoints (no restart) until it passes
containers:
  - name: web
    startupProbe:           # slow boot: allow up to 30 × 10s = 300s
      httpGet: { path: /healthz, port: 8080 }
      failureThreshold: 30
      periodSeconds: 10
    livenessProbe:          # restart if deadlocked
      httpGet: { path: /healthz, port: 8080 }
      periodSeconds: 10
    readinessProbe:         # pull from load balancer until deps are up
      httpGet: { path: /ready, port: 8080 }
      periodSeconds: 5
Common gotcha: an over-aggressive livenessProbe (short timeout, hitting a slow endpoint) causes restart loops. Use readinessProbe for dependency checks (DB not ready) and livenessProbe only for true deadlock detection. Use startupProbe for legacy apps with long warm-up.
Try it: oc set probe deployment/myapp --readiness --get-url=http://:8080/ready — then watch endpoints update with oc get endpoints myapp -w.

Deployments & Workloads

How to deploy applications and choose the right workload controller on OpenShift.

Deployment Methods

Application Deployment Paths on OpenShift
SOURCES Git Repository Source code Container Image Pre-built image Helm Chart Templated manifests YAML / Kustomize Raw manifests METHODS oc new-app Auto-detect & deploy Source-to-Image (S2I) Build from source Helm Install Chart deployment GitOps (ArgoCD) Declarative CD Running App Pods + Service + Route WORKLOAD CONTROLLERS Deployment StatefulSet DaemonSet Job CronJob

Deployment Sources

SourceWhat It IsWhen to Use
Git RepositoryApplication source code in a git repo (GitHub, GitLab, Bitbucket). OpenShift can build directly from source using S2I or Dockerfile.Developers pushing code. CI/CD pipelines. No pre-built image available.
Container ImagePre-built OCI image from a registry (Quay, Docker Hub, internal registry). Already contains app + dependencies.Most common. Image built externally by CI pipeline. Vendor-provided images.
Helm ChartPackage of templated Kubernetes YAML manifests with configurable values.yaml. Versioned, reusable, shareable.Complex apps with many resources. Community-maintained charts. Parameterized deployments.
YAML / KustomizeRaw Kubernetes manifests applied directly. Kustomize adds patching and overlay for environment-specific config.Full control over resources. GitOps workflows. Environment overlays (dev/staging/prod).

Deployment Methods

MethodHow It WorksBest For
oc new-appAuto-detects source type (git, image, template) and creates Deployment + Service + BuildConfig. One command to go from zero to running app.Quick start. Dev/test environments. Learning OpenShift.
Source-to-Image (S2I)OpenShift-specific build strategy. Combines source code + builder image (e.g., python-311) → produces runnable image. No Dockerfile needed.Developers who don’t want to write Dockerfiles. Standardized builds across teams.
Helm Installhelm install renders chart templates with values and applies to cluster. Manages releases with upgrade/rollback.Packaged applications. OperatorHub alternatives. Multi-environment deploys.
GitOps (ArgoCD)OpenShift GitOps operator deploys ArgoCD. Watches git repo, auto-syncs cluster state to match repo. Declarative, auditable.Production. Multi-cluster. Compliance. Audit trail. Team collaboration.
oc apply -fApplies YAML manifests directly. Supports directories, URLs, kustomize overlays (oc apply -k).CI/CD pipelines. Scripted deployments. Full manifest control.
Tekton PipelinesOpenShift Pipelines operator. Kubernetes-native CI/CD. Tasks run as pods. Pipeline = ordered tasks (build, test, deploy).Cloud-native CI/CD. Replace Jenkins. Reusable task catalog.
# Deploy from container image
oc new-app --image=registry.example.com/team/my-app:v1.0 --name=my-app

# Deploy from git repo (S2I auto-detect)
oc new-app https://github.com/team/my-app.git --name=my-app

# Deploy from Helm chart
helm install my-app ./chart --set replicas=3

# Deploy from YAML
oc apply -f deployment.yaml

# Deploy with Kustomize overlays
oc apply -k overlays/production/

# Expose app externally
oc expose svc/my-app

Workload Controllers

ControllerPurposeUse Case
DeploymentManages stateless pods via ReplicaSets. Rolling updates, rollback, scaling.Web apps, APIs, microservices — most workloads
StatefulSetOrdered pod creation with stable network identity (pod-0, pod-1...) and per-pod PVCs.Databases, Kafka, ZooKeeper, etcd — stateful apps
DaemonSetRuns exactly one pod per node (or per matching node). Auto-scales with cluster.Monitoring agents, log collectors, CSI drivers, CNI plugins
JobRuns pod(s) to completion, then stops. Tracks success/failure count.Batch processing, data migration, one-time tasks
CronJobCreates Jobs on a cron schedule. Manages job history (successfulJobsHistoryLimit).Scheduled reports, backups, cleanup tasks

Deployment Strategies

🔄
RollingUpdate
Default. Gradually replaces old pods with new ones. Zero downtime. Controls maxSurge and maxUnavailable.
Recreate
Kills all old pods, then creates new ones. Brief downtime. Use when old & new versions can't coexist.
🎯
Blue-Green
Run two identical environments. Switch traffic at Route level. Instant rollback by switching back.
🌌
Canary
Route a small % of traffic to new version. Monitor metrics. Gradually increase if healthy.

Autoscaling

Automatically adjust pod replicas, container resources, and cluster capacity to match workload demand.

Why Autoscaling?

Static resource allocation wastes capacity during low demand and causes outages during spikes. OpenShift provides four complementary autoscaling mechanisms that operate at two distinct levels:

Autoscaling Architecture

Pod-Level vs Node-Level Autoscaling
POD-LEVEL SCALING HPA Horizontal Pod Autoscaler VPA Vertical Pod Autoscaler Metrics Server CPU / Memory metrics WORKER NODE (existing) Pod 1 cpu: 200m Pod 2 cpu: 200m Pod 3 + HPA Pod 4 + HPA scale replicas Pod (right-sized) cpu: 200m → 350m mem: 128Mi → 256Mi right-size ⚠ Don't use HPA + VPA on same CPU/memory metric NODE-LEVEL SCALING Cluster Autoscaler cluster-scoped | decides scale up/down autoscaling.openshift.io/v1 MachineAutoscaler per-MachineSet | min/max bounds autoscaling.openshift.io/v1beta1 triggers MachineSet machine.openshift.io/v1beta1 sets replicas New Worker Node provisioned by Machine API creates Pending Pods (Unschedulable) insufficient CPU/memory on nodes trigger pending pods scheduled onto new node Scale down: nodes removed when underutilised & pods can be rescheduled

Autoscaling Mechanisms Comparison

MechanismScopeWhat It ChangesAPI / KindWhen to Use
HPA Pod-level Replica count of a Deployment, StatefulSet, or ReplicaSet autoscaling/v2
HorizontalPodAutoscaler
Workloads with variable request rates — web APIs, queue consumers
VPA Pod-level CPU/memory requests & limits on pod containers autoscaling.k8s.io/v1
VerticalPodAutoscaler
Right-sizing long-running pods; avoid over/under-provisioning
Cluster Autoscaler Cluster-level Number of nodes (via MachineSets) — add when pods pending, remove when underused autoscaling.openshift.io/v1
ClusterAutoscaler
Elastic clusters on IaaS; scale capacity to match demand
MachineAutoscaler Per-MachineSet Min/max replica bounds for a specific MachineSet autoscaling.openshift.io/v1beta1
MachineAutoscaler
Limit per-AZ or per-instance-type node counts; required companion for Cluster Autoscaler

HPA — Horizontal Pod Autoscaler

HPA watches metrics (CPU utilisation, memory, or custom/external metrics) and adjusts the replicas field on a target workload controller. It runs a control loop every 15 seconds by default and uses the autoscaling/v2 API for multi-metric and custom metric support.

HPA YAML Example

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
  namespace: my-project
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70    # scale up when avg CPU > 70%
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # wait 5 min before scaling down
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60
# Quick HPA via CLI
oc autoscale deployment/my-app --min=2 --max=10 --cpu-percent=70

# Check HPA status
oc get hpa my-app-hpa
# NAME          REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS
# my-app-hpa    Deployment/my-app  45%/70%   2         10        3

VPA — Vertical Pod Autoscaler

VPA analyses historical and real-time resource usage and recommends (or applies) right-sized CPU and memory requests. On OpenShift it is delivered by the VerticalPodAutoscaler Operator installed from OperatorHub.

VPA Modes

ModeBehaviourWhen to Use
OffCalculates recommendations only — does not apply them. View with oc get vpa -o yaml.Audit existing workloads without risk. First step for any new VPA rollout.
InitialSets requests at pod creation time only. Running pods are not restarted.Batch jobs, CronJobs — each new pod gets optimised requests.
AutoUpdates requests on running pods (evicts and recreates with new values).Long-running workloads where occasional restarts are acceptable.
HPA + VPA conflict: Do not target the same CPU or memory metric with both HPA and VPA on the same workload. They will fight over the resource values. You can combine HPA on a custom metric (e.g. requests-per-second) with VPA on CPU/memory.

VPA YAML Example

VerticalPodAutoscaler Custom Resource
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
  namespace: my-project
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: Auto         # Off | Initial | Auto
  resourcePolicy:
    containerPolicies:
      - containerName: '*'
        minAllowed:
          cpu: 100m
          memory: 64Mi
        maxAllowed:
          cpu: 2
          memory: 2Gi

Cluster Autoscaler

The Cluster Autoscaler is a cluster-scoped singleton CR that watches for unschedulable pods (pods stuck in Pending because no node has enough resources). When detected, it triggers a scale-up of the appropriate MachineSet. It also scales nodes down when they are underutilised and their pods can be rescheduled elsewhere.

ClusterAutoscaler YAML

apiVersion: autoscaling.openshift.io/v1
kind: ClusterAutoscaler
metadata:
  name: default             # must be named "default" (singleton)
spec:
  podPriorityThreshold: -10  # ignore pods with priority below this
  resourceLimits:
    maxNodesTotal: 24        # hard cap on total cluster nodes
    cores:
      min: 8
      max: 256
    memory:
      min: 16
      max: 1024              # GiB
  scaleDown:
    enabled: true
    delayAfterAdd: 10m      # wait after adding a node
    delayAfterDelete: 5m
    unneededTime: 5m        # node must be underused this long
  balanceSimilarNodeGroups: true

MachineAutoscaler

A MachineAutoscaler sets the minReplicas and maxReplicas bounds for a single MachineSet. Create one MachineAutoscaler per MachineSet (typically one per availability zone or instance type). The Cluster Autoscaler uses these bounds when deciding which MachineSet to scale.

MachineAutoscaler YAML

apiVersion: autoscaling.openshift.io/v1beta1
kind: MachineAutoscaler
metadata:
  name: worker-us-east-1a
  namespace: openshift-machine-api
spec:
  minReplicas: 1
  maxReplicas: 6
  scaleTargetRef:
    apiVersion: machine.openshift.io/v1beta1
    kind: MachineSet
    name: cluster-abc-worker-us-east-1a

How They Work Together

In a well-configured cluster, autoscaling forms a chain reaction:

  1. HPA increases pod replicas when request load rises.
  2. New pods go Pending if existing nodes lack capacity.
  3. Cluster Autoscaler detects unschedulable pods and picks a MachineSet that can satisfy the request.
  4. MachineAutoscaler bounds constrain how many new Machines (nodes) that MachineSet can add.
  5. The MachineSet creates a new Machine → the Machine API provisions a VM/instance → it joins as a new worker node.
  6. Pending pods are scheduled onto the new node.
  7. When load drops, HPA reduces replicas, the Cluster Autoscaler detects underused nodes, and scales them down (respecting PDBs).
Recommendation: Start HPA with real application metrics (requests-per-second, queue depth) rather than raw CPU alone — CPU utilisation is often a lagging indicator. Use VPA in Off mode first to gather recommendations before enabling Auto. Always set maxNodesTotal on the Cluster Autoscaler to prevent runaway costs. Create one MachineAutoscaler per MachineSet per availability zone, and set minReplicas ≥ 1 to maintain HA spread.
Try it: oc autoscale deployment/my-app --min=2 --max=8 --cpu-percent=60 — then generate load with oc run load --image=busybox --restart=Never -- /bin/sh -c "while true; do wget -q -O- http://my-app:8080; done" and watch replicas scale with oc get hpa -w.

Installation & Bootstrap

How OpenShift clusters are installed across different platforms, and the bootstrap process that brings a cluster to life.

Installation by Platform

Recommended Installation Method per Platform
VMware vSphere Recommended: IPI (full automation) or UPI (custom control) IPI: vCenter admin privilege, DHCP required, auto node naming UPI: vCenter read-only OK, static IPs, custom naming, manual LB MachineSet scaling supported on both | MachineAPI for day-2 node management Bare Metal Recommended: Agent-Based or Assisted Installer Agent-Based: CLI-driven, air-gapped native, single bootable ISO Assisted: Web UI, SaaS or on-prem hub, guided host discovery MachineSet scaling via MachineAPI for day-2 node management Cloud (AWS/Azure/GCP) Recommended: IPI Full automation: VPC, subnets, LB, DNS, instances. UPI also supported. Edge / SNO Recommended: Agent-Based Single Node OpenShift. ZTP at scale via ACM + SiteConfig + GitOps (ArgoCD). Disconnected / Air-Gapped Recommended: Agent-Based ISO self-contained. Mirror registry via oc-mirror. No internet at install.

VMware vSphere — IPI vs UPI

VMware vSphere supports both IPI and UPI. IPI automates everything; UPI gives full control at cost of manual steps.

TaskIPI (Installer-Provisioned)UPI (User-Provisioned)
Build NetworkAutomatedManual
Setup Load BalancerAutomatedManual
Configure DNSAutomatedManual
Hardware ProvisioningAutomatedManual
OS InstallationAutomated (RHCOS PXE/ISO)Manual (RHCOS ISO/PXE)
Ignition ConfigsGenerated & applied automaticallyGenerated, manually applied
Node ScalingMachineSet auto-scalingManual node addition

IPI vs UPI Decision Matrix

RequirementIPIUPI
DHCPRequiredOptional (static IPs)
DNSAuto-configuredPre-configured required
vCenter PrivilegeAdmin-level requiredRead-only sufficient
Node NamingAuto-generatedCustom naming
External Load BalancerAuto-provisionedPre-configured required
IP AssignmentDHCP onlyStatic or DHCP

Bootstrap Process

The bootstrap process is a temporary, self-destructing sequence that brings the cluster from zero to a running control plane.

OpenShift Bootstrap Process — 9 Steps to a Running Cluster
1 Bootstrap node boots Ignition config, temporary etcd + API 2 Masters fetch resources Pull ignition from bootstrap MCS 3 etcd scales to 4 members Bootstrap + 3 master etcd nodes 4 etcd operator scales down to 3 Bootstrap etcd member removed 5 Temp K8s control plane starts On bootstrap node 6 Production CP scheduled API, controllers on masters 7 Temp CP shuts down Bootstrap API no longer needed 8 OCP components injected CVO deploys all cluster operators 9 Bootstrap torn down Workers join cluster INSTALLATION MODES Connected Pulls images from registry.redhat.io | Updates from Cincinnati graph Disconnected (Air-Gapped) Mirror registry | oc-mirror v2 | IDMS / ITMS DNS PREREQUISITES api.<cluster>.<domain> → LB VIP api-int.<cluster>.<domain> → LB VIP *.apps.<cluster>.<domain> → Ingress VIP LOAD BALANCER PORTS TCP 6443 — Kubernetes API TCP 22623 — Machine Config Server TCP 443/80 — Ingress (HTTPS/HTTP)

Network Connectivity Ports

ProtocolPort RangePurpose
TCP6443Kubernetes API server
TCP22623Machine Config Server (bootstrap)
TCP2379-2380etcd server + peer communication
TCP9000-9999Host-level services (node exporter, etc.)
TCP10250-10259Kubernetes node ports (kubelet, controllers)
TCP30000-32767NodePort services
UDP4789VXLAN (legacy SDN)
UDP6081Geneve (OVN-Kubernetes)
UDP9000-9999Host-level services
TCP/UDP500, 4500IPsec (if enabled)

Agent-Based Installer

Generates a single bootable ISO containing the Assisted Service, discovery agent, and OCP release image. Fully self-contained — no external service or internet needed. One host acts as the rendezvous host, running the Assisted Service locally.

Agent-Based Installer Workflow
install-config.yaml agent-config.yaml Cluster + host config openshift-install agent create image Bootable ISO Self-contained Rendezvous Host Runs Assisted Service Other hosts register here Validate & Install RHCOS → disk → cluster KEY CRDs (ZTP MANIFEST METHOD) AgentClusterInstall ClusterDeployment InfraEnv NMStateConfig ClusterImageSet When to Use Agent-Based Installer Disconnected / air-gapped | Edge with ZTP | CLI automation | No external service dependency | Single cluster at a time agent-config.yaml key fields: rendezvousIP | hosts[].hostname | hosts[].role | hosts[].rootDeviceHints | hosts[].interfaces[].macAddress | hosts[].networkConfig (NMState)

Disconnected / Air-Gapped Installation

Agent-Based Installer — Disconnected Environment Flow
CONNECTED ENVIRONMENT (prep) oc-mirror Mirror releases + catalogs Mirror Registry Local Quay / registry openshift-install agent create image install-config mirror mappings + additionalTrustBundle (mirror CA) Transfer ISO + registry data via USB / portable media AIR GAP DISCONNECTED ENVIRONMENT (install) Mirror Registry Loaded from portable media Boot hosts from ISO Rendezvous host runs Assisted Service locally Validate → Write RHCOS to disk → Cluster forms → Operators deploy All images pulled from mirror registry — zero internet required Running OCP Cluster (fully disconnected, telemetry disabled, internal catalog sources)
What is a Mirror Registry?
A mirror registry is an OCI-compliant container image registry that holds copies of images from external sources (Red Hat registries, Docker Hub, etc.). In disconnected environments, it replaces internet-based registries entirely — all nodes pull images from it instead of going online.

How it works: Use the supported oc-mirror --v2 workflow to select and mirror OCP release payloads, Operator catalogs, and additional images. It supports registry-to-registry and file-based workflows for fully disconnected sites. Apply the generated cluster resources, including ImageDigestMirrorSet (IDMS), ImageTagMirrorSet (ITMS), catalog resources, and update-service configuration as required.

Registry options: The mirror registry for Red Hat OpenShift is a small-scale registry included with an OpenShift subscription. Production deployments can use Red Hat Quay or a compatible third-party registry, but the registry must be reachable by every cluster machine and should match the cluster's availability requirements. The OpenShift integrated image registry cannot be the mirroring target.

Legacy note: ImageContentSourcePolicy (ICSP) and oc-mirror v1 are deprecated. Agent-based installation still consumes mirror mappings in install-config.yaml; use the output and procedure documented for the exact installer and OCP release.

Day-2: Preserve the oc-mirror v2 workspace/cache and repeat the workflow for release and Operator updates. Review generated resources and available update edges before starting a disconnected cluster update.

Bare Metal — Assisted Installer

Red Hat’s SaaS-hosted installation service at console.redhat.com or deployable on-prem via the Infrastructure Operator (part of MCE/RHACM). Provides a web UI and REST API for guided cluster deployment.

Assisted Installer Workflow (SaaS & On-Prem)
console.redhat.com or Hub Cluster Web UI + REST API Create Cluster Name, domain, version Discovery ISO Minimal ~100MB / Full ~1GB Host Discovery HW inventory CPU, RAM, disk, NICs Validate Host + cluster Install Cluster ready SaaS Mode (console.redhat.com) Hosted by Red Hat. Requires internet from target hosts. Web UI for config & monitoring. Free to use. On-Prem Mode (Infrastructure Operator) Deployed on hub cluster via MCE/RHACM. AgentServiceConfig CRD. Disconnected capable. GitOps integration. Agent-Based vs Assisted Installer FEATURE AGENT-BASED ASSISTED Service Embedded in ISO (self-contained) SaaS (console.redhat.com) or Hub cluster Disconnected Native (air-gapped by design) On-prem mode only (via Infrastructure Operator)

Networking & Routes

How traffic flows from external clients to your application pods inside OpenShift.

Network Architecture

OpenShift has three distinct networks, each serving a different purpose:

Network Architecture — Three Cluster Networks (Layered View)
POD NETWORK 10.128.0.0/14 Real IP per pod — each node gets /23 subnet (510 IPs) OVERLAY Node 1 — subnet 10.128.2.0/23 Pod A 10.128.2.5 Pod C 10.128.2.9 Geneve tunnel Node 2 — subnet 10.128.4.0/23 Pod B 10.128.4.8 Pod D 10.128.4.10 ↕ OVN translates ClusterIP ↔ Pod IP (endpoint load balancing) SERVICE NETWORK 172.30.0.0/16 No real NIC — implemented entirely by OVN OVS flow rules VIRTUAL svc/frontend ClusterIP 172.30.1.10 port: 3000 svc/backend ClusterIP 172.30.1.42 endpoints: [10.128.4.8] CoreDNS backend.my-app.svc → resolves to 172.30.1.42 No kube-proxy OVN replaces kube-proxy with native OVS LB rules ↕ Geneve encapsulation (UDP 6081) carries pod traffic over physical wires HOST NETWORK Infrastructure-dependent Real NICs — the only network with actual packets on wire PHYSICAL Node 1 — 192.168.1.10 API :6443 | etcd :2379 | kubelet :10250 OVS bridge br-int (Geneve tunnel endpoint) physical Node 2 — 192.168.1.11 kubelet :10250 | CRI-O OVS bridge br-int (Geneve tunnel endpoint) Example: Pod A calls svc/backend Pod A → curl backend:8080 → CoreDNS172.30.1.42 → OVN rewrites → 10.128.4.8Geneve Node1→Node2Pod B DNS resolves name → ClusterIP (virtual). OVN selects endpoint pod IP (real). If cross-node, Geneve tunnel carries packet over Host Network.

Ingress Traffic Flow

Full Traffic Path — External Client to Application Pod
EXTERNAL OPENSHIFT CLUSTER Client Browser / curl DNS *.apps.cluster.com Load Balancer L4 TCP :443 Ingress Controller HAProxy Router TLS termination Service ClusterIP Pod :8080 TLS TERMINATION MODES Edge Client —HTTPS→ Router —HTTP→ Pod Passthrough Client —HTTPS→ Router —HTTPS→ Pod Re-encrypt Client —HTTPS→ Router —HTTPS'→ Pod SERVICE TYPES ClusterIP NodePort LoadBalancer Headless

How Services Work

A Service provides a stable VIP and DNS name for a set of pods. It uses label selectors to dynamically discover backend pods. When pods scale or restart, endpoints update automatically.

Service — Label Selector Based Load Balancing
Service Name: backend ClusterIP: 172.30.1.42 selector: app=backend Pod 10.128.2.11:8080 app=backend ✓ Pod 10.128.4.22:8080 app=backend ✓ Pod 10.128.6.33:8080 app=backend ✓ Pod 10.128.2.44:3000 app=frontend ✗ DNS Resolution (CoreDNS) backend.my-app.svc.cluster.local → 172.30.1.42 EndpointSlice Healthy backends: 10.128.2.11, 10.128.4.22, 10.128.6.33 Service YAML (key fields): spec.selector: {app: backend} spec.ports: [{port: 8080, targetPort: 8080}] spec.type: ClusterIP (default) Frontend Pod curl http://backend:8080/api

Routes & Ingress Controller

A Route exposes a Service to external traffic via a hostname. The Ingress Controller (HAProxy) watches Route objects and configures proxying rules automatically.

Route — External Access to Internal Services
Route host: myapp.apps.cluster.com to: Service/backend tls: edge (termination mode) Ingress Controller HAProxy pods (openshift-ingress) Watches Route objects → updates config TLS termination + hostname routing Service backend:8080 172.30.1.42 Pod :8080 Pod :8080 Pod :8080 Create Routes oc expose svc/backend— HTTP route, auto hostname: backend-ns.apps.cluster.com oc create route edge --service=backend --hostname=api.example.com— HTTPS edge, custom host oc create route passthrough --service=backend --hostname=api.example.com— E2E TLS

MetalLB — LoadBalancer Services on Bare Metal

On a cloud provider, a Service of type LoadBalancer automatically gets a real external IP via the cloud's LB API. Bare-metal clusters have no such API — MetalLB fills that gap, assigning IPs from a pool you own and announcing them to the local network so external routers/switches know where to send traffic.

MetalLB — IP Assignment & Announcement
Service type: LoadBalancer status.loadBalancer.ingress pending MetalLB Controller Assigns IP from IPAddressPool Writes status.loadBalancer.ingress Single Deployment (leader elected) speaker (DaemonSet) L2: ARP/NDP from one elected node BGP: route advertised from every node Runs on every node Router / Switch
ModeHow Traffic ArrivesTrade-offs
Layer 2 (L2)One node is elected leader for each IP and answers ARP (IPv4) / NDP (IPv6) requests for it — all traffic for that IP enters through that single node.Simple, no router config needed. Not true load balancing (one node is the bottleneck) and failover takes a few seconds.
BGPEvery node peers with an upstream router and advertises a route for each service IP; the router ECMP-balances traffic across all nodes.True multi-node load balancing and fast failover, but requires a BGP-speaking router and network team coordination.
# Pool of external IPs MetalLB can hand out
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: apps-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.240-192.168.1.250

---
# Announce that pool via Layer 2 (ARP/NDP)
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: apps-l2
  namespace: metallb-system
spec:
  ipAddressPools:
    - apps-pool
Common use: Front the Ingress Controller (HAProxy) itself with a MetalLB-assigned LoadBalancer Service instead of hand-configuring an external VIP — a frequent choice for bare-metal/UPI clusters covered in the Installation & Bootstrap section.

Cluster Network Defaults

NetworkDefault CIDRDetails
Pod Network10.128.0.0/14/23 per node = 512 subnets, 510 IPs per node
Service Network172.30.0.0/1665,534 ClusterIP addresses
Host NetworkInfrastructure-dependentPhysical/VM network connecting nodes
Network Prerequisites: Before any traffic flows, DNS records and load balancers must be configured:
api.<cluster>.<domain> → API LB VIP (port 6443) — control plane access
api-int.<cluster>.<domain> → Internal API LB — node-to-API communication
*.apps.<cluster>.<domain> → Ingress LB VIP (ports 443/80) — application routes
See Installation & Bootstrap section for full DNS and load balancer details.

OVN-Kubernetes

OVN-Kubernetes is the default CNI plugin since OpenShift 4.12. It replaces the legacy OpenShift SDN.

EgressIP

By default, outbound traffic from pods uses the node IP as source. This is a problem when external firewalls need to whitelist your cluster — you'd need to allow ALL node IPs, and they change when nodes scale. EgressIP assigns a predictable, static source IP to all outbound traffic from a namespace.

Without EgressIP — Unpredictable Source IPs (Default Behavior)
OPENSHIFT CLUSTER worker-1 Node IP: 192.168.1.30 Pod A Pod B worker-2 Node IP: 192.168.1.31 Pod C worker-3 Node IP: 192.168.1.32 Pod D Pod E Each pod outbound SNAT'd to its own node IP src: 192.168.1.30 src: 192.168.1.31 src: 192.168.1.32 EXTERNAL External Database / API Firewall rules needed: ALLOW .30 AND .31 AND .32 ✗ Problems: ✗ Must whitelist every node IP ✗ New node = new IP = update firewall ✗ Node scale-down = orphan rules
With EgressIP — Predictable Source IP for Outbound Traffic
OPENSHIFT CLUSTER Project: my-app Pod A 10.128.2.15 Pod B 10.128.4.22 EgressIP: 192.168.1.155 worker-1 (egress-assignable) EgressIP 192.168.1.155 bound Node IP: 192.168.1.30 worker-2 (failover) Takes over EgressIP if worker-1 down SNAT src: .155 Node label: k8s.ovn.org/egress-assignable="" — required on egress nodes Without EgressIP: Pod uses random node IP → must whitelist ALL node IPs → breaks on scale With EgressIP: one static IP per namespace, auto-failover, no firewall changes on scale EXTERNAL External Database / API Firewall rule: ALLOW src 192.168.1.155 ✓ One IP to whitelist instead of all node IPs When to use EgressIP: • External DB with IP-based firewall • Third-party API with IP allowlist • Compliance: traceable outbound IPs • Partner VPN requiring static source
# 1. Label nodes as egress-assignable
oc label node worker-1 k8s.ovn.org/egress-assignable=""
oc label node worker-2 k8s.ovn.org/egress-assignable=""

# 2. Create EgressIP object
oc apply -f - <<EOF
apiVersion: k8s.ovn.org/v1
kind: EgressIP
metadata:
  name: my-app-egress
spec:
  egressIPs:
    - 192.168.1.155
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: my-app
EOF

# 3. Verify EgressIP assignment
oc get egressip
oc get egressip my-app-egress -o yaml

Multus CNI

Multus is a meta-plugin — it does not provide networking itself but delegates to other CNI plugins. Every OpenShift pod already gets a primary interface (eth0) from OVN-Kubernetes. Multus lets you attach additional interfaces (e.g., net1, net2) using plugins like macvlan, SR-IOV, bridge, or IPVLAN. This is essential for telco/NFV workloads that need dedicated data-plane networks, storage networks, or direct L2 access to physical infrastructure.

Multus CNI — Multiple Network Interfaces per Pod
Worker Node Pod: my-telco-app eth0 — 10.128.2.15 primary (OVN-K cluster network) net1 (macvlan) — 192.168.1.50 secondary • VLAN 100 net2 (SR-IOV VF) — 10.10.10.5 secondary • hardware passthrough Multus CNI (meta-plugin / delegator) OVN-K Macvlan SR-IOV OVN-K Overlay (Geneve) Cluster network • Pod CIDR • Services • NetworkPolicy br-int → Geneve tunnel (UDP 6081) → remote nodes Physical Network (VLAN 100) Direct L2 access • No overlay • Separate MultiNetworkPolicy High-Perf Network (SR-IOV PF) Hardware VF passthrough • Near-native NIC perf NetworkAttachmentDefinition CRs Define each secondary network • Referenced by pod annotation
Secondary-network policy is separate: Kubernetes NetworkPolicy governs the default or primary pod network. For supported Multus secondary networks, a cluster administrator can enable the MultiNetworkPolicy API and target a NetworkAttachmentDefinition. When that feature or plugin support is unavailable, enforce secondary-network security with VLAN ACLs, firewalls, and other infrastructure controls.
Multus is pre-installed on every OpenShift 4.x cluster — no operator needed for macvlan, bridge, or IPVLAN plugins. SR-IOV requires the SR-IOV Network Operator from OperatorHub (configures VFs on physical NICs and manages the SriovNetwork CRs).

Secondary CNI Plugin Comparison

PluginLayerUse CaseOperator Needed
MacvlanL2Direct physical network access — pod gets its own MAC on the host NICNo
SR-IOVHardwareTelco / NFV — VF passthrough, near-native NIC performance, DPDKYes (SR-IOV Network Operator)
BridgeL2Pod-to-pod on same node via Linux bridgeNo
IPVLANL3Like macvlan but shared MAC address — fewer switch-level issuesNo
Host-deviceL2Move entire host NIC into pod network namespaceNo
NetworkAttachmentDefinition — Macvlan Example
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: macvlan-conf
  namespace: my-project
spec:
  config: '{
    "cniVersion": "0.3.1",
    "type": "macvlan",
    "master": "ens4",
    "mode": "bridge",
    "ipam": {
      "type": "whereabouts",
      "range": "192.168.1.0/24",
      "exclude": ["192.168.1.0/32", "192.168.1.1/32"]
    }
  }'
Pod with Secondary Interface Annotation
apiVersion: v1
kind: Pod
metadata:
  name: my-multi-nic-pod
  annotations:
    k8s.v1.cni.cncf.io/networks: macvlan-conf   # attach net1
spec:
  containers:
  - name: app
    image: registry.redhat.io/ubi9/ubi:latest
    command: ["sleep", "infinity"]

# Verify inside the pod:
# oc exec my-multi-nic-pod -- ip a
#   eth0  → 10.128.2.15  (OVN-K, cluster network)
#   net1  → 192.168.1.50 (macvlan, physical network)
SR-IOV NetworkAttachmentDefinition (via Operator)
# Step 1: SriovNetworkNodePolicy — configure VFs on physical NIC
apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetworkNodePolicy
metadata:
  name: policy-dpdk
  namespace: openshift-sriov-network-operator
spec:
  nodeSelector:
    feature.node.kubernetes.io/network-sriov.capable: "true"
  numVfs: 8
  nicSelector:
    pfNames: ["ens5f0"]
  deviceType: vfio-pci          # for DPDK; use netdevice for kernel driver
  resourceName: dpdk_nic

---
# Step 2: SriovNetwork — auto-creates NetworkAttachmentDefinition
apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetwork
metadata:
  name: sriov-dpdk-net
  namespace: openshift-sriov-network-operator
spec:
  networkNamespace: my-project
  resourceName: dpdk_nic
  ipam: '{ "type": "whereabouts", "range": "10.10.10.0/24" }'

Deployment Strategies via Routes

🔵
Blue-Green
Deploy new version alongside old. Switch Route to new Service. Instant rollback by switching Route back to old Service.
🔸
A/B Testing
Route splits traffic between two Services by weight (e.g., 90/10). Gradually shift traffic. Use alternateBackends in Route spec.

NetworkPolicy

Default is open: Without any NetworkPolicy, ALL pod-to-pod traffic is allowed. Creating any policy in a namespace enables isolation for selected pods. Always include DNS egress (port 53) when restricting outbound traffic.

NetworkPolicy

Kubernetes-native microsegmentation — control which pods can talk to which at the network level.

How NetworkPolicy Works

Default = wide open. Without any NetworkPolicy, all pod-to-pod traffic is allowed (flat network). The moment you create ANY NetworkPolicy selecting a pod, that pod becomes isolated for the policy type (Ingress/Egress). Only explicitly allowed traffic passes.
NetworkPolicy at Namespace Level — Traffic Control Between Projects
openshift-ingress HAProxy HAProxy openshift-dns CoreDNS :53 Project: frontend deny-all + allow-dns + allow-from-ingress FE Pod :8080 FE Pod :8080 FE Pod :8080 Project: backend deny-all + allow-dns + allow-from-frontend-ns API Pod :8080 API Pod :8080 API Pod :8080 Project: database deny-all + allow-from-backend-ns | no egress DB Primary :5432 DB Replica :5432 :8080 :8080 :5432 Project: other-app Unauthorized namespace Pod Pod How It Works 1. Apply deny-all NetworkPolicy in each namespace — blocks all ingress and egress 2. Add allow-dns policy in each namespace — port 53 to openshift-dns (required or nothing resolves) 3. Add allow rules per tier using namespaceSelector + podSelector 4. Policies are additive — multiple policies = union of allowed traffic Key Concept: NetworkPolicy is namespace-scoped Each policy applies only within its namespace. Use namespaceSelector to reference other namespaces. Combine with podSelector for fine-grained control.
AND vs OR logic: Within one from item, all selectors are ANDed (podSelector AND namespaceSelector must both match). Across multiple from items, they are ORed (match ANY item). This is the most common source of confusion.
# Allow DNS egress (add to every namespace with deny-all)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: openshift-dns
      ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP
# View policies in a namespace
oc get networkpolicy -n my-app

# Test connectivity between pods
oc exec deploy/frontend -- curl -s -o /dev/null -w "%{http_code}" http://backend:8080/health

Storage & CSI Drivers

How persistent storage works in OpenShift using the Container Storage Interface.

Storage Architecture

Storage Architecture — Dynamic Provisioning Flow
DEVELOPER CREATES App Pod volumeMount: /data mounts PVC 10Gi, RWO storageClassName: thin-csi AUTO-CREATED PV Cluster-scoped auto-provisioned by CSI Bound ADMIN CONFIGURES StorageClass provisioner: csi.driver.name reclaimPolicy | volumeBindingMode references triggers provisioning CSI Driver Controller: create volume Node: format + mount Provided by storage vendor creates Backend EBS | VMDK Ceph | NFS CSI DRIVER COMPONENTS Controller Plugin (Deployment) — Provision, Attach, Snapshot, Resize Node Plugin (DaemonSet) — Format, Mount, Bind into Pod ACCESS MODES RWO — ReadWriteOnce RWX — ReadWriteMany ROX — ReadOnlyMany

Static vs Dynamic Provisioning

Static Provisioning
Admin manually creates PV → Developer creates PVC → Binding → Pod mounts. Used when storage already exists (NFS exports, pre-created LUNs).
Dynamic Provisioning
Admin creates StorageClass → Developer creates PVC referencing SC → PV auto-provisioned → Binding → Pod mounts. Preferred for most use cases.

Reclaim Policies

PolicyBehaviorNotes
RetainPV persists after PVC deletion; admin must manually clean upSafe for important data; requires manual intervention
DeletePV and backend storage are automatically deleted when PVC is removedDynamic provisioning only; default for most cloud StorageClasses

Volume Binding Mode

volumeBindingMode is a field on the StorageClass that controls when a PVC is actually bound to a PV — immediately on PVC creation, or only once a Pod that uses it has been scheduled to a node.

Volume Binding Mode — Immediate vs WaitForFirstConsumer
IMMEDIATE WAITFORFIRSTCONSUMER PVC Created volumeBindingMode: Immediate provisioned + bound instantly PV Provisioned + Bound Instantly — before any pod exists pod scheduled later Pod Scheduled Later Scheduler must fit the existing PV risk: node mismatch ⚠ Node Affinity Conflict Volume placed before scheduler input May land in the wrong zone or node PVC Created volumeBindingMode: WaitForFirstConsumer stays Pending PVC Stays Pending Normal — waiting for first consumer scheduler picks node first Pod Created & Scheduled Scheduler picks the node first PV provisioned on node PV Provisioned + Bound On/near the chosen node Topology guaranteed to match
ImmediateWaitForFirstConsumer
When binding happensAs soon as the PVC is createdAfter a Pod using the PVC is scheduled
PVC state before pod existsBoundPending (normal, expected)
Topology awarenessNone — provisioner picks blindScheduler-driven — volume follows the chosen node
Typical useNetwork storage reachable from every node (Ceph, NFS)Zonal cloud disks, local storage, LVM Storage
Default when unspecifiedImmediate
Issues with Immediate: In multi-zone clusters, the PV can be provisioned in zone A before the pod is scheduled — the pod is then pinned to that zone or becomes unschedulable, surfacing as a volume node affinity conflict error. For local disks or LVM storage, the volume is pinned to a specific node before the scheduler ever weighs CPU, memory, or affinity rules — the Pod can end up stuck Pending forever if that node can't also satisfy the pod's other requirements.
OpenShift reality: vSphere CSI (thin-csi), cloud provider StorageClasses (AWS EBS gp3-csi, Azure Disk), and LVM Storage all ship with WaitForFirstConsumer by default. ODF's ceph-rbd / cephfs StorageClasses ship with Immediate — that's fine because Ceph is reachable from every node in the cluster. Seeing a PVC sit in Pending under a WaitForFirstConsumer StorageClass before any pod uses it is normal, not an error.

Access Modes

ModeAbbreviationDescriptionBackends
ReadWriteOnceRWOSingle node read/writeAll (EBS, Ceph RBD, vSphere VMDK)
ReadOnlyManyROXMultiple nodes read-onlyNFS, CephFS, some CSI drivers
ReadWriteManyRWXMultiple nodes read/write simultaneouslyCephFS, NFS, Azure Files
ReadWriteOncePodRWOPSingle pod read/write (GA since OCP 4.16)CSI drivers with support

CSI Architecture Details

CSI Controller (Deployment, 3+ containers): CSI driver container + external-provisioner sidecar + external-attacher sidecar. Handles create/delete/attach/detach volumes. Runs as a Deployment with leader election.
CSI Node (DaemonSet): CSI driver container + driver-registrar sidecar. Runs on every node. Handles NodeStageVolume (format + mount to staging path) and NodePublishVolume (bind-mount into pod).

Complete Example — PVC + Deployment

Create a PersistentVolumeClaim, then reference it in a Deployment. StorageClass triggers dynamic provisioning automatically.

# 1. PersistentVolumeClaim — request 10Gi block storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
  namespace: my-app
spec:
  accessModes:
    - ReadWriteOnce         # Single node read-write
  storageClassName: thin-csi  # StorageClass name (vSphere thin disk)
  resources:
    requests:
      storage: 10Gi         # Requested size
# 2. Deployment — mount PVC into container
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: registry.example.com/my-app:1.0
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: data
              mountPath: /var/data    # Path inside container
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: app-data    # References PVC above
# Apply and verify
oc apply -f pvc.yaml -f deployment.yaml

# Check PVC is Bound
oc get pvc -n my-app
# NAME       STATUS   VOLUME           CAPACITY   ACCESS   STORAGECLASS
# app-data   Bound    pvc-a1b2c3d4..   10Gi       RWO      thin-csi

# Verify mount inside pod
oc exec deploy/my-app -- df -h /var/data

Security & SCC

How OpenShift enforces security at the pod level using Security Context Constraints.

Security Context Constraints (SCC)

SCC are an OpenShift-specific security mechanism that controls what a pod can do at the OS level: run as root, access host network, mount volumes, use capabilities, etc. They are evaluated during pod admission.

SCC Admission Flow

How OpenShift Selects & Enforces SCC
Pod Create API request Check SA ServiceAccount's allowed SCCs Match SCC Most restrictive that satisfies spec ✓ Admitted ✗ Denied SCC Priority restricted-v2most restrictive nonroot-v2any non-root UID anyuidrun as any UID privilegedfull access Grant SCC to ServiceAccount oc adm policy add-scc-to-user anyuid -z my-service-account -n my-namespace OPENSHIFT SECURITY LAYERS RBAC SCC NetworkPolicy OAuth / OIDC SELinux

SCC Controls

Security Context Constraints control the following OS-level capabilities for pods:

ControlDescription
Privileged containersAllow/deny running as privileged (full host access)
Linux capabilitiesAdd/drop specific capabilities (NET_ADMIN, SYS_PTRACE, etc.)
Host directoriesAllow/deny hostPath volume mounts
SELinux contextEnforce specific SELinux labels on containers
User IDRun as specific UID or UID range (MustRunAs, MustRunAsRange, RunAsAny)
Host namespacesAllow/deny access to host PID, IPC namespaces
Host networkingAllow/deny use of host network + host ports
FSGroupSet filesystem group ownership for volumes
Supplemental groupsControl additional group memberships
Read-only root FSForce read-only root filesystem
Volume typesRestrict allowed volume types (emptyDir, configMap, PVC, etc.)
Seccomp profilesApply syscall filtering profiles

RBAC — Role-Based Access Control

RBAC in OpenShift controls who can do what on which resources.

Cluster Role vs Local Role

OpenShift has two scopes for roles: ClusterRole (cluster-scoped, reusable across namespaces) and Role (namespace-scoped, applies only within its namespace). The binding type determines where permissions take effect:

CombinationScopeUse Case
Role + RoleBindingSingle namespace onlyCustom per-project permissions (e.g., bot SA with limited access)
ClusterRole + RoleBindingSingle namespace (scoped down)Reuse standard roles (view/edit/admin) in specific projects
ClusterRole + ClusterRoleBindingAll namespaces cluster-wideCluster-admin, global monitoring, platform-level operators
Key insight: A ClusterRole bound via RoleBinding is scoped down to that namespace. This is the most common pattern — the default view, edit, and admin ClusterRoles are reused across projects via namespace-scoped RoleBindings. You rarely need to create a local Role unless permissions are truly project-specific.
RBAC — Cluster Role vs Local Role Binding
SUBJECTS Service Account (Bot) Group 1 User 1 User 2 User 3 PROJECT LOCAL ROLE BINDING Bot can interact with specific resources LOCAL ROLE BINDING Group 1 can view in project LOCAL ROLE BINDING User 1 can edit in project LOCAL ROLE BINDING User 2 can admin in project CLUSTER ROLE BINDING User 3 can cluster admin in all projects LOCAL ROLE (Bot) Rule Rule CLUSTER-SCOPED CLUSTER ROLE (View) Rule Rule CLUSTER ROLE (Edit) Rule Rule CLUSTER ROLE (Admin) Rule Rule CLUSTER ROLE (Cluster Admin) Rule Rule

RBAC Authorization Flow

RBAC — Subject → Binding → Role → Rules
SUBJECTS User Group ServiceAccount RoleBinding or ClusterRoleBinding Links subject → role Role or ClusterRole Collection of rules Rules apiGroups:["", "apps", "batch"] resources:["pods", "deployments", "services"] verbs:["get", "list", "watch", "create", "delete"]

Default Cluster Roles

ClusterRoleScopePermissions
cluster-adminClusterFull access to all resources in all namespaces. Superuser.
adminNamespaceFull control within a namespace. Can create roles and bindings. Cannot modify quota or namespace itself.
editNamespaceRead/write most resources (pods, deployments, services, configmaps, secrets). Cannot manage roles or bindings.
viewNamespaceRead-only access. Cannot view secrets or roles. No write operations.
self-provisionerClusterAllows creating new projects. Bound to all authenticated users by default. Remove to lock down project creation.
basic-userClusterCan read own user info. Get basic access to projects they belong to.

ServiceAccounts

Every namespace has a default ServiceAccount. Pods use it for API authentication. Custom SAs isolate permissions per workload. Tokens are auto-mounted at /var/run/secrets/kubernetes.io/serviceaccount/token.

# Create a ServiceAccount
oc create sa my-app -n my-namespace

# Grant a role to a user in a namespace
oc adm policy add-role-to-user edit developer -n my-project

# Grant cluster-admin to a user
oc adm policy add-cluster-role-to-user cluster-admin admin-user

# Check if a user can perform an action
oc auth can-i create deployments -n my-project --as developer

# List role bindings in a namespace
oc get rolebindings -n my-project

# Remove self-provisioner from all authenticated users
oc adm policy remove-cluster-role-from-group self-provisioner system:authenticated:oauth
Aggregated ClusterRoles: The default admin, edit, and view roles are built using aggregation labels. When operators install CRDs, they can add rules to these roles automatically by setting rbac.authorization.k8s.io/aggregate-to-admin: "true" on their ClusterRoles.

OAuth Authentication Flow

OpenShift OAuth Authentication Flow
User oc login API Server :6443 OAuth Server Redirects to IDP Identity Provider LDAP / OIDC / etc. Identity + User object created in OCP Token Bearer token SUPPORTED IDENTITY PROVIDERS HTPasswd LDAP OpenID Connect GitHub Keystone

cert-manager — Certificate Lifecycle

cert-manager automates issuing and renewing X.509 certificates — the day-1 pain it removes is manually generating certs, copying them into Secrets, and remembering to rotate them before they expire.

cert-manager — Certificate Request & Renewal Flow
Certificate dnsNames, issuerRef, secretName CertificateRequest Carries the CSR Submitted for signing Issuer / ClusterIssuer Self-signed, internal CA, or ACME (Let's Encrypt) TLS Secret tls.crt tls.key Route / Ingress Automatic Renewal cert-manager re-runs this whole flow at ~2/3 of the certificate's lifetime and updates the Secret in place — no manual rotation.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: platform-team@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: openshift-default

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-tls
  namespace: my-app
spec:
  secretName: myapp-tls-secret
  dnsNames:
    - myapp.apps.cluster.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
Ingress/Route certs: Use the cert from the Secret cert-manager maintains in a Route (edge or re-encrypt termination) — either copy it into spec.tls.certificate/key, or reference the Secret directly via spec.tls.externalCertificate (newer OCP releases). Or use the cert-manager Operator for Red Hat OpenShift to manage the cluster's default Ingress Controller certificate and API server certificate the same declarative way — replacing the self-signed certs OpenShift ships with by default.

Logging & Monitoring

Core platform monitoring plus optional Operators for log collection, storage, and console integration.

Monitoring Architecture

OpenShift ships a fully integrated monitoring stack managed by the Cluster Monitoring Operator. All components are deployed automatically and run in the openshift-monitoring namespace.

OpenShift Monitoring Architecture (Prometheus + Thanos)
SCRAPE TARGETS kubelet cAdvisor node-exporter kube-state-metrics ocp-state-metrics etcd Prometheus (x2 HA) openshift-monitoring TSDB (ephemeral by default) 15-day default retention Configure PVCs for durable history Thanos Sidecar StoreAPI :10901 Thanos Querier Unified PromQL endpoint Aggregates all Sidecars Console Dashboards Alertmanager (x2 HA) route → notify Slack / PD / email / webhook Prometheus Adapter Custom metrics for HPA USER WORKLOAD MONITORING (optional — enable in cluster-monitoring-config ConfigMap) Namespace: openshift-user-workload-monitoring UWM Prometheus Scrapes user ServiceMonitors Thanos Sidecar Registers with Querier Thanos Ruler User alerting + recording rules User ServiceMonitor / PodMonitor Created by users in their namespace MONITORING SIZING REFERENCE (block storage preferred) 50 Nodes / 1,800 Pods 6.3 GB/day | 94 GB/15d | 6 GB RAM 100 Nodes / 3,600 Pods 13 GB/day | 195 GB/15d | 10 GB RAM 200 Nodes / 7,200 Pods 25 GB/day | 375 GB/15d | 14 GB RAM Cluster Monitoring Operator — deploys all above Enable User Workload Monitoring oc -n openshift-monitoring edit cm cluster-monitoring-config → set enableUserWorkload: true → then create ServiceMonitor in your namespace
Retention is not persistence: Core platform metrics have a 15-day default retention setting, but a new multi-node cluster does not have durable Prometheus or Alertmanager storage until an administrator configures PVCs in cluster-monitoring-config. Without persistent storage, pod recreation can lose metrics, silences, and notification state.

Monitoring Components

ComponentWhat It DoesDetails
Prometheus (x2 HA)Time-series database. Scrapes metrics from platform targets via the pull model.Runs in openshift-monitoring. Core metrics have 15-day default retention, but storage is ephemeral until an administrator configures a PVC for each replica.
Thanos SidecarRuns alongside each Prometheus. Exposes StoreAPI for remote reads.Enables Thanos Querier to aggregate data from multiple Prometheus instances without data duplication.
Thanos QuerierUnified PromQL query endpoint. Aggregates data from all Thanos Sidecars.Single entry point for OCP Console dashboards and external Grafana. Deduplicates HA pairs.
Alertmanager (x2 HA)Receives firing alerts from Prometheus. Routes, deduplicates, groups, and sends notifications.Supports Slack, PagerDuty, email, webhook, OpsGenie. Silencing and inhibition rules. Cluster-scoped HA gossip.
node-exporterDaemonSet exposing host-level metrics: CPU, memory, disk, network per node.Scraped by Prometheus. Metrics like node_cpu_seconds_total, node_memory_MemAvailable_bytes.
kube-state-metricsGenerates metrics from Kubernetes object state (deployments, pods, nodes, etc.).Metrics like kube_deployment_status_replicas, kube_pod_status_phase. Not resource usage — object state.
Prometheus AdapterExposes custom Prometheus metrics via Kubernetes Metrics API.Enables HPA (Horizontal Pod Autoscaler) to scale based on custom metrics like requests-per-second.
UWM PrometheusSeparate Prometheus instance for user workloads.Scrapes user-created ServiceMonitor/PodMonitor resources. Isolated from platform monitoring. Must be enabled.

Logging Architecture

Logging is not installed by default with core OCP. A supported Loki-based deployment uses Loki for storage, Vector for collection, and the ClusterLogForwarder CR for routing. Layered Operators work together: Logging Operator (deploys Vector), Loki Operator (manages LokiStack), and Cluster Observability Operator (console integration).

OpenShift Logging Architecture (Loki + Vector)
OPERATORS (install from OperatorHub) Logging Operator — deploys Vector DaemonSet Loki Operator — manages LokiStack Cluster Observability Operator — UIPlugin LOG SOURCES Application Logs User container stdout/stderr Infrastructure Logs openshift-*/kube-*/journald Audit Logs K8s API + OAuth + OVN audit Vector DaemonSet on every node Rust-based | low memory footprint /var/log/containers/ + journald ClusterLogForwarder CR Defines pipelines + outputs LokiStack Distributor → Ingester → Compactor Querier → Query-frontend Index-gateway | Indexes metadata only Object Storage S3 / ODF NooBaa / Azure Log chunk storage backend OCP Console Log viewer via UIPlugin External Forwarding Elasticsearch | Splunk | CloudWatch Kafka | Syslog | HTTP endpoint Filter by type + namespace LOKI LOG TENANTS (built-in) "application" — user workloads "infrastructure" — platform components "audit" — API + OAuth + OVN ClusterLogForwarder Example spec: outputs: - name: loki-internal type: lokiStack lokiStack: {target: {name: logging-loki, namespace: openshift-logging}} - name: splunk-external type: splunk url: https://splunk.corp.com:8088 secret: {name: splunk-token} pipelines: - inputRefs: [application, infrastructure] outputRefs: [loki-internal] | - inputRefs: [audit] outputRefs: [splunk-external]

Logging Components

ComponentWhat It DoesDetails
VectorLog collector. DaemonSet running on every node. Reads container logs and journald.Rust-based, low memory (~50MB). Reads from /var/log/containers/ and journald. Replaces Fluentd (deprecated). Supports parsing, filtering, transforms.
ClusterLogForwarderCustom resource defining log routing pipelines. Connects inputs (log types) to outputs (destinations).Three input types: application, infrastructure, audit. Output types: lokiStack, splunk, elasticsearch, cloudwatch, kafka, syslog, http. Filter by namespace.
LokiStackLog storage backend. Stores log data in object storage with metadata indexing.Components: Distributor (receives), Ingester (writes chunks), Compactor (optimizes), Querier (reads), Query-frontend (caches). Requires S3-compatible object storage (ODF NooBaa, AWS S3, Azure Blob).
Logging OperatorDeploys and manages Vector DaemonSet. Watches ClusterLogForwarder CR.Install from OperatorHub. Creates collector pods in openshift-logging namespace.
Loki OperatorDeploys and manages LokiStack. Handles sizing, storage config, tenant separation.Sizes: 1x.demo, 1x.extra-small, 1x.small, 1x.medium. Manages schema upgrades and compaction.
Cluster Observability OperatorProvides UIPlugin for log viewing in OCP Console.Adds “Logs” tab to pod/namespace views. LogQL query support. Replaces legacy Kibana UI.
Log TenantsLoki enforces RBAC via three built-in tenants.application: user pod logs. infrastructure: openshift-*/kube-* pod logs + journald. audit: API server, OAuth, OVN audit logs.
# Check monitoring stack health
oc get pods -n openshift-monitoring
oc get pods -n openshift-user-workload-monitoring

# Check logging stack health
oc get pods -n openshift-logging

# View Prometheus alerts firing
oc get prometheusrules -A
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- promtool query instant http://localhost:9090 'ALERTS{alertstate="firing"}'

# Check log collector status
oc get clusterlogforwarder -n openshift-logging
oc logs ds/collector -n openshift-logging --tail=20

Operators & OLM

How OpenShift uses the Operator pattern to manage platform components and applications.

What is an Operator?

An Operator encodes human operational knowledge into software. It watches Custom Resources (CRs) and takes automated action: provisioning, scaling, backup, upgrades, failover. OpenShift itself is managed by ~30 built-in Cluster Operators.

Operator Lifecycle

OLM — How Operators Are Installed & Managed
OperatorHub 200+ operators Subscription Channel + approval ClusterServiceVersion Metadata + RBAC Operator Pod Controller running Custom Resources User creates CRs ~30 Built-in Cluster Operators (managed by CVO, not OLM) Ingress | Network | DNS | Monitoring | Registry | Console | Auth | MCO | Storage | etcd | kube-apiserver | ... OperatorHub Catalog Sources Red Hat Operators Certified (ISV) Community Custom Catalog

Operator Pattern — How It Works

An Operator is a controller that watches Custom Resources (CRDs) and runs a reconciliation loop: observe current state → compare with desired state → take corrective action. This encodes human operational knowledge into software.

Operator Reconciliation Loop & CVO vs OLM
Custom Resource Desired state (YAML) Controller Watch → Compare → Act Reconciliation loop Managed Resources Actual state (cluster) CVO (ClusterVersion Operator) Manages ~30 built-in Cluster Operators Coordinates upgrades. Cannot be uninstalled. OLM (Operator Lifecycle Manager) — Install Flow CatalogSource Subscription InstallPlan ClusterServiceVersion Operator Deployment Approval: Automatic (auto-approve InstallPlans) or Manual (admin must approve each update) OPERATOR MATURITY MODEL (CAPABILITY LEVELS) L1: Basic Install L2: Seamless Upgrades L3: Full Lifecycle L4: Deep Insights L5: Auto Pilot BUILD WITH OPERATOR SDK Go-based Ansible-based Helm-based

Common Operators

OperatorSourceWhat It Manages
Logging (Loki)Red HatLog collection (Vector DaemonSet), storage (Loki), and forwarding
ComplianceRed HatCIS/NIST/PCI-DSS compliance scanning and remediation
Network ObservabilityRed HateBPF-based network flow metrics, topology, and tracing
ODFRed HatCeph storage cluster (block, file, object)
ACS (StackRox)Red HatContainer security: CVE scanning, admission control, runtime monitoring
ACMRed HatMulti-cluster lifecycle, governance, observability
cert-managerRed Hat (community version also available)TLS certificate automation (Let's Encrypt, ACME) — "cert-manager Operator for Red Hat OpenShift"
External SecretsCommunitySync secrets from external stores (Vault, AWS SM, Azure KV)
# List installed operators (ClusterServiceVersions)
oc get csv -A

# List subscriptions
oc get sub -A

# Check pending install plans (Manual approval)
oc get installplan -A

# Approve a pending install plan
oc patch installplan <name> -n <ns> --type merge -p '{"spec":{"approved":true}}'

# Check cluster operators status
oc get co

ACM — Advanced Cluster Management

Optional layered product for managing multiple OpenShift and Kubernetes clusters from a single hub.

What is ACM?

Red Hat ACM provides multi-cluster lifecycle management: provision clusters, enforce governance policies, deliver applications, aggregate observability, and search resources across the fleet.

MCE vs ACMMultiClusterEngine (MCE) is the foundation operator handling cluster lifecycle: Hive (IPI provisioning), Assisted Installer, Hosted Control Planes (HyperShift), and the klusterlet registration. ACM (MultiClusterHub) installs MCE and layers fleet management on top: Governance (GRC), Application delivery, Observability, and Search. MCE ships standalone with every OpenShift subscription; the full ACM feature set requires an ACM subscription.

Hub & Spoke Architecture

ACM Hub-Spoke Model
ACM HUB GRC | App Lifecycle | Observability | Search MCE: Hive | HyperShift | Assisted Installer Management cluster AWS Cluster klusterlet agent vSphere Cluster On-premises Azure Cluster AKS / ARO Edge (SNO) ZTP provisioned KEY CAPABILITIES Cluster Lifecycle Governance (GRC) App Delivery Observability Cross-Cluster Search
ACM Console — Cluster Lifecycle
ACM Clusters view — managed cluster list with status, infrastructure, OCP version, labels, and node counts

Hosted Control Planes (HyperShift)

Through MCE, the ACM hub can run Hosted Control Planes: spoke clusters whose control planes (API server, etcd, controllers) run as pods on the hub, while only worker nodes live at the spoke site. This cuts per-cluster cost (no 3 dedicated control-plane machines), speeds up cluster creation to minutes, and decouples control-plane and worker upgrades — widely used for fleet, edge, and telco deployments.

Governance & Policy Framework

ACM’s Governance, Risk & Compliance (GRC) framework lets you define policies on the hub and distribute them to managed clusters. Each policy specifies what to check, where to apply it (via Placement), and how to respond (inform or enforce).

ACM Console — Governance Overview
ACM Governance dashboard — policy set violations, policy violations, cluster compliance, standards, categories, and controls
Policy Lifecycle — Hub to Managed Clusters
ACM HUB PolicyGenerator Kustomize plugin Policy policy.open-cluster… PlacementBinding Links Policy ↔ Placement Placement Label selectors Policy Controller Propagates policies Aggregates status POLICY TEMPLATES ConfigurationPolicy CertificatePolicy Gatekeeper OperatorPolicy MANAGED CLUSTERS Cluster: production Policy Framework Agent Config Policy Controller ✓ Compliant Cluster: staging Policy Framework Agent Config Policy Controller ✗ NonCompliant Cluster: edge-01 Policy Framework Agent Config Policy Controller ✓ Compliant

Policy Types

Policy TypePurposeExample Use Case
ConfigurationPolicyEnforce or audit any K8s resource configurationRequire resource limits on all Deployments
CertificatePolicyMonitor certificate expirationAlert when certs expire within 30 days
OperatorPolicyEnsure operators are installed & configuredRequire compliance-operator on all clusters
Gatekeeper constraintAdmission-time enforcement via OPA (ACM deploys & manages Gatekeeper)Reject pods without required labels at admission
IamPolicy is removed. The legacy IamPolicy template (limit cluster-admin role bindings) is deprecated and removed in current ACM releases. Replace it with a Gatekeeper constraint or a ConfigurationPolicy that audits ClusterRoleBindings.

Live Example — CertificatePolicy Catching an Expiring Certificate

A CertificatePolicy named check-cert monitors TLS secrets in the swongpai-helm namespace with a minimum duration of 300 hours. When a certificate falls below that threshold, ACM flags it as NonCompliant and shows exactly which secret is about to expire.

Step 1 — CertificatePolicy Configuration (minimum duration: 300h)
ACM Governance — Edit CertificatePolicy check-cert, minimum duration 300h, namespace swongpai-helm
Step 2 — Violation Detected: apps-wildcard-tls Expiring
ACM Governance — policy-certificate shows Violations status, related resource apps-wildcard-tls Secret expiring on 2026-06-10
ACM Console — Policy Sets
ACM Policy Sets view — bundle-init policy set with cluster violations, individual policy status and remediation actions

How Policy Works

A Policy is a wrapper that contains one or more policy templates (ConfigurationPolicy, CertificatePolicy, etc.). The Policy is created on the hub, bound to target clusters via PlacementBinding + Placement, and distributed by the Policy Controller. On each managed cluster, a local controller evaluates the templates and reports compliance status back to the hub.

Policy Structure

Every Policy has three core settings:

FieldValuesMeaning
remediationActioninform / enforceinform = audit only, report violations. enforce = auto-create/update/delete resources to match desired state.
severitylow / medium / high / criticalDisplayed in dashboard. No enforcement impact — purely informational for prioritization.
disabledtrue / falseToggle policy on/off without deleting it. Useful for testing before rollout.
inform vs enforce: Every policy has a remediationAction. Set to inform to audit and report violations without changing anything. Set to enforce to automatically remediate — ACM will create/update/delete resources to match desired state. Start with inform in production, switch to enforce after validation. The policy-level remediationAction overrides the template-level setting.

ConfigurationPolicy — Compliance Types

ConfigurationPolicy is the most common and versatile template. It compares desired object definitions against actual cluster state using three compliance types:

complianceTypeBehaviorWhen to use
musthaveObject must exist with at least the specified fields. Extra fields on the object are ignored.Ensure a Namespace has specific labels, a LimitRange exists, or a NetworkPolicy is present.
mustonlyhaveObject must exist with exactly the specified fields. Extra fields are removed on enforce.Lock down a SecurityContextConstraint or RBAC RoleBinding — no extra permissions allowed.
mustnothaveObject must not exist. If found, enforce deletes it.Forbid default ServiceAccount tokens, remove permissive NetworkPolicies, delete test namespaces in production.

ConfigurationPolicy Example

ConfigurationPolicy — Require LimitRange in all namespaces
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
  name: require-limitrange
spec:
  remediationAction: inform   # start with audit
  severity: medium
  namespaceSelector:
    include: ["*"]
    exclude: ["openshift-*", "kube-*"]
  object-templates:
    - complianceType: musthave
      objectDefinition:
        apiVersion: v1
        kind: LimitRange
        metadata:
          name: default-limits
        spec:
          limits:
            - type: Container
              default:
                cpu: "500m"
                memory: "512Mi"
              defaultRequest:
                cpu: "100m"
                memory: "128Mi"
ConfigurationPolicy — Forbid privileged pods (mustnothave)
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
  name: no-privileged-pods
spec:
  remediationAction: inform
  severity: high
  namespaceSelector:
    include: ["*"]
    exclude: ["openshift-*", "kube-*"]
  object-templates:
    - complianceType: mustnothave
      objectDefinition:
        apiVersion: v1
        kind: Pod
        metadata:
          namespace: "{{ .metadata.namespace }}"
        spec:
          containers:
            - securityContext:
                privileged: true

Full Policy Wrapper

A ConfigurationPolicy doesn’t work alone — it must be wrapped in a Policy and bound to clusters via PlacementBinding + Placement. One frequently missed prerequisite: Placement only selects clusters from ManagedClusterSets that are bound to the policy namespace via a ManagedClusterSetBinding. Here’s the complete set of resources:

Complete Policy + ManagedClusterSetBinding + Placement + PlacementBinding YAML
# 1. Policy — wraps ConfigurationPolicy template
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
  name: policy-require-limitrange
  namespace: open-cluster-management-policies
  annotations:
    policy.open-cluster-management.io/standards: NIST SP 800-53
    policy.open-cluster-management.io/categories: CM Configuration Management
    policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
spec:
  remediationAction: inform
  disabled: false
  policy-templates:
    - objectDefinition:
        apiVersion: policy.open-cluster-management.io/v1
        kind: ConfigurationPolicy
        metadata:
          name: require-limitrange
        spec:
          remediationAction: inform
          severity: medium
          namespaceSelector:
            include: ["*"]
            exclude: ["openshift-*", "kube-*"]
          object-templates:
            - complianceType: musthave
              objectDefinition:
                apiVersion: v1
                kind: LimitRange
                metadata:
                  name: default-limits
                spec:
                  limits:
                    - type: Container
                      default:
                        cpu: "500m"
                        memory: "512Mi"
---
# 2. ManagedClusterSetBinding — Placement only selects clusters from
#    ManagedClusterSets bound to the policy namespace
apiVersion: cluster.open-cluster-management.io/v1beta2
kind: ManagedClusterSetBinding
metadata:
  name: global
  namespace: open-cluster-management-policies
spec:
  clusterSet: global
---
# 3. Placement — select target clusters
apiVersion: cluster.open-cluster-management.io/v1beta1
kind: Placement
metadata:
  name: placement-require-limitrange
  namespace: open-cluster-management-policies
spec:
  predicates:
    - requiredClusterSelector:
        labelSelector:
          matchExpressions:
            - key: env
              operator: In
              values: ["production", "staging"]
---
# 4. PlacementBinding — links Policy ↔ Placement
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
  name: binding-require-limitrange
  namespace: open-cluster-management-policies
placementRef:
  apiGroup: cluster.open-cluster-management.io
  kind: Placement
  name: placement-require-limitrange
subjects:
  - apiGroup: policy.open-cluster-management.io
    kind: Policy
    name: policy-require-limitrange

PolicyGenerator — GitOps for Policies

PolicyGenerator is a Kustomize plugin that eliminates boilerplate. Instead of writing Policy + PlacementBinding + Placement by hand for each rule, you write a compact policyGenerator.yaml and run kustomize build — it generates all three resources automatically. This is the recommended approach for managing policies in Git (GitOps).

How PolicyGenerator Works

ConceptWhat it does
policyDefaultsSet defaults for all policies: namespace, remediationAction, severity, placement selectors. Individual policies can override.
policies[].manifestsPoint to YAML files containing the raw Kubernetes objects you want to enforce (LimitRange, NetworkPolicy, etc.). PolicyGenerator wraps each in a ConfigurationPolicy automatically.
placementBindingDefaultsDefault name for generated PlacementBinding resources.
policies[].policyAnnotationsMap compliance standards/categories/controls for audit reporting (NIST, CIS, PCI-DSS).
Key benefit: With PolicyGenerator, your Git repo contains plain Kubernetes manifests (the desired state) — not ACM-specific Policy CRDs. PolicyGenerator wraps them at build time. This means the same manifests can be used standalone or via ACM, and compliance annotations are managed separately from the enforced objects.

PolicyGenerator Example

policyGenerator.yaml — Kustomize plugin
apiVersion: policy.open-cluster-management.io/v1
kind: PolicyGenerator
metadata:
  name: gen-security-baseline
placementBindingDefaults:
  name: binding-security-baseline
policyDefaults:
  namespace: open-cluster-management-policies
  remediationAction: inform
  severity: medium
  consolidateManifests: false   # one ConfigurationPolicy per manifest file
  placement:
    labelSelector:
      matchExpressions:
        - key: env
          operator: In
          values: ["production", "staging"]
policies:
  - name: require-resource-limits
    manifests:
      - path: manifests/limitrange.yaml
    policyAnnotations:
      policy.open-cluster-management.io/standards: NIST SP 800-53
      policy.open-cluster-management.io/categories: CM Configuration Management
  - name: require-network-policies
    severity: high
    remediationAction: enforce   # override default for this policy
    manifests:
      - path: manifests/deny-all-netpol.yaml
      - path: manifests/allow-dns-netpol.yaml
    policyAnnotations:
      policy.open-cluster-management.io/standards: CIS Kubernetes Benchmark
  - name: require-pod-disruption-budget
    manifests:
      - path: manifests/pdb.yaml
kustomization.yaml — wire up PolicyGenerator
# kustomization.yaml — in the same directory as policyGenerator.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generators:
  - policyGenerator.yaml
# Run: kustomize build --enable-alpha-plugins .
# Output: Policy + PlacementBinding + Placement for each entry

PolicyGenerator Workflow

StepActionTool
1Write plain K8s manifests (LimitRange, NetworkPolicy, etc.)kubectl / editor
2Write policyGenerator.yaml referencing manifestseditor
3Run kustomize build --enable-alpha-plugins .kustomize CLI
4Review generated Policy + PlacementBinding + Placementreview
5Commit to Git → ArgoCD/GitOps applies to hubgit + ArgoCD
6ACM distributes policies to matched clustersACM Policy Controller
Recommendation: Use PolicyGenerator for all policy management. Store policyGenerator.yaml + raw manifests in a Git repo. Use ArgoCD ApplicationSet (with ACM Placement) to apply them to the hub — full GitOps lifecycle for governance. Start all policies with remediationAction: inform, review compliance dashboards, then switch to enforce per-policy.

Application Deployment

ACM delivers applications to managed clusters using ArgoCD ApplicationSets with ACM Placement. ArgoCD supports two deployment modes: Push (hub ArgoCD applies to remote clusters) and Pull (each cluster runs its own ArgoCD that pulls from Git). Both use the same ApplicationSet + Placement CRDs on the hub.

For ArgoCD fundamentals (Application, AppProject, sync/health status, drift detection) that this multi-cluster model builds on, see the GitOps & Pipelines section.

Subscription model is removed. The legacy Subscription model (Channel + Subscription + PlacementRule) was deprecated in ACM 2.12 and is removed in current releases. Use ArgoCD ApplicationSet with ACM Placement for all multi-cluster application delivery.
ArgoCD Push vs Pull Model — Application Delivery
Push Model Hub ArgoCD applies to remote clusters Git Repository Application manifests / Helm / Kustomize watches ACM HUB CLUSTER ArgoCD (Hub) openshift-gitops ApplicationSet + Placement applies via API prod-cluster ✓ synced stg-cluster ✓ synced dev-cluster ✓ synced PUSH MODEL CHARACTERISTICS ✓ Hub ArgoCD needs API access to all managed clusters ✓ Single ArgoCD instance manages all deployments ✓ Centralized visibility & drift detection ✓ Simpler setup — no ArgoCD on spoke clusters ⚠ Hub is a single point of failure for deployments ⚠ Requires network path from hub to all spokes USE: <50 clusters, connected networks, centralized ops Pull Model Each cluster’s ArgoCD pulls from Git Git Repository Application manifests / Helm / Kustomize ACM HUB CLUSTER ApplicationSet + Placement Argo CD Agent distributes config distributes Application CR prod-cluster ArgoCD (local) stg-cluster ArgoCD (local) edge-cluster ArgoCD (local) pulls from Git pulls from Git PULL MODEL CHARACTERISTICS ✓ Each cluster pulls from Git independently ✓ No API access from hub to spoke required ✓ Works in disconnected / restricted networks ✓ No single point of failure — clusters self-heal ✓ Scales to thousands of clusters (edge/telco) ⚠ Requires ArgoCD or agent on every managed cluster USE: edge, telco, disconnected, large fleets (>50 clusters)
Push Model
Hub ArgoCD applies manifests to remote clusters via their API servers. Simple setup, centralized control. Best for smaller, connected fleets.
Pull Model
Each managed cluster runs its own ArgoCD (or Argo CD Agent) that pulls from Git independently. No hub-to-spoke network path needed. Best for edge and disconnected.
🔄
ApplicationSet + Placement
Both models use the same CRDs on the hub: ApplicationSet generates per-cluster ArgoCD Applications, Placement selects target clusters. Only the delivery mechanism differs.

Push vs Pull — How to Choose

FactorPush ModelPull Model
Network requirementHub must reach spoke API serversSpokes only need Git access (outbound)
ArgoCD on spokesNot requiredRequired (or Argo CD Agent)
Drift detectionCentralized on hubLocal per cluster
Failure blast radiusHub failure stops all deploymentsHub failure only stops new placements; existing clusters self-heal
ScaleTens of clustersHundreds to thousands
Disconnected / air-gapNot possibleSupported (Git mirror on-site)
Best forCentralized ops, dev/staging, small fleetsEdge, telco, regulated, large fleets

ApplicationSet with ACM Placement


apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
  namespace: openshift-gitops
spec:
  generators:
    - clusterDecisionResource:
        configMapRef: acm-placement
        labelSelector:
          matchLabels:
            cluster.open-cluster-management.io/placement: my-placement
        requeueAfterSeconds: 180
  template:
    metadata:
      name: 'my-app-{{name}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/org/my-app.git
        targetRevision: main
        path: 'overlays/{{name}}'
      destination:
        server: '{{server}}'
        namespace: my-app

Live Example — ApplicationSet Deploying via GitOps

An ApplicationSet named timecheck pulls from a Git repo (github.com/Surote/simple-fastapi-deployment) via ArgoCD push model. ACM Placement targets the local cluster. The topology view shows the full resource tree: ApplicationSet → Placement → Cluster → Route + Deployment + Service → ReplicaSet → Pod.

ACM Console — ApplicationSet Overview (Push Model)
ACM Applications — timecheck ApplicationSet overview showing type Push model (Argo), Git repo, 5 cluster resources synced, namespace openshift-gitops
ACM Console — Application Topology View
ACM Applications — timecheck topology tree: ApplicationSet → Placement → Cluster → Route, Deployment, Service → ReplicaSet → Pod, all green/healthy
OpenShift Console — Deployed Workload Running
OpenShift console Pods view — timecheck-deployment pod Running 1/1, owned by ReplicaSet, 57.5 MiB memory, namespace swongpai-timecheck-public-demo

Observability

ACM aggregates metrics from all managed clusters into a central Thanos deployment on the hub. Each managed cluster runs a metrics-collector (based on Prometheus) that pushes metrics to the hub’s Thanos Receive endpoint. Grafana dashboards on the hub provide fleet-wide visibility: cluster health, resource utilization, API server latency, etcd performance, and custom alerts across the entire fleet.

ACM Observability — Multi-Cluster Metrics Pipeline
ACM HUB CLUSTER Grafana Fleet dashboards Thanos Query Cross-cluster PromQL Alertmanager Fleet-wide alerts Thanos Receive Ingests remote-write Object Storage (S3) Long-term metrics Thanos Store Gateway to S3 MANAGED CLUSTERS Cluster: production Prometheus metrics-collector remote-write to hub Cluster: staging Prometheus metrics-collector remote-write to hub Cluster: edge-01 Prometheus metrics-collector remote-write to hub
Storage requirement: Observability stores metrics in an S3-compatible object store (e.g., ODF/MCG, MinIO, AWS S3). Configure MultiClusterObservability CR with a thanos.yaml secret containing bucket credentials. Retention defaults to 5 days for recent, 1 year for downsampled.

MultiClusterObservability CR

MultiClusterObservability Custom Resource YAML
apiVersion: observability.open-cluster-management.io/v1beta2
kind: MultiClusterObservability
metadata:
  name: observability
spec:
  observabilityAddonSpec:
    enableMetrics: true
    interval: 300   # collection interval (seconds)
  storageConfig:
    metricObjectStorage:
      name: thanos-object-storage   # Secret with S3 creds
      key: thanos.yaml
    statefulSetSize: 10Gi   # local cache per Thanos component
  retentionConfig:
    retentionResolutionRaw: 5d
    retentionResolution5m: 14d
    retentionResolution1h: 365d
  advanced:
    queryFrontend:
      replicas: 2
    receive:
      replicas: 3
thanos.yaml Secret (S3 bucket config)
# thanos.yaml — referenced by MultiClusterObservability
type: s3
config:
  bucket: acm-observability
  endpoint: s3.openshift-storage.svc:443
  insecure: false
  access_key: <from ObjectBucketClaim Secret>
  secret_key: <from ObjectBucketClaim Secret>

What's New in Recent ACM Releases

VersionHighlights
2.14Observability backup/restore for hub migrations. namespaceMapping on restore (OADP) — restore resources into a different namespace.
2.15Fleet virtualization perspective — manage OpenShift Virtualization VMs across all clusters from the hub. Right-sizing recommendations for cluster resources. Argo CD agent (Tech Preview) — pull-based GitOps that scales to large/edge fleets.
2.16Baseline used by this guide — see the ACM 2.16 release notes for details.

ACS — Advanced Cluster Security

Optional layered product for full-lifecycle container security from build to runtime.

What is ACS?

Red Hat ACS (formerly StackRox) provides vulnerability management, compliance scanning, network segmentation, and runtime threat detection across your entire container environment. It secures workloads at every stage — build, deploy, and runtime — from a single centralized console.

ACS ≠ OCP Native Security (SCC/RBAC) — OpenShift includes built-in security primitives: SCC constrains pod capabilities, RBAC controls API access, and NetworkPolicy segments traffic. ACS complements these by adding visibility (what’s running, what’s vulnerable), detection (anomalous runtime behavior), and governance (policy enforcement across clusters). Use OCP security for platform-level access control; use ACS for workload-level security posture management.

Key Capabilities

🛡
Vulnerability Management
Scan images for CVEs. Prioritize by risk score combining CVSS, exploit availability, and deployment exposure. Track fix status.
Compliance Scanning
CIS Kubernetes, NIST SP 800-190, PCI-DSS, HIPAA, NERC-CIP checks. Continuous compliance monitoring with exportable reports.
🔗
Network Segmentation
Visualize actual network flows between pods. Auto-generate NetworkPolicies from observed traffic. Detect unexpected connections.
Runtime Detection
CO-RE BPF-based process, network, and file monitoring. Detect crypto mining, shell-in-container, privilege escalation. Kill or alert.
Risk Profiling
Score every deployment by combined risk: CVE severity, misconfigurations, network exposure, runtime activity. Prioritize remediation.
CI/CD Integration
roxctl CLI in pipelines. Fail builds on policy violations. Shift security left — catch issues before deploy, not after.

ACS Architecture

ACS uses a hub-spoke model. Central runs on a dedicated cluster (or the ACM hub). Each protected cluster runs Secured Cluster components that report to Central via gRPC/mTLS.

ACS Architecture — Central + Secured Clusters
ACS CENTRAL (Hub Cluster) Central API REST + gRPC Dashboard UI Risk + compliance Scanner V4 Image CVE analysis Policy Engine Built-in policy library Central DB PostgreSQL roxctl CLI CI/CD integration Integrations: Slack, Jira, email, webhook, Splunk, Sumo Logic gRPC / mTLS SECURED CLUSTERS Secured Cluster: production Sensor Watches K8s API & image metadata Collector (CO-RE BPF) Process, network & file monitoring Admission Controller Block non-compliant deploys Secured Cluster: staging Sensor Watches K8s API & image metadata Collector (CO-RE BPF) Process, network & file monitoring Admission Controller Block non-compliant deploys Secured Cluster: edge Sensor Watches K8s API & image metadata Collector (CO-RE BPF) Process, network & file monitoring Admission Controller Block non-compliant deploys

Policy & Violation Flow

ACS ships with an extensive built-in security policy library covering CVEs, misconfigurations, runtime anomalies, and compliance standards. Policies evaluate at build, deploy, or runtime — violations trigger alerts, block deployments, or kill pods depending on enforcement configuration. Policies can also be managed declaratively as code via the SecurityPolicy CRD (GitOps-friendly).

Policy Evaluation & Enforcement Flow
Policy Definition Built-in or custom Criteria + scope + action ⚙ BUILD Check roxctl image scan in CI ⚠ DEPLOY Check Admission Controller 💥 RUNTIME Check Collector (CO-RE BPF) Violation Severity: Critical → Low ✗ Block / Fail Build ⚠ Alert (Slack/Jira/email) 🛑 Kill Pod (runtime) Example Built-in Policies BUILD • Critical CVE in image • Image from untrusted registry • Image uses :latest tag • Fixable CVSS ≥ 7.0 DEPLOY • Privileged container • No resource limits set • Writable root filesystem • Container runs as UID 0 RUNTIME • Crypto mining process • Shell spawned in container • Unexpected network flow • Privilege escalation attempt COMPLIANCE • CIS Kubernetes Benchmark • NIST SP 800-190 • PCI DSS 4.0 • HIPAA

Deploying ACS on OpenShift

ACS is deployed via the RHACS Operator from OperatorHub. Install Central on a hub cluster, then deploy SecuredCluster bundles on each managed cluster.

Deployment Steps

#StepDetail
1Install RHACS OperatorOperatorHub → Advanced Cluster Security for Kubernetes → Install
2Create Central CRDeploys Central, Scanner V4, Dashboard UI, Central DB on hub cluster
3Generate init-bundleroxctl central init-bundles generate — creates cluster-specific TLS secrets
4Apply init-bundleApply generated Secret to each managed cluster’s stackrox namespace
5Create SecuredCluster CRDeploys Sensor + Collector + Admission Controller on each managed cluster

Central CR

Central Custom Resource YAML
apiVersion: platform.stackrox.io/v1alpha1
kind: Central
metadata:
  name: stackrox-central-services
  namespace: stackrox
spec:
  central:
    exposure:
      route:
        enabled: true   # auto-create Route
    persistence:
      persistentVolumeClaim:
        claimName: stackrox-db
    db:
      isEnabled: Default   # managed PostgreSQL
      persistence:
        persistentVolumeClaim:
          claimName: central-db
  scanner:
    analyzer:
      scaling:
        autoScaling: Enabled
        maxReplicas: 5
        minReplicas: 2
        replicas: 3
    scannerComponent: AutoSense   # V4 when available

SecuredCluster CR

SecuredCluster Custom Resource YAML
apiVersion: platform.stackrox.io/v1alpha1
kind: SecuredCluster
metadata:
  name: stackrox-secured-cluster
  namespace: stackrox
spec:
  clusterName: production
  centralEndpoint: central-stackrox.apps.hub.example.com:443
  admissionControl: # 4.9+ console: single ON/OFF enforcement toggle
    listenOnCreates: true
    listenOnUpdates: true
    listenOnEvents: true   # runtime enforcement
    contactImageScanners: ScanIfMissing
  perNode:
    collector:
      collection: CORE_BPF   # only supported method since ACS 4.5
      imageFlavor: Regular
    taintToleration: TolerateTaints
Recommendation: Deploy Central on the ACM hub cluster for unified management. Collector uses CO-RE BPF (Compile Once – Run Everywhere) — the only collection method since ACS 4.5; no per-kernel drivers or modules needed. Start with inform enforcement on all policies — switch to enforce per-policy after tuning to avoid false-positive blocks in production.

Key Features Deep Dive

Vulnerability Management

ACS scans images using Scanner V4 — the default scanner since ACS 4.8 (the legacy StackRox Scanner is deprecated) — and correlates CVEs with deployment context: is the vulnerable package in the running container? Is the port exposed? Is there a known exploit? This produces a risk score that prioritizes remediation beyond raw CVSS. Since ACS 4.10, base-image layers are distinguished from application-added layers, so platform and app teams each see the CVEs they own.

CapabilityDetail
Image scanningScans on first deploy + continuous re-scan as new CVEs are published
Risk prioritizationCombines CVSS + exploit availability + deployment exposure + network reachability
Fix trackingShows which CVEs have fixes available in newer package versions
Image policyBlock images from untrusted registries, with :latest tag, or exceeding CVE thresholds
ACS Console — Vulnerability Management
ACS Vulnerability Management — workload CVE findings filtered by namespace, showing deployments with CVE severity breakdown

Network Segmentation

ACS builds a real-time network flow graph from Collector observations. Visualize actual pod-to-pod and pod-to-external traffic, then generate NetworkPolicy YAML from observed flows — zero-trust microsegmentation without manual policy writing.

ACS Console — Network Graph
ACS Network Graph — visualizing pod-to-pod traffic flows with baseline and anomalous flow detection

Runtime Detection

Collector uses CO-RE BPF probes (Compile Once – Run Everywhere eBPF) on each node to monitor all container processes, network connections, and file access in real-time — without modifying containers, requiring sidecars, or loading per-kernel drivers.

Live Example — Process Discovery in Action

Run a few commands inside a pod terminal, and Collector picks them up instantly. Below: curl and tracepath executed in a toolbox pod, then flagged by ACS under Risk → Process Discovery with full parent-process lineage and timestamps.

Step 1 — Commands Executed in Pod Terminal
OpenShift console pod terminal — running curl google.com and tracepath google.com inside a ubi9-toolbox container
Step 2 — ACS Process Discovery Flags the Activity
ACS Risk view — Process Discovery tab showing flagged processes /usr/bin/curl, /usr/bin/sh, /usr/sbin/tracepath in the ubi9-toolbox container with event timeline counts
Step 3 — Event Timeline with Process Lineage
ACS Event Timeline detail — process activity entries for sh, curl and tracepath with arguments, parent process names, UIDs and timestamps
ACS Console — Violations
ACS Violations view — policy violations by severity, category, and lifecycle stage for workloads

Compliance Scanning

ACS continuously evaluates clusters against industry compliance standards and generates audit-ready reports. Current ACS delivers this through Compliance v2: the Compliance Operator runs the actual node/platform scans on each secured cluster, while ACS schedules scans, aggregates fleet-wide results, and produces reports.

ACS Console — CIS-OCP Compliance Coverage (98% passing)
ACS Compliance — CIS-OCP profile ocp4-cis-node, profile version 1.9.0, 98% passing 2% failing, 188 checks across clusters with pass/fail/manual status
ACS Console — Compliance Check Detail (NetworkPolicy requirement)
ACS Compliance — ocp4-cis-configure-network-policies-namespaces check failing, CIS-OCP 5.3.2 control, description and remediation instructions for ensuring NetworkPolicy in all namespaces

CI/CD Integration (roxctl)

The roxctl CLI integrates ACS into CI/CD pipelines to shift security left — catch issues before deployment.

Policy as Code (SecurityPolicy CRD)

ACS policies can be managed declaratively with the SecurityPolicy custom resource on the Central cluster — store policies in Git, apply via ArgoCD, and keep security rules under the same GitOps review flow as application manifests.

SecurityPolicy CRD — declarative ACS policy
apiVersion: config.stackrox.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: no-latest-tag
  namespace: stackrox   # must be on the Central cluster
spec:
  policyName: "Image uses :latest tag"
  description: "Forbid mutable :latest tags in production"
  severity: HIGH_SEVERITY
  categories:
    - DevOps Best Practices
  lifecycleStages:
    - BUILD
    - DEPLOY
  scope:
    - cluster: production
  policySections:
    - sectionName: Image tag
      policyGroups:
        - fieldName: Image Tag
          values:
            - value: latest

Live Example — "Image uses :latest tag" Policy as Code

A SecurityPolicy CRD deployed via GitOps creates the "Image uses :latest tag" policy in ACS. The policy is marked Externally managed (origin), meaning it’s controlled by the CRD — not editable in the UI. When a deployment uses :latest, ACS raises a deploy-time violation.

Step 1 — Policy in ACS Policy Management (Externally Managed)
ACS Policy Management — 88 policies listed, 'Image uses :latest tag' shown as Enabled, Externally managed origin, High severity, Build/Deploy lifecycle
Step 2 — Policy Details, Behavior & Criteria
ACS policy detail — 'Image uses :latest tag', severity High, category DevOps Best Practices, origin Externally managed, lifecycle Build/Deploy, response Inform, criteria: Image tag is 'latest'
Step 3 — Violation Triggered on Deployment
ACS Violations — 'Image uses :latest tag' violation on simple-app Deployment in swongpai-helm namespace, High severity, Deploy lifecycle, DevOps Best Practices category

What's New in Recent ACS Releases

VersionHighlights
4.8Scanner V4 becomes the default scanner (StackRox Scanner deprecated). External IP visibility in the network graph.
4.9Auto-locking process baselines (scope a policy to a namespace — new deployments alert from Day 1). Simplified admission controller: single ON/OFF enforcement toggle. Machine-to-machine OIDC auth for API automation. Cluster registration secret default expiry reduced to 1 hour.
4.10VM vulnerability scanning for OpenShift Virtualization (Tech Preview). OpenShift Console security plugin — vulnerability data inside the OCP console (Tech Preview). Base-image vs application-layer CVE separation. File activity monitoring (Tech Preview). CVE fix-date policy criterion for remediation SLAs.

ODF — OpenShift Data Foundation

Optional layered storage product powered by Ceph, providing block, file, and object services.

What is ODF?

OpenShift Data Foundation (ODF) is Red Hat's software-defined storage solution for OpenShift, built on Ceph and managed by the Rook operator. It provides block (RBD), file (CephFS), and S3-compatible object (NooBaa MCG) storage as native Kubernetes resources. ODF eliminates external storage array dependencies by running storage services directly on OpenShift nodes with local or attached disks.

ODF ≠ External CSI Drivers — External storage (NetApp Trident, AWS EBS CSI, vSphere CSI) connects OpenShift to pre-existing storage arrays. ODF creates a storage cluster inside your OpenShift cluster using local disks, providing three storage types from a single platform. Use external CSI when you already have enterprise storage infrastructure; use ODF when you want hyper-converged or dedicated storage nodes without external dependencies.

Key Capabilities

💾
Unified Storage
Block, file, and object from a single Ceph cluster. One operator, three StorageClasses, all access patterns covered.
📷
Snapshots & Clones
CSI VolumeSnapshots for point-in-time copies. Clones for instant read-write duplicates. Backup integration via OADP.
🔒
Encryption at Rest
Cluster-wide or per-PV encryption using LUKS. Key management via Vault or KMS integration. Transparent to applications.
🔄
Disaster Recovery
Metro-DR (stretch clusters) and Regional-DR (async replication) between ODF clusters. RBD mirroring + Ramen orchestration.
Multi-Cloud Gateway
NooBaa aggregates storage from AWS S3, Azure Blob, GCP, and local Ceph. S3-compatible API with bucket policies and lifecycle.
Operator Managed
Full lifecycle via ODF Operator. Automated deployment, scaling, upgrades, and self-healing. Day-2 operations via console or CLI.

ODF Architecture

Ceph Architecture & Storage Types
Application Pods RBD (Block) RWO | Databases, VMs, general storage CephFS (File) RWX | Shared filesystem across pods & nodes NooBaa MCG (Object) S3 API | Multi-cloud gateway CEPH CLUSTER (managed by Rook Operator) MON (x3) MGR (x2) MDS (x2) OSDs — one per disk | 3-way replication | CRUSH placement Storage Nodes (x3+ min) — labeled cluster.ocs.openshift.io/openshift-storage — raw disks for OSDs

Data Flow: PVC to Disk

When a pod requests persistent storage, the request flows through Kubernetes and ODF layers. The path differs by storage type: RBD for block (RWO) and CephFS for shared file (RWX). Both ultimately write to OSDs on physical disks, distributed by the CRUSH algorithm.

PVC Provisioning & I/O Path
Application Pod volumeMount: /data PersistentVolumeClaim accessMode + size + storageClassName StorageClass provisioner + reclaimPolicy + parameters (pool, encryption) RWO path RWX path RBD CSI Driver openshift-storage.rbd.csi.ceph.com CephFS CSI Driver openshift-storage.cephfs.csi.ceph.com RBD Pool ocs-storagecluster-cephblockpool CephFS Pool + MDS ocs-storagecluster-cephfilesystem OSDs → Physical Disks (3-way replication across failure domains)

Storage Types Comparison

ODF exposes three distinct storage types. Choose based on access mode requirements and workload characteristics.

TypeProtocolAccess ModeUse CasesBacking ServiceStorageClass
RBD (Block)Kernel RBDRWO, RWOP, RWX (Block)Databases (PostgreSQL, MySQL), VMs, general-purposeCeph RADOS Block Deviceocs-storagecluster-ceph-rbd
CephFS (File)POSIX filesystemRWX, RWOShared config, CMS uploads, ML training data, CI artifactsCeph MDS + OSDsocs-storagecluster-cephfs
NooBaa MCG (Object)S3 APIN/A (API)Backups (OADP), log archives (Loki), ML datasets, multi-cloud tieringNooBaa + backing storesopenshift-storage.noobaa.io
OpenShift Virtualization: ODF also creates ocs-storagecluster-ceph-rbd-virtualization — an RBD StorageClass tuned for VM disks (RWX block mode for live migration). Use it as the default StorageClass for VMs instead of the regular RBD class.

Deploying ODF on OpenShift

ODF is deployed via the ODF Operator from OperatorHub. The operator installs the Rook-Ceph operator, NooBaa operator, and CSI drivers. Internal mode uses local disks on labeled nodes; external mode connects to a pre-existing Ceph cluster. A third option, provider mode, lets one dedicated ODF cluster serve storage to multiple consumer OpenShift clusters.

Deployment Steps

#StepDetail
1Label storage nodesoc label node <name> cluster.ocs.openshift.io/openshift-storage="" — minimum 3 nodes
2Install ODF OperatorOperatorHub → Red Hat OpenShift Data Foundation → Install (creates openshift-storage namespace)
3Create StorageClusterConsole → Installed Operators → ODF → Create StorageCluster → select internal-attached devices (the StorageSystem CRD was removed in ODF 4.19 — StorageCluster is created directly)
4Verify StorageClassesoc get sc — confirm ocs-storagecluster-ceph-rbd, ocs-storagecluster-cephfs, openshift-storage.noobaa.io
5Create first PVCUse new StorageClasses in PVC manifests or set ocs-storagecluster-ceph-rbd as default StorageClass

StorageCluster CR

StorageCluster Custom Resource YAML
apiVersion: ocs.openshift.io/v1
kind: StorageCluster
metadata:
  name: ocs-storagecluster
  namespace: openshift-storage
spec:
  manageNodes: false
  monDataDirHostPath: /var/lib/rook
  storageDeviceSets:
    - name: ocs-deviceset
      count: 1   # devices per node
      dataPVCTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 512Gi   # size per OSD
          storageClassName: localblock
          volumeMode: Block
      placement: {}
      portable: true
      replica: 3   # 3-way replication
  multiCloudGateway:
    reconcileStrategy: standalone

Example PVCs

RBD (Block) + CephFS (File) PVC Examples
# RBD block volume (RWO) — databases, general storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-data
  namespace: my-app
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: ocs-storagecluster-ceph-rbd
---
# CephFS shared volume (RWX) — shared across pods/nodes
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-uploads
  namespace: my-app
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 50Gi
  storageClassName: ocs-storagecluster-cephfs
OCP Console — PVCs Bound to ODF StorageClasses (RWO ceph-rbd + RWX cephfs)
OpenShift Console PersistentVolumeClaims — rwo-01 Bound to ocs-storagecluster-ceph-rbd (1 GiB RWO), rwx-01 Bound to ocs-storagecluster-cephfs (1 GiB RWX)
Recommendation: Start with internal-attached mode using at least 3 nodes with dedicated disks (minimum 512 GiB each). Use ocs-storagecluster-ceph-rbd as the default StorageClass for most workloads. Reserve CephFS for workloads that genuinely need RWX shared access. Monitor cluster health via the ODF dashboard in the OpenShift console or ceph status from the Rook toolbox pod.

Key Concepts Deep Dive

CRUSH Algorithm & Failure Domains

CRUSH (Controlled Replication Under Scalable Hashing) is Ceph's data placement algorithm. Instead of centralized lookup tables, CRUSH computes where data should be stored algorithmically based on the cluster topology. This enables Ceph to scale without metadata bottlenecks.

Replication & Erasure Coding

ODF supports two data protection strategies. Replication (default) writes N full copies across failure domains. Erasure coding splits data into data + parity chunks for better space efficiency at higher CPU cost.

Snapshots & Clones

ODF supports CSI VolumeSnapshots and clones for both RBD and CephFS volumes. Snapshots are point-in-time, copy-on-write captures. Clones create instant read-write copies from snapshots.

NooBaa Multi-Cloud Gateway (MCG)

NooBaa provides an S3-compatible object storage API on OpenShift. It aggregates multiple backing stores behind a single S3 endpoint, with data placement policies for tiering, mirroring, and spreading.

OCP Console — Object Bucket Claims (Loki OBC bound to NooBaa)
OpenShift Console Object Storage — Object Bucket Claims tab showing OBC 'loki' in openshift-storage namespace, Bound status, Secret 'loki', StorageClass openshift-storage.noobaa.io
OCP Console — Bucket Contents (object upload & browsing)
OpenShift Console Object Storage — Loki bucket detail showing Objects tab with uploaded file acm-gevernance.png (79.76 KiB), MCG badge, Created via OBC, Bound status, drag-and-drop upload area
OCP Console — Presigned URL Sharing (time-limited S3 access)
OpenShift Console Object Storage — Share object with presigned URL dialog, granting third-party access to acm-gevernance.png with expiry, copy presigned URL button

Disaster Recovery: Metro-DR vs Regional-DR

ODF supports two DR topologies, both orchestrated with ACM and the Ramen (OpenShift DR) operators. Metro-DR stretches a single Ceph cluster synchronously across two sites (RPO zero, distance-limited). Regional-DR asynchronously replicates between two independent ODF clusters (minutes of RPO, unlimited distance). Since ODF 4.20, DR-protected VMs can fail over individually rather than only as a whole namespace.

ODF Disaster Recovery Topologies
Metro-DR (Synchronous) RPO = 0 | <10ms RTT between sites Site A (Zone 1) OCP + ODF nodes OSD replicas Site B (Zone 2) OCP + ODF nodes OSD replicas Single stretched Ceph cluster — sync writes Arbiter (Site C) Tie-breaker MON — quorum on site loss Both sites active — data written to both before ack Failover orchestrated by ACM + Ramen (DRPlacementControl) USE: campus / metro distance, zero data loss Regional-DR (Asynchronous) RPO = minutes | any distance Region 1 (Primary) OCP + ODF cluster Workload active Region 2 (Standby) OCP + ODF cluster Receives mirror RBD mirroring — snapshot-based async replication Ramen + ACM Hub DRPolicy / DRPlacementControl CRs Failover/relocate per application — 4.20: per-VM failover Sync interval set in DRPolicy (e.g. 5m) USE: cross-region, tolerates minutes of data loss

What's New in Recent ODF Releases

VersionHighlights
4.19StorageSystem CRD removed — StorageCluster is created directly; simpler deployment flow.
4.20Per-VM failover/failback for DR-protected VMs (no longer all-or-nothing per namespace). Multus network support extended to IPv6. Pool-level near-full/full alerts with actionable messages. DR recipes with exec hooks for more workload types. Encryption annotations auto-added when KMS is missing.

Quay — Enterprise Container Registry

Optional enterprise registry product for securing, scanning, and distributing container images at scale.

What is Red Hat Quay?

Red Hat Quay is an enterprise container image registry that provides secure storage, vulnerability scanning, and global distribution of container images. It supports OCI and Docker image formats, integrates deeply with OpenShift, and offers governance features for multi-team environments.

Quay ≠ OpenShift Internal Registry — OpenShift includes a built-in image registry (image-registry.openshift-image-registry.svc) for basic internal image storage. Quay is an enterprise-grade registry for cross-cluster, multi-team, production image management with security scanning, geo-replication, and access governance. Use the internal registry for dev/CI builds; use Quay for production image distribution.

Key Features

🛡
Clair Security Scanning
Automatic vulnerability scanning on every image push. CVE database updated continuously. Block deployments based on severity thresholds.
Robot Accounts
Service accounts for CI/CD pipelines and automated image pulls. Scoped permissions per repository. No shared human credentials.
🔄
Repository Mirroring
Mirror external registries (Docker Hub, upstream vendors) into Quay for air-gapped and disconnected cluster deployments. Scheduled sync.
🌐
Geo-Replication
Replicate images across multiple storage backends in different regions. Pulls served from nearest location. Single global namespace.
Quota Management
Set storage quotas per organization and repository. Prevent runaway image accumulation. Track usage with built-in reporting.
📦
OCI Artifacts
Store Helm charts, cosign signatures, SBOMs, and zstd images alongside container images. OCI Referrers API links signatures/SBOMs to their images.

Quay Architecture

Quay consists of multiple microservice components deployed as pods on OpenShift, managed by the Quay Operator via a single QuayRegistry custom resource.

Quay Component Architecture
Red Hat Quay on OpenShift Developers push / pull images CI/CD Pipelines Robot Accounts Quay Application Quay API v2 Registry API Quay UI v2 UI (default 3.16+) Mirror Worker Repo sync jobs Clair Scanner CVE detection & reporting Clair PostgreSQL Vulnerability DB scan Managed by QuayRegistry CR — Quay Operator Backend Services PostgreSQL Metadata, users, permissions Redis Build logs, real-time events Object Storage (S3 API) Image layers & blobs MCG (Multicloud Object Gateway) S3-compatible — part of ODF ObjectBucketClaim → auto-provisions bucket NooBaa Operator OCP Clusters pull images

Image Flow — Push to Pull

From developer workstation to running pod, every image passes through Quay’s security pipeline.

Image Lifecycle Through Quay
⚙ Build podman build or Tekton Pipeline ↑ Push podman push quay.example.com 🛡 Clair Scan Auto on push CVE detection ☐ Store S3 / MCG backend Geo-replicated ↓ Pull CRI-O on OCP node ImagePullSecret Scan Result Flow ✓ No Critical CVEs Image available for pull ✗ Critical CVEs Found Notification → team alerted → fix & rebuild

Deploying Quay on OpenShift

Quay is deployed via the Quay Operator from OperatorHub. Install the operator, then create a QuayRegistry custom resource. The operator manages all components (Clair, PostgreSQL, Redis, object storage) automatically.

Deployment Steps

#StepDetail
1Install Quay OperatorOperatorHub → Red Hat Quay → Install (all namespaces or dedicated)
2Install ODF OperatorRequired for MCG object storage backend
3Create ObjectBucketClaimMCG provisions an S3-compatible bucket for image blobs
4Create QuayRegistry CROperator deploys all Quay components automatically
5Access Quay consoleRoute auto-created at quay-<namespace>.apps.<cluster>

MCG Integration — Object Storage for Quay

Quay stores image layers and blobs in S3-compatible object storage. When running on OpenShift with ODF, Multicloud Object Gateway (MCG) powered by NooBaa provides this storage natively — no external S3 service needed.

Quay + MCG (ODF) Integration
Quay Application Pods quay-app, quay-mirror — read/write image blobs via S3 API S3 API (port 443) ObjectBucketClaim Requests bucket → generates Secret (access key, endpoint) MCG — Multicloud Object Gateway (NooBaa) S3-compatible object service — part of ODF Storage Backends: Ceph RGW (ODF)  |  AWS S3  |  Azure Blob  |  GCS  |  PV Backing Store MCG aggregates multiple backends — data placement policies control where blobs land
OCP Console — Quay Image Blobs in the MCG Bucket
OpenShift Console Object Storage — browsing the quay-datastore NooBaa bucket at path datastorage/registry/uploads/, showing 48 image blob objects (UUID names, sizes from 97 B to 30.35 MiB) written by Quay via the S3 API

QuayRegistry CR

QuayRegistry Custom Resource YAML
apiVersion: quay.redhat.com/v1
kind: QuayRegistry
metadata:
  name: central
  namespace: quay-enterprise
spec:
  configBundleSecret: quay-config-bundle
  components:
    - kind: clair
      managed: true
    - kind: clairpostgres
      managed: true
    - kind: postgres
      managed: true
    - kind: redis
      managed: true
    - kind: objectstorage
      managed: true   # uses ODF/MCG via ObjectBucketClaim
    - kind: route
      managed: true
    - kind: horizontalpodautoscaler
      managed: true
    - kind: mirror
      managed: true
    - kind: monitoring
      managed: true
    - kind: tls
      managed: true

ObjectBucketClaim for MCG

ObjectBucketClaim YAML (MCG / NooBaa)
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: quay-bucket
  namespace: quay-enterprise
spec:
  generateBucketName: quay-bucket
  storageClassName: openshift-storage.noobaa.io
  additionalConfig:
    bucketclass: noobaa-default-bucket-class
---
# After OBC is bound, a Secret and ConfigMap are created:
#   Secret:    quay-bucket  (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
#   ConfigMap: quay-bucket  (BUCKET_NAME, BUCKET_HOST, BUCKET_PORT)
Recommendation: For production, set managed: true for all components to let the operator handle lifecycle. Use ODF/MCG for object storage to keep everything on-cluster. For air-gapped environments, deploy a mirror registry alongside Quay and configure repository mirroring to sync images from external registries on a schedule.

Key Features Deep Dive

Clair Security Scanning

Clair V4 (indexer/matcher architecture) automatically scans every image pushed to Quay against continuously updated vulnerability databases. Scans happen asynchronously — results appear in the Quay UI under each image tag. Supports indexing OCI and Docker v2 manifest formats.

CapabilityDetail
Scan triggerAutomatic on push; can re-scan existing images via API
DatabasesNVD, Red Hat OVAL v2, OSV.dev, Ubuntu Tracker, Alpine SecDB, Debian Tracker
OutputCVE list with severity, fixed-in version, CVSS score
IntegrationNotifications on new CVEs found in previously scanned images

Robot Accounts

Robot accounts are service-level credentials scoped to specific repositories or organizations. They replace shared human credentials in CI/CD pipelines and automated deployments.

Repository Mirroring

Mirror external registries into Quay on a schedule. Essential for disconnected / air-gapped environments common in telco and government deployments.

Repository Mirroring — External to Quay
EXTERNAL REGISTRIES Docker Hub docker.io/library/* Red Hat Registry registry.redhat.io Vendor / OCI Registry ghcr.io, quay.io, ECR, etc. Mirror Worker Scheduled sync (cron) Tag filter (regex) QUAY (AIR-GAPPED) Mirrored Repository Read-only in Quay Source of truth = upstream OCP Clusters pull mirrored images Sync: hourly / daily / weekly Only new/changed layers transferred

Live Example — Mirroring from quay.io to Internal Quay

Mirror an upstream image (quay.io/rh_ee_swongpai/fast-localtime-check) into an internal Quay instance as repository swongpai/timesync. Set up a Robot Account for pull credentials, configure the mirror with a tag filter and sync schedule, trigger a sync, then watch the mirrored tag appear with a Clair scan.

Step 1 — Robot Account for Mirror Pull Credentials
Red Hat Quay swongpai organization Robot accounts tab — robot account swongpai+robot, no teams, 1 repository, created Jun 23 2026, used as mirror pull credentials
Step 2 — Mirror Configuration on Internal Quay
Red Hat Quay Repository Mirroring config for swongpai/timesync — Enabled, Registry Location quay.io/rh_ee_swongpai/fast-localtime-check, Tags rhhi-latest, All architectures, next sync date, Sync Now button
Step 3 — Sync Logs Show Successful Mirror
Red Hat Quay usage logs chart for swongpai/timesync — Push to repository, Started Repository Mirror, Repository Mirror sync success, and Repository Mirror tag sync successful each show count 3
Step 4 — Mirrored Tag Appears in Internal Quay
Red Hat Quay Tags tab for swongpai/timesync — rhhi-latest tag mirrored, 24.9 kB to 59.5 MB, linux/amd64 child manifest, security None Detected, SHA256 manifest digest

Geo-Replication

Replicate image data across multiple storage backends in different geographic regions. All Quay instances share a single metadata database but store blobs locally for low-latency pulls.

Geo-Replication — Multi-Region Image Distribution
Shared PostgreSQL + Redis Single metadata DB + shared Redis — users, repos, tags, permissions REGION: US-EAST Quay Instance quay-us-east.example.com S3 Storage (US-East) Local blob copy OCP clusters pull locally REGION: EU-WEST Quay Instance quay-eu-west.example.com S3 Storage (EU-West) Local blob copy OCP clusters pull locally REGION: AP-SOUTHEAST Quay Instance quay-ap-se.example.com S3 Storage (AP-SE) Local blob copy OCP clusters pull locally replication replication Single namespace: quay.example.com/org/repo:tag — works from any region Push to any region → blobs replicate to all regions automatically → pulls served from nearest storage

Quota Management & Auto-Pruning

Control storage consumption per organization and repository to prevent unbounded growth.

What's New in Recent Quay Releases

VersionHighlights
3.13–3.14Enhanced auto-pruning: tag-pattern (regex) filters and multiple policies per org/repo.
3.15Image pull statistics (FEATURE_IMAGE_PULL_STATS) — see how often and when tags are pulled. Superuser panel in the v2 UI.
3.16v2 React-based UI becomes the default UI.

Day-2 Operations

Upgrading, monitoring, and maintaining your OpenShift cluster over its lifetime.

Declarative day-2 ops: Many of the operations on this page (MachineConfig rollouts, Compliance Operator policies, upgrade channel changes) are commonly managed the same way as application manifests — committed to Git and applied via ArgoCD. See the GitOps & Pipelines section for the ArgoCD reconciliation model.

Cluster Upgrade Flow

OpenShift Over-the-Air Upgrade Process
Update Channel stable / fast / eus CVO Pulls Release Cincinnati graph Update Operators Sequential rollout MCO Rolls Nodes 1 at a time, reboot ✓ Complete All ops ready MCO NODE UPDATE SEQUENCE Cordon Drain Apply Config Reboot Uncordon ✓ CORE MONITORING + OPTIONAL OBSERVABILITY Prometheus Alertmanager Console Dashboards Loki (optional) UWM (optional)

Control Plane Only Updates (formerly EUS-to-EUS)

Extended Update Support (EUS) releases are selected even-numbered minor versions with an extended support phase. A Control Plane Only update minimizes compute-node reboots between consecutive EUS releases by pausing all non-control-plane MachineConfigPools while the cluster completes two explicit minor-version updates: 4.y → 4.y+1 → 4.y+2.

The odd-numbered release is not bypassed: the cluster must reach it successfully before starting the target EUS update. Paused compute nodes remain on their earlier machine configuration until the target control plane is healthy and compatible layered Operators have been reviewed. Unpausing pools then applies the accumulated configuration, often with one compute-node reboot.

Update Channels

ChannelContentsUse
stable-4.xThoroughly tested releases; delayed availabilityProduction clusters requiring maximum stability
fast-4.xAll GA releases as soon as they pass CINon-production or early adopter environments
candidate-4.xRelease candidates; may contain bugsPre-release testing only
eus-4.xRecommended updates and paths for an EUS targetConventional EUS updates and eligible Control Plane Only paths

Control Plane Only Update Outline: 4.20 → 4.22

# 0. Confirm 4.20 is healthy, fully updated, and offers the expected graph edges
oc get clusterversion,clusteroperators
oc get mcp
oc adm upgrade

# 1. Select the target EUS channel
oc adm upgrade channel eus-4.22

# 2. Pause EVERY non-control-plane MCP (worker and any custom pools)
oc patch mcp/worker --type merge -p '{"spec":{"paused":true}}'
# Repeat for custom pools, for example: oc patch mcp/infra ...

# 3. Update explicitly to a RECOMMENDED 4.21.z shown by "oc adm upgrade"
oc adm upgrade --to=<recommended-4.21.z>
# Wait until ClusterVersion and ClusterOperators are healthy at 4.21.z.
# Review/update installed OLM Operators before the next minor update.

# 4. Update explicitly to a RECOMMENDED 4.22.z shown by "oc adm upgrade"
oc adm upgrade --to=<recommended-4.22.z>
oc get clusterversion,clusteroperators

# 5. When the 4.22 control plane is healthy, unpause pools one at a time
oc patch mcp/worker --type merge -p '{"spec":{"paused":false}}'
oc get mcp --watch
This is an outline, not a substitute for the release-specific runbook. Use only update targets displayed by oc adm upgrade, read all conditional-update risks, back up etcd, verify Operator compatibility at both minor versions, and follow the official Control Plane Only update procedure. Do not leave pools paused: queued certificates, security fixes, and host configuration do not reach those nodes until the pools resume.

MachineConfig & MachineConfigPool

A MachineConfig (machineconfiguration.openshift.io/v1) is a declarative, Ignition-based specification for node OS configuration: files, systemd units, kernel arguments, container-runtime settings, and registry config. A MachineConfigPool (MCP) groups nodes by label (e.g. master, worker, or custom pools like infra) and the Machine Config Operator (MCO) merges all matching MachineConfigs into a single rendered MachineConfig, then rolls it out node-by-node respecting maxUnavailable.

This is the same cordon → drain → apply → reboot → uncordon sequence shown in the Cluster Upgrade Flow diagram above. Every cluster upgrade produces new MachineConfigs, and every MachineConfig change triggers the MCO rolling update.

MachineConfig Rendering & Rolling Update
MACHINECONFIGS 99-worker-chrony NTP / chrony config file 99-worker-kargs Kernel arguments 01-worker-kubelet kubelet configuration 99-worker-registries Mirror registry config MCO Machine Config Operator Rendered MC Merged final config MCP: worker maxUnavailable: 1 paused: false MCO ROLLING NODE UPDATE (maxUnavailable: 1) worker-0 UPDATING cordon → drain → reboot worker-1 WAITING queued for update worker-2 WAITING queued for update worker-3 UPDATED ✓ rendered-worker-abc MCP: infra custom pool separate rollout maxUnavailable controls how many nodes update concurrently. Default: 1. Increase for faster rollout (risk: more simultaneous capacity loss). Paused MCP: nodes do NOT update. Control Plane Only updates pause every non-control-plane pool temporarily.

Example MachineConfig (kernel argument)

MachineConfig — add kernel argument
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
  labels:
    machineconfiguration.openshift.io/role: worker
  name: 99-worker-kargs-nosmt
spec:
  kernelArguments:
    - nosmt                # disable hyper-threading
    - audit=0             # disable kernel audit (example)

Example MachineConfigPool

MachineConfigPool — custom infra pool
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfigPool
metadata:
  name: infra
spec:
  machineConfigSelector:
    matchExpressions:
      - key: machineconfiguration.openshift.io/role
        operator: In
        values: [worker, infra]
  nodeSelector:
    matchLabels:
      node-role.kubernetes.io/infra: ""
  maxUnavailable: 1
  paused: false

etcd Backup

etcd is the key-value store that holds ALL cluster state — every resource, secret, config, and status. Regular backups are critical.

etcd Backup Best Practices:
  • Backup regularly to a secure location outside the cluster
  • Run from any master node with a running etcd pod
  • Backup during non-peak hours to minimize performance impact
  • Do NOT backup before the first certificate rotation (24h after install)
# etcd backup command (run on any master node)
sudo /usr/local/bin/cluster-backup.sh /home/core/assets/backup

etcd Restore Steps

Restoring etcd is a destructive operation that rolls the cluster back to the backup state. All changes after the backup are lost.

  1. Stop etcd on all control plane nodes
  2. Stop kube-apiserver on all control plane nodes
  3. Move etcd data directory to a backup location
  4. Run the recovery script on the recovery host with the backup snapshot
  5. Restart kubelet on all control plane nodes
  6. Turn off quorum guard to allow single-member bootstrap
  7. Force etcd redeployment to rebuild the cluster from the restored member
  8. Turn quorum guard back on after all members rejoin
  9. Force new rollout for kube-apiserver, kube-controller-manager, and kube-scheduler

OADP — Application Backup & Restore

OADP (OpenShift API for Data Protection) is the Red Hat-supported operator that packages Velero with CSI snapshots and file-system backup (Kopia/restic). It backs up namespaced Kubernetes resources + persistent volume data to S3-compatible object storage (AWS S3, ODF MCG/NooBaa, MinIO).

Contrast with etcd backup: etcd captures the entire cluster control-plane state as a single snapshot. OADP provides selective, application-level backup and restore — choose specific namespaces, label selectors, or resource types. OADP is also used for namespace migration and cluster-to-cluster migration.

OADP Backup & Restore Architecture
BACKUP FLOW Backup CR velero.io/v1 Velero backup controller + CSI plugin K8s Resources Deployments, Services, CMs... PV Data CSI snapshot / Kopia Object Storage S3 / MCG (NooBaa) resources.json + PV tarballs or CSI VolumeSnapshots Schedule CR cron-based auto-backup RESTORE FLOW Object Storage backup data source Restore CR velero.io/v1 Velero restore controller Restored Namespace resources recreated + PVs restored DataProtectionApplication (DPA) oadp.openshift.io/v1alpha1 — configures Velero, plugins, and backup locations
OADP Use Cases: disaster recovery (restore namespaces after accidental deletion), namespace migration (move workloads between clusters), cluster-to-cluster migration (rebuild on new infrastructure), and scheduled backups for compliance.

DataProtectionApplication (DPA)

DPA — configure Velero with S3 backend
apiVersion: oadp.openshift.io/v1alpha1
kind: DataProtectionApplication
metadata:
  name: velero-dpa
  namespace: openshift-adp
spec:
  configuration:
    velero:
      defaultPlugins:
        - openshift          # OpenShift-specific resource handling
        - aws                # S3-compatible object storage
        - csi                # CSI VolumeSnapshot integration
    nodeAgent:
      enable: true
      uploaderType: kopia # file-system backup (replaces restic)
  backupLocations:
    - velero:
        provider: aws
        default: true
        objectStorage:
          bucket: oadp-backups
          prefix: velero
        config:
          region: us-east-1
          s3ForcePathStyle: "true"   # required for MCG/MinIO
          s3Url: https://s3.openshift-storage.svc
        credential:
          name: cloud-credentials
          key: cloud

Backup & Restore CRs

Backup — back up a namespace
apiVersion: velero.io/v1
kind: Backup
metadata:
  name: myapp-backup
  namespace: openshift-adp
spec:
  includedNamespaces:
    - myapp-prod
  storageLocation: velero-dpa-1
  ttl: 720h                   # retain for 30 days
  defaultVolumesToFsBackup: false  # use CSI snapshots by default
Restore — restore from backup
apiVersion: velero.io/v1
kind: Restore
metadata:
  name: myapp-restore
  namespace: openshift-adp
spec:
  backupName: myapp-backup
  includedNamespaces:
    - myapp-prod
  restorePVs: true
OADP does not back up cluster-scoped resources by default. CRDs, ClusterRoles, and StorageClasses must be explicitly included or recreated separately. Always test restore in a non-production namespace first.

Essential Day-2 Commands

# Check cluster version and upgrade status
oc get clusterversion
oc adm upgrade

# Check all cluster operators
oc get co

# Check node status and drain
oc get nodes
oc adm drain <node> --ignore-daemonsets --delete-emptydir-data

# Check certificate expiry
oc -n openshift-kube-apiserver-operator get secret kube-apiserver-to-kubelet-signer -o jsonpath='{.metadata.annotations.auth\.openshift\.io/certificate-not-after}'

# etcd backup
oc debug node/<master-node> -- chroot /host /usr/local/bin/cluster-backup.sh /home/core/backup

must-gather & Insights

oc adm must-gather collects cluster-wide diagnostic data — logs, resource definitions, events, and operator status — into a local tarball for Red Hat support case attachment. It supports targeted collection via --image for specific components (ODF, networking, OADP, etc.). For individual resources, oc adm inspect exports a single resource or namespace. At the node level, sosreport (via oc debug node) collects OS-level diagnostics.

The Insights Operator runs continuously, sending anonymized cluster telemetry to console.redhat.com. It surfaces proactive risk recommendations, security advisories, and upgrade readiness checks directly in the OpenShift web console. This data is part of the broader Telemetry system and can be disabled in disconnected environments.

Diagnostic Tools Comparison

ToolWhat It CollectsWhen to Use
oc adm must-gatherCluster-wide: operator logs, CRDs, events, node status, image info. Extensible via --image for ODF, SR-IOV, Logging, etc.Opening a Red Hat support case; broad cluster diagnostics
oc adm inspectSingle resource or namespace: YAML definitions, events, related objectsQuick inspection of a specific resource (e.g. a failing ClusterOperator)
sosreport (via debug node)OS-level: system logs, hardware info, kernel config, network state, storageNode-level issues: kernel panics, storage failures, network driver problems
Insights OperatorAnonymized telemetry: cluster version, operator health, configuration risks, CVE exposureProactive — runs automatically; check console.redhat.com for recommendations

Common Diagnostic Commands

# Collect full cluster diagnostics (default must-gather)
oc adm must-gather

# Targeted must-gather for ODF
oc adm must-gather --image=registry.redhat.io/odf4/ocs-must-gather-rhel9:latest

# Targeted must-gather for networking
oc adm must-gather --image=registry.redhat.io/openshift4/network-tools-rhel8:latest

# Inspect a specific ClusterOperator
oc adm inspect clusteroperator/kube-apiserver

# Inspect an entire namespace
oc adm inspect ns/openshift-monitoring

# Node-level sosreport via debug pod
oc debug node/<node-name>
chroot /host
sosreport --batch --tmp-dir /host/var/tmp

# Check Insights status
oc get clusteroperator insights
oc get insightsoperator cluster -o yaml
must-gather output may contain secrets. The tarball can include ConfigMaps, environment variables, and certificate details. Review before sharing outside your organization. Use --dest-dir to control output location.

Compliance Operator

The Compliance Operator enables automated compliance scanning and remediation against security benchmarks (CIS, NIST, PCI-DSS).

Compliance Operator Workflow
TailoredProfile Custom benchmark rules ScanSettingBinding Profile + scan schedule ComplianceSuite Scan execution CheckResults PASS / FAIL / MANUAL per rule per node Remediation Auto-apply fixes ComplianceRemediation creates MachineConfig changes — nodes may reboot. Review before applying in production.

Network Observability

Network Observability Operator (GA at 4.12): Uses eBPF agents on every node to capture network flow data without packet mirroring. Provides network topology visualization, traffic flow analysis, DNS tracking, and dropped packet detection directly in the OpenShift Console. No application changes required.

OpenShift Virtualization

Layered Operator and entitled capability for running virtual machines alongside containers on the same cluster.

KubeVirt Architecture

OpenShift Virtualization is Red Hat's productized distribution of the upstream KubeVirt project. It extends the Kubernetes API with VM-shaped CRDs and wraps QEMU/KVM inside ordinary pods, so a VM is scheduled, monitored, and governed exactly like any other workload.

KubeVirt Control Plane & Per-Node Components
CLUSTER CONTROL PLANE virt-api Aggregated API + admission Validates VM/VMI CRDs Kubernetes API Server VM / VMI objects in etcd virt-controller Cluster-wide, watches VM/VMI Creates virt-launcher Pod spec admission watch + create Pod WORKER NODE (virt-handler runs as a DaemonSet on every node) virt-handler DaemonSet — 1 per node Talks to libvirt/QEMU locally Reports VMI phase to API Drives live migration on-node Configures pod networking virt-launcher Pod 1 pod = 1 running VM qemu-kvm Guest OS guest OS runs inside qemu-kvm process scheduled like any other pod virt-launcher Pod 1 pod = 1 running VM qemu-kvm Guest OS guest OS runs inside qemu-kvm process scheduled like any other pod Key Concept: VM = Pod Same scheduler & node affinity Same ResourceQuota / LimitRange Same RBAC / SCC / NetworkPolicy Same PVCs for VM disks oc get pods shows virt-launcher-*
virtctl: The virtctl CLI plugin (installed alongside the operator) wraps common VM operations — virtctl start/stop/restart, virtctl console (serial console), virtctl vnc, and virtctl migrate. Under the hood it manipulates the same VirtualMachine/VMI CRDs.
VirtualMachine — Example YAML (Fedora VM with DataVolume)
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: fedora-vm
  namespace: my-vms
spec:
  running: true
  template:
    metadata:
      labels:
        kubevirt.io/domain: fedora-vm
    spec:
      domain:
        cpu:
          cores: 2
        resources:
          requests:
            memory: 4Gi
        devices:
          disks:
            - name: rootdisk
              disk:
                bus: virtio
            - name: cloudinitdisk
              disk:
                bus: virtio
          interfaces:
            - name: default
              masquerade: {}          # default pod network binding
      networks:
        - name: default
          pod: {}
      volumes:
        - name: rootdisk
          dataVolume:
            name: fedora-vm-rootdisk
        - name: cloudinitdisk
          cloudInitNoCloud:
            userData: |
              #cloud-config
              user: fedora
              password: changeme
              chpasswd: { expire: False }
  dataVolumeTemplates:
    - metadata:
        name: fedora-vm-rootdisk
      spec:
        source:
          registry:
            url: "docker://quay.io/containerdisks/fedora:39"
        storage:
          accessModes: [ReadWriteOnce]
          resources:
            requests:
              storage: 30Gi
VirtualMachineInstance — Auto-Generated Runtime Object

You rarely author a VirtualMachineInstance (VMI) directly — virt-controller creates one automatically from the VM's template whenever spec.running becomes true, the same way a Deployment creates ReplicaSets/Pods. It disappears when the VM stops.

# oc get vmi fedora-vm -n my-vms -o yaml (excerpt)
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
  name: fedora-vm
  ownerReferences:
    - kind: VirtualMachine
      name: fedora-vm
status:
  phase: Running
  nodeName: worker-1
  interfaces:
    - ipAddress: 10.128.2.55
      interfaceName: eth0

Live Migration

Live migration moves a running VM from one node to another with (typically) sub-second downtime — no reboot, no dropped connections. virt-handler on the source and destination nodes coordinate the copy while virt-controller tracks overall progress via a VirtualMachineInstanceMigration object.

Live Migration — Pre-Copy Memory Transfer
Node: worker-1 (source) virt-launcher (fedora-vm) VM state: Running → Migrating Streams memory pages to target Node: worker-2 (destination) virt-launcher (fedora-vm) — new Receives memory pages Takes over on final cutover 1. iterative pre-copy of memory (VM keeps running) 2. brief pause → copy final dirty pages → resume on destination Shared RWX Storage (PVC) Both nodes mount the same VM disk — disk data is not copied
RWX storage is required. Live migration needs the VM's disk(s) to use a ReadWriteMany (RWX) StorageClass so both the source and destination nodes can mount it simultaneously — e.g. ODF's ocs-storagecluster-ceph-rbd-virtualization. With RWO-only storage, the VM can still be evicted and restarted elsewhere, but that causes downtime (evictionStrategy: None vs LiveMigrate).
# Trigger a live migration
virtctl migrate fedora-vm -n my-vms

# ...or apply the CR directly
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstanceMigration
metadata:
  name: migrate-fedora-vm
  namespace: my-vms
spec:
  vmiName: fedora-vm

VM Networking & Storage

VMs reuse the same cluster networking and storage stack as containers — there is no separate virtualization network or datastore to manage.

Networking — Interface Bindings

BindingNetworkUse Case
masqueradeDefault pod network (NAT)Simplest option — outbound access via the pod network. Default for most VMs.
bridgeMultus secondary network (L2)VM needs its own MAC/IP directly on the physical LAN — e.g. legacy apps expecting a routable VM IP.
SR-IOVMultus + SR-IOV Network OperatorNear line-rate NIC passthrough for high packet-rate or latency-sensitive VM workloads.

See the Networking & Routes section for the full Multus CNI plugin comparison (macvlan, bridge, SR-IOV, IPVLAN).

Storage — DataVolume & CDI

The Containerized Data Importer (CDI) populates a PVC with a VM disk image before the VM starts, tracked by a DataVolume CRD.

DataVolume Import Flow (CDI)
Image Source http / registry / S3 / clone CDI Importer Pod Streams & converts image to disk Tracked by DataVolume status PVC (VM disk) Attached to VM as rootdisk
Source TypeExample
registryContainer disk image, e.g. quay.io/containerdisks/fedora:39
httpqcow2/raw image fetched from a URL
pvc (clone)Clone an existing PVC — fast way to fan out a golden image
uploadvirtctl image-upload streams a local file into a new DataVolume
DataVolume — Import a VM Disk via CDI
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: rhel9-import
  namespace: my-vms
spec:
  source:
    http:
      url: "https://example.com/images/rhel9.qcow2"
  storage:
    accessModes: [ReadWriteMany]     # RWX needed for live migration
    resources:
      requests:
        storage: 40Gi
    storageClassName: ocs-storagecluster-ceph-rbd-virtualization

See the Storage & CSI Drivers section for StorageClass and CSI driver fundamentals.

Migration Toolkit for Virtualization (MTV)

MTV imports VMs from an existing hypervisor (primarily VMware vSphere, also oVirt/RHV and OpenStack) into OpenShift Virtualization. It connects to the source as a Provider, and a Plan groups the VMs to migrate and the target namespace/storage/network mappings; running the Plan creates a Migration CR that CDI executes disk-by-disk.

Cold Migration
Source VM is powered off, disks are copied in full, then the VM boots on OpenShift. Simple and reliable; incurs downtime for the copy duration.
Warm Migration
Disks are pre-copied while the source VM keeps running; only a short final cutover (delta sync + power-off/on) causes downtime. Preferred for production VMs.

GitOps & Pipelines

Layered Operators for declarative delivery with OpenShift GitOps (Argo CD) and CI automation with OpenShift Pipelines (Tekton).

OpenShift GitOps (ArgoCD)

OpenShift GitOps deploys ArgoCD as an operator-managed instance (namespace openshift-gitops). Git becomes the single source of truth: ArgoCD continuously compares the manifests in a repo against the live state of the cluster and reconciles any difference.

ArgoCD — Repo to Cluster Reconciliation Loop
Git Repository Manifests / Kustomize / Helm Desired state (source of truth) poll / webhook ArgoCD Application Compares desired (Git) vs live (cluster) Sync StatusSynced / OutOfSync Health StatusHealthy / Degraded AppProjectAllowed repos / destinations / RBAC apply OpenShift Cluster Resources Deployments, Services, Routes, ConfigMaps… Live state namespace scoped via destination.namespace Drift Detection & Self-Heal A manual oc edit / oc delete on the cluster is detected as OutOfSync. With syncPolicy.automated.selfHeal: true, ArgoCD reverts it back to match Git automatically.
App-of-Apps & ApplicationSet: A single root Application can manage a set of child Application objects (app-of-apps), and ApplicationSet template-generates one Application per cluster/environment from a generator (list, cluster, Git directory, etc). ACM builds on ApplicationSet + Placement to drive multi-cluster delivery from a hub — see the ACM section for the full push/pull architecture.
ArgoCD Application — Example YAML
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: openshift-gitops
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/my-app-manifests.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  syncPolicy:
    automated:
      prune: true          # delete resources removed from Git
      selfHeal: true       # revert manual cluster drift
    syncOptions:
      - CreateNamespace=true

OpenShift Pipelines (Tekton)

OpenShift Pipelines is a Kubernetes-native CI engine built on Tekton. Every pipeline step runs as a container in a pod — there is no separate build server to manage.

Tekton — Webhook Trigger to Pipeline Execution
TRIGGER FLOW git push webhook EventListener TriggerBinding + TriggerTemplate createsPipelineRun PIPELINE EXECUTION (DAG of Tasks) Pipeline: build-and-deploy fetch-sourceTask: git-clone build-imageTask: buildah run-testsTask: unit-test deployTask: openshift-client (oc apply)
Each Task = 1 TaskRun = 1 pod. Steps within a Task share a pod (and its filesystem); Tasks share data across pods via a mounted Workspace (PVC). Use oc get pipelinerun / oc get taskrun to watch execution, or the web console's Pipelines pages for the visual DAG.
Simple Tekton Pipeline — Example YAML
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-and-deploy
spec:
  params:
    - name: git-url
  workspaces:
    - name: shared-workspace
  tasks:
    - name: fetch-source
      taskRef: { name: git-clone }
      workspaces: [{ name: output, workspace: shared-workspace }]
      params: [{ name: url, value: $(params.git-url) }]
    - name: build-image
      runAfter: [fetch-source]
      taskRef: { name: buildah }
      workspaces: [{ name: source, workspace: shared-workspace }]
    - name: deploy
      runAfter: [build-image]
      taskRef: { name: openshift-client }
      params: [{ name: SCRIPT, value: "oc rollout restart deploy/my-app" }]
---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: build-and-deploy-
spec:
  pipelineRef: { name: build-and-deploy }
  params:
    - name: git-url
      value: https://github.com/my-org/my-app.git
  workspaces:
    - name: shared-workspace
      volumeClaimTemplate:
        spec:
          accessModes: [ReadWriteOnce]
          resources: { requests: { storage: 1Gi } }

Helm vs Operator vs GitOps — When to Use Which

ApproachWhat It IsBest For
HelmTemplated YAML packaged as a chart; installed/upgraded imperatively (helm install/upgrade) or via ArgoCD as a source type.Parameterized app packaging & reuse across environments. No ongoing reconciliation by itself.
Operator (OLM)A controller + CRDs that encode operational knowledge for one application (upgrades, backups, failover). See Operators & OLM.Stateful, complex apps that need day-2 lifecycle automation beyond "apply YAML" — databases, message queues, the platform itself.
GitOps (ArgoCD)Continuous reconciliation of a Git repo's declared state against the live cluster, with drift detection and self-heal.The delivery mechanism for everything above — deploying Helm charts, Operator subscriptions, or plain manifests, across any number of clusters, with a full audit trail.
They compose, not compete. A typical production pipeline uses all three together: Tekton builds the image, Helm packages the app, an ArgoCD Application deploys it (as a Helm source), and Operators manage any stateful dependencies the app needs. GitOps is the delivery layer that ties Helm charts, Operator subscriptions, and raw manifests together under one reconciliation loop.

⚠️ Unofficial Document — This content is not affiliated with, endorsed by, or officially associated with Red Hat, Inc. or IBM. It is created for educational and reference purposes only. OpenShift, Red Hat, and related trademarks are the property of Red Hat, Inc.