OpenShift Guide · Introduction
📚 Complete Learning Path

Master OpenShift
From Zero to Pro

A complete hands-on guide covering every major concept in Red Hat OpenShift 4.x — from containers and pods to advanced CI/CD, autoscaling, and cluster administration.

17
Chapters
100+
Examples
4.21
Version
0→Pro
Journey
🚀 What is OpenShift?
Understanding Red Hat's enterprise Kubernetes platform and why it matters

The Big Picture

Red Hat OpenShift is an enterprise-grade Kubernetes distribution — think of it as Kubernetes with a comprehensive set of tools layered on top: a developer console, automated builds, integrated monitoring, advanced security, and a software lifecycle management system called the Operator Lifecycle Manager.

While vanilla Kubernetes gives you the engine, OpenShift gives you the fully assembled vehicle. It is built on top of Kubernetes and adds everything an enterprise team needs to run production workloads: security policies, image management, CI/CD pipelines, monitoring dashboards, and a curated catalog of middleware.

📌 Current VersionOpenShift 4.21 (released early 2026) ships with Kubernetes 1.34 and CRI-O 1.34. The 4.x line moved to a Kubernetes-native architecture — Cluster Operators manage every component of the cluster itself.

OpenShift vs Kubernetes

FeatureVanilla KubernetesOpenShift
InstallationDIY (kubeadm, kops…)Installer-Provisioned (IPI) or User-Provisioned (UPI)
Web ConsoleBasic dashboardFull-featured developer & admin console
Image BuildsNot includedBuilds, S2I, BuildConfig built-in
RegistryNot includedIntegrated internal registry
CI/CDNot includedTekton Pipelines + Argo CD (GitOps)
MonitoringBring your ownPrometheus + Alertmanager built-in
SecurityRBAC onlyRBAC + SCCs + OPA Gatekeeper
NetworkingBring your own CNIOVN-Kubernetes (only CNI since 4.17)
OperatorsManual installOperator Lifecycle Manager (OLM)
UpdatesManualOver-the-air cluster updates via CVO

Flavours of OpenShift

🏠
OpenShift Container Platform (OCP)
Self-managed on bare-metal, VMware, AWS, Azure, GCP. Full control. Enterprise support.
☁️
ROSA (AWS)
Red Hat OpenShift Service on AWS — managed control plane, billed through AWS marketplace.
🔷
ARO (Azure)
Azure Red Hat OpenShift — jointly managed by Microsoft and Red Hat.
🌩️
OpenShift Dedicated
Red Hat-managed single-tenant clusters on AWS or GCP. SRE team operates the cluster.
🧪
OKD
The upstream community distribution. Free to use. OpenShift is OKD + Red Hat support + RHEL CoreOS.
💻
OpenShift Local (CRC)
Single-node cluster running in a VM on your laptop. Perfect for learning and development.

Core Concepts at a Glance

OpenShift uses all standard Kubernetes objects (Pod, Deployment, Service, ConfigMap…) and adds its own extensions:

Project (≈ Namespace) Route (HTTP/S exposure) BuildConfig ImageStream Operator SCC (Security Context Constraint) ClusterOperator OLM CatalogSource

🧠 Quick Check — What is OpenShift?

A) A replacement for Docker that runs containers without Kubernetes
B) An enterprise Kubernetes distribution with built-in CI/CD, security, and lifecycle management
C) A cloud-only service that only runs on AWS
D) A container image format developed by Red Hat
📦 Containers & Images
How containers work, OCI images, and OpenShift's internal registry

What is a Container?

A container is a process running in isolation using Linux kernel features: namespaces (isolate PID, network, mount, UTS, IPC) and cgroups (limit CPU, memory, I/O). Unlike a VM, a container shares the host kernel — it is not a full operating system, just an isolated process tree with its own filesystem view.

💡 Key InsightContainers are not magic — they are ordinary Linux processes that can only see their own slice of the system. docker run and podman run just call the kernel APIs (clone, unshare) to set up those boundaries.

Container Images

An image is an immutable, layered filesystem in OCI (Open Container Initiative) format. Each instruction in a Dockerfile/Containerfile creates a new layer. Layers are content-addressed (SHA-256) and shared across images — if two images use the same base, they share those layers on disk.

# Containerfile (OCI-standard name; Dockerfile also works)
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
WORKDIR /app
COPY target/myapp.jar .
RUN microdnf install -y java-17-openjdk-headless && microdnf clean all
EXPOSE 8080
USER 1001          # Non-root — required by OpenShift SCCs
CMD ["java", "-jar", "myapp.jar"]

OCI vs Docker Image Format

OpenShift uses the OCI Image Specification everywhere. Docker images are also OCI-compatible. The image is stored as a manifest (JSON) pointing to a config blob and a set of layer tarballs. Any OCI-compliant runtime (CRI-O, containerd, podman) can run these images.

CRI-O: OpenShift's Container Runtime

Since OpenShift 4.0, the only supported container runtime is CRI-O. Docker is not used inside OpenShift. CRI-O was purpose-built for Kubernetes — it implements the CRI (Container Runtime Interface) and uses runc / crun to actually create containers. It is lightweight and has no daemon managing images independently of Kubernetes.

⚠️ No Docker SocketOpenShift nodes do not have a Docker socket at /var/run/docker.sock. Builds that rely on Docker-in-Docker (DinD) will not work. Use Buildah, Kaniko, or OpenShift Builds instead.

OpenShift Internal Registry

Every OpenShift cluster includes an integrated image registry (Image Registry Operator). Images built by OpenShift Builds are pushed here automatically. External CI pipelines can also push images to it.

