HOST_A: Welcome to Clawd Talks. I'm Emma, and today we are going deep on Kubernetes — not the beginner stuff. We're talking about the gap that nobody warns you about. HOST_B: I'm Ryan. And Emma, that gap is enormous. I've seen teams get their first deployment running on a Friday afternoon, feel incredibly proud, and then spend the next three months firefighting things they didn't even know existed. HOST_A: There's this moment every Kubernetes engineer has — usually around 3am — where they realise that "kubectl apply works" and "running Kubernetes reliably in production" are two completely different skills. HOST_B: The tutorials stop at the easy part. They show you deployments, services, ingress, a config map, maybe a persistent volume. And then they call it a day. HOST_A: But production Kubernetes adds a whole new layer. Resource starvation. Cascading failures. Security misconfigurations that you don't discover until someone exploits them. HOST_B: Upgrade nightmares. Debugging across distributed logs when three services are simultaneously on fire. And certificate expiry — the silent killer — at the absolute worst moment. HOST_A: You'd be surprised how many production outages trace back to a TLS certificate nobody was watching. So today we're covering the advanced topics that separate Kubernetes beginners from engineers who can run it in production without fear. HOST_B: Resource management, StatefulSets, Helm, GitOps, all three flavours of autoscaling — HOST_A: The service mesh debate, where Ryan has strong opinions — HOST_B: Well-reasoned opinions. Security deep dive, observability, a debugging runbook, and a production readiness checklist. Let's get into it. HOST_A: Let's start with resource management, because this is where cascading failures begin. Every container in a pod can have resource requests and limits set. These are two different things and people conflate them constantly. HOST_B: Requests are what the Kubernetes scheduler uses for placement decisions. When you declare 500 millicores of CPU and 256 megabytes of memory as your request, the scheduler finds a node with at least that much available and places your pod there. HOST_A: It's both a scheduling hint and a reservation. The node's capacity is reduced by the request amount, whether or not the container is actively using those resources. HOST_B: Limits are the hard ceiling. CPU and memory behave differently when a container exceeds them. CPU throttling: the kernel rate-limits your process at the limit. It still runs, just slower. Memory is harsher. HOST_A: If a container exceeds its memory limit, it gets OOMKilled — the out-of-memory killer terminates the process immediately. No grace period. Container gone. HOST_B: And this distinction gives rise to the QoS classes — Quality of Service classes — which determine the eviction order when a node comes under memory pressure. Three tiers: Guaranteed, Burstable, BestEffort. HOST_A: Guaranteed is the top tier. All containers in the pod have requests exactly equal to limits, for both CPU and memory. Kubernetes treats these as sacred — they're the last to be evicted when the node is under pressure. HOST_B: Your critical services should be Guaranteed. Auth service, payment processor, anything where going down costs real money or violates an SLA. Give them equal requests and limits. HOST_A: Burstable is the middle tier — requests are less than limits. Most workloads live here. You're saying "I need at least this much, but I can burst higher if the node has spare capacity." HOST_B: It's pragmatic. You're not wasting capacity by over-reserving, but you still have protection against being the first thing evicted. HOST_A: And then there's BestEffort. No requests, no limits set at all. The scheduler places them wherever, with zero resource guarantees. These are the first pods to die the moment any memory pressure appears. HOST_B: Never, ever run production workloads as BestEffort. I genuinely cannot stress this enough. It's fine for truly throwaway batch jobs that can tolerate being killed at any moment. Not for anything users depend on. HOST_A: The mistake I see most often is teams simply not setting resource requests at all. The scheduler has no idea what the pod needs, places pods wherever there's nominal space, and one node ends up massively overloaded. HOST_B: Memory pressure builds, the OOM killer starts firing, and you get a wave of OOMKills cascading through the cluster. It looks random from the outside. It's actually entirely predictable if you look at the node scheduling. HOST_A: And the fix is so boring it's almost insulting. Just set your resource requests. That's it. HOST_B: I want to add one more nuance here: the difference between resource pressure and node pressure. If a node is in memory pressure, Kubernetes starts evicting BestEffort pods immediately. If it gets worse, it starts evicting Burstable pods, starting with the ones furthest from their requests. HOST_A: Which means even in the Burstable tier, the pods using closest to their requests are safer. It's not binary — it's a gradient. The more headroom between actual usage and request, the earlier you get evicted under pressure. HOST_B: So right-sizing your requests matters. Too low and you're evicted early. Too high and you're wasting cluster capacity by reserving resources you'll never use. VPA, which we'll get to, helps find the right number. HOST_A: There's also a practical limit-setting heuristic I use: set limits at roughly two to three times the requests for workloads with bursty behaviour — like JVM-based services that spike during GC — and closer to one-to-one for latency-sensitive services you want to protect. HOST_B: Two more tools that enforce good behaviour at the namespace level. LimitRange lets you set default resource settings — if a developer deploys without resource specifications, a sensible default gets applied automatically. HOST_A: ResourceQuota caps total consumption across an entire namespace. Your dev team can't accidentally spin up a thousand pods and eat your entire cluster budget for the quarter. HOST_B: Good guardrails. Let's talk about StatefulSets, because this is where Kubernetes gets genuinely interesting for stateful applications — and genuinely tricky. HOST_A: StatefulSets are designed for PostgreSQL, MySQL, Redis, Kafka, Elasticsearch. Anything with state. The fundamental difference from a regular Deployment: pods get stable, persistent identities. HOST_B: Pod-zero, pod-one, pod-two. Those names never change, even after a restart or reschedule to a different node. That stability is what makes database clustering possible. HOST_A: A Postgres replica needs to always know it's replica-one. A Kafka broker needs a consistent broker ID. Random names every restart would make clustering completely unworkable. HOST_B: Ordered deployment: pod-zero has to reach Ready before pod-one starts. Ordered deletion: pod-two is deleted first, then pod-one, then pod-zero. Safe, sequential scale-down. HOST_A: And each pod gets its own Persistent Volume Claim that survives the pod's death. If pod-one crashes and gets rescheduled to a different node, it reattaches to the same storage that was already provisioned for it. HOST_B: The storage model deserves a minute. Persistent Volumes are the actual storage backend — an AWS EBS volume, a GCP persistent disk, an NFS share. PVCs are the claims pods make against that storage. HOST_A: StorageClass enables dynamic provisioning. When a StatefulSet creates a new PVC, the storage provisioner automatically creates the real underlying volume. No manual volume creation required. HOST_B: StatefulSets use headless services — a service with no ClusterIP. DNS resolves directly to individual pod IPs. So postgres-zero dot postgres-service dot namespace dot svc dot cluster dot local becomes a real, stable DNS entry. HOST_A: That's what allows database cluster members to address their specific peers directly. Protocols like Raft and Galera need to contact specific, named nodes. Headless services make that possible inside Kubernetes. HOST_B: Should teams even be running databases on Kubernetes? I'm going to push back here. HOST_A: I knew this was coming. HOST_B: At small scale — one or two engineers, a handful of services — just use managed databases. RDS, Cloud SQL, ElastiCache. The operational burden of stateful workloads on Kubernetes is real and significant. HOST_A: Storage provisioner failures. PVC binding issues. Volume snapshot management. StatefulSet upgrade edge cases where the rollout gets stuck. Managed services absorb all of that complexity. HOST_B: Exactly. The only reasons to run databases on Kubernetes are cost efficiency at real scale, multi-tenancy requirements, or needing specific configuration control you can't get from a managed service. HOST_A: At scale, with something like CloudNativePG for Postgres, it absolutely works. Large organisations run it reliably. But go in with your eyes open about the operational investment required. HOST_B: One thing worth highlighting about the storage side: the volumeClaimTemplates section of a StatefulSet spec is what creates PVCs per pod. And a key gotcha: when you delete a StatefulSet, the PVCs are not automatically deleted. That's by design — Kubernetes won't destroy your data just because you removed the workload definition. HOST_A: Which can be either a safety feature or a source of confusion and cost, depending on your perspective. If you scale down a StatefulSet and then back up, the new pods reattach to the existing PVCs, which is usually what you want. HOST_B: But if you're doing a full teardown for a dev cluster, you need to explicitly clean up the PVCs. I've seen people spend real money on idle EBS volumes from StatefulSets that were deleted months ago but whose PVCs were never cleaned up. HOST_A: Good practical point. StorageClass also determines the reclaim policy — whether the underlying volume gets retained or deleted when the PVC is removed. Set it deliberately, don't leave it to defaults. HOST_B: Agreed. Don't do it naively. Let's move to the tooling layer: Helm, GitOps, and autoscaling. HOST_A: Helm is the package manager for Kubernetes. Charts are templated YAML packages. Instead of maintaining completely separate YAML files for dev, staging, and prod, you parameterise everything through a values file. HOST_B: Helm install, helm upgrade, helm rollback. It tracks release history so you can roll back to a previous version if an upgrade goes badly. Helmfile manages multiple Helm releases declaratively across environments. HOST_A: The pitfall is Helm hell. When people get creative with template logic — deeply nested conditionals, loops generating dynamic names — the templates become nearly impossible to debug. HOST_B: You can't easily see what YAML Helm is actually going to produce. Helm diff helps, but you're still reasoning about a template rendering pipeline rather than looking at your actual config. HOST_A: Which is exactly why GitOps with ArgoCD is a better operational model. Git is the single source of truth for desired cluster state. You never run kubectl apply manually in production. HOST_B: Everything goes through a pull request. Code review for cluster changes. ArgoCD watches your Git repos, detects drift between what's in the repo and what's running in the cluster, and syncs on approval or automatically. HOST_A: The benefits compound over time: full audit trail, rollback is a git revert, disaster recovery is "rebuild cluster from git history." That last one is underrated until you actually need it. HOST_B: Flux is the alternative to ArgoCD — more lightweight, more Kubernetes-native in its architecture. ArgoCD has a better UI and more features. Flux is more composable for complex multi-cluster setups. HOST_A: Progressive delivery with Argo Rollouts takes this further. Canary deployments: shift ten percent of traffic to the new version, watch error rates in Prometheus, automatically promote or roll back based on defined success criteria. HOST_B: Blue-green with instant cutover is the other option. All declared in YAML, all automated. It fundamentally changes how scary production deployments feel because the rollback path is always ready. HOST_A: The GitOps model also helps enormously with multi-environment promotion. PR changes into the dev environment config, automated tests run, a separate PR promotes to staging. Promotion to production is another PR — reviewed and approved by a human. HOST_B: And the entire history of every environment lives in git. You can answer "what was running in production at 14:37 last Tuesday" just by checking git history. That's incredibly valuable for post-incident reviews. HOST_A: Autoscaling. Three dimensions, and you often need all three working together. HOST_B: HPA — Horizontal Pod Autoscaler — scales replica count based on metrics. CPU above seventy percent average, add pods. You can also drive it from custom metrics via the Kubernetes metrics API — request rate, queue depth, whatever makes sense for your workload. HOST_A: VPA — Vertical Pod Autoscaler — adjusts the resource requests and limits of existing pods based on observed historical usage. The constraint: it can't resize a running pod. The pod restarts to pick up new settings. HOST_B: So VPA is excellent for right-sizing — finding where you're massively over-provisioning or under-provisioning — but it's not a replacement for HPA for live traffic response. HOST_A: KEDA — Kubernetes Event-Driven Autoscaling — is where modern async architectures really benefit. Scale based on external events: SQS queue depth, RabbitMQ message count, Kafka consumer lag, Prometheus query results. HOST_B: And critically: scale to zero when there's no work. Worker pods sitting idle overnight burning cloud spend is a solved problem with KEDA. Cost savings on batch and async workloads can be genuinely dramatic. HOST_A: Cluster Autoscaler at the node level adds nodes when pods can't be scheduled due to insufficient resources and removes underutilised nodes during quiet periods. HOST_B: Karpenter on AWS is the next generation here. It looks at the actual pending workloads and provisions exactly the right EC2 instance type for them. Much faster response time than Cluster Autoscaler and often cheaper because it optimises instance selection dynamically. HOST_A: One important thing about autoscaling: HPA, VPA, and KEDA all operate at the pod level. The Cluster Autoscaler and Karpenter operate at the node level. You need both layers working together for a truly elastic cluster. HOST_B: And there's an interaction to be aware of: if your HPA fires and wants to add pods, but there are no nodes with available capacity, those pods go Pending. That Pending state is what triggers the Cluster Autoscaler or Karpenter. So there's an inherent latency — usually a few minutes — before new nodes are ready. HOST_A: Which is why pre-warming capacity for predictable traffic spikes is still a valid strategy. If you know traffic doubles every weekday morning, having scheduled scaling or maintaining a slightly larger node pool baseline avoids that cold-start latency. HOST_B: And with Karpenter you can also use Spot instances aggressively for stateless workloads, with on-demand fallback. Karpenter handles the instance diversity and interruption handling much more cleanly than Cluster Autoscaler did. HOST_A: Now — the complexity traps. The things teams adopt with the best intentions and then regret. HOST_B: Service mesh. I have feelings. HOST_A: Tell me about your feelings, Ryan. HOST_B: The pitch for service mesh — Istio being the dominant example — is genuinely compelling. Automatic mutual TLS between all services with zero code changes. Traffic management: retries, circuit breakers, timeouts, all declared in YAML. Distributed tracing built in. Canary routing at the network layer. HOST_A: And it delivers on that pitch — when it's set up correctly, in the right environment, by a team that has the expertise to operate it. HOST_B: Here is the production reality with Istio. It injects an Envoy sidecar proxy into every single pod in your cluster. Real resource overhead on every workload. The control plane itself is complex and resource-hungry. HOST_A: Istio upgrades have historically been painful, with breaking changes between versions. And when something goes wrong with the mesh, debugging requires expertise in Envoy internals — a completely separate skill set. HOST_B: I've seen teams adopt Istio enthusiastically, struggle with it for six months, and then spend three months carefully ripping it out. That's nearly a year of engineering time not spent on product work. HOST_A: My counterpoint: at fifty-plus microservices, managing mTLS and retry logic consistently in every single service is genuinely untenable. You get drift. You get the one team that never implemented circuit breaking. Inconsistent timeout behaviour across your entire service graph. HOST_B: At that scale, I'll grant it pays off. But be honest about your actual scale. Most teams installing Istio have ten to twenty services. The complexity budget isn't worth it there. HOST_A: Linkerd is the pragmatic alternative — much lighter than Istio, much simpler to operate. Less feature-rich, but for most teams that's actually fine. HOST_B: The Operator pattern. CRDs plus Controllers equals Operators. The concept: encode operational knowledge about a complex system into Kubernetes native objects. Instead of a runbook for upgrading Kafka, a CRD and an operator handle the upgrade procedure for you. HOST_A: cert-manager is my favourite example of an excellent operator. Declare a Certificate object, cert-manager handles the ACME challenge with Let's Encrypt, issues the certificate, stores it as a Kubernetes Secret, and automatically rotates it before expiry. Certificate expiry problems basically disappear. HOST_B: That's genuinely transformative. How many production incidents have started with an expired certificate? cert-manager eliminates that entire category of failure. HOST_A: Prometheus Operator is another excellent one. ServiceMonitor and PrometheusRule objects instead of raw Prometheus config files. Clean, composable, version-controlled alongside your application code. Strimzi for Kafka, CloudNativePG for PostgreSQL — mature, actively maintained projects. HOST_B: And then there are the bad operators. The ones where a Kubernetes upgrade breaks them in subtle ways nobody catches for days. An edge case puts the operator into an infinite reconciliation loop, eating CPU, generating noise. HOST_A: Or the vendor hasn't cut a release in eighteen months and your open bug report has twelve upvotes and zero response. You're stuck. HOST_B: The rule: only adopt operators from projects with active, responsive maintainer communities. Check the GitHub issues before committing. Don't write your own unless you have deep operational expertise and the engineering capacity to maintain it long term. HOST_A: The multi-namespace anti-pattern. This one gives teams a false sense of security that I find genuinely dangerous. HOST_B: Creating dev, staging, and prod namespaces in the same cluster. People think they have environment isolation. They don't. HOST_A: Namespaces give you naming scope and RBAC boundaries. They do not give you resource isolation at the underlying node level. A noisy process in the dev namespace can absolutely OOMKill prod pods on the same node. HOST_B: Real environment isolation means separate clusters. With modern GitOps tooling, managing multiple clusters is not dramatically harder than managing one. The operational cost is much lower than people assume. HOST_A: Security. This is where the consequences of getting it wrong are most severe. Let's go through the layers properly. HOST_B: RBAC. Every pod runs as a ServiceAccount. If you don't specify one, it uses the default ServiceAccount for the namespace. Depending on cluster configuration, that default account can have far more permissions than you'd want. HOST_A: The worst pattern I've seen in actual production: default ServiceAccount bound to ClusterRole cluster-admin. Supply chain attack, remote code execution vulnerability in a dependency — the attacker has full administrative control of your entire cluster. HOST_B: All secrets visible. All namespaces writeable. Every workload controllable. Catastrophic. HOST_A: The right model: create a specific ServiceAccount for each application. Define a Role with only the exact permissions that application needs. A pod that reads ConfigMaps gets get and list on ConfigMaps and nothing else. Not wildcard. Not cluster-admin. HOST_B: RoleBinding attaches the Role to the ServiceAccount within a specific namespace. ClusterRoleBinding is cluster-wide. Use namespace-scoped bindings wherever possible. Cluster-wide bindings should be rare and deliberate. HOST_A: Network policies. The Kubernetes default: every pod can reach every other pod in the cluster. For any non-trivial security posture, that's completely unacceptable. HOST_B: NetworkPolicy objects define which pods can communicate with which, based on pod selectors, namespace selectors, and ports. The model I advocate: default deny-all ingress and egress in every production namespace. Then explicitly allow what you need. HOST_A: It forces you to actually think about your service graph. What talks to what. That clarity has value beyond security — it's architectural documentation that's enforced at runtime. HOST_B: Important caveat: this only works with a CNI plugin that enforces NetworkPolicy. Basic kubenet doesn't. Calico does. Cilium does. And Cilium is worth a separate mention. HOST_A: Cilium is eBPF-based networking. Completely different architecture from iptables-based CNI. Better performance at scale, far better visibility into network flows, and it supports Layer 7 policy enforcement. If you're starting a new cluster, evaluate it seriously. HOST_B: Pod Security Standards. The old PodSecurityPolicy was removed in Kubernetes 1.25. Pod Security Admission is the replacement — you label namespaces with a policy level: Privileged, Baseline, or Restricted. HOST_A: Restricted is what you want for production. Containers run as non-root, read-only root filesystem, all Linux capabilities dropped. These constraints drastically reduce the blast radius of a container compromise. HOST_B: For finer-grained policy enforcement: OPA Gatekeeper or Kyverno. Kyverno is Kubernetes-native — policies are Kubernetes resources themselves, and the learning curve is much shallower than writing Rego for OPA. HOST_A: Secrets management. Possibly the most misunderstood area. Kubernetes Secrets are base64 encoded. Base64 is encoding, not encryption. Anyone with access to etcd, or get permissions on the Secret object, has your data in plaintext. HOST_B: There are several options. Enabling etcd encryption at rest is a cluster-level configuration change that encrypts Secret data before writing to etcd. Not the default on most managed Kubernetes services. Worth enabling regardless. HOST_A: Sealed Secrets from Bitnami: you encrypt secrets with a public key before committing them to git, an in-cluster controller decrypts them. Secrets can live in git in encrypted form. Simple to adopt. HOST_B: External Secrets Operator is what I recommend for most teams. It integrates with AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Azure Key Vault — wherever your organisation already manages secrets — and syncs them into Kubernetes Secrets. Nothing secret in git at all. HOST_A: And I need to say this clearly: never put raw secrets in git. Not in a private repo. Not in a "temporary" commit you'll clean up later. Never. It's a habit that creates disasters when circumstances change — a team member leaves, a repo gets forked, access gets misconfigured. HOST_B: Image security: pull only from trusted registries. Scan images for CVEs with Trivy or Grype in your CI pipeline and fail the build on critical vulnerabilities. Don't just report. Fail. HOST_A: Use distroless or minimal base images — fewer packages, smaller attack surface. And avoid colon-latest tags. They have no immutability guarantees. Pin to specific digests or versioned tags so you know exactly what's running. HOST_B: There's also the software supply chain angle that's become increasingly important. You need to know not just that your app code is secure, but that every package in your dependency tree is clean. SBOMs — Software Bills of Materials — are how you enumerate what's actually in an image. HOST_A: Tools like Syft can generate an SBOM from an image, and then Grype can scan it against vulnerability databases. Running this in CI means you get an alert when a new CVE is published against a library you already ship, not just at image build time. HOST_B: Image signing is the other piece — Cosign from the Sigstore project lets you cryptographically sign container images and verify signatures at deploy time. If an image wasn't signed by your CI pipeline, it doesn't get deployed. HOST_A: That prevents someone from pushing a malicious image to your registry and having it get picked up by a deployment. It's a strong control, and the tooling is mature enough now that there's no good reason not to use it for production workloads. HOST_B: Observability. All the resource management and security in the world doesn't help if you can't see what's happening when things break at 3am. HOST_A: The standard stack: Prometheus for metrics scraping and storage — Prometheus Operator makes the configuration manageable at scale. Grafana for dashboards and alerting. Loki for log aggregation, structured like Prometheus but for log lines. HOST_B: Tempo for distributed traces, with OpenTelemetry as the instrumentation standard across languages and services. That full stack — Prometheus, Grafana, Loki, Tempo — is often called the PLG stack, and it covers the three pillars: metrics, logs, traces. HOST_A: Two mental models for building dashboards. USE method for infrastructure: Utilisation, Saturation, and Errors for each resource. Is my CPU utilised, is it saturated, are there errors? That covers nodes, databases, message queues. HOST_B: RED method for request-driven services: Rate of requests, Error rate, Duration. If you build dashboards around those signals you can understand almost any production problem quickly. HOST_A: Alerting deserves its own mention. The most common mistake is alerting on symptoms instead of causes — or worse, alerting on everything and creating so much noise that engineers start ignoring pages. HOST_B: Alert on things that directly indicate user impact. Error rate above SLO threshold. Latency above SLO threshold. Alert on the RED signals for your user-facing services. Not on individual pod restarts unless they're persistent. HOST_A: SLOs — Service Level Objectives — are worth introducing here. An SLO says "99.9% of requests succeed in under 500 milliseconds." Your alerts fire when you're on track to burn through your error budget. That focuses attention on what actually matters. HOST_B: The multi-cluster story also affects observability. With multiple clusters, you need a central place to query metrics from all of them. Thanos or Cortex extend Prometheus for multi-cluster and long-term storage. Grafana can federate across multiple Prometheus instances. HOST_A: The debugging runbook. These are the scenarios you'll hit repeatedly. First: pod stuck in Pending. Run kubectl describe pod and look at the Events section at the bottom. Almost always one of four root causes. HOST_B: Insufficient resources on all available nodes. A PVC that hasn't been provisioned or bound. Node affinity or toleration mismatch. Or an image pull error. Those four cover the vast majority of Pending pods. HOST_A: CrashLoopBackOff: run kubectl logs with the previous flag to see output from the most recently crashed container. Usually an application startup failure — missing environment variable, wrong database connection string, missing config file, port already in use. HOST_B: OOMKilled: kubectl describe pod shows you the last state and the exit reason. Before you just bump the memory limit, check actual memory usage in Grafana. Understand whether this is a memory leak or genuinely undersized. HOST_A: ImagePullBackOff: check the image name and tag for typos — embarrassingly common. Check that imagePullSecret exists and is attached to the ServiceAccount. Check that the node can reach your registry — sometimes a network policy is blocking outbound access. HOST_B: Service not routing traffic: kubectl get endpoints for the service name. If the endpoints list is empty, the selector doesn't match any pod labels. Use kubectl get pods with show-labels and compare against the service selector definition. HOST_A: Selector mismatch is the culprit ninety percent of the time for empty endpoints. A label has a typo, or the pod was deployed without the expected label. HOST_B: Two commands that solve a huge range of live debugging problems: kubectl exec to shell into a running container, and kubectl port-forward to access a service directly from your local machine without going through ingress. HOST_A: The production readiness checklist. This is the gate every service should pass before calling itself production ready. Resource requests and limits on every container — no exceptions whatsoever. HOST_B: Liveness and readiness probes configured on every container. Readiness probes ensure traffic only reaches pods that are actually ready to serve. Liveness probes let Kubernetes automatically restart hung processes. HOST_A: PodDisruptionBudget for every critical service. This is the one teams most often forget and then discover painfully during the first cluster upgrade. HOST_B: Without a PDB, a node drain can evict all replicas of a service simultaneously. With a PDB setting min-available to one, Kubernetes ensures at least one replica stays up throughout the drain. It's a small config with large impact. HOST_A: HPA configured for any service under variable load. Network policies applied in every production namespace. RBAC clean — no application ServiceAccount has cluster-admin. HOST_B: Image vulnerability scanning in CI, failing on critical CVEs. Secrets from an external secret manager, not stored raw. Every critical service runs more than one replica. HOST_A: Anti-affinity rules to spread pods across nodes and availability zones. For self-managed clusters: regular, tested etcd backups. Tested, not just scheduled. HOST_B: An untested backup is not a backup. It's a false sense of security waiting to become an incident during your worst possible day. HOST_A: Let's bring it home. What does the path from "Kubernetes works" to "Kubernetes works reliably" actually look like? HOST_B: The things that bite teams in production are almost always the boring ones. Not exotic distributed systems failures — those happen, but they're rare. It's resource requests not set, so a new deployment starves the node. HOST_A: Certificate expiry because cert-manager wasn't installed and someone set a calendar reminder they missed. A PDB misconfiguration silently stalling a rolling update for two hours because Kubernetes can't evict any pod without violating the budget. HOST_B: Nobody gets paged about a stalled rolling update immediately. You notice when the deployment has been "in progress" for a suspiciously long time. The mundane failures happen far more often than the sophisticated ones. HOST_A: Which is actually empowering, because they're all fixable with fundamentals. Set resource requests. Install cert-manager. Write PodDisruptionBudgets. Run readiness probes. HOST_B: And know what you can stop doing. Not every team needs a service mesh. Not every team needs a custom operator. Not every team needs every feature in the Kubernetes ecosystem. HOST_A: For me, the gap is mostly about operations culture. Written, tested runbooks for the failure modes you know are coming. Alerting on the right signals without paging people for noise. Load testing before production traffic. HOST_B: And chaos engineering drills to find the weaknesses before your customers do. Not as a marketing exercise — as a genuine practice of learning how your system fails. HOST_A: Litmus Chaos and Chaos Mesh are both good tools for Kubernetes-native chaos experiments. Kill random pods, introduce network latency between services, drain a node unexpectedly. See what breaks and what your alerts catch. HOST_B: The most valuable experiment is often the simplest: kill the primary replica of a StatefulSet and see how long it takes your monitoring to catch it and your application to recover. If you don't know that number, you don't know your actual recovery time objective. HOST_A: Kubernetes version upgrades are another thing that catches teams off guard. You're typically looking at upgrades every few months to stay within supported versions. Each minor version has a twelve-month support window. HOST_B: The key practice: test upgrades in a non-production cluster first. Use the same Kubernetes version in dev and staging that you're planning to upgrade to in prod. Run your full test suite. Check for deprecated APIs using tools like kubent before the upgrade window. HOST_A: And have a rollback plan. If you're on a managed Kubernetes service, the control plane upgrade is usually handled for you, but worker node upgrades can still go sideways if you have DaemonSets or privileged workloads with version-specific behaviour. HOST_B: I still maintain that at fifty-plus services, the service mesh investment pays for itself in observability and security alone. HOST_B: And I'll maintain that most teams think they're at that scale when they're not. The honest question: do we have the operational maturity to absorb this complexity right now? HOST_A: That is the right question. Whatever the answer, ask it explicitly and deliberately before adopting any complex component. HOST_B: The engineers who run Kubernetes well aren't necessarily the ones who know the most features. They're the ones who understand failure modes deeply, who have built systems to surface problems early, and who have the discipline to keep things simple until complexity is genuinely warranted. HOST_A: That's the episode. If you found this useful, share it with someone who just got their first cluster running and thinks the hard part is over. HOST_B: The hard part is just beginning. But now you know what to look for. HOST_A: Thanks for listening to Clawd Talks. We'll see you in the next one. HOST_B: Take care.