<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pradeep Kandepaneni</title>
    <description>The latest articles on DEV Community by Pradeep Kandepaneni (@pradeep_kandepaneni).</description>
    <link>https://dev.to/pradeep_kandepaneni</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4039222%2F9a1dc160-51ad-490a-a15b-a11040122abc.png</url>
      <title>DEV Community: Pradeep Kandepaneni</title>
      <link>https://dev.to/pradeep_kandepaneni</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pradeep_kandepaneni"/>
    <language>en</language>
    <item>
      <title>Making a Service Survive an Availability Zone Outage — and Proving It for $0</title>
      <dc:creator>Pradeep Kandepaneni</dc:creator>
      <pubDate>Tue, 21 Jul 2026 08:01:47 +0000</pubDate>
      <link>https://dev.to/pradeep_kandepaneni/making-a-service-survive-an-availability-zone-outage-and-proving-it-for-0-565h</link>
      <guid>https://dev.to/pradeep_kandepaneni/making-a-service-survive-an-availability-zone-outage-and-proving-it-for-0-565h</guid>
      <description>&lt;p&gt;A reproducible multi-AZ resilience walkthrough: spread a service across simulated zones, kill one under load, and measure the dropped requests — plus the parts that only show up in real production.&lt;/p&gt;

&lt;p&gt;Originally published on the AWS Builder Center: &lt;a href="https://builder.aws.com/content/3GnvKe3PBz3fP3m3pFFDFghjIxX/making-a-service-survive-an-availability-zone-outage-and-proving-it-for-dollar0" rel="noopener noreferrer"&gt;https://builder.aws.com/content/3GnvKe3PBz3fP3m3pFFDFghjIxX/making-a-service-survive-an-availability-zone-outage-and-proving-it-for-dollar0&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/PradeepKandepaneni/golden-path-resilience" rel="noopener noreferrer"&gt;https://github.com/PradeepKandepaneni/golden-path-resilience&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most articles about availability zones tell you to "spread across three AZs" and stop there, as if the words alone did the work. They don't. Spreading pods across zones is easy to write and easy to get subtly wrong, and the only way to know whether your setup actually survives a zone going dark is to make one go dark and watch what happens.&lt;/p&gt;

&lt;p&gt;So that's what this does. It takes a small service, spreads it across three simulated zones, then kills one zone while a stream of requests is hitting the service — and counts how many of those requests fail. If the architecture holds, that number is zero. And because the whole thing runs on a local cluster and free CI, you can clone it and watch your own zone die without spending a cent or waiting for a real outage.&lt;/p&gt;

&lt;p&gt;This is a sequel to a earlier piece where I built a "golden path" — a free, end-to-end pipeline from a git push to a deployed Kubernetes workload. Same tiny service here; the interesting part this time isn't the pipeline, it's the topology. Repo: github.com/PradeepKandepaneni/golden-path-resilience.&lt;/p&gt;

&lt;p&gt;I'll be honest up front about one boundary, because it matters: the demo simulates zones on a local cluster. That reproduces the Kubernetes-level behaviour — scheduling, spread, eviction, rescheduling, traffic routing — which is most of what you actually tune. It does not reproduce real cross-zone latency or partial network partitions. I'll cover those separately, from the parts of this that only show up in production. Keeping that line clear is the difference between a demo that teaches and one that quietly lies.&lt;/p&gt;

&lt;p&gt;The setup: three zones on one laptop&lt;/p&gt;

&lt;p&gt;Real AWS worker nodes get a topology.kubernetes.io/zone label automatically. Locally, I fake it: a kind cluster with three worker nodes, and a script that labels them az-a, az-b, az-c. Now Kubernetes believes it has three zones to reason about, and every zone-aware feature behaves as it would in a real account.&lt;/p&gt;

&lt;p&gt;That single label is the hinge the whole thing turns on. Everything below — the spread, the eviction behaviour, the rescheduling — keys off it.&lt;/p&gt;

&lt;p&gt;The three pieces that actually do the work&lt;/p&gt;

&lt;p&gt;Zone resilience for a stateless service isn't one setting. It's three, and each covers a gap the others don't.&lt;/p&gt;

&lt;p&gt;Topology spread constraints distribute the pods evenly across the zones. Six replicas, three zones, two per zone. Without this, the scheduler is free to pile most of your pods into one zone, and "we run in three AZs" becomes a lie the first time the wrong one fails.&lt;/p&gt;

&lt;p&gt;The important detail is one field, and it's the field people get wrong:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
topologySpreadConstraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
  matchLabels:
    app: golden-path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;whenUnsatisfiable: ScheduleAnyway is deliberate, and it's the opposite of what a lot of people reach for. The stricter DoNotSchedule sounds safer — enforce the even spread, always. But think about what happens during an actual outage: a zone dies, its pods are evicted, and they need somewhere to go. With DoNotSchedule, rescheduling those pods onto the two surviving zones would break the even-spread rule, so the scheduler refuses, and the pods sit Pending. You've traded away availability to preserve a tidy distribution during the exact moment availability matters most. ScheduleAnyway says: spread evenly when you can, but during degradation, keep the service up. That's the resilient choice, and it's a one-word decision that most tutorials never explain.&lt;/p&gt;

&lt;p&gt;A PodDisruptionBudget is the second piece. Draining a node is a voluntary disruption, and Kubernetes will happily evict everything on it unless you tell it not to. minAvailable: 4 (of six) means the drain of a single zone can proceed — it only needs to evict two — while four pods are guaranteed to stay serving the entire time. The PDB is what turns "drain the node" from a gamble into a guarantee.&lt;/p&gt;

&lt;p&gt;Readiness probes are the quiet third piece. When the evicted pods reschedule onto surviving zones, they're not instantly ready. The readiness probe gates traffic so a pod only receives requests once /healthz is green. Without it, you'd route requests at pods that are still starting and serve errors during the recovery — snatching a small outage from the jaws of a successful failover.&lt;/p&gt;

&lt;p&gt;None of these three is exotic. The point is that resilience is the combination, and dropping any one of them leaves a gap that only shows up when a zone actually fails.&lt;/p&gt;

&lt;p&gt;Killing a zone, and counting the damage&lt;/p&gt;

&lt;p&gt;Description is cheap, so the repo doesn't describe — it measures. scripts/az-outage-demo.sh starts a load generator firing a few requests a second at the service, waits for steady state, then simulates the outage:&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
kubectl cordon "$NODE"&lt;br&gt;
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --force&lt;/p&gt;

&lt;p&gt;Cordon stops new pods landing on the doomed node; drain evicts the ones already there. From Kubernetes' point of view, that zone is gone. The load generator keeps firing the whole time, logging each request as ok or FAIL, and at the end the script tallies them:&lt;/p&gt;

&lt;p&gt;==================================================&lt;br&gt;
 Requests sent during the zone outage : 175&lt;/p&gt;

&lt;h1&gt;
  
  
   Requests that FAILED                 : 0
&lt;/h1&gt;

&lt;p&gt;PASS: the service survived the loss of zone az-a.&lt;/p&gt;

&lt;p&gt;Zero failures across the outage. The four pods in the surviving zones never stopped serving; the two evicted pods rescheduled onto those zones and rejoined once ready; the Service only ever routed traffic to pods that were actually up. The client saw nothing.&lt;/p&gt;

&lt;p&gt;The CI job asserts this on every push — it fails the build if more than a couple of requests drop. That's the part that makes this more than a story: a green check means the failover genuinely worked on real (if simulated) infrastructure, and you can read the run yourself instead of trusting my screenshot. Because it's a public repo, the whole thing — multi-node cluster, zone outage, load test — runs on free GitHub runners at no cost.&lt;/p&gt;

&lt;p&gt;Why "ScheduleAnyway" is the whole lesson in one field&lt;/p&gt;