# Expose the registry (if not already exposed)
oc patch configs.imageregistry.operator.openshift.io cluster \
  --type merge --patch '{"spec":{"defaultRoute":true}}'

# Get the registry hostname
oc get route default-route -n openshift-image-registry

# Log in to the internal registry using your OpenShift token
podman login -u $(oc whoami) -p $(oc whoami -t) \
  default-route-openshift-image-registry.apps.mycluster.example.com

Image Pull Policy

Always
Always pull the image from the registry, even if it exists locally. Use this for :latest tags in dev.
IfNotPresent
Pull only if the image is not already on the node. Default for versioned tags (e.g. :1.2.3).
Never
Never pull — fail if the image isn't already on the node. Used in air-gapped environments.
🚫 Don't use :latest in productionThe :latest tag is mutable — it can point to a different image after every push. Use immutable digests (sha256:abc123…) or ImageStreams (which pin a tag to a digest) for reproducible deployments.
⚡ Pods & Lifecycle
The smallest deployable unit in Kubernetes and how OpenShift manages it

What is a Pod?

A Pod is a group of one or more containers that share: the same network namespace (they talk via localhost), the same UTS namespace (same hostname), and optionally the same volumes. Containers within a Pod are always co-scheduled on the same node and start/stop together.

In practice, most Pods have a single container. Multi-container Pods are used for sidecar patterns (log shipper, service mesh proxy) and init containers (setup tasks before the main app starts).

# minimal Pod manifest
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  containers:
  - name: app
    image: quay.io/myorg/my-app:1.2.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"      # 250 millicores = 0.25 CPU
      limits:
        memory: "256Mi"
        cpu: "500m"
    livenessProbe:
      httpGet:
        path: /health/live
        port: 8080
      initialDelaySeconds: 15
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080

Pod Lifecycle Phases

Pending
Pod accepted by the API server but not yet scheduled, or images are being pulled.
▶️
Running
Pod is bound to a node. At least one container is running or starting.
Succeeded
All containers exited with code 0. Final state for Job Pods.
Failed
All containers terminated; at least one exited with non-zero code or was OOMKilled.
Unknown
Pod state cannot be obtained — usually means the node went offline.

Probes

Liveness probe — If this fails repeatedly, kubelet restarts the container. Use for detecting deadlocks.

Readiness probe — If this fails, the Pod is removed from Service endpoints (traffic stops). Use for checking if the app is ready to serve requests.

Startup probe — Disables liveness/readiness probes until it succeeds. Critical for slow-starting apps (JVM apps, database migrations).

Init Containers

Init containers run sequentially before the main containers start. They must exit with code 0 for the Pod to proceed. Use cases: wait for a database to be ready, fetch secrets from Vault, pre-populate a shared volume.

initContainers:
- name: wait-for-db
  image: busybox:1.36
  command: ['sh', '-c',
    'until nc -z postgres-svc 5432; do echo waiting; sleep 2; done']

Resource Requests vs Limits

📌 Requests vs LimitsRequest — the amount of CPU/memory the scheduler guarantees. The node must have this much free to place the Pod.
Limit — the maximum the container can use. CPU is throttled at the limit; exceeding memory limit causes OOMKill.

Always set both requests and limits. Without requests, the scheduler places Pods blindly. Without limits, a runaway container can starve other workloads on the node.

🏗️ Architecture Deep Dive
Control plane, worker nodes, Cluster Operators, and RHCOS

Control Plane Components

📡
kube-apiserver
The front door. All kubectl/oc commands hit this REST API. Validates and persists objects to etcd.
🗃️
etcd
Distributed key-value store. Single source of truth for all cluster state. Runs as a 3 or 5-node cluster for HA.
🧠
kube-scheduler
Watches for unscheduled Pods and assigns them to nodes based on resources, affinity, taints, and policies.
🔁
kube-controller-manager
Runs control loops: ReplicaSet controller, Endpoint controller, Node controller, and dozens more.
🔴
openshift-apiserver
Extends the Kubernetes API with OpenShift-specific resources: Routes, Builds, ImageStreams, Projects.
⚙️
Cluster Version Operator (CVO)
Manages over-the-air cluster updates. Reconciles ClusterOperator resources and applies upgrade payloads.

Worker Node Components

🤝
kubelet
Agent on every node. Watches the API for Pods assigned to this node and manages their lifecycle via CRI-O.
🌐
kube-proxy
Programs iptables/nftables rules for Service virtual IPs. In OVN-K mode this is handled by OVN directly.
🔒
CRI-O
Container runtime. Pulls images, creates containers via runc/crun. No persistent daemon state.
🕸️
OVN-Kubernetes
CNI plugin. Provides Pod networking, Network Policies, and Service load-balancing using Open Virtual Network.

RHCOS — Red Hat CoreOS

All OpenShift 4.x nodes run RHCOS (Red Hat Enterprise Linux CoreOS). RHCOS is immutable and managed by the Machine Config Operator (MCO). You never SSH in to install software — instead you create a MachineConfig CR and MCO applies it via an in-place update (drains the node, applies the change, reboots).

⚠️ Don't patch nodes manuallyAny manual changes to an RHCOS node will be overwritten the next time MCO applies a MachineConfig. All node-level config must go through MachineConfig CRs.

Cluster Operators

OpenShift manages itself through Cluster Operators (COs) — each CO is responsible for one component of the cluster (authentication, DNS, image registry, ingress, monitoring, etc.). The CVO reconciles all COs, and each CO keeps its component in the desired state.

# List all Cluster Operators and their status
oc get clusteroperators

