What Kubernetes provides, mapped to problems you have today
Kubernetes is an open source platform for running containerized workloads across a cluster of machines (overview). You declare the desired state, such as "three replicas of this image, each with 512 MiB of memory," and the control plane continuously compares that declaration with what is actually running on the worker nodes. When the two diverge because a container crashed, a node disappeared, or you pushed a new image, controllers act to close the gap. This reconciliation loop is the core of the system. Every other capability is built on top of it.
The official overview lists these capabilities. Each one is worth adopting only if you can name the problem it would solve for you now.
| Capability | Pain it removes |
|---|---|
| Service discovery and load balancing | Hand-maintained host lists and load balancer configs that drift as instances change |
| Automated rollouts and rollbacks | Deploy scripts that leave a half-updated fleet when they fail midway |
| Self-healing | Pages for crashed processes that a restart would have fixed |
| Automatic bin packing | Underused VMs, because each service gets its own machine |
| Horizontal scaling | Manual capacity changes during traffic spikes |
| Secret and config management | Rebuilding images or editing servers to change a setting |
| Batch execution | Cron jobs on a single box that fail silently |
The core rule
Apply one rule to this table: any feature you would not use this quarter counts as cost, not benefit. Each capability brings concepts your team has to learn, configuration it has to review, and failure modes it has to debug. If you have no use for bin packing or autoscaling, you still carry the complexity behind them.
+---------------------------+
| Desired state (manifest) |
| "3 replicas, 512 MiB" |
+------------+--------------+
| apply
v
+----------------------------------------------------------+
| Control plane |
| +----------------------+ +----------------------+ |
| | Desired state store |----->| Controllers | |
| +----------------------+ | compare desired with | |
| | actual, then act | |
| +---+--------------^---+ |
+------------------------------------|--------------|------+
schedule, restart, replace | | observed state
v |
+----------------------------------------------------------+
| Worker nodes |
| +-------------+ +-------------+ +-------------+ |
| | Node A | | Node B | | Node C | |
| | [pod] [pod] | | [pod] [pod] | | [pod] | |
| +-------------+ +-------------+ +-------------+ |
+----------------------------------------------------------+
+----------------------------------------------------------+
| Built on this loop: discovery + load balancing, rollouts |
| and rollbacks, self-healing, bin packing, scaling, |
| secrets and config, batch jobs |
+----------------------------------------------------------+
You declare the state you want, the control plane's controllers compare it with what is running on the worker nodes and act to close any gap, and every other Kubernetes capability is built on this reconciliation loop.
Checklist: workload shape and team capacity
1. Does your workload shape call for an orchestrator?
Count the services you deploy independently, meaning each has its own release cadence and its own rollback. Then look at how often each one ships and how much its load varies over a day or a week.
Yes if you run many services that deploy on their own schedules, or if traffic swings widely enough that autoscaling and bin packing would let you run fewer idle nodes or absorb spikes that currently cause incidents.
No if you run a few services with steady traffic. A small fixed fleet behind a load balancer can be sized once and left alone, so a scheduler has little to schedule. You would pay the cluster's complexity cost without getting its scaling or packing benefits.
2. Who will own the platform?
Name a person or team for each of these: cluster version upgrades, CNI and ingress networking, RBAC policy, observability for the cluster itself, and on-call response when the platform fails rather than the application.
Yes if at least two people can handle each area, and that work is planned in their time.
No if the answer is "whoever set it up." When knowledge sits with one engineer, that engineer becomes a single point of failure. Upgrades wait for their availability, and incidents stall while you wait for them to be paged. Unowned overhead also spreads to application engineers, who end up debugging DNS resolution, expired certificates, or permission errors when they should be shipping features. Nobody planned for that work, so it never shows up in a roadmap, but it still slows the team down.
Checklist: containers, health checks and state
Kubernetes assumes a lot about your workload before it can help. Its self-healing model depends on your app telling the truth about its health, so check these first:
- Each service builds into an image that starts from configuration alone, with no manual steps on the host.
- Readiness and liveness answer different questions. Readiness means "send me traffic." Liveness means "restart me." If the liveness check calls your database, a database outage restarts every replica at once. Restarting fixes nothing, and the pods end up in
CrashLoopBackOff. - Probe timing matches startup time. A JVM that needs 40 seconds to warm up will be killed over and over by a liveness probe that starts checking after 10.
- Requests and limits come from measured usage. A container that exceeds its memory limit is OOM-killed. A node running short on memory evicts pods, and pods using more than they requested go first. A readiness check that returns 200 before caches or connections are ready lets a rollout finish while users see errors.
- State has an owner. Persistent volumes are only part of the problem. You still need tested backups, restore drills, and failover that keeps replicas consistent. That is why many teams run databases on managed services and keep the cluster stateless.
- Secrets are actually protected. By default, built-in Secrets are only base64-encoded in etcd. Before they are secure, you need encryption at rest, RBAC that limits who can read them, or an integration with an external secret store.
This work is worth doing even if you never adopt Kubernetes. Honest health checks, measured resource limits, and tested restores make a service easier to run on any platform.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api
spec:
replicas: 3
selector:
matchLabels:
app: orders-api
template:
metadata:
labels:
app: orders-api
spec:
containers:
- name: orders-api
image: registry.example.com/orders-api:1.4.2 # placeholder image
ports:
- containerPort: 8080
# Liveness and readiness are held off until this passes, so slow JVM warm-up isn't killed
startupProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 5
failureThreshold: 24 # allows up to 120s to start
# Liveness: is the process itself stuck? Never check the database here
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 10
failureThreshold: 3
# Readiness: return 200 only when caches and connections are ready for traffic
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 2
# Set from measured usage; going over the memory limit gets the container OOM-killed
resources:
requests: { cpu: 250m, memory: 512Mi }
limits: { memory: 768Mi }
A minimal Deployment that gives a slow-starting service a startup probe, keeps the liveness check separate from the readiness check, and sets resource requests and a memory limit based on measured usage.
Managed k8s is not zero-ops
A managed offering changes who carries the pager for the control plane. It does not remove the cluster from your team's workload. The provider runs etcd, the API server and the scheduler, and handles their backups, quorum and certificate rotation. Those are the parts that are hardest to recover when they fail. Everything that touches your workloads stays with you.
| Responsibility | Self-managed | Managed control plane |
|---|---|---|
| etcd backups and control-plane availability | You | Provider |
| Control-plane version upgrades | You, on your schedule | Provider, on its support window |
| Node pools, OS images, node upgrades | You | You (some automation offered) |
| Add-ons: ingress, DNS, metrics, cert management | You | Mostly you |
| Manifests, Helm charts, RBAC, network policy | You | You |
| Cost visibility per team or service | You | You |
What remains either way
Deprecations are scheduled work. Kubernetes removes beta APIs on a published cadence. A provider that ends support for older minor versions forces an upgrade, so manifests and charts must be audited before each one. Plan this as recurring work, not an incident.
The ecosystem moves fast. Ingress controllers, operators and charts each have their own release cycle and compatibility matrix. Every add-on you install is software you now patch.
Portability has limits. Your workloads use provider-specific load balancer annotations, storage classes and IAM bindings for service accounts. Moving between providers is easier than leaving Kubernetes, but you still have to rewrite and retest that layer.
Kubernetes alternatives and when each fits better
If none of that portability work appeals to you, one of these categories may fit better. Each one corresponds to a different set of checklist answers.
| Category | Better fit when your checklist shows | Main trade-off |
|---|---|---|
| PaaS-style platform | Few services, stateless HTTP apps, no dedicated platform engineer | Limited control over networking and runtime |
| Serverless container runtime | Spiky or low baseline traffic, request-driven work, short-lived processes | Cold starts, execution limits, per-request pricing |
| Simpler orchestrator | Containerized fleet of moderate size, a small ops team, some mixed workloads | Smaller ecosystem, fewer off-the-shelf integrations |
| VMs or autoscaling groups behind a load balancer, with configuration management | Stable traffic, a handful of long-running services, existing VM tooling and skills | Slower rollouts, coarser bin packing |
When to avoid Kubernetes
Avoid it if your answers show fewer services than engineers, no one with time to own cluster upgrades, or stateful components you cannot yet run outside a managed database. In those cases the scheduler, controllers and add-ons give you nothing your current problems require. You still have to patch, monitor and upgrade them.
The migration cost is usually smaller
Choosing a simpler option now may mean migrating later. That migration is usually cheaper than running a platform you do not need. The expensive parts of adopting Kubernetes are reliable images, honest health checks, externalized config and state kept out of containers, and every option above rewards that same work. If your workloads already meet those criteria, a later move consists mostly of translating deployment definitions. Running an unneeded cluster costs you engineering time every week until that move happens.
Impact on cost and engineering time: an illustrative estimate
Put rough numbers on that weekly cost before deciding. The model below compares what the platform saves against what it costs to run. Every number is assumed, so substitute your own.
Monthly net = (b × N) + (I × P) − (H × R) − C − O
- N: monthly compute spend
- b: fraction of N recovered by bin packing
- I: rollout incidents avoided per month
- P: cost per incident (engineer time plus customer impact)
- H: engineer-hours per month on cluster upkeep (upgrades, add-ons, debugging)
- R: loaded hourly engineer cost
- C: control-plane and add-on overhead (ingress, monitoring, cert management)
- O: idle headroom from overprovisioned nodes
Illustrative estimate (assumed numbers, not a benchmark)
| Input | Small team | Larger org |
|---|---|---|
| Services / deploys per week | 8 / 10 | 60 / 80 |
| N × b (bin packing at 15%) | $3,000 × 0.15 = $450 | $40,000 × 0.15 = $6,000 |
| I × P | 0.5 × $1,000 = $500 | 3 × $2,000 = $6,000 |
| H × R | 30 h × $100 = $3,000 | 80 h × $100 = $8,000 |
| C + O | $400 + $600 = $1,000 | $1,500 + $1,000 = $2,500 |
| Net per month | −$3,050 | +$1,500 |
The small team loses money for two reasons. First, bin packing can only recover a percentage of a small compute bill. Second, upkeep hours have a floor: someone still has to patch the cluster, upgrade add-ons and debug networking, whether it runs 8 services or 60. Node headroom has a floor too, because you need spare capacity to survive losing a node.
The numbers flip when b × N + I × P exceeds H × R + C + O. In practice, that happens when compute spend and deploy frequency grow faster than upkeep hours do.
Scoring the checklist and a staged adoption path
Mark each checklist item as strong yes, weak yes, or no. A strong yes means the problem exists today and costs you something you can name. A weak yes means you expect it soon. The rule of thumb below is a starting point, so adjust the thresholds to your risk tolerance:
- Any no on team capacity blocks adoption, even if every other item is yes. A cluster nobody can debug at 3 a.m. is a liability, not a platform.
- Most items at strong yes: start the staged path now.
- Mostly weak yes: revisit in a quarter and fix the prerequisites in the meantime.
| Item | Strong yes / Weak yes / No | Evidence (incident, metric, ticket) |
|---|---|---|
| Team capacity to own the cluster | ||
| Many services with independent deploys | ||
| Images build reproducibly | ||
| Health checks reflect real readiness | ||
| State lives outside the container |
Staged adoption path
- Containerize and add health checks first. Both pay off on any platform, and Kubernetes self-healing only works if your probes tell the truth.
- Standardize config and secrets handling. Move values out of images and into environment-injected config, so the migration doesn't also become a config cleanup.
- Migrate one low-risk stateless service. Pick one where a bad deploy is cheap. Run it long enough to exercise a rollout, a rollback, and a node loss.
- Define rollback criteria before touching anything stateful. Before you move a database, write down the latency, error-rate, and data-integrity thresholds that trigger a return to the old setup, and test that path.
# Stage 3: first low-risk stateless service. It relies on stages 1-2:
# an immutable image, real health endpoints, and config injected at runtime.
# Practice rollback: kubectl rollout undo deployment/catalog-api
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-api
spec:
replicas: 2 # two replicas so losing a node doesn't take the service down
selector:
matchLabels:
app: catalog-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # keep full capacity while a rollout runs
maxSurge: 1
template:
metadata:
labels:
app: catalog-api
spec:
containers:
- name: catalog-api
image: registry.example.com/catalog-api:1.4.2 # pinned tag, not :latest
ports:
- containerPort: 8080
envFrom: # stage 2: config and secrets live outside the image
- configMapRef:
name: catalog-api-config
- secretRef:
name: catalog-api-secrets
readinessProbe: # /ready checks dependencies; gates traffic during rollouts
httpGet: {path: /ready, port: 8080}
periodSeconds: 5
failureThreshold: 3
livenessProbe: # /healthz checks the process only; a failure triggers a restart
httpGet: {path: /healthz, port: 8080}
initialDelaySeconds: 10
periodSeconds: 10
A Deployment for the first stateless service to migrate, combining stage 1 (separate readiness and liveness probes), stage 2 (config and secrets injected from a ConfigMap and Secret), and a zero-downtime rolling update you can undo with kubectl rollout undo.
How OneEnv can help
Disclosure: OneEnv is the product behind this blog.
OneEnv (oneenv.io) is a workspace-based governance platform for the things services share — environment variables, database structure, API routes and validation rules. A change to a shared value is staged on a branch, reviewed once, and then every affected service explicitly approves or rejects it before it goes live, so you can see who a change touches before it ships instead of finding out in production.
Applied to do you need kubernetes, this is where it helps:
Saves time
- Every service that inherits a shared value is listed in one place, so nobody searches repos and chats to find out who is affected.
- One review per branch replaces coordinating each team separately; each service still keeps its own approve/reject.
- Reviewers see the exact diff and the list of affected services up front, so approval is a decision rather than an investigation.
Saves cost
- Fewer production incidents and rollbacks caused by a shared value, route or column changing under a consumer unannounced.
- Less duplicated configuration to maintain: one shared definition inherited by many services.
- Per-service approve/reject means a bad shared value can be refused where it would break something, before it is live.
- OneEnv has a free plan (1 workspace, unlimited services and members, full governance), so trying it costs nothing.
These are qualitative benefits, not measured results — how much you save depends on how many services share the value.
Want to try it? Start at oneenv.io.