Table of Contents
Key Characteristics of Cloud-Native Applications
Cloud-native applications are designed specifically to run on modern platforms like Kubernetes and OpenShift. Rather than “lifting and shifting” traditional applications into containers, cloud-native apps embrace patterns and practices that make them:
- Scalable – easy to grow or shrink based on demand
- Resilient – able to tolerate failures and recover automatically
- Observable – easy to monitor, debug, and understand
- Automatable – easy to deploy, update, and roll back using pipelines and Git-based workflows
- Portable – not tightly coupled to a specific machine or environment
These applications are typically:
- Built as small, loosely coupled services (microservices or service-oriented)
- Packaged as containers
- Managed by an orchestrator (like Kubernetes/OpenShift)
- Integrating with platform services (databases, message queues, identity, storage, etc.)
From “Traditional” to Cloud-Native
Traditional applications were often:
- Monolithic – one large deployable unit, all features bundled together
- Stateful in-process – holding important state directly in memory or on local disk
- Tightly coupled to servers – depending on specific machines, IPs, or OS layouts
- Manually managed – deployments and configuration changes done by hand
Cloud-native applications, by contrast, are:
- Distributed – multiple services communicating over the network
- Stateless at the compute layer – user sessions and state moved to external stores
- Location-agnostic – they do not care which node or IP they run on
- Designed for automation – suitable for continuous delivery and automatic scaling
You don’t need to fully redesign every system to be cloud-native, but understanding these principles helps you decide how to structure new services or modernize existing ones.
Core Design Principles
1. Stateless vs Stateful Components
Cloud-native workloads try to separate compute from state:
- Stateless components
- Process a request using only the data that comes with the request or from external services
- Can be safely killed, rescheduled, or scaled without losing important data
- Examples: frontend web servers, API gateways, simple REST services
- Stateful components
- Manage persistent data (databases, caches, message brokers)
- Require stable storage and often more careful scaling patterns
In a cloud-native design:
- Put business logic into stateless services (running in containers)
- Put data into managed backing services (databases, object stores, queues)
- Avoid storing critical data on the container’s local filesystem or memory only
This separation lets platforms like OpenShift freely restart and reschedule stateless pods while keeping data safe elsewhere.
2. Loose Coupling and Service Boundaries
Cloud-native applications are made up of independently deployable services that communicate over well-defined APIs:
- Each service has a clear responsibility (e.g., “orders”, “payments”, “notifications”)
- Services interact via network calls (HTTP/REST, gRPC, messaging)
- Changes in one service should not require changing or redeploying others
Loose coupling enables:
- Independent scaling of hot services
- Independent releases and rollbacks
- Teams to work on different services in parallel
In practice, this means:
- Design API contracts carefully
- Avoid sharing databases directly between services (favor APIs or events)
- Hide internal implementation details behind service boundaries
3. Resilience and Fault Tolerance
In a distributed, dynamic environment, failures are normal: nodes die, network connections drop, pods are rescheduled. Cloud-native applications are built with this in mind.
Common resilience patterns include:
- Retries with backoff – retry failed calls but with delays and limits
- Timeouts – never wait forever for a response
- Circuit breakers – stop calling a failing service for a time to let it recover
- Bulkheads – isolate resource usage so one failure does not cascade
Applications should:
- Be able to tolerate pod restarts at any time
- Use idempotent operations when possible (repeating the same request doesn’t cause harm)
- Handle partial outages gracefully (degrade functionality instead of complete failure)
Platforms like OpenShift provide self-healing for pods, but the application must still be written to behave predictably during failures.
4. Scalability and Elasticity
Cloud-native applications are designed to scale horizontally:
- Instead of making one instance bigger (vertical scaling), you run more instances
- All instances should be equivalent and able to handle any request (for stateless services)
To support this:
- Avoid relying on local state that is not shared across instances
- Ensure requests can be load-balanced across multiple replicas
- Remove or reduce bottlenecks like single-threaded components or shared locks
Horizontal scaling works well when:
- Work can be divided into independent units (e.g., different HTTP requests, jobs, or messages)
- Each instance only needs what is in the request and external storage
Cloud-native design makes it easier to use Kubernetes/OpenShift autoscaling features effectively.
5. Configurability and Declarative Behavior
Cloud-native applications separate configuration from code:
- Code is built once and stored in a container image
- Configuration (URLs, credentials, feature flags, resource limits) is supplied at runtime
Common patterns:
- Use environment variables for configuration that changes per environment
- Load configuration from external sources (e.g., ConfigMaps, Secrets)
- Avoid hardcoding environment-specific values in code or images
This enables:
- The same image to run in dev, test, and production with different configuration
- Declarative configuration in YAML/JSON that matches how OpenShift is managed
- Easier automation and reproducible deployments
6. Observability and Telemetry
In a cluster, you often cannot “SSH into the box” to debug. Cloud-native apps must be observable from the outside:
- Logging
- Write logs to standard output (stdout/stderr)
- Include context (request IDs, user IDs, service name, version)
- Metrics
- Expose metrics about performance and health (latency, errors, throughput)
- Use consistent labels and names to make them easy to aggregate and alert on
- Tracing
- Propagate correlation IDs or trace headers through service calls
- Instrument key paths so you can see a request’s journey across services
This observability lets operations and development teams:
- Detect issues quickly (via alerts)
- Understand behavior under load
- Troubleshoot without needing direct access to running containers
Common Architectural Patterns
1. 12-Factor Influences
Many cloud-native practices are influenced by the "12-Factor App" methodology. Without going into full detail, some relevant ideas are:
- Strict separation of code vs configuration
- Treating backing services (databases, queues) as attached resources
- Binding to services via environment variables and service discovery
- Logs as event streams, not files to manage manually
Cloud-native designs often align with these principles to fit naturally into platforms like OpenShift.
2. Microservices vs Modular Monoliths
Cloud-native does not require microservices, but they are common:
- Microservices
- Many small, loosely coupled services
- Each can be deployed, scaled, and updated independently
- Modular monoliths
- One deployable unit with clear internal modules
- Can still be cloud-native if it is stateless, observable, and scalable
Key point: being cloud-native is more about how the application behaves and less about the exact number of services.
3. Event-Driven and Asynchronous Communication
Cloud-native systems often use events and messaging:
- Services publish events when things happen (e.g., “order_created”)
- Other services subscribe to events and react asynchronously
- Message queues and streaming platforms help decouple producers from consumers
Benefits:
- Reduces tight coupling between services
- Improves resilience (messages can be retried or persisted)
- Allows better handling of bursts of work
This pattern fits well into dynamic cluster environments where services scale up and down.
Practical Design Considerations for Cloud-Native Apps
When designing or refactoring applications to run on OpenShift or similar platforms:
- Assume your pod can disappear at any time
- Do not store important state in memory only
- Design for quick startup and graceful shutdown
- Design for configuration per environment
- No environment-specific values hardcoded in the image
- Treat credentials and secrets as external inputs
- Use health checks
- Expose readiness and liveness endpoints so the platform can route traffic correctly and restart unhealthy pods
- Prepare for rolling updates
- Ensure new and old versions can briefly coexist
- Avoid breaking changes in APIs or data formats during upgrades
- Think about dependencies
- What happens if your database or another service is slow or unavailable?
- Implement timeouts and fallback behavior
These considerations turn a containerized app into a cloud-native app that behaves predictably and efficiently in a platform like OpenShift.
How Cloud-Native Design Relates to Containers and OpenShift
Cloud-native applications are a natural fit for container orchestration platforms:
- Containers provide a lightweight packaging unit for each service
- Orchestrators like Kubernetes/OpenShift handle scheduling, scaling, and self-healing
- Declarative manifests describe the desired state of your app (how many replicas, which image, how to expose it)
- Platform services (storage, networking, identity, observability) provide common building blocks
Designing with cloud-native principles means you can:
- Take full advantage of OpenShift capabilities (autoscaling, rolling updates, Operators, etc.)
- Reduce manual operations overhead
- Improve reliability and responsiveness to change
This chapter provides the high-level mindset; later chapters will show how these ideas are expressed concretely in OpenShift objects and workflows.