# Example output:
# NAME                      VERSION   AVAILABLE   PROGRESSING   DEGRADED
# authentication            4.21.0    True        False         False
# dns                       4.21.0    True        False         False
# image-registry            4.21.0    True        False         False
# ingress                   4.21.0    True        False         False
# monitoring                4.21.0    True        False         False

Projects vs Namespaces

An OpenShift Project is a Kubernetes Namespace with extra metadata and access controls. When you create a Project, OpenShift also creates the corresponding Namespace, sets up RBAC roles for the project admin, and applies any project template defined by the cluster admin. In all API calls and YAML, they are interchangeable — a Project IS a Namespace.

💻 oc CLI & Web Console
Day-to-day tools for interacting with OpenShift clusters

Installing the oc CLI

# Download from the cluster's Downloads page (always matches your cluster version)
# Or via the OpenShift mirror:
curl -L https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz \
  | tar xz
sudo mv oc kubectl /usr/local/bin/

# Verify
oc version
# Client Version: 4.21.x   Server Version: 4.21.x   Kubernetes Version: v1.34.x

Logging In

# Login with username/password
oc login https://api.mycluster.example.com:6443 \
  -u admin -p mypassword

# Login via web console token (copy from: user menu → Copy login command)
oc login --token=sha256~AbCdEf... --server=https://api.mycluster.example.com:6443

# Check who you are
oc whoami
oc whoami -t     # print token

Essential Daily Commands

# Projects
oc new-project my-app           # create & switch to project
oc project my-app               # switch to existing project
oc get projects                  # list all projects you can see

# Resources
oc get pods                      # list pods in current project
oc get pods -A                   # all namespaces
oc get pods -o wide              # show node and IP
oc describe pod my-pod           # full details + events
oc logs my-pod                   # container logs
oc logs -f my-pod               # follow (stream) logs
oc logs -c sidecar my-pod       # specific container

# Debug
oc exec -it my-pod -- /bin/sh   # shell into container
oc debug pod/my-pod              # debug copy of pod
oc debug node/worker-1           # shell on a node (as root)

# Apply / Delete
oc apply -f deployment.yaml      # create or update
oc delete -f deployment.yaml     # delete by manifest
oc delete pod my-pod             # delete specific resource

# Events (great for debugging)
oc get events --sort-by=.lastTimestamp

OpenShift Web Console

The web console is available at https://console-openshift-console.apps.<cluster-domain>. It has two perspectives:

👨‍💻 Developer Perspective

  • Topology view (visual app graph)
  • +Add wizard (Git, Dockerfile, Helm)
  • Build logs and pipeline runs
  • Observe: metrics for your project

🔧 Administrator Perspective

  • Cluster-wide resource management
  • Node and machine management
  • OperatorHub catalog
  • Cluster monitoring dashboards
  • User and RBAC management
💡 oc vs kubectlThe oc CLI is a superset of kubectl. Every kubectl command works as oc. The oc CLI adds OpenShift-specific commands: oc new-project, oc new-app, oc start-build, oc rollout, oc debug node/, etc.
🔄 Deployments & Services
Managing stateless apps, rolling updates, and network service discovery

Deployment

A Deployment is the standard Kubernetes way to manage stateless application replicas. It creates a ReplicaSet that ensures the desired number of identical Pods are always running. When you update the image or config, Deployment performs a rolling update — gradually replacing old Pods with new ones with zero downtime.

🚫 DeploymentConfig is deprecatedThe OpenShift-specific DeploymentConfig was deprecated in 4.14 and will be removed in a future version. Always use the standard Kubernetes Deployment object.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1         # 1 extra Pod during rollout
      maxUnavailable: 0   # no downtime
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: quay.io/myorg/my-app:1.2.0
        ports:
        - containerPort: 8080

Rollout Commands

# Trigger a rollout (e.g. after a config change)
oc rollout restart deployment/my-app

# Watch rollout progress
oc rollout status deployment/my-app

# Rollback to previous version
oc rollout undo deployment/my-app

# See rollout history
oc rollout history deployment/my-app

Services

A Service gives a stable IP (ClusterIP) and DNS name to a dynamic set of Pods selected by labels. Traffic to the Service is load-balanced across healthy Pods by kube-proxy / OVN-K. Services are internal to the cluster — to expose them externally, use a Route or Ingress.

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  selector:
    app: my-app       # matches Pod labels
  ports:
  - name: http
    port: 80           # Service port (DNS: my-app-svc.my-ns.svc.cluster.local:80)
    targetPort: 8080  # container port

Service Types

TypeAccessible FromUse Case
ClusterIP (default)Inside cluster onlyService-to-service communication
NodePortAny node IP + portDev/test; not recommended in production
LoadBalancerExternal via cloud LBCloud deployments without Ingress
ExternalNameN/A — DNS aliasAlias an external DNS name inside cluster

Routes (OpenShift Ingress)

A Route is OpenShift's way to expose a Service over HTTP/HTTPS via the cluster's Ingress (HAProxy-based). Each Route gets a hostname like my-app-myproject.apps.mycluster.example.com.

# Expose a service as a Route (auto-generates hostname)
oc expose svc/my-app-svc

# Create a TLS-terminated route with a custom hostname
oc create route edge my-app-tls \
  --service=my-app-svc \
  --hostname=my-app.example.com \
  --cert=tls.crt --key=tls.key
⚙️ Config, Secrets & Storage
Managing application configuration, sensitive data, and persistent storage

ConfigMaps

A ConfigMap stores non-sensitive key-value configuration. It can be mounted as a volume (each key becomes a file) or injected as environment variables into a Pod.

