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.
OpenShift vs Kubernetes
| Feature | Vanilla Kubernetes | OpenShift |
|---|---|---|
| Installation | DIY (kubeadm, kops…) | Installer-Provisioned (IPI) or User-Provisioned (UPI) |
| Web Console | Basic dashboard | Full-featured developer & admin console |
| Image Builds | Not included | Builds, S2I, BuildConfig built-in |
| Registry | Not included | Integrated internal registry |
| CI/CD | Not included | Tekton Pipelines + Argo CD (GitOps) |
| Monitoring | Bring your own | Prometheus + Alertmanager built-in |
| Security | RBAC only | RBAC + SCCs + OPA Gatekeeper |
| Networking | Bring your own CNI | OVN-Kubernetes (only CNI since 4.17) |
| Operators | Manual install | Operator Lifecycle Manager (OLM) |
| Updates | Manual | Over-the-air cluster updates via CVO |
Flavours of OpenShift
Core Concepts at a Glance
OpenShift uses all standard Kubernetes objects (Pod, Deployment, Service, ConfigMap…) and adds its own extensions:
🧠 Quick Check — What is OpenShift?
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.
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.
/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
:latest tags in dev.:1.2.3).: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.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
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
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.
Control Plane Components
Worker Node Components
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).
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.
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 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.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.
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
| Type | Accessible From | Use Case |
|---|---|---|
| ClusterIP (default) | Inside cluster only | Service-to-service communication |
| NodePort | Any node IP + port | Dev/test; not recommended in production |
| LoadBalancer | External via cloud LB | Cloud deployments without Ingress |
| ExternalName | N/A — DNS alias | Alias 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
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
| Mode | Abbreviation | Meaning |
|---|---|---|
| ReadWriteOnce | RWO | Mounted read-write by a single node. Most block storage. |
| ReadOnlyMany | ROX | Mounted read-only by many nodes simultaneously. |
| ReadWriteMany | RWX | Mounted read-write by many nodes. Requires NFS or CephFS. |
| ReadWriteOncePod | RWOP | Mounted 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):
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) 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.
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.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.
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
| Mode | TLS terminated at | Backend traffic | Use when |
|---|---|---|---|
| Edge | Ingress router (HAProxy) | HTTP (plain) | Simple HTTPS, cert managed by cluster |
| Passthrough | Application Pod | TLS end-to-end | App handles its own TLS (mutual TLS, custom cipher) |
| Re-encrypt | Ingress router | TLS (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
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.
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.# 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
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
Kafka, PostgresCluster, Elasticsearch.PostgresCluster CR describes a specific database cluster you want.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
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.
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.
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]
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.
# 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
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.
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.
ServiceMeshControlPlane is replaced by Istio CR in OSSM 3.Core Service Mesh Concepts
# 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 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
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)
oc explain to explore any resource's schema, and refer back to this cheat sheet whenever you need it.