&lt;p&gt;If you take one thing from the reproducible half of this, make it that field. It's a perfect small example of how resilience decisions are counterintuitive: the setting that sounds stricter and safer (DoNotSchedule) is the one that hurts you when it counts, and the "looser" one (ScheduleAnyway) is what keeps you up. You cannot reason your way to that from the words "spread across three AZs." You get there by asking what happens after a zone is already gone — which is the question most resilience writing forgets to ask.&lt;/p&gt;

&lt;p&gt;Where the simulation ends and production begins&lt;/p&gt;

&lt;p&gt;Everything above is reproducible on your laptop, and that's exactly why it's limited to the orchestration layer. Real multi-AZ adds things a local cluster can't fake, and pretending otherwise would undercut the honest parts. Three in particular are worth naming.&lt;/p&gt;

&lt;p&gt;Cross-zone latency is real and it's not free. In a real account, every hop between zones adds a millisecond or two, and traffic that ping-pongs across zones under load adds up — in latency and, on AWS, in cross-AZ data transfer cost. Local kind has effectively zero inter-node latency, so it can't show you this. In production it shapes real decisions, like topology-aware routing to keep traffic in-zone when possible.&lt;/p&gt;

&lt;p&gt;Partial failures are messier than a clean drain. My demo removes a zone cleanly. Real zones fail in uglier ways — a network partition where the zone is reachable but slow, or intermittently up. Those are the scenarios that expose whether your health checks and timeouts are tuned correctly, and a clean kubectl drain never triggers them.&lt;/p&gt;

&lt;p&gt;The data layer is the hard half, and it's deliberately not in the reproducible demo. Stateless failover — what this repo shows — is the easy part precisely because any pod can serve any request. The moment state is involved, resilience gets genuinely hard: a multi-AZ database failover has a measurable failover window, in-flight transactions to reason about, and connection pools that need to survive the primary moving.&lt;/p&gt;

&lt;p&gt;Those three are why the honest framing of this whole exercise is "I reproduced the orchestration behaviour for free, and here's where production goes further" — not "I built a production multi-AZ system on my laptop." The reproducible part is genuinely useful and genuinely verifiable. The rest is where real accounts and real incidents teach you things a simulation can't.&lt;/p&gt;

&lt;p&gt;Takeaways&lt;/p&gt;

&lt;p&gt;Resilience is a combination, not a setting. Spread, disruption budget, and readiness each cover a gap the others leave. Ship one without the others and you've built a service that looks resilient right up until a zone fails.&lt;/p&gt;

&lt;p&gt;Ask what happens after the failure, not before. The ScheduleAnyway decision only makes sense if you reason about the moment a zone is already gone. Most resilience mistakes come from designing for the healthy state and never simulating the broken one.&lt;/p&gt;

&lt;p&gt;Prove it, don't describe it. "Spread across three AZs" is a claim. "Here's the demo that kills a zone under load and reports zero dropped requests, and here's the CI run" is evidence — and evidence is worth more than any architecture diagram.&lt;/p&gt;

&lt;p&gt;The repo is public and the demo runs green for $0: github.com/PradeepKandepaneni/golden-path-resilience. Clone it, run make up &amp;amp;&amp;amp; make demo, and kill a zone yourself.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>architecture</category>
      <category>devops</category>
      <category>availabilty</category>
    </item>
    <item>
      <title>Preventing Unsafe Regional Failover in Amazon EKS with Policy-Gated Recovery</title>
      <dc:creator>Pradeep Kandepaneni</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:30:42 +0000</pubDate>
      <link>https://dev.to/pradeep_kandepaneni/preventing-unsafe-regional-failover-in-amazon-eks-with-policy-gated-recovery-3c8l</link>
      <guid>https://dev.to/pradeep_kandepaneni/preventing-unsafe-regional-failover-in-amazon-eks-with-policy-gated-recovery-3c8l</guid>
      <description>&lt;p&gt;This article was originally published on AWS Builder Center.&lt;/p&gt;

&lt;p&gt;A secondary Amazon EKS cluster can appear healthy while still being unsafe to receive production traffic. This article presents a policy-gated recovery model that validates data replication, compute capacity, application health, container images, secrets, certificates, and synthetic business transactions before promoting a recovery Region.&lt;/p&gt;

&lt;p&gt;This article was originally published on [AWS Builder Center]&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://builder.aws.com/content/3GnlrIlqWy7Hel5zlqaIMsg2rPM/preventing-unsafe-regional-failover-in-amazon-eks-with-policy-gated-recovery" rel="noopener noreferrer"&gt;https://builder.aws.com/content/3GnlrIlqWy7Hel5zlqaIMsg2rPM/preventing-unsafe-regional-failover-in-amazon-eks-with-policy-gated-recovery&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why a Healthy Standby Region Can Still Be Unsafe
&lt;/h2&gt;

&lt;p&gt;Running an application across multiple Availability Zones protects against many infrastructure failures, but it does not provide complete protection from a regional outage. A common recovery design is to maintain a second Amazon Elastic Kubernetes Service (Amazon EKS) cluster in another AWS Region and redirect traffic when the primary Region becomes unavailable.&lt;/p&gt;

&lt;p&gt;The problem is that a reachable cluster is not necessarily ready to operate as the active production Region.&lt;/p&gt;

&lt;p&gt;Kubernetes readiness probes may succeed while the database replica is behind, required container images are missing, regional secrets contain incorrect endpoints, certificates are unavailable, or the recovery cluster lacks enough compute capacity. A DNS or load-balancer health check cannot detect all of these conditions.&lt;/p&gt;

&lt;p&gt;For stateful workloads, premature promotion can be more damaging than a temporary outage. It can introduce data loss, conflicting writes, authentication failures, partial application availability, or a recovery environment that collapses as soon as production traffic arrives.&lt;/p&gt;

&lt;p&gt;A safer approach is to treat regional promotion as a controlled engineering decision rather than a simple routing change.&lt;/p&gt;

&lt;p&gt;This article presents a policy-gated recovery model for Amazon EKS. The model uses mandatory safety gates and a weighted recovery-readiness score to validate infrastructure capacity, application health, data replication, container images, secrets, certificates, GitOps state, writer fencing, and synthetic business transactions before traffic and write authority move to the recovery Region.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reference Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture uses two AWS Regions: a primary Region serving production traffic and a warm-standby recovery Region maintained at reduced capacity.&lt;/p&gt;

&lt;p&gt;Each Region contains an independent Amazon EKS cluster, regional networking, application load balancing, monitoring, secrets, certificates, and container images. Application configuration is synchronized through a GitOps workflow so that the recovery cluster continuously receives approved Kubernetes manifests and deployment changes.&lt;/p&gt;

&lt;p&gt;Stateful data is replicated using Amazon Aurora Global Database. Container images are copied through Amazon Elastic Container Registry (Amazon ECR) cross-Region replication, while AWS Secrets Manager provides regional secret replicas.&lt;/p&gt;

&lt;p&gt;These replication mechanisms must be validated independently. Successful replication configuration does not prove that every required image, secret value, certificate, endpoint, or database change is available and usable in the recovery Region.&lt;/p&gt;

&lt;p&gt;Amazon Application Recovery Controller (ARC) Region switch coordinates the recovery workflow. Region switch uses recovery plans composed of ordered or parallel execution steps. The recovery plan can be initiated manually or through approved automation, depending on the workload's operational requirements.&lt;/p&gt;

&lt;p&gt;A separate recovery-readiness evaluator checks mandatory safety conditions before database promotion, workload scaling, or traffic movement is permitted.&lt;/p&gt;

&lt;p&gt;The design separates four concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure readiness&lt;/strong&gt; — The recovery Region has sufficient networking, compute, quota, and worker-node capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application readiness&lt;/strong&gt; — Critical workloads are synchronized, schedulable, healthy, and able to complete synthetic business transactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data readiness&lt;/strong&gt; — Replication lag is within the approved recovery-point objective, and conflicting writes are prevented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency readiness&lt;/strong&gt; — Required images, secrets, certificates, identity services, queues, caches, and external integrations are available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A regional endpoint health check alone cannot prove these conditions. Promotion is allowed only after every mandatory safety gate passes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Endpoint Health Is Not Recovery Readiness
&lt;/h2&gt;

&lt;p&gt;Traditional failover designs often rely on a small number of signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load balancer endpoint availability&lt;/li&gt;
&lt;li&gt;DNS health-check status&lt;/li&gt;
&lt;li&gt;Kubernetes pod readiness&lt;/li&gt;
&lt;li&gt;Application response codes&lt;/li&gt;
&lt;li&gt;Infrastructure resource availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These signals are valuable, but they answer only narrow questions.&lt;/p&gt;

&lt;p&gt;A successful readiness probe confirms that a container can respond to a configured health endpoint. It does not prove that the application can authenticate users, perform database writes, retrieve secrets, process queue messages, contact third-party services, or sustain production traffic.&lt;/p&gt;

&lt;p&gt;Consider the following failure conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application pods are healthy, but the Aurora secondary cluster has replication lag beyond the accepted recovery-point objective.&lt;/li&gt;
&lt;li&gt;Kubernetes Deployments exist, but worker nodes cannot scale because of service quotas or insufficient subnet address capacity.&lt;/li&gt;
&lt;li&gt;The image tag exists in the recovery Region, but the required immutable image digest does not.&lt;/li&gt;
&lt;li&gt;Secrets were replicated, but a connection string still references a primary-Region endpoint.&lt;/li&gt;
&lt;li&gt;The recovery cluster is synchronized, but the GitOps controller reverses emergency replica changes.&lt;/li&gt;
&lt;li&gt;TLS certificates are missing, expired, or not attached to the recovery load balancer.&lt;/li&gt;
&lt;li&gt;The database is promoted, but the original writer continues accepting requests, creating a split-brain risk.&lt;/li&gt;
&lt;li&gt;The login endpoint responds, but a complete business transaction fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regional recovery readiness must therefore be evaluated as a combined system property, not as a single health-check result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separate Hard Gates from Readiness Scores
&lt;/h2&gt;

&lt;p&gt;The proposed model uses two types of controls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hard promotion gates&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weighted readiness signals&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation is critical.&lt;/p&gt;

&lt;p&gt;A weighted score is useful for summarizing operational health, but it must never compensate for a failed data-safety requirement. A recovery Region with excellent application health but unsafe database state should not receive production writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard Promotion Gates
&lt;/h3&gt;

&lt;p&gt;Hard gates represent conditions that are non-negotiable. If any hard gate fails, promotion stops.&lt;/p&gt;

&lt;h4&gt;
  
  
  Database Replication
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Replication status must be healthy.&lt;/li&gt;
&lt;li&gt;Replication lag must remain below the approved threshold.&lt;/li&gt;
&lt;li&gt;The target database cluster must be eligible for failover or switchover.&lt;/li&gt;
&lt;li&gt;Database engine versions must be compatible with the selected recovery operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Writer Fencing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The previous writer must be unavailable, isolated, or explicitly fenced before the recovery Region accepts writes.&lt;/li&gt;
&lt;li&gt;Applications must not maintain writable connections to both Regions.&lt;/li&gt;
&lt;li&gt;Connection pools and DNS caches must be considered during the transition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Amazon EKS Capacity
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The required Kubernetes resources must exist in the recovery cluster.&lt;/li&gt;
&lt;li&gt;Sufficient worker-node capacity must be available or capable of scaling.&lt;/li&gt;
&lt;li&gt;Subnets, IP addresses, instance quotas, and Availability Zone distribution must support the desired replica count.&lt;/li&gt;
&lt;li&gt;Critical pods must become schedulable within the recovery timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Application Health
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Required Deployments must have the minimum number of ready replicas.&lt;/li&gt;
&lt;li&gt;Pod disruption budgets, topology constraints, and health probes must be valid.&lt;/li&gt;
&lt;li&gt;Critical services must pass internal and external health validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Container Image Availability
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Required repositories must exist in the recovery Region.&lt;/li&gt;
&lt;li&gt;The exact image digests referenced by production manifests must be available.&lt;/li&gt;
&lt;li&gt;Image pull permissions and node access must be validated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Amazon ECR replication rules should be configured before images are pushed. Images that existed before replication was configured should not be assumed to appear automatically in the destination Region.&lt;/p&gt;

&lt;h4&gt;
  
  
  Secret and Configuration Readiness
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Required secrets must exist in the recovery Region.&lt;/li&gt;
&lt;li&gt;Secret versions must match the expected deployment version.&lt;/li&gt;
&lt;li&gt;Regional endpoints embedded in secret values must be validated.&lt;/li&gt;
&lt;li&gt;AWS Key Management Service keys and permissions must be available in the recovery Region.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Certificate Readiness
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Certificates must be valid and available in the required Region.&lt;/li&gt;
&lt;li&gt;Load balancer listeners must reference the correct certificate.&lt;/li&gt;
&lt;li&gt;Certificate expiration and domain validation status must be checked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  GitOps Synchronization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Critical applications must be synchronized.&lt;/li&gt;
&lt;li&gt;No unresolved high-severity drift should exist.&lt;/li&gt;
&lt;li&gt;GitOps tools must not reverse recovery-time scaling changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Synthetic Business Transactions
&lt;/h4&gt;

&lt;p&gt;A synthetic test should validate more than a basic HTTP response. Depending on the application, it can perform steps such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate a test user.&lt;/li&gt;
&lt;li&gt;Create a temporary transaction.&lt;/li&gt;
&lt;li&gt;Read the newly created record.&lt;/li&gt;
&lt;li&gt;Update the record.&lt;/li&gt;
&lt;li&gt;Delete or clean up the test data.&lt;/li&gt;
&lt;li&gt;Verify that logs and metrics were produced.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  ARC Recovery-Plan Evaluation
&lt;/h4&gt;

&lt;p&gt;Before execution, the Region switch plan should pass its available plan-evaluation checks. Custom policy gates should complement—not replace—the validations performed by ARC.&lt;/p&gt;




&lt;h2&gt;
  
  
  Weighted Recovery-Readiness Score
&lt;/h2&gt;

&lt;p&gt;After all hard gates pass, weighted signals can provide a consolidated operational score.&lt;/p&gt;

&lt;p&gt;A reference weighting model could use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;25% application health&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20% infrastructure capacity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20% data health&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;15% dependency health&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10% observability health&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10% policy compliance&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The weights must reflect the workload's actual risk profile. A transaction-processing system may assign greater weight to data health, while a stateless content-delivery workload may emphasize capacity and dependency availability.&lt;/p&gt;

&lt;p&gt;The score should not be interpreted as a universal standard. It is an operational decision aid.&lt;/p&gt;

&lt;p&gt;A simple decision policy is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF any hard gate fails:
    BLOCK PROMOTION

ELSE IF readiness score is below 90:
    REQUIRE OPERATOR REVIEW

ELSE:
    ALLOW THE APPROVED RECOVERY WORKFLOW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A score of 95 must never override a failed writer-fencing gate, excessive database replication lag, or a missing production image.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Recovery Policy
&lt;/h2&gt;

&lt;p&gt;The following example represents a simplified policy definition. Production implementations should store thresholds in version-controlled configuration and require peer review for changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;recoveryPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;targetRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-west-2&lt;/span&gt;

  &lt;span class="na"&gt;hardGates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;maximumDatabaseLagSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
    &lt;span class="na"&gt;minimumAvailableReplicasPercent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;95&lt;/span&gt;

    &lt;span class="na"&gt;requiredSyntheticTests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;login&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create-order&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;read-order&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update-order&lt;/span&gt;

    &lt;span class="na"&gt;requireDatabasePromotionEligibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireImageDigestValidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireRegionalSecretValidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireCertificateValidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireGitOpsSynchronization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireWriterFencing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;requireArcPlanEvaluation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;readinessScore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;minimumAutomaticApprovalScore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;

    &lt;span class="na"&gt;weights&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;applicationHealth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
      &lt;span class="na"&gt;infrastructureCapacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="na"&gt;dataHealth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="na"&gt;dependencyHealth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
      &lt;span class="na"&gt;observabilityHealth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;policyCompliance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The threshold values above are examples. They must be aligned with the organization's recovery-time objective, recovery-point objective, application behavior, data-loss tolerance, and operational approval model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Readiness Evaluation
&lt;/h2&gt;

&lt;p&gt;The following Python example demonstrates the decision logic. It intentionally keeps hard-gate evaluation separate from weighted scoring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HardGate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeightedSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_recovery_readiness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;hard_gates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HardGate&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;weighted_signals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WeightedSignal&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;minimum_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hard_gates&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed_gates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;signals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weighted_signals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BLOCK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed_gates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weighted-signals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No weighted readiness signals were provided.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;total_weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_weight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total signal weight must be greater than zero.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;normalized_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_weight&lt;/span&gt;

    &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;normalized_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;minimum_score&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REVIEW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed_gates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production implementation, each signal should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collection timestamp&lt;/li&gt;
&lt;li&gt;Source system&lt;/li&gt;
&lt;li&gt;Region&lt;/li&gt;
&lt;li&gt;Resource identifier&lt;/li&gt;
&lt;li&gt;Evaluation result&lt;/li&gt;
&lt;li&gt;Threshold&lt;/li&gt;
&lt;li&gt;Evidence location&lt;/li&gt;
&lt;li&gt;Expiration period&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stale evidence should fail closed. A database-lag measurement collected 30 minutes earlier should not approve a current regional promotion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recovery Workflow with AWS ARC Region Switch
&lt;/h2&gt;

&lt;p&gt;A human-approved automated workflow is usually more defensible than fully automatic promotion for business-critical stateful workloads.&lt;/p&gt;

&lt;p&gt;A reference recovery sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An operator declares a regional recovery event or initiates a controlled exercise.&lt;/li&gt;
&lt;li&gt;ARC Region switch evaluates the configured recovery plan.&lt;/li&gt;
&lt;li&gt;The recovery-readiness evaluator collects current evidence from both Regions.&lt;/li&gt;
&lt;li&gt;Hard promotion gates are evaluated.&lt;/li&gt;
&lt;li&gt;The recovery EKS cluster scales critical Kubernetes resources.&lt;/li&gt;
&lt;li&gt;Worker-node autoscaling increases node capacity as required.&lt;/li&gt;
&lt;li&gt;The GitOps controller verifies application synchronization.&lt;/li&gt;
&lt;li&gt;Aurora Global Database performs the appropriate failover operation.&lt;/li&gt;
&lt;li&gt;Applications reconnect to the new database writer.&lt;/li&gt;
&lt;li&gt;Synthetic transactions run against the recovery Region.&lt;/li&gt;
&lt;li&gt;Writer fencing is confirmed.&lt;/li&gt;
&lt;li&gt;ARC routing controls or the approved traffic-management mechanism moves traffic.&lt;/li&gt;
&lt;li&gt;Post-promotion monitoring verifies error rate, latency, saturation, and business transactions.&lt;/li&gt;
&lt;li&gt;The workflow completes, pauses, or rolls back based on validation results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ARC Region switch can scale supported EKS workload resources as part of a recovery plan. However, increasing Kubernetes replica counts does not independently create worker-node capacity. The cluster still depends on a node-provisioning mechanism such as Karpenter, Cluster Autoscaler, or EKS Auto Mode.&lt;/p&gt;

&lt;p&gt;Recovery testing must therefore validate both layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desired Kubernetes replica capacity&lt;/li&gt;
&lt;li&gt;Actual schedulable worker-node capacity&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prevent GitOps from Reversing Recovery Changes
&lt;/h2&gt;

&lt;p&gt;GitOps systems continuously reconcile actual cluster state with the state stored in Git. This is normally desirable, but it can conflict with disaster-recovery actions.&lt;/p&gt;

&lt;p&gt;For example, ARC may increase a Deployment from three replicas to twelve replicas during recovery. If the Git repository still specifies three replicas, the GitOps controller may attempt to restore the lower count.&lt;/p&gt;

&lt;p&gt;The same conflict can occur when a recovery workflow modifies Horizontal Pod Autoscaler behavior to prevent immediate scale-down.&lt;/p&gt;

&lt;p&gt;Recovery design should explicitly define which system owns temporary scaling changes.&lt;/p&gt;

&lt;p&gt;Options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure GitOps to ignore the Deployment replica field.&lt;/li&gt;
&lt;li&gt;Configure GitOps to ignore specific Horizontal Pod Autoscaler fields.&lt;/li&gt;
&lt;li&gt;Commit recovery-state changes to a dedicated Git branch.&lt;/li&gt;
&lt;li&gt;Temporarily suspend reconciliation for selected resources.&lt;/li&gt;
&lt;li&gt;Use environment-specific overlays that represent active and standby capacity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The selected approach must be tested. "GitOps is configured" is not sufficient evidence that GitOps and the recovery workflow will cooperate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kubernetes Resilience Controls
&lt;/h2&gt;

&lt;p&gt;The recovery Region should enforce the same—or stricter—workload-resilience controls used in the primary Region.&lt;/p&gt;

&lt;p&gt;The following example spreads application replicas across Availability Zones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;topology.kubernetes.io/zone&lt;/span&gt;
          &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
          &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;

      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/checkout-api@sha256:IMAGE_DIGEST&lt;/span&gt;

          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1Gi"&lt;/span&gt;

          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health/ready&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health/live&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using immutable image digests reduces ambiguity during recovery. The promotion gate can verify that the exact digest referenced by the workload exists in the recovery Region.&lt;/p&gt;




&lt;h2&gt;
  
  
  Database Promotion and Write Safety
&lt;/h2&gt;

&lt;p&gt;Aurora Global Database supports planned switchovers and disaster-recovery failovers. These operations should not be treated as equivalent.&lt;/p&gt;

&lt;p&gt;A planned switchover is used when the current primary and secondary Regions are healthy and the organization intentionally moves the primary role. An unplanned failover is used when the primary Region is impaired or unavailable.&lt;/p&gt;

&lt;p&gt;The recovery workflow must account for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current replication lag&lt;/li&gt;
&lt;li&gt;Engine-version compatibility&lt;/li&gt;
&lt;li&gt;Promotion eligibility&lt;/li&gt;
&lt;li&gt;Application reconnection behavior&lt;/li&gt;
&lt;li&gt;DNS caching&lt;/li&gt;
&lt;li&gt;Connection-pool expiration&lt;/li&gt;
&lt;li&gt;Transaction retries&lt;/li&gt;
&lt;li&gt;Potential data loss during unplanned failure&lt;/li&gt;
&lt;li&gt;Reintroduction of the previous primary as a secondary&lt;/li&gt;
&lt;li&gt;Failback sequencing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applications should use retry logic that distinguishes transient connectivity failure from permanent transaction rejection. Idempotency controls are especially important for payment, order, provisioning, and messaging workflows.&lt;/p&gt;

&lt;p&gt;Writer fencing must be validated before the recovery Region accepts writes. This can involve isolating the previous writer, revoking application access, disabling routing, applying security controls, or otherwise proving that dual-writer operation cannot occur.&lt;/p&gt;