# Create from literals
oc create configmap app-config \
  --from-literal=DB_HOST=postgres \
  --from-literal=DB_PORT=5432

# Create from a file
oc create configmap app-config --from-file=application.properties

# Use in Deployment as environment variables
envFrom:
- configMapRef:
    name: app-config

# Or mount as files
volumes:
- name: config-vol
  configMap:
    name: app-config
volumeMounts:
- name: config-vol
  mountPath: /etc/config

Secrets

Secrets store sensitive data (passwords, tokens, certificates). Values are base64-encoded at rest in etcd (and encrypted if EncryptionConfiguration is set). Treat Secrets like ConfigMaps in terms of usage, but follow least-privilege access — only the Pods that need them should be able to read them.

# Create a generic secret
oc create secret generic db-creds \
  --from-literal=username=admin \
  --from-literal=password=s3cr3t

# Create a TLS secret from cert files
oc create secret tls my-tls \
  --cert=tls.crt --key=tls.key

# Create an image pull secret for a private registry
oc create secret docker-registry quay-pull \
  --docker-server=quay.io \
  --docker-username=myuser \
  --docker-password=mytoken

Persistent Storage

Containers are ephemeral — their filesystem is lost when the Pod is deleted. For stateful data, use PersistentVolumes (PV) and PersistentVolumeClaims (PVC).

# PVC: request storage from the cluster
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-data
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 10Gi
  storageClassName: ocs-storagecluster-ceph-rbd

# Mount the PVC in a Pod
volumes:
- name: data
  persistentVolumeClaim:
    claimName: my-data
volumeMounts:
- name: data
  mountPath: /var/lib/data

Access Modes

ModeAbbreviationMeaning
ReadWriteOnceRWOMounted read-write by a single node. Most block storage.
ReadOnlyManyROXMounted read-only by many nodes simultaneously.
ReadWriteManyRWXMounted read-write by many nodes. Requires NFS or CephFS.
ReadWriteOncePodRWOPMounted read-write by exactly one Pod (Kubernetes 1.22+).

StorageClasses

A StorageClass defines how storage is dynamically provisioned. When a PVC references a StorageClass, the cluster automatically creates a PV using the class's provisioner. Common classes in OpenShift clusters running OpenShift Data Foundation (ODF/Ceph):

ocs-storagecluster-ceph-rbd (RWO, block) ocs-storagecluster-cephfs (RWX, file) gp3-csi (AWS EBS, RWO) managed-premium (Azure Disk, RWO)
🔨 Builds, S2I & ImageStreams
OpenShift-native CI: building container images directly in the cluster

Why Build in the Cluster?

OpenShift can build container images inside the cluster using the Build subsystem. This is useful when you don't have an external CI pipeline yet, or when you want a tight integration between source code and deployment. Builds run as Pods, so they are isolated, auditable, and consume cluster resources.

Build Strategies

🐍
Source-to-Image (S2I)
Detects language (Python, Node, Java…) and injects your source into a builder image. No Dockerfile needed.
🐳
Docker / Containerfile
Builds from a Dockerfile or Containerfile in your repo. Full control over the build process.
📦
Custom Build
Your own builder image. Complete flexibility for advanced use cases.
🔗
Pipeline Build (Tekton)
Triggers a Tekton Pipeline from a BuildConfig. Preferred for complex CI workflows.

Source-to-Image (S2I) in Practice

S2I is OpenShift's most developer-friendly build strategy. Point it at a Git repo and it figures out the runtime, injects your code, runs your build tool (mvn, npm, pip), and produces a ready-to-run image. No Dockerfile required.

# Create app from Git using S2I (auto-detects Node.js)
oc new-app nodejs~https://github.com/myorg/my-node-app \
  --name=my-app

# The above creates: BuildConfig, ImageStream, Deployment, Service

# Trigger a new build manually
oc start-build my-app

# Follow build logs
oc logs -f bc/my-app

# List builds
oc get builds

BuildConfig

A BuildConfig is the blueprint for how OpenShift builds an image. It defines the source (Git URL, branch), the strategy (S2I, Docker), the output (which ImageStream tag to push to), and optional build triggers (webhook, ImageChange).

apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  name: my-app
spec:
  source:
    type: Git
    git:
      uri: https://github.com/myorg/my-app.git
      ref: main
  strategy:
    type: Source
    sourceStrategy:
      from:
        kind: ImageStreamTag
        name: nodejs:18-ubi9
        namespace: openshift
  output:
    to:
      kind: ImageStreamTag
      name: my-app:latest
  triggers:
  - type: GitHub
    github:
      secret: my-webhook-secret
  - type: ImageChange   # rebuild when builder image updates

ImageStreams

An ImageStream is a virtual repository of container image references. Instead of hardcoding image digests in your Deployment, you reference an ImageStream tag. When a new image is pushed to that tag, OpenShift can automatically trigger a new rollout. This decouples the image lifecycle from the deployment spec.

📌 ImageStream tags pin to digestWhen you push my-app:latest to an ImageStream, OpenShift records the exact SHA-256 digest behind that tag. Your Deployment always runs the precise image that was current at deploy time — even if someone pushes a new :latest later.
🌐 Networking & Routes
OVN-Kubernetes, Services, Routes, Ingress, and Network Policies

OVN-Kubernetes CNI

Since OpenShift 4.17, OVN-Kubernetes is the only supported CNI plugin (OpenShift SDN was removed). OVN-K uses Open Virtual Network to create a software-defined network overlay. It provides: Pod-to-Pod communication across nodes, Service load-balancing via OVN, Network Policy enforcement (including Admin Network Policy), and EgressIP.

⚠️ SDN removed in 4.17If you are migrating a cluster from 4.16 or earlier that used OpenShift SDN, you must migrate to OVN-K before upgrading past 4.16. The migration is in-place but requires a node drain cycle.

DNS in OpenShift

Every Service gets an automatic DNS record managed by CoreDNS running in the openshift-dns namespace. The full form is: <service>.<namespace>.svc.cluster.local. Within the same namespace you can use just the Service name (postgres). Cross-namespace calls need the full form (postgres.data-ns.svc.cluster.local).

Routes — Three TLS Termination Modes

ModeTLS terminated atBackend trafficUse when
EdgeIngress router (HAProxy)HTTP (plain)Simple HTTPS, cert managed by cluster
PassthroughApplication PodTLS end-to-endApp handles its own TLS (mutual TLS, custom cipher)
Re-encryptIngress routerTLS (new cert)Compliance requires E2E encryption with different certs
# Edge route (TLS at router, HTTP to pod)
oc create route edge --service=my-app --hostname=app.example.com

# Passthrough route (TLS goes all the way to the pod)
oc create route passthrough --service=my-app --hostname=secure.example.com

# Route with custom cert (edge)
oc create route edge my-tls-route \
  --service=my-app \
  --cert=tls.crt \
  --key=tls.key \
  --ca-cert=ca.crt \
  --hostname=app.example.com

Network Policies

By default in OpenShift, all Pods in a namespace can communicate with each other, but are isolated from other namespaces. You can restrict this further with NetworkPolicy objects.

# Allow only traffic from pods in same namespace with label app=frontend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-only
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080

EgressIP

OVN-Kubernetes supports EgressIP — assigning a specific stable outbound IP to all traffic from a namespace (or set of Pods). This is essential for firewalls and external services that allowlist by IP.

# Label a namespace for EgressIP
oc label namespace my-ns k8s.ovn.org/egress-assignable=""

# Create the EgressIP object
apiVersion: k8s.ovn.org/v1
kind: EgressIP
metadata:
  name: prod-egress
spec:
  egressIPs: ["10.0.100.50"]
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: my-ns
🔒 Security, RBAC & SCCs
Role-Based Access Control, Security Context Constraints, and pod security

RBAC Fundamentals

OpenShift uses standard Kubernetes RBAC to control who can do what to which resources. The four key objects are: Role, ClusterRole, RoleBinding, and ClusterRoleBinding.

Role / RoleBinding

  • Scoped to a single namespace/project
  • RoleBinding links a Role to users/groups/SAs
  • Example: give dev team edit access in dev-ns

ClusterRole / ClusterRoleBinding

  • Cluster-wide scope
  • Can also be bound per-namespace via RoleBinding
  • Example: cluster-admin, view, edit built-in roles
# Give user Alice edit access in my-project
oc adm policy add-role-to-user edit alice -n my-project

# Give group dev-team view access cluster-wide
oc adm policy add-cluster-role-to-group view dev-team

# Create a custom Role
oc create role pod-reader \
  --verb=get,list,watch \
  --resource=pods -n my-project

# Bind the custom Role to a ServiceAccount
oc create rolebinding pod-reader-binding \
  --role=pod-reader \
  --serviceaccount=my-project:my-sa -n my-project

Security Context Constraints (SCCs)

SCCs are OpenShift's powerful admission control mechanism for Pod security. They define what a Pod is allowed to do on the node: run as root, use host networking, mount host paths, use certain Linux capabilities, etc. Every Pod must be admitted by at least one SCC that the Pod's ServiceAccount has access to.

🚫 Don't use anyuid in productionThe anyuid SCC allows a container to run as any UID including root. Grant it only to trusted workloads that genuinely require it. Prefer nonroot or a custom SCC.
🔐
restricted-v2
Default. Non-root UID, no privilege escalation, no host access. Most workloads should use this.
🔓
nonroot
Any non-root UID. Allows the container to choose its own UID as long as it's not 0.
⚠️
anyuid
Any UID including root. Required for legacy images that hard-code UID 0.
🖥️
privileged
Full access to host resources. Only for trusted infrastructure components (CNI, storage drivers).
# Grant a ServiceAccount the anyuid SCC (use sparingly)
oc adm policy add-scc-to-user anyuid \
  -z my-service-account -n my-project

# Check which SCC a Pod actually ran under
oc get pod my-pod -o jsonpath='{.metadata.annotations.openshift\.io/scc}'

# See which SCCs a ServiceAccount can use
oc adm policy who-can use scc anyuid

Service Accounts

Every Pod runs as a ServiceAccount (SA). If you don't specify one, it uses the default SA of the namespace. Best practice: create a dedicated SA per application and grant it only the permissions it needs (principle of least privilege).

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

# Use it in a Deployment
spec:
  serviceAccountName: my-app-sa
🤖 Operators & OLM
Extending Kubernetes with custom controllers and the Operator Lifecycle Manager

What is an Operator?

An Operator is a Kubernetes controller that encodes human operational knowledge about a specific application. Instead of a human following a runbook to install, upgrade, and manage PostgreSQL, an Operator automates all of that by watching Custom Resources (CRs) and reconciling the cluster state to match the desired state defined in those CRs.

Operators use the Operator SDK (Go, Ansible, or Helm) to scaffold the controller code. The controller implements a reconcile loop: watch CRs → compare desired vs actual → take action.

Key Concepts

📜
CRD (CustomResourceDefinition)
Extends the Kubernetes API with new resource types, e.g. Kafka, PostgresCluster, Elasticsearch.
📋
CR (Custom Resource)
An instance of a CRD. E.g. a PostgresCluster CR describes a specific database cluster you want.
🔄
Controller / Operator
Watches CRs and reconciles cluster state. Creates/updates Deployments, Services, Secrets as needed.
📦
CSV (ClusterServiceVersion)
OLM's packaging unit. Describes the Operator's CRDs, RBAC, install strategy, and upgrade paths.

Operator Lifecycle Manager (OLM)

OLM manages the installation, upgrade, and lifecycle of Operators on OpenShift. It provides a curated catalog (OperatorHub in the web console) with hundreds of certified and community Operators. OLM handles dependency resolution and upgrade approval workflows.

# List available operators in the catalog
oc get packagemanifests -n openshift-marketplace

# Create a Subscription to install an Operator
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: my-operator
  namespace: openshift-operators
spec:
  channel: stable
  name: my-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  installPlanApproval: Automatic   # or Manual for gated upgrades

# Check Operator installation status
oc get csv -n openshift-operators
oc get installplan -n openshift-operators

Using an Operator: Crunchy Postgres Example

# After installing the Crunchy Postgres Operator, create a cluster CR:
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
  name: my-pg
spec:
  postgresVersion: 16
  instances:
  - name: instance1
    replicas: 2
    dataVolumeClaimSpec:
      accessModes: [ReadWriteOnce]
      resources:
        requests:
          storage: 20Gi
  backups:
    pgbackrest:
      repos:
      - name: repo1
        volume:
          volumeClaimSpec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi
💡 Operator Maturity LevelsOLM defines 5 maturity levels: Basic Install → Seamless Upgrades → Full Lifecycle → Deep Insights → Auto Pilot. Check an Operator's maturity before adopting it in production. Red Hat Certified operators are verified to work on OpenShift.
📊 Monitoring & Logging
Built-in Prometheus stack, Alertmanager, Loki, and observability best practices

Built-in Monitoring Stack

OpenShift ships a fully managed monitoring stack in the openshift-monitoring namespace. It cannot be deleted or disabled — it monitors the cluster's own components. You can also enable a separate stack for user workloads.

📈
Prometheus
Scrapes metrics from cluster components every 30s. Long-term storage via Thanos Querier.
🔔
Alertmanager
Routes alerts to PagerDuty, Slack, email. Deduplicates and groups alerts.
🔍
Thanos Querier
Provides a unified query API across platform and user workload metrics.
📊
Console Observe
Built-in dashboards in the web console. Replaces the stand-alone Grafana (removed in 4.11).

Enabling User Workload Monitoring

# Enable user workload monitoring (one-time cluster-admin task)
oc apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: cluster-monitoring-config
  namespace: openshift-monitoring
data:
  config.yaml: |
    enableUserWorkload: true
EOF

Exposing Custom Metrics (ServiceMonitor)

# Tell Prometheus to scrape your app's /metrics endpoint
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  labels:
    app: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  endpoints:
  - port: http
    path: /metrics
    interval: 30s

Creating Alerts (PrometheusRule)

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: my-app-alerts
spec:
  groups:
  - name: my-app.rules
    rules:
    - alert: HighErrorRate
      expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
      for: 2m
      labels:
        severity: critical
      annotations:
        summary: "High error rate on {{ $labels.instance }}"

Logging with Loki + Vector

OpenShift Logging 6.0+ uses Loki (log aggregation store) and Vector (log collector/forwarder), replacing the older EFK (Elasticsearch + Fluentd + Kibana) stack. Install via the Red Hat OpenShift Logging Operator from OperatorHub.

📌 EFK is goneThe EFK stack (Elasticsearch, Fluentd, Kibana) was removed in OpenShift Logging 6.0. Migrate to the Loki stack or forward logs to an external SIEM. The ClusterLogging and ClusterLogForwarder CRDs are still used but reference the new components.
# Forward all application logs to an external Elasticsearch
apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder
metadata:
  name: instance
spec:
  outputs:
  - name: elastic-out
    type: elasticsearch
    url: https://my-elastic.example.com:9200
  pipelines:
  - name: app-logs
    inputRefs: [application]
    outputRefs: [elastic-out]
🔁 CI/CD: Pipelines & GitOps
Tekton Pipelines, OpenShift GitOps (Argo CD), and end-to-end delivery

Tekton Pipelines

OpenShift Pipelines (based on Tekton) is a cloud-native CI/CD framework. Unlike Jenkins, Tekton runs each pipeline step as a separate Pod — fully isolated, scalable, and Kubernetes-native. There is no persistent CI server to manage.

🔧
Task
A unit of work. Contains one or more Steps (containers). E.g. "run unit tests", "build image".
📋
Pipeline
A directed graph of Tasks. Tasks can run sequentially or in parallel.
▶️
PipelineRun
An execution instance of a Pipeline. Provides parameters and workspace bindings.
🗂️
Workspace
Shared storage between Tasks in a Pipeline run. Backed by a PVC or ConfigMap.
📦
ClusterTask / Tekton Hub
Reusable community Tasks: git-clone, buildah, kubectl-deploy, s2i-*, trivy-scan…
# Trigger a PipelineRun with the tkn CLI
tkn pipeline start build-deploy \
  --param git-url=https://github.com/myorg/my-app \
  --param image=quay.io/myorg/my-app:$(git rev-parse --short HEAD) \
  --workspace name=source,claimName=pipeline-pvc \
  -n my-project --showlog

# Watch pipeline runs
tkn pipelinerun list -n my-project
tkn pipelinerun logs -f build-deploy-run-xyz