&lt;h2&gt;
  
  
  Container Image Validation
&lt;/h2&gt;

&lt;p&gt;Amazon ECR supports cross-Region and cross-account private-image replication. However, replication configuration is not equivalent to complete image readiness.&lt;/p&gt;

&lt;p&gt;The recovery workflow should verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Destination repository existence&lt;/li&gt;
&lt;li&gt;Expected image digest&lt;/li&gt;
&lt;li&gt;Multi-architecture manifest availability&lt;/li&gt;
&lt;li&gt;Image-pull permissions&lt;/li&gt;
&lt;li&gt;Encryption configuration&lt;/li&gt;
&lt;li&gt;Vulnerability-scanning status, where required&lt;/li&gt;
&lt;li&gt;Node access to the destination repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Images pushed before a replication rule was configured should not be assumed to exist in the destination Region. Validate the exact digests required by active Kubernetes manifests.&lt;/p&gt;

&lt;p&gt;A simple validation process can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract all image references from production workloads.&lt;/li&gt;
&lt;li&gt;Resolve tags to immutable digests.&lt;/li&gt;
&lt;li&gt;Query the destination ECR registry.&lt;/li&gt;
&lt;li&gt;Compare expected and available digests.&lt;/li&gt;
&lt;li&gt;Block promotion when any required digest is missing.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Secret and Certificate Validation
&lt;/h2&gt;

&lt;p&gt;AWS Secrets Manager can replicate encrypted secret data and metadata across Regions. Replication is useful, but it does not understand the semantic meaning of the values stored inside a secret.&lt;/p&gt;

&lt;p&gt;A replicated secret may still contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A primary-Region database endpoint&lt;/li&gt;
&lt;li&gt;A Region-specific queue URL&lt;/li&gt;
&lt;li&gt;A private service hostname unavailable from the recovery Region&lt;/li&gt;
&lt;li&gt;A certificate identifier valid only in the primary Region&lt;/li&gt;
&lt;li&gt;An external allowlist that excludes recovery egress addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recovery gate must validate both existence and correctness.&lt;/p&gt;

&lt;p&gt;A secret-readiness test should verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expected secret version&lt;/li&gt;
&lt;li&gt;Recovery-Region encryption key&lt;/li&gt;
&lt;li&gt;Resource policy&lt;/li&gt;
&lt;li&gt;Application IAM access&lt;/li&gt;
&lt;li&gt;Regional endpoint values&lt;/li&gt;
&lt;li&gt;Rotation status&lt;/li&gt;
&lt;li&gt;Successful application retrieval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Certificates should also be validated by Region, expiration date, domain name, listener association, and deployment status.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability During Recovery
&lt;/h2&gt;

&lt;p&gt;Recovery without observability is guesswork.&lt;/p&gt;

&lt;p&gt;The recovery Region must provide independent visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes replica availability&lt;/li&gt;
&lt;li&gt;Unschedulable pods&lt;/li&gt;
&lt;li&gt;Worker-node provisioning&lt;/li&gt;
&lt;li&gt;Application error rate&lt;/li&gt;
&lt;li&gt;Request latency&lt;/li&gt;
&lt;li&gt;Database replication lag&lt;/li&gt;
&lt;li&gt;Database connection failures&lt;/li&gt;
&lt;li&gt;Queue depth and consumer lag&lt;/li&gt;
&lt;li&gt;Cache availability&lt;/li&gt;
&lt;li&gt;Synthetic transaction success&lt;/li&gt;
&lt;li&gt;DNS or routing state&lt;/li&gt;
&lt;li&gt;ARC recovery-plan execution&lt;/li&gt;
&lt;li&gt;GitOps synchronization&lt;/li&gt;
&lt;li&gt;Certificate errors&lt;/li&gt;
&lt;li&gt;Secret-access failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not make the recovery workflow dependent solely on a monitoring system hosted in the Region being deactivated.&lt;/p&gt;

&lt;p&gt;Critical dashboards, alarms, logs, and operational evidence should remain available during a regional impairment.&lt;/p&gt;

&lt;p&gt;Post-promotion validation should continue for a defined stabilization period. Passing a single synthetic transaction immediately after traffic movement does not prove sustained recovery.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recovery-Time Budget
&lt;/h2&gt;

&lt;p&gt;A recovery-time objective should be decomposed into measurable stages.&lt;/p&gt;

&lt;p&gt;The following is a worked reference target—not a production benchmark:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recovery stage&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event declaration and operator approval&lt;/td&gt;
&lt;td&gt;3 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARC plan evaluation and policy checks&lt;/td&gt;
&lt;td&gt;4 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EKS workload and node-capacity scaling&lt;/td&gt;
&lt;td&gt;8 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database promotion and application reconnection&lt;/td&gt;
&lt;td&gt;6 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthetic transaction validation&lt;/td&gt;
&lt;td&gt;4 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traffic movement and stabilization&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total target recovery budget&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each stage should produce timestamps and evidence.&lt;/p&gt;

&lt;p&gt;A recovery exercise should record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start and completion time&lt;/li&gt;
&lt;li&gt;Duration of each execution step&lt;/li&gt;
&lt;li&gt;Manual interventions&lt;/li&gt;
&lt;li&gt;Failed gates&lt;/li&gt;
&lt;li&gt;Overrides&lt;/li&gt;
&lt;li&gt;Database lag&lt;/li&gt;
&lt;li&gt;Replica scaling time&lt;/li&gt;
&lt;li&gt;Node-provisioning time&lt;/li&gt;
&lt;li&gt;Synthetic-test results&lt;/li&gt;
&lt;li&gt;Routing-change time&lt;/li&gt;
&lt;li&gt;Stabilization results&lt;/li&gt;
&lt;li&gt;Corrective actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a small number of exercises, publish every result along with the minimum, median, and maximum. Avoid statistically weak percentile claims from insufficient samples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failback Is Part of the Design
&lt;/h2&gt;

&lt;p&gt;Failover is only half of disaster recovery.&lt;/p&gt;

&lt;p&gt;After the original Region becomes healthy, the organization must decide whether and when to return production traffic. Immediate failback can introduce additional risk while teams are still stabilizing the environment.&lt;/p&gt;

&lt;p&gt;A controlled failback process should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm that the original Region is stable.&lt;/li&gt;
&lt;li&gt;Rebuild or rejoin the original database cluster as required.&lt;/li&gt;
&lt;li&gt;Re-establish replication in the intended direction.&lt;/li&gt;
&lt;li&gt;Validate that no divergent writes exist.&lt;/li&gt;
&lt;li&gt;Synchronize GitOps state.&lt;/li&gt;
&lt;li&gt;Validate images, secrets, certificates, queues, caches, and dependencies.&lt;/li&gt;
&lt;li&gt;Restore sufficient EKS and worker-node capacity.&lt;/li&gt;
&lt;li&gt;Run synthetic business transactions.&lt;/li&gt;
&lt;li&gt;Fence the current writer before changing write authority.&lt;/li&gt;
&lt;li&gt;Perform a planned database switchover when supported.&lt;/li&gt;
&lt;li&gt;Move application traffic.&lt;/li&gt;
&lt;li&gt;Monitor the restored primary Region through a stabilization period.&lt;/li&gt;
&lt;li&gt;Return the other Region to the intended standby capacity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Failback should use the same policy discipline as failover. "The original Region is back online" is not sufficient evidence that it is safe to become primary again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;A recovery platform has permission to alter critical infrastructure and routing. Its security model deserves the same scrutiny as a production deployment system.&lt;/p&gt;

&lt;p&gt;Recommended controls include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dedicated IAM roles for recovery-plan execution&lt;/li&gt;
&lt;li&gt;Least-privilege EKS access entries&lt;/li&gt;
&lt;li&gt;Namespace-scoped Kubernetes permissions where practical&lt;/li&gt;
&lt;li&gt;Separate permissions for evaluation and execution&lt;/li&gt;
&lt;li&gt;Multi-person approval for high-risk recovery actions&lt;/li&gt;
&lt;li&gt;AWS CloudTrail logging&lt;/li&gt;
&lt;li&gt;Immutable audit records&lt;/li&gt;
&lt;li&gt;Encryption for data and secrets in both Regions&lt;/li&gt;
&lt;li&gt;Restricted emergency-access procedures&lt;/li&gt;
&lt;li&gt;Periodic credential and role review&lt;/li&gt;
&lt;li&gt;Protection against unauthorized policy-threshold changes&lt;/li&gt;
&lt;li&gt;Signed or reviewed recovery configuration&lt;/li&gt;
&lt;li&gt;Separation of duties between application, platform, and security teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual override capability may be necessary, but overrides should require explicit authorization, justification, timestamped evidence, and post-event review.&lt;/p&gt;

&lt;p&gt;Data-safety gates such as writer fencing should not support routine bypass.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations and Trade-Offs
&lt;/h2&gt;

&lt;p&gt;This architecture introduces operational discipline, but it does not eliminate complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;A warm-standby environment costs more than a pilot-light design because a functional copy of the platform remains active at reduced capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovery Speed
&lt;/h3&gt;

&lt;p&gt;A pilot-light architecture can reduce cost but generally requires more provisioning and scaling during recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS Behavior
&lt;/h3&gt;

&lt;p&gt;DNS-based traffic movement is affected by resolver and client caching. Configured TTL values do not guarantee that every client immediately honors the new destination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Architecture
&lt;/h3&gt;

&lt;p&gt;Applications with hard-coded regional dependencies, non-idempotent workflows, or tightly coupled external services may require significant redesign.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Consistency
&lt;/h3&gt;

&lt;p&gt;Asynchronous cross-Region replication can introduce data-loss exposure during an unplanned failure. The organization must define an acceptable recovery-point objective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automation Risk
&lt;/h3&gt;

&lt;p&gt;Fully automatic failover may be inappropriate for stateful systems when failure detection is uncertain or write safety cannot be proven automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Availability
&lt;/h3&gt;

&lt;p&gt;AWS service features, quotas, instance types, and database-engine support vary by Region and version. Validate the target Regions before implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scoring Limitations
&lt;/h3&gt;

&lt;p&gt;A readiness score is a decision aid. It is not a substitute for hard safety gates, engineering review, or repeated recovery exercises.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended Adoption Approach
&lt;/h2&gt;

&lt;p&gt;Do not begin by applying this model to every production workload.&lt;/p&gt;

&lt;p&gt;Start with one representative application that has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear recovery objectives&lt;/li&gt;
&lt;li&gt;Defined business transactions&lt;/li&gt;
&lt;li&gt;Known dependencies&lt;/li&gt;
&lt;li&gt;Testable data-replication behavior&lt;/li&gt;
&lt;li&gt;Manageable compliance constraints&lt;/li&gt;
&lt;li&gt;An engaged application owner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical adoption sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inventory all regional dependencies.&lt;/li&gt;
&lt;li&gt;Define recovery-time and recovery-point objectives.&lt;/li&gt;
&lt;li&gt;Classify hard promotion gates.&lt;/li&gt;
&lt;li&gt;Build the recovery Region with infrastructure as code.&lt;/li&gt;
&lt;li&gt;Configure application and data replication.&lt;/li&gt;
&lt;li&gt;Implement evidence collection.&lt;/li&gt;
&lt;li&gt;Implement synthetic transactions.&lt;/li&gt;
&lt;li&gt;Create the ARC Region switch plan.&lt;/li&gt;
&lt;li&gt;Integrate policy evaluation.&lt;/li&gt;
&lt;li&gt;Run recovery exercises without traffic movement.&lt;/li&gt;
&lt;li&gt;Run controlled failover and failback exercises.&lt;/li&gt;
&lt;li&gt;Correct gaps before expanding to additional applications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not to automate every step immediately. The goal is to make every decision explicit, measurable, repeatable, and auditable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A secondary Amazon EKS cluster should not be promoted merely because its API endpoint and pods are reachable.&lt;/p&gt;

&lt;p&gt;Safe regional recovery requires evidence that compute capacity, data, application state, identity, container images, certificates, dependencies, routing, and business transactions are ready together.&lt;/p&gt;

&lt;p&gt;Hard promotion gates protect non-negotiable safety conditions. A weighted readiness score gives operators a consolidated view of the remaining operational signals. Amazon Application Recovery Controller Region switch provides an orchestration layer for executing the recovery workflow, while GitOps, infrastructure as code, replication services, synthetic testing, and observability provide the supporting controls.&lt;/p&gt;

&lt;p&gt;The result is not zero-risk failover. No architecture can honestly promise that.&lt;/p&gt;

&lt;p&gt;The objective is a recovery process in which unsafe conditions block promotion, operational decisions are supported by current evidence, and every recovery exercise improves the next one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;The architecture, code examples, thresholds, and recovery-time budget in this article are provided as a reference scenario. They must be adapted and validated against each organization's workload characteristics, recovery objectives, security requirements, compliance obligations, service quotas, AWS Region availability, and operational procedures.&lt;/p&gt;

&lt;p&gt;This article represents the author's technical perspective. It is not an AWS-supported solution, an official architecture from AWS, or a guarantee of availability, recovery time, or data protection.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/r53recovery/latest/dg/region-switch.html" rel="noopener noreferrer"&gt;Region switch in Amazon Application Recovery Controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/r53recovery/latest/dg/eks-resource-scaling-block.html" rel="noopener noreferrer"&gt;Amazon EKS resource scaling execution block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/r53recovery/latest/dg/aurora-global-database-block.html" rel="noopener noreferrer"&gt;Amazon Aurora Global Database execution block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/r53recovery/latest/dg/arc-routing-controls-block.html" rel="noopener noreferrer"&gt;ARC routing control execution block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database-disaster-recovery.html" rel="noopener noreferrer"&gt;Using switchover or failover in Amazon Aurora Global Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/replication.html" rel="noopener noreferrer"&gt;Private image replication in Amazon ECR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/replicate-secrets.html" rel="noopener noreferrer"&gt;Replicate AWS Secrets Manager secrets across Regions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_planning_for_recovery_disaster_recovery.html" rel="noopener noreferrer"&gt;AWS Well-Architected Reliability Pillar: disaster-recovery strategies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>sre</category>
      <category>aws</category>
    </item>
    <item>
      <title>I Built a Platform Engineering Golden Path That Runs for $0 — Every Decision, and Everything That Broke</title>
      <dc:creator>Pradeep Kandepaneni</dc:creator>
      <pubDate>Tue, 21 Jul 2026 05:33:22 +0000</pubDate>
      <link>https://dev.to/pradeep_kandepaneni/i-built-a-platform-engineering-golden-path-that-runs-for-0-every-decision-and-everything-that-114p</link>
      <guid>https://dev.to/pradeep_kandepaneni/i-built-a-platform-engineering-golden-path-that-runs-for-0-every-decision-and-everything-that-114p</guid>
      <description>&lt;p&gt;Originally published on the AWS Builder Center: &lt;a href="https://builder.aws.com/content/3GnbxJrdbgQ5sXNJTRXvHIB5OaV/i-built-a-platform-engineering-golden-path-that-runs-for-dollar0-every-decision-and-everything-that-broke" rel="noopener noreferrer"&gt;https://builder.aws.com/content/3GnbxJrdbgQ5sXNJTRXvHIB5OaV/i-built-a-platform-engineering-golden-path-that-runs-for-dollar0-every-decision-and-everything-that-broke&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/PradeepKandepaneni/golden-path" rel="noopener noreferrer"&gt;https://github.com/PradeepKandepaneni/golden-path&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most "platform engineering" articles show you a diagram. A tidy box labeled Internal Developer Platform, some arrows pointing at happy little developer icons, a paragraph about reducing cognitive load. Then the article ends, and you're holding a concept you can't run, can't fork, and can't check.&lt;/p&gt;

&lt;p&gt;I wanted the opposite. The smallest thing a developer could actually use — push code, get back a tested, hardened, deployed workload, no tickets, no clicking — and I wanted it to cost nothing to run, so anyone reading this could clone it and watch it work instead of trusting me. No AWS bill to reproduce it. No "works on my cluster, promise."&lt;/p&gt;

&lt;p&gt;So that's what I built, and this is the honest write-up. The repo is public and green on every push, so none of what follows requires taking my word for it: github.com/PradeepKandepaneni/golden-path. Every design decision, why I threw out the alternatives, and — the part actually worth your time — the one bug that took the whole thing down and what it taught me about hardened containers.&lt;/p&gt;

&lt;p&gt;What a "golden path" actually is&lt;/p&gt;

&lt;p&gt;The term gets thrown around, so let me pin it down. A golden path is the paved road a platform team hands its developers: one opinionated, well-lit route from "I have code" to "it's running," with the boring-but-critical parts already handled. Tests. Immutable image tags. Health checks. A pod that doesn't run as root. The developer doesn't wire those up per service, and doesn't get to forget them either. They're in the road.&lt;/p&gt;

&lt;p&gt;Platform engineering, stripped of the conference gloss, is mostly the work of building and maintaining those roads so product teams stop rebuilding CI pipelines and Kubernetes manifests from scratch every time they ship something. Pave the road once; every team that drives on it inherits consistency, security, and speed for free. That's the whole pitch.&lt;/p&gt;

&lt;p&gt;The trap — and I nearly walked straight into it — is believing you need a giant portal to deliver any of that. You don't. You need one road that works.&lt;/p&gt;

&lt;p&gt;Decision 1: A template repo, not Backstage&lt;/p&gt;

&lt;p&gt;The flashy way to demo an Internal Developer Platform is Backstage. It's the name everyone knows, it has a slick UI, and "built an IDP with Backstage" looks great on a repo.&lt;/p&gt;

&lt;p&gt;I skipped it, and I'd tell you to skip it too, at least at the start. Backstage is a full-stack app you have to run, host, and babysit. For a solo project meant to show a pattern, that's weeks of wrestling with the portal before you've paved a single road — and a half-finished portal is worse than nothing, because it advertises that you started something big and quit.&lt;/p&gt;

&lt;p&gt;A template repository plus GitHub Actions gets you the same idea — one automated path that scaffolds, tests, scans, and deploys a service — with zero portal code. It's also honestly closer to what a lot of real teams run day to day. Golden paths don't need a portal. They need an opinionated, automated pipeline. That's the thing I built, and it's a thing I could finish.&lt;/p&gt;

&lt;p&gt;If I had to compress the lesson to four words: ship what you can finish.&lt;/p&gt;

&lt;p&gt;Decision 2: kind in CI, not a cloud cluster&lt;/p&gt;

&lt;p&gt;Here's the constraint that shaped everything else: the project had to run for free, in CI and on my laptop.&lt;/p&gt;

&lt;p&gt;That killed running a real EKS cluster as the default. EKS is the right answer in production — it's on the roadmap as the documented production path — but the control plane alone runs about $0.10 an hour before you add a single node, load balancer, or NAT gateway. Leave one up while you iterate and it quietly turns into a $150–300/month surprise. Worse than the money: nobody can cheaply reproduce your work, which defeats the entire point of a demo repo.&lt;/p&gt;

&lt;p&gt;So the default cluster is kind — Kubernetes running inside Docker. Locally, make up creates a cluster, builds the image, loads it, deploys, and smoke-tests it. In CI, the pipeline spins a kind cluster up inside the GitHub Actions runner, deploys to it, checks it, and throws it away when the job ends. My real run created the cluster and had the control plane Ready in 19 seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ready after 19s 💚
Set kubectl context to "kind-golden-path"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The economics are the quiet superpower. On a public repo, Actions minutes are unlimited and free, and the cluster lives and dies inside that free runner. The entire pipeline — a live Kubernetes deploy plus a real HTTP smoke test — costs nothing. Every push. Forever.&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: golden-path
config: kind/kind-config.yaml&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole "provision Kubernetes" step, and it runs on GitHub's dime.&lt;/p&gt;

&lt;p&gt;Decision 3: The image tag is the git SHA. Never :latest&lt;/p&gt;

&lt;p&gt;Small, and non-negotiable. The image is tagged with the short git SHA of the commit that built it, and the manifest is pinned to that exact tag before the deploy.&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;name: Compute image tag from git SHA&lt;br&gt;
id: vars&lt;br&gt;
run: echo "sha=${GITHUB_SHA::7}" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;name: Build image&lt;br&gt;
run: |&lt;br&gt;
docker build \&lt;br&gt;
  -t golden-path:${{ steps.vars.outputs.sha }} \&lt;br&gt;
  --build-arg VERSION=${{ steps.vars.outputs.sha }} .&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;:latest is a lie. It's a mutable label pointing at different bytes over time, so "what's running" and "what I think is running" drift apart the second someone pushes again. A content-addressable tag means a running pod can tell you exactly which commit it came from — and this service does, because I bake the SHA into the binary at build time with an ldflags override. You'll see it pay off at the end of the war story below, in the actual smoke-test output.&lt;/p&gt;

&lt;p&gt;Tiny discipline. Enormous payoff the first time you're staring at a production incident at 2 a.m. wondering what's actually deployed.&lt;/p&gt;

&lt;p&gt;Decision 4: Distroless and non-root, by default&lt;/p&gt;

&lt;p&gt;A golden path only earns its name if good practice is the default, not a thing you remember to bolt on. So the service runs on a distroless base — no shell, no package manager, almost nothing to pivot into — and the pod is hardened right in the manifest:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
securityContext:&lt;br&gt;
  runAsNonRoot: true&lt;br&gt;
  allowPrivilegeEscalation: false&lt;br&gt;
  readOnlyRootFilesystem: true&lt;br&gt;
  capabilities:&lt;br&gt;
    drop: ["ALL"]&lt;/p&gt;

&lt;p&gt;Non-root. No privilege escalation. Read-only root filesystem. Every Linux capability dropped. It's the posture you'd eventually want a cluster-wide policy engine to enforce (Kyverno or OPA, on the roadmap), but here it starts as the baked-in default. The distroless choice also just produces a tiny image — the final artifact came out at 10.4MB, which the pipeline confirmed after loading it onto the node:&lt;/p&gt;

&lt;p&gt;docker.io/library/golden-path   fbab3d2   ae676b1d9427   10.4MB&lt;/p&gt;

&lt;p&gt;And this decision is exactly where the project bit me. Which is the part I actually want to talk about.&lt;/p&gt;

&lt;p&gt;The bug: CreateContainerConfigError&lt;/p&gt;

&lt;p&gt;I pushed the first real version, opened the Actions tab, and watched it fail. Not at build. Not at cluster creation. At the deploy:&lt;/p&gt;

&lt;p&gt;Waiting for deployment "golden-path" rollout to finish: 0 of 2 updated replicas are available...&lt;br&gt;
error: timed out waiting for the condition&lt;br&gt;
Error: Process completed with exit code 1&lt;/p&gt;

&lt;p&gt;Zero of two replicas. A three-minute hang, then a red X.&lt;/p&gt;

&lt;p&gt;Here's the first trap, and it's worth internalizing: that error is useless. "Timed out waiting for the condition" tells you the rollout didn't finish. It says nothing about why. The pods could be failing to pull an image, crashing on boot, flunking a health check, any of a dozen things. kubectl rollout status flattens all of them into the same timeout.&lt;/p&gt;

&lt;p&gt;My honest first guess was an image problem — the tag hadn't matched, the load had failed, something like that. Wrong, as it turned out. But I couldn't even tell I was wrong, because the pipeline wasn't telling me anything.&lt;/p&gt;

&lt;p&gt;So my first change wasn't a fix. It was making the pipeline talk. I added a diagnostics step that only fires when the deploy fails and dumps everything about the broken pods:&lt;/p&gt;

&lt;p&gt;yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Diagnostics (only if the deploy failed)
if: failure()
run: |
kubectl get pods -o wide
kubectl describe pods -l app=golden-path
kubectl get events --sort-by=.lastTimestamp | tail -40
kubectl logs -l app=golden-path --all-containers --tail=100 || true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pushed again. Same timeout — but this time the log told the truth right underneath it:&lt;/p&gt;

&lt;p&gt;NAME                           READY   STATUS                       RESTARTS   AGE&lt;br&gt;
golden-path-6445746597-2czz8   0/1     CreateContainerConfigError   0          3m&lt;br&gt;
golden-path-6445746597-svdct   0/1     CreateContainerConfigError   0          3m&lt;/p&gt;

&lt;p&gt;CreateContainerConfigError. Not an image problem at all — the image had loaded fine (so much for my first guess). Not a crash — the container never even started. Kubernetes was refusing to create it from its config.&lt;/p&gt;

&lt;p&gt;The cause is a genuinely sharp edge. I'd set runAsNonRoot: true, which tells the kubelet to refuse to start a container that would run as UID 0. To enforce that at create time, the kubelet needs to know the numeric user ID the container will run as. My distroless image runs as a non-root user — but it doesn't declare a numeric UID the kubelet can read at that moment. No number to check against, so the kubelet can't prove the container is non-root, and rather than risk running something as root, it refuses. Config error. Pods parked. Rollout hangs. Generic timeout.&lt;/p&gt;

&lt;p&gt;One line fixes it: pin the UID so the check has a number. The distroless nonroot variant runs as UID 65532:&lt;/p&gt;

&lt;p&gt;yaml&lt;br&gt;
securityContext:&lt;br&gt;
  runAsNonRoot: true&lt;br&gt;
  runAsUser: 65532        # &amp;lt;- the fix&lt;br&gt;
  allowPrivilegeEscalation: false&lt;br&gt;
  readOnlyRootFilesystem: true&lt;br&gt;
  capabilities:&lt;br&gt;
    drop: ["ALL"]&lt;/p&gt;

&lt;p&gt;Pushed. Green. And this time the rollout log shows the recovery happening in real time — two replicas coming up over about eight seconds:&lt;/p&gt;

&lt;p&gt;Waiting for deployment "golden-path" rollout to finish: 0 of 2 updated replicas are available...&lt;br&gt;
Waiting for deployment "golden-path" rollout to finish: 1 of 2 updated replicas are available...&lt;br&gt;
deployment "golden-path" successfully rolled out&lt;/p&gt;

&lt;p&gt;Then the in-cluster smoke test hit the service and got a real response back:&lt;/p&gt;

&lt;p&gt;{"message":"hello from the golden path","version":"fbab3d2"}&lt;/p&gt;

&lt;p&gt;Look at that version field: fbab3d2. That's the git SHA from Decision 3, baked into the binary, reported by the running pod. The immutable-tag discipline isn't theory in that line — the workload is telling me exactly which commit it came from.&lt;/p&gt;

&lt;p&gt;Why give a whole section to one missing line of YAML? Because it's the shape of nearly every hardening bug you'll meet. Doing the secure thing — refusing to run as root — is what caused the failure. Had I left the container running as root like a lazy default, it would've started instantly, and I'd have shipped something worse. Security correctness cost me an explicit config I didn't know I owed, and the failure mode (CreateContainerConfigError, hiding behind a generic rollout timeout) pointed nowhere near the actual cause. That gap — between where it breaks and why it breaks — is where most of the real work in this field lives.&lt;/p&gt;

&lt;p&gt;Why "free" is a feature, not a shortcut&lt;/p&gt;

&lt;p&gt;It'd be easy to read the zero-cost constraint as me being cheap. It's the reverse. Free-to-run is what makes the work verifiable, and verifiable is the whole point.&lt;/p&gt;

&lt;p&gt;Piece   Cost&lt;br&gt;
GitHub Actions (public repo)    Free, unlimited&lt;br&gt;
Local kind cluster  Free (your CPU)&lt;br&gt;
All tooling — Go, Terraform, Argo CD, Syft, Trivy, Kyverno    Free / open source&lt;br&gt;
EKS (roadmap, optional) Paid — destroy when done&lt;/p&gt;

&lt;p&gt;Because the pipeline deploys to a throwaway cluster and fails the build unless the app actually serves traffic, a green check here doesn't mean "the code compiled." It means "the golden path works, end to end, and here's the run that proves it." Anyone can open the workflow, click into a run, and see the deploy happen — and clone it to reproduce the whole thing locally without spending a cent. That's a different kind of credibility than a screenshot.&lt;/p&gt;

&lt;p&gt;What's deliberately not done yet&lt;/p&gt;

&lt;p&gt;I want to be straight about scope, because the temptation with a piece like this is to pretend the toy is a platform. It isn't. This is the thin slice: one service, end to end, green. The value is that it's finished and runs, not that it's complete.&lt;/p&gt;

&lt;p&gt;Here's what bolts on next, each a self-contained addition to a spine that already works:&lt;/p&gt;

&lt;p&gt;Supply-chain security. Generate an SBOM with Syft and scan the image with Trivy in CI, failing the build on critical CVEs. One of the loudest DevOps themes of 2026, and it drops straight into the existing pipeline.&lt;br&gt;
Policy as code. Enforce that hardening posture cluster-wide with Kyverno or OPA Gatekeeper, so the golden path is guaranteed rather than merely suggested. (Which, satisfyingly, would have caught my non-root bug at admission time with a far clearer message than CreateContainerConfigError.)&lt;br&gt;
GitOps delivery. Hand deploys to Argo CD so a commit becomes the source of truth instead of an imperative kubectl apply.&lt;br&gt;
Real infrastructure. A Terraform/OpenTofu module for an EKS cluster as the documented production path — spun up to capture evidence, then terraform destroy'd, because it isn't free and nobody should leave it running.&lt;/p&gt;

&lt;p&gt;Building them one at a time, on top of something that already works, is the entire method. A finished thin slice beats a half-built everything, every single time.&lt;/p&gt;

&lt;p&gt;Three things I'd hand to anyone building their own&lt;/p&gt;

&lt;p&gt;Ship the thin slice first. The failure mode of ambitious platform projects isn't bad architecture — it's never finishing. Get one service deploying end to end with a single green check, then add layers. Everything I built hangs off that spine.&lt;/p&gt;

&lt;p&gt;Make your pipeline talk before you make it work. The hours I'd have burned guessing at 0 of 2 replicas available collapsed to minutes the moment I added a diagnostics step. Instrument the failure path early. You'll lean on it constantly.&lt;/p&gt;

&lt;p&gt;Hardening forces explicitness, and that's the job. runAsNonRoot without a runAsUser is the small, sharp, real edge between "I read a tutorial" and "I ran this and hit the wall." Secure defaults make you spell things out; learning where they make you spell things out is most of the craft.&lt;/p&gt;

&lt;p&gt;The repo is here, green and free to run: github.com/PradeepKandepaneni/golden-path. Clone it, run make up, break it, and tell me what you'd pave next.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