A Simple Pipeline Example

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-deploy
spec:
  params:
  - name: git-url
  - name: image
  workspaces:
  - name: source
  tasks:
  - name: clone
    taskRef:
      name: git-clone
      kind: ClusterTask
    params:
    - name: url
      value: $(params.git-url)
    workspaces:
    - name: output
      workspace: source
  - name: build
    runAfter: [clone]
    taskRef:
      name: buildah
      kind: ClusterTask
    params:
    - name: IMAGE
      value: $(params.image)
    workspaces:
    - name: source
      workspace: source

OpenShift GitOps (Argo CD)

OpenShift GitOps installs Argo CD as an Operator. With GitOps, your cluster state is defined in a Git repository. Argo CD continuously watches the repo and syncs any drift — ensuring that what's in Git is what's running in the cluster. This gives you: audit trail, easy rollback, pull-based deployments, and environment promotion.

# Create an Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: openshift-gitops
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-config
    targetRevision: main
    path: apps/my-app/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
📈 Autoscaling & Scheduling
HPA, VPA, KEDA, cluster autoscaler, node affinity, and taints/tolerations

Horizontal Pod Autoscaler (HPA)

HPA automatically adjusts the number of Pod replicas based on observed metrics (CPU, memory, or custom). It queries the metrics API every 15 seconds and scales up/down within the defined min/max bounds.

# HPA targeting 70% CPU utilization
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
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

KEDA (Event-Driven Autoscaling)

KEDA (Kubernetes Event-Driven Autoscaling) extends HPA with scalers for 60+ event sources: Kafka lag, RabbitMQ queue depth, Prometheus metrics, Redis list length, AWS SQS, etc. KEDA can scale deployments to zero replicas (no traffic = 0 Pods). Install via OperatorHub.

# Scale based on Kafka consumer lag
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: kafka-consumer-scaler
spec:
  scaleTargetRef:
    name: kafka-consumer
  minReplicaCount: 0    # scale to zero!
  maxReplicaCount: 20
  triggers:
  - type: kafka
    metadata:
      bootstrapServers: kafka:9092
      topic: my-topic
      lagThreshold: "50"

Node Affinity

Node affinity lets you constrain which nodes a Pod can be scheduled on, based on node labels. Use requiredDuringScheduling for hard constraints and preferredDuringScheduling for soft preferences.

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: node-role.kubernetes.io/infra
          operator: Exists

Taints & Tolerations

A taint on a node repels Pods unless they have a matching toleration. Use taints to dedicate nodes to specific workloads (GPU nodes, infra nodes, high-memory nodes).

# Add a taint to a node
oc adm taint node worker-gpu gpu=true:NoSchedule

# Pod that tolerates the taint
tolerations:
- key: gpu
  operator: Equal
  value: "true"
  effect: NoSchedule

Cluster Autoscaler

The Cluster Autoscaler (installed via the Machine API) automatically adds or removes worker nodes when Pods are pending due to insufficient resources, or when nodes are consistently underutilized. Works alongside MachineSet to provision actual cloud VMs.

💡 Scale to zeroWith KEDA + Cluster Autoscaler, you can achieve true scale-to-zero: no work = no Pods = no nodes. This is especially cost-effective for batch and event-driven workloads in cloud environments.
🕸️ Service Mesh & Serverless
OpenShift Service Mesh 3.x (Sail Operator), Istio, and OpenShift Serverless (Knative)

OpenShift Service Mesh 3.x

OpenShift Service Mesh 3.x (OSSM 3) is a major rewrite. It uses the Sail Operator to install and manage upstream Istio directly, replacing the old Maistra fork (OSSM 2.x). OSSM 3 brings OpenShift closer to upstream Istio — many concepts and APIs are now identical to vanilla Istio.

🚫 OSSM 2.x is end-of-lifeOSSM 2.x (Maistra) is deprecated. If you are running OSSM 2.x, plan a migration to OSSM 3.x. The CRD API names changed — ServiceMeshControlPlane is replaced by Istio CR in OSSM 3.

Core Service Mesh Concepts

💉
Sidecar Proxy (Envoy)
Injected into every Pod. Intercepts all traffic in/out. Handles mTLS, retries, circuit breaking, telemetry.
🗺️
VirtualService
Defines traffic routing rules: canary split (90/10), header-based routing, fault injection.
🚪
DestinationRule
Defines load balancing policy, connection pool settings, and outlier detection (circuit breaking) per service.
🌉
Gateway
Configures inbound traffic at the mesh boundary. Replaces standard Ingress for mesh-aware traffic.
🔐
PeerAuthentication
Enforces mTLS between services. Set to STRICT to require mutual TLS for all in-mesh traffic.
📊
Kiali
Service mesh observability UI. Visualizes service topology, traffic flow, and health at a glance.
# VirtualService: 90% stable, 10% canary
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts: [my-app]
  http:
  - route:
    - destination:
        host: my-app
        subset: stable
      weight: 90
    - destination:
        host: my-app
        subset: canary
      weight: 10

OpenShift Serverless (Knative)

OpenShift Serverless is built on Knative. It provides: Knative Serving (auto-scaling HTTP workloads to zero), and Knative Eventing (event-driven architecture with CloudEvents, Brokers, and Triggers).

# Deploy a Knative Service (scales to zero automatically)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hello-serverless
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/minScale: "0"
        autoscaling.knative.dev/maxScale: "10"
    spec:
      containers:
      - image: quay.io/myorg/hello:latest
        env:
        - name: TARGET
          value: World
🛠️ Cluster Administration
Updates, etcd backup, node management, resource quotas, and LimitRanges

Cluster Updates (CVO)

OpenShift updates are fully automated via the Cluster Version Operator. Updates are delivered as payloads from the Red Hat update graph. You can update from the web console (Administration → Cluster Settings) or via CLI. OpenShift supports minor version hops following the update graph — you can't skip versions arbitrarily.

# Check available update channels
oc get clusterversion

# List available updates
oc adm upgrade

# Trigger update to a specific version
oc adm upgrade --to=4.21.3

# Monitor update progress
oc get clusterversion -w
oc get clusteroperators     # all should be Available=True when done

etcd Backup

Always back up etcd before a cluster update or major change. The backup captures all cluster state. In a disaster, you can restore from an etcd backup to recover the cluster to a known good state.

# SSH to a master node and run the backup script (as root)
oc debug node/master-0

# Inside the debug pod:
chroot /host
/usr/local/bin/cluster-backup.sh /home/core/backup

# The backup creates:
# /home/core/backup/snapshot_TIMESTAMP.db  (etcd snapshot)
# /home/core/backup/static-pod-resources/  (static pod manifests)

ResourceQuotas

ResourceQuotas limit the total resources a namespace can consume — total CPU requests, memory limits, number of Pods, PVCs, Services, etc. Enforced by the admission controller.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: project-quota
spec:
  hard:
    pods: "20"
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    persistentvolumeclaims: "5"
    services: "10"

LimitRanges

LimitRanges set default and maximum resource requests/limits per container or Pod in a namespace. They prevent Pods without resource specs from being scheduled without any limits — which would violate quota.

apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
spec:
  limits:
  - type: Container
    default:                   # applied if not set
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 100m
      memory: 128Mi
    max:                       # hard ceiling per container
      cpu: 2
      memory: 2Gi

Node Management

# List nodes with role labels
oc get nodes -L node-role.kubernetes.io/worker

# Drain a node (evict pods) for maintenance
oc adm drain worker-1 --ignore-daemonsets --delete-emptydir-data

# Mark node as unschedulable (no new pods)
oc adm cordon worker-1

# Return node to service
oc adm uncordon worker-1

# Force-delete a stuck pod
oc delete pod stuck-pod --grace-period=0 --force
📋 Commands Cheat Sheet
The most important oc / kubectl commands for day-to-day OpenShift work

Authentication & Context

oc login <API_URL> -u <user> -p <pass>   # Login
oc logout                                    # Logout
oc whoami                                    # Current user
oc whoami --show-server                      # API URL
oc config get-contexts                       # List kubeconfig contexts
oc config use-context <name>                 # Switch context

Projects & Namespaces

oc new-project <name>                        # Create project
oc project <name>                            # Switch project
oc projects                                  # List accessible projects
oc delete project <name>                     # Delete project + all resources

Getting Resources

oc get <resource>                            # List in current namespace
oc get <resource> -A                         # All namespaces
oc get <resource> -o yaml                    # Full YAML output
oc get <resource> -o json                    # JSON output
oc get <resource> -o wide                    # Extra columns
oc get <resource> --watch                    # Live updates
oc get <resource> -l app=my-app              # Filter by label
oc describe <resource> <name>               # Detailed info + events
oc explain pod.spec.containers               # Schema help

Pods & Logs

oc logs <pod>                               # Container logs
oc logs -f <pod>                            # Follow / stream
oc logs --previous <pod>                    # Previous (crashed) container
oc logs -c <container> <pod>               # Specific container in pod
oc exec -it <pod> -- bash                  # Shell into container
oc cp <pod>:/remote/path ./local/path        # Copy files from pod
oc port-forward <pod> 8080:8080             # Local port forwarding
oc debug pod/<pod>                          # Debug clone of pod
oc debug node/<node>                        # Root shell on node

Deployments

oc rollout status deployment/<name>         # Watch rollout progress
oc rollout history deployment/<name>        # Rollout history
oc rollout undo deployment/<name>           # Rollback
oc rollout restart deployment/<name>        # Force restart all pods
oc scale deployment/<name> --replicas=5    # Manual scale
oc set image deployment/<name> app=img:tag  # Update image
oc set env deployment/<name> KEY=VALUE      # Set env var

Builds

oc start-build <bc-name>                    # Trigger a build
oc start-build <bc-name> --follow          # Trigger and tail logs
oc cancel-build <build-name>               # Cancel running build
oc get builds                               # List builds
oc logs build/<build-name>                  # Build logs

RBAC & Security

oc adm policy add-role-to-user edit <user>  # Grant edit role
oc adm policy add-scc-to-user anyuid -z <sa> # Grant SCC to SA
oc auth can-i create pods                    # Check own permission
oc auth can-i create pods --as=<user>       # Check another user's permission
oc get rolebindings -o wide                  # List role bindings

Cluster Administration

oc get nodes                                # List nodes
oc get clusteroperators                     # Health of cluster operators
oc get clusterversion                       # Cluster version + update status
oc adm upgrade                              # Check / trigger updates
oc adm drain <node> --ignore-daemonsets    # Drain node for maintenance
oc adm cordon <node>                        # Mark unschedulable
oc adm uncordon <node>                      # Return to service
oc get events -A --sort-by=.lastTimestamp   # Recent cluster events
oc top nodes                                # Node resource usage
oc top pods -A                              # Pod resource usage (all ns)
🎉 You've completed the guide!You now have a comprehensive understanding of OpenShift from containers and pods all the way through advanced topics like Operators, GitOps, Service Mesh, and cluster administration. Keep practicing with oc explain to explore any resource's schema, and refer back to this cheat sheet whenever you need it.