<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en_IN"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://rajatgupta.work/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rajatgupta.work/" rel="alternate" type="text/html" hreflang="en_IN" /><updated>2026-10-23T14:59:41+05:30</updated><id>https://rajatgupta.work/feed.xml</id><title type="html">Rajat Gupta</title><subtitle>AVP Engineering at RBIH. I lead platform, reliability, and security engineering for systems where downtime has a real cost.</subtitle><author><name>Rajat Gupta</name></author><entry><title type="html">GitOps: Beyond the Hype</title><link href="https://rajatgupta.work/posts/gitops/" rel="alternate" type="text/html" title="GitOps: Beyond the Hype" /><published>2026-03-25T12:00:00+05:30</published><updated>2026-03-25T12:00:00+05:30</updated><id>https://rajatgupta.work/posts/gitops</id><content type="html" xml:base="https://rajatgupta.work/posts/gitops/"><![CDATA[<p>It is holiday, and your on-call engineer is staring at a production cluster that looks nothing like what is in Git. Someone ran a <code class="language-plaintext highlighter-rouge">kubectl apply</code> three weeks ago to “quickly fix” a routing issue. That fix was never committed. Now a GitOps reconciliation loop just overwrote it, and 40% of your traffic is hitting a service that no longer exists.</p>

<figure class="figure">
  <img src="/assets/img/20260325-gitops/1.webp" alt="Two illustrations side by side. Left: Git branches, main, two feature branches and a hotfix, flowing cleanly into a Kubernetes cluster. Right: an engineer with head in hands at a desk of monitors showing errors, surrounded by broken YAML files." width="1376" height="768" fetchpriority="high" />
  <figcaption>GitOps as pitched, and GitOps after a fix that never made it into Git.</figcaption>
</figure>

<p>I have seen this exact scenario play out at two different companies. Both had adopted GitOps. Both had Argo CD dashboards glowing green. And both learned the hard way that declaring “we do GitOps” is very different from actually doing GitOps well.</p>

<p>The numbers suggest GitOps has crossed the chasm. According to the CNCF 2024 survey (n=689), 77% of cloud-native organizations have adopted some form of GitOps. Octopus Deploy’s research (n=660) found 93% plan to continue or increase their investment. Argo CD alone commands roughly 60% market share with an NPS of 79.</p>

<p>But here is the thing. Adoption numbers do not tell you whether teams are succeeding. And behind the impressive stats, there is a messier, more interesting story about what GitOps actually looks like in practice, where it genuinely shines, and where it quietly falls apart.</p>

<h2 id="the-promise-vs-the-reality">The Promise vs. The Reality</h2>

<p>GitOps, at its core, is a simple idea: declare your desired system state in Git, and let a controller reconcile reality to match. You get version history, pull requests as change management, and automated rollbacks. The pitch practically sells itself.</p>

<p>And for many teams, the pitch delivers. The data backs this up. Organizations practicing GitOps consistently show improvements across all four DORA metrics. Deployment frequency goes up. Lead time drops. Change failure rates decrease. Mean time to recovery shrinks.</p>

<p>But GitOps did not emerge in a vacuum. It evolved from infrastructure-as-code practices, configuration management, and the broader shift toward declarative systems. Weaveworks coined the term in 2017, built a company around it, and then shut down in February 2024. The fact that the company that invented GitOps could not survive as a business should tell you something about the gap between the idea and the business of selling the idea.</p>

<p>What actually matters is not whether you “do GitOps.” It is whether the principles behind it (declarative configuration, version-controlled desired state, automated reconciliation, continuous feedback loops) are solving real problems for your team. The label is far less important than the outcome.</p>

<h2 id="where-gitops-actually-delivers">Where GitOps Actually Delivers</h2>

<p>Let me be specific. Not “GitOps is great for deployments” specific, but “here is what happened at real companies with real numbers” specific.</p>

<h3 id="speed-that-compounds">Speed That Compounds</h3>

<p>Intuit runs over 2,500 services on Kubernetes. After adopting Argo CD at scale, their mean time to recovery dropped from 45 minutes to under 5 minutes. Deployments that previously took days now complete in minutes. That is not a marginal improvement. That is a fundamentally different operating model where deploying becomes so cheap that you do it constantly, which in turn makes each deployment smaller and safer.</p>

<p>Zepto, the Indian quick-commerce company, reduced developer onboarding from 2 days to 10 minutes using GitOps-driven environment provisioning. New engineers go from “here is your laptop” to “here is your running development environment” almost immediately. When you are scaling engineering teams aggressively, that kind of friction reduction is not a nice-to-have. It is a competitive advantage.</p>

<p>Slite went from 4 deployments per day to 20 after implementing GitOps. More interestingly, they reported that the increase was not because they forced more deployments. Developers simply started deploying more often because the process became boring and predictable. That is the real sign of a healthy deployment pipeline: when shipping code is unremarkable.</p>

<h3 id="scale-without-proportional-headcount">Scale Without Proportional Headcount</h3>

<p>This is where GitOps gets genuinely impressive. Deutsche Telekom manages over 200 Kubernetes clusters with a platform team of just 10 engineers using Flux. That ratio (20 clusters per engineer) would be unthinkable with imperative management approaches. The declarative model means adding cluster number 201 looks almost identical to adding cluster number 2.</p>

<p>CERN took cluster deployment time from 3 hours down to 15 minutes. When you are running physics experiments that require spinning up and tearing down compute environments frequently, that is the difference between “we can run this experiment today” and “maybe next week.”</p>

<p>Adobe processes over 4 million containers daily using a pure-pull GitOps architecture. Their approach is worth studying because they explicitly chose pull-based reconciliation over push-based deployment pipelines. The pull model means the cluster itself decides when to converge, which eliminates an entire class of “the deploy pipeline had credentials to push but the cluster was not ready” failures.</p>

<figure class="figure">
  <img src="/assets/img/20260325-gitops/3.webp" alt="GitOps at scale, case-study metrics from production teams. Deutsche Telekom: 200 Kubernetes clusters managed by 10 engineers. Adobe: 4 million containers a day on a pure-pull GitOps architecture. Intuit: MTTR down from 45 minutes to under 5 across 2,500+ services. Zepto: developer onboarding down from 2 days to 10 minutes with GitOps-driven environments." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>Four of the production results described above.</figcaption>
</figure>

<h3 id="the-compliance-story-nobody-talks-about">The Compliance Story Nobody Talks About</h3>

<p>Tidepool, a healthcare company building diabetes management tools, uses GitOps to maintain HIPAA compliance. Every configuration change goes through a pull request. Every pull request has a review trail. Every deployment is traceable to a specific commit. Their auditors love it because the audit trail is not a separate system bolted on after the fact. It is the deployment system itself.</p>

<p>Mettle (part of NatWest Group) saw 50% faster and 65% more frequent deployments after adopting GitOps. But the more interesting metric for a financial services company is that their compliance reviews became faster because reviewers could look at a Git diff instead of trying to understand what changed in a running system.</p>

<p>This pattern keeps showing up in regulated industries. The version control that GitOps requires for operational reasons happens to be exactly what compliance teams need for audit reasons. It is one of the few cases in engineering where the thing that makes developers faster also makes auditors happier.</p>

<h2 id="the-architecture-decision-you-are-actually-making">The Architecture Decision You Are Actually Making</h2>

<p>When teams say “we are adopting GitOps,” they are usually making a series of smaller architectural decisions that deserve more scrutiny than they typically get.</p>

<figure class="figure">
  <img src="/assets/img/20260325-gitops/2.webp" alt="Push and pull deployment compared. Push-based CI/CD: the CI pipeline holds credentials and pushes from the container registry into the Kubernetes cluster, a wider blast radius. Pull-based GitOps: an agent such as Argo CD or Flux runs inside the cluster, pulls desired state from the Git repo and reconciles, so credentials stay inside the cluster and the blast radius is reduced." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>Push gives your CI system credentials to the cluster. Pull keeps them inside it.</figcaption>
</figure>

<h3 id="push-vs-pull-it-matters-more-than-you-think">Push vs. Pull: It Matters More Than You Think</h3>

<p>In a push model, your CI pipeline builds an artifact and pushes it to your cluster. In a pull model, an agent running inside your cluster watches a Git repository and pulls changes when it detects drift.</p>

<p>The pull model is what most people mean when they say “GitOps.” It has a meaningful security advantage: your cluster never needs to expose an API endpoint for external systems to push to. The credentials flow is simpler. The blast radius of a compromised CI system is smaller.</p>

<p>Adobe’s choice of pure-pull architecture is instructive. At their scale (4M+ containers/day), the coordination problems with push-based deployments become severe. With pull, each cluster independently converges to the desired state. There is no central orchestrator that can become a bottleneck or single point of failure.</p>

<p>Here is what a basic Argo CD Application manifest looks like:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Argo CD Application - the core GitOps primitive</span>
<span class="c1"># This tells Argo CD: "make the cluster look like what's in this Git repo"</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">argoproj.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Application</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">my-service</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">argocd</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">project</span><span class="pi">:</span> <span class="s">default</span>
  <span class="na">source</span><span class="pi">:</span>
    <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/org/gitops-config</span>
    <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">main</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">services/my-service/overlays/production</span>
  <span class="na">destination</span><span class="pi">:</span>
    <span class="na">server</span><span class="pi">:</span> <span class="s">https://kubernetes.default.svc</span>
    <span class="na">namespace</span><span class="pi">:</span> <span class="s">production</span>
  <span class="na">syncPolicy</span><span class="pi">:</span>
    <span class="na">automated</span><span class="pi">:</span>
      <span class="na">prune</span><span class="pi">:</span> <span class="no">true</span>        <span class="c1"># Remove resources not in Git</span>
      <span class="na">selfHeal</span><span class="pi">:</span> <span class="no">true</span>     <span class="c1"># Revert manual changes</span>
    <span class="na">syncOptions</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">CreateNamespace=true</span>
    <span class="na">retry</span><span class="pi">:</span>
      <span class="na">limit</span><span class="pi">:</span> <span class="m">5</span>
      <span class="na">backoff</span><span class="pi">:</span>
        <span class="na">duration</span><span class="pi">:</span> <span class="s">5s</span>
        <span class="na">maxDuration</span><span class="pi">:</span> <span class="s">3m</span>
        <span class="na">factor</span><span class="pi">:</span> <span class="m">2</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">selfHeal: true</code> setting is where things get interesting and also where that 2 AM incident I described can happen. Self-healing means the controller will revert any manual changes to match Git. That is exactly what you want in production. It is also exactly what will bite you if someone made an undocumented manual change that your system depends on.</p>

<h3 id="mono-repo-vs-multi-repo-the-scaling-question">Mono-Repo vs. Multi-Repo: The Scaling Question</h3>

<p>Skyscanner processes over 10,000 deployments per month using a cell-based architecture. They learned early that a single GitOps repository becomes a bottleneck at scale. Pull request contention, merge conflicts on generated YAML, and slow Git operations on large repos all compound.</p>

<p>The pattern that works at scale is typically: one repository per team or bounded context for application configs, with a separate repository (or set of repositories) for platform-level configuration. This mirrors how most organizations actually make decisions. Application teams own their deployment configuration, and platform teams own the infrastructure that applications run on.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Kustomize overlay structure - how teams customize shared bases</span>
<span class="c1"># Base defines the common configuration, overlays customize per environment</span>
<span class="c1"># File: services/my-service/overlays/production/kustomization.yaml</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kustomize.config.k8s.io/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Kustomization</span>
<span class="na">namespace</span><span class="pi">:</span> <span class="s">production</span>
<span class="na">resources</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">../../base</span>
<span class="na">patchesStrategicMerge</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">deployment-patch.yaml</span>
<span class="na">configMapGenerator</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my-service-config</span>
    <span class="na">literals</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">LOG_LEVEL=warn</span>
      <span class="pi">-</span> <span class="s">ENABLE_PROFILING=false</span>
<span class="na">images</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my-service</span>
    <span class="na">newTag</span><span class="pi">:</span> <span class="s">v2.4.1</span>  <span class="c1"># Updated via automated PR from CI pipeline</span>
</code></pre></div></div>

<h3 id="the-secret-management-problem">The Secret Management Problem</h3>

<p>Here is something that every GitOps tutorial glosses over: secrets do not belong in Git. But your applications need secrets. So you end up building a parallel system for secret delivery that operates outside your GitOps workflow.</p>

<p>The common solutions (Sealed Secrets, External Secrets Operator, SOPS, Vault integration) all work. But they all add complexity that is not captured in the “Git is your single source of truth” narrative. Your actual source of truth is Git plus Vault plus whatever secret rotation system you are running. Acknowledging this is important because it changes how you think about disaster recovery and environment reproducibility.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># External Secrets Operator - bridging GitOps and secret management</span>
<span class="c1"># The ESO definition lives in Git; the actual secret lives in Vault</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">external-secrets.io/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ExternalSecret</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">my-service-secrets</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">production</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">refreshInterval</span><span class="pi">:</span> <span class="s">1h</span>
  <span class="na">secretStoreRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">vault-backend</span>
    <span class="na">kind</span><span class="pi">:</span> <span class="s">ClusterSecretStore</span>
  <span class="na">target</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">my-service-secrets</span>
    <span class="na">creationPolicy</span><span class="pi">:</span> <span class="s">Owner</span>
  <span class="na">data</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretKey</span><span class="pi">:</span> <span class="s">DATABASE_URL</span>
      <span class="na">remoteRef</span><span class="pi">:</span>
        <span class="na">key</span><span class="pi">:</span> <span class="s">secret/data/production/my-service</span>
        <span class="na">property</span><span class="pi">:</span> <span class="s">database_url</span>
    <span class="pi">-</span> <span class="na">secretKey</span><span class="pi">:</span> <span class="s">API_KEY</span>
      <span class="na">remoteRef</span><span class="pi">:</span>
        <span class="na">key</span><span class="pi">:</span> <span class="s">secret/data/production/my-service</span>
        <span class="na">property</span><span class="pi">:</span> <span class="s">api_key</span>
</code></pre></div></div>

<h2 id="the-uncomfortable-truths">The Uncomfortable Truths</h2>

<p>Not everyone is sold on GitOps, and some of the skeptics have very good points.</p>

<h3 id="the-gitops-is-a-placebo-argument">The “GitOps is a Placebo” Argument</h3>

<p>Steve Smith, author of <em>Measuring Continuous Delivery</em>, argues that GitOps is essentially a placebo: it gives teams the feeling of control without necessarily delivering better outcomes. His point is that the real improvements come from good deployment practices (small batches, automated testing, monitoring), and GitOps is just one possible implementation of those practices, not a requirement.</p>

<p>There is some truth here. I have seen teams adopt Argo CD, set up beautiful GitOps workflows, and still have terrible deployment outcomes because their tests were flaky, their monitoring was nonexistent, and their services were not designed for graceful degradation. GitOps does not fix bad engineering. It just makes bad engineering more version-controlled.</p>

<h3 id="the-kelsey-hightower-critique">The Kelsey Hightower Critique</h3>

<p>Kelsey Hightower has called GitOps a “dead end” for managing infrastructure and databases. His argument is that Git is great for tracking desired state of stateless workloads, but breaks down for stateful systems where the current state matters as much as the desired state.</p>

<p>He is right about the edges. Try managing a database schema migration through GitOps and you will quickly discover that “desired state” for a database is not the same as “desired state” for a Kubernetes Deployment. One is declarative and convergent. The other is sequential and path-dependent. Pretending otherwise leads to tools that technically work but feel like you are fighting the abstraction.</p>

<h3 id="the-hidden-tax">The Hidden Tax</h3>

<p>Ádám Sándor, a platform engineering consultant, estimates that teams spend 30% or more of their development time building and maintaining automation around their GitOps repositories. The YAML generation, the PR automation, the validation webhooks, the promotion pipelines between environments. None of this is trivial, and most of it is custom to your organization.</p>

<p>Viktor Farcic points out something even more uncomfortable: many GitOps tools violate their own principles. Argo CD, for instance, stores significant state in its own database (sync status, health checks, RBAC policies) that is not in Git. The tool that is supposed to ensure Git is the source of truth has its own source of truth that is not Git.</p>

<p>Kaspar von Grünberg, founder of Humanitec, describes GitOps at scale as “extremely frustrating.” When you have hundreds of services and multiple environments, the sheer volume of YAML that needs to be generated, validated, and promoted becomes its own engineering problem.</p>

<h2 id="the-pitfalls-i-keep-seeing">The Pitfalls I Keep Seeing</h2>

<p>After working with teams adopting GitOps across different organizations, a few failure patterns show up again and again.</p>

<figure class="figure">
  <img src="/assets/img/20260325-gitops/4.webp" alt="A road from GitOps adoption to GitOps maturity with five warning signs along it: YAML Ocean, Secret Sprawl, Drift Panic, Multi-Environment Maze and Observability Gap." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>The five pitfalls covered in this section.</figcaption>
</figure>

<p><strong>The YAML Ocean.</strong> Teams start with clean, well-organized repositories. Six months later, they have thousands of nearly identical YAML files with subtle differences between environments. Kustomize overlays help. Helm charts help. But the fundamental problem is that Kubernetes configuration is verbose, and GitOps means all of it lives in your repository. loveholidays, which runs 1,500+ deployments per month, eventually migrated from Flux to Argo CD partly because managing configuration at that scale required better tooling for visualization and multi-tenancy.</p>

<p><strong>The Drift Panic.</strong> Self-healing sounds great until it heals something you did not want healed. A common scenario: an engineer makes an emergency change during an incident. The GitOps controller reverts it. The incident gets worse. Now you are fighting both the outage and your own tooling. The fix is having clear runbook procedures for pausing reconciliation during incidents, but many teams learn this the hard way.</p>

<p><strong>The Secret Sprawl.</strong> As mentioned earlier, secrets sit outside your GitOps workflow. But teams often do not realize how many things are effectively secrets: database connection strings, API keys, TLS certificates, OAuth tokens, service account credentials. Each one needs its own lifecycle management. Each one is a potential point of failure that your GitOps dashboard will not show you.</p>

<p><strong>The Multi-Environment Maze.</strong> Promoting changes from dev to staging to production through Git branches or directory structures seems straightforward. In practice, the promotion logic becomes the most complex part of the system. Argo CD’s ApplicationSets help, but they add another layer of abstraction that teams need to understand and debug.</p>

<p><strong>The Observability Gap.</strong> GitOps tells you what should be running. It does not tell you if what is running is actually working. Teams that invest heavily in GitOps tooling but underinvest in observability end up with clusters that are perfectly reconciled and completely broken. The dashboard says everything is in sync. The users say the site is down.</p>

<h2 id="making-it-actually-work-practical-guidance">Making It Actually Work: Practical Guidance</h2>

<p>If you are adopting GitOps (or trying to fix a GitOps implementation that is not working), here is what I would focus on.</p>

<figure class="figure">
  <img src="/assets/img/20260325-gitops/5.webp" alt="A winding road from a GitOps Adoption signpost to a GitOps Maturity marker, passing the same five hazards: YAML Ocean, Drift Panic, Secret Sprawl, Multi-Environment Maze and Observability Gap." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>The road from adoption to maturity passes the same five pitfalls.</figcaption>
</figure>

<p><strong>Start with one team, one repo, one cluster.</strong> The teams that succeed with GitOps almost always start small. Get the workflow right for a single service before you try to scale it to your entire platform. Monzo, which runs 100+ daily production deployments, did not start there. They grew into it incrementally.</p>

<p><strong>Invest in the developer experience around GitOps, not just the GitOps tooling.</strong> The reconciliation controller is maybe 20% of the work. The other 80% is: how do developers create new services, how do they promote changes between environments, how do they debug when something goes wrong, and how do they make emergency changes safely. If your developers need to understand Kustomize overlays to ship a feature, you have a developer experience problem.</p>

<p><strong>Build escape hatches from day one.</strong> You need a documented, practiced process for pausing GitOps reconciliation during incidents. You need a way to make emergency manual changes that are automatically captured back into Git afterward. Pretending that you will never need to break the GitOps workflow is the fastest way to ensure that when you do break it, it will be chaotic.</p>

<p><strong>Treat your GitOps repository as a product.</strong> It needs documentation. It needs a clear structure. It needs guardrails (admission controllers, PR validation, automated testing of manifests). It needs someone who owns it. The Argo CD 2024 survey shows that 42% of users are now managing 500+ applications (up from 15% in 2023). At that scale, repo quality is the difference between a team that ships confidently and a team that is afraid to merge.</p>

<p><strong>Do not GitOps everything.</strong> Databases, stateful workloads, one-off migration jobs, infrastructure provisioning. These can be managed declaratively, but they do not always benefit from the continuous reconciliation loop that defines GitOps. Use the right tool for the job. GitOps for stateless application deployments. Terraform or Pulumi for infrastructure. Migration frameworks for database schemas.</p>

<h2 id="where-this-is-heading">Where This Is Heading</h2>

<p>The Weaveworks shutdown was a watershed moment for the GitOps community. The company that coined the term and created Flux could not sustain itself as a business. But Flux survived as a CNCF project, and Argo CD continued to grow. The tools outlived their origin story.</p>

<p>What I see emerging is a more mature, less ideological approach to GitOps. The early days were about purity: everything in Git, always reconciling, no exceptions. The current reality is more pragmatic. Teams use GitOps where it makes sense and other approaches where it does not. They combine pull-based reconciliation with push-based deployments. They acknowledge that Git is the source of truth for some things and not for others.</p>

<p>The 77% adoption figure from CNCF is impressive, but the more telling number might be the 93% who plan to continue or increase investment. That suggests GitOps is not a fad that teams are abandoning once the hype fades. It is a practice that delivers enough value to keep, even when teams discover its rough edges.</p>

<p>The hype was never really about GitOps itself. It was about the promise of making infrastructure as reviewable, auditable, and reversible as application code. That promise is real. It just requires more work, more nuance, and more humility than the conference talks suggest.</p>

<p>If your team is considering GitOps, go for it. Just go in with open eyes, a small scope to start, and the understanding that the Git repository is the beginning of the story, not the end of it.</p>]]></content><author><name>Rajat Gupta</name></author><category term="gitops" /><category term="kubernetes" /><category term="devops" /><summary type="html"><![CDATA[What GitOps looks like in practice: where it delivers, with numbers from Intuit, Deutsche Telekom and Adobe, where it quietly falls apart, and how to make it work.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Terraform at Scale: Patterns and Anti-Patterns</title><link href="https://rajatgupta.work/posts/terraform-at-scale/" rel="alternate" type="text/html" title="Terraform at Scale: Patterns and Anti-Patterns" /><published>2026-03-19T14:00:00+05:30</published><updated>2026-03-19T14:00:00+05:30</updated><id>https://rajatgupta.work/posts/terraform-at-scale</id><content type="html" xml:base="https://rajatgupta.work/posts/terraform-at-scale/"><![CDATA[<p>Only 6% of organizations have achieved full cloud codification. That means 94% of companies running Terraform are still managing significant chunks of their infrastructure by clicking around in a console. And according to the Firefly State of IaC 2025 report, 65% of respondents say cloud complexity has actually <em>increased</em> over the last two years, despite better tooling. Terraform is not the problem. How we use it is.</p>

<p>This post is about what separates teams that run Terraform smoothly at scale from those drowning in state file corruption, 45-minute plan times, and midnight pages caused by drift they did not know existed. I have spent years working with infrastructure at scale, and the patterns here come from studying how companies like Cloudflare, Stripe, Uber, and Benchling actually operate, not from vendor marketing.</p>

<h2 id="the-problem-with-just-use-terraform">The Problem with “Just Use Terraform”</h2>

<figure class="figure">
  <img src="/assets/img/terraform-at-scale/01.webp" alt="Timeline of four eras of Terraform use. Laptop era, 2014 to 2017: engineers run Terraform from their laptops with shared state and manual changes. CI/CD era, 2018 to 2020: plans and applies run in CI pipelines. Platform abstraction era, 2021 to 2022: internal platforms and self-service tools generate Terraform behind the scenes. AI and scale era, 2024 to 2026: context-aware AI with guardrails runs large-scale, governed Terraform." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>How Terraform use moved from laptops to governed platforms.</figcaption>
</figure>

<p>Terraform holds roughly 34% of the configuration management market. The AWS provider alone has crossed 5 billion historical downloads. It is, by any measure, the dominant IaC tool.</p>

<p>But dominance does not equal maturity. Most organizations are stuck in what I call the “laptop era” of Terraform usage: engineers running <code class="language-plaintext highlighter-rouge">terraform apply</code> from their machines, sharing state files over S3 with no locking, and treating modules like copy-paste templates. This worked when you had 50 resources. It falls apart at 500. It becomes actively dangerous at 5,000.</p>

<p>The real shift that high-performing organizations have made is treating Terraform not as a CLI tool, but as the backend execution engine of a governed, automated platform. Application developers in these environments rarely touch HCL directly. They interact with internal platforms, constrained modules, or self-service portals that generate Terraform configurations behind the scenes.</p>

<p>The 2024 DORA report (surveying over 39,000 technology professionals) adds an important wrinkle here. AI-assisted code generation is accelerating IaC authoring, with tools like GitHub Copilot delivering a roughly 55% productivity boost for HCL syntax generation. But the same report found that for every 25% increase in AI adoption, delivery stability drops by 7.2% and throughput decreases by 1.5%. Writing Terraform faster is not the bottleneck. Validating, testing, and safely deploying it is.</p>

<p>That tension, between the speed of writing infrastructure code and the ability to safely ship it, is the central challenge of running Terraform at scale.</p>

<h2 id="how-the-best-teams-actually-run-terraform">How the Best Teams Actually Run Terraform</h2>

<h3 id="kill-manual-execution-first">Kill Manual Execution First</h3>

<p>The single most impactful change you can make is banning <code class="language-plaintext highlighter-rouge">terraform apply</code> from developer laptops. Full stop.</p>

<p>Cloudflare learned this the hard way. As the company scaled to hundreds of internal accounts and dozens of products, relying on manual UI dashboard changes and individual administrator accounts led to untracked configuration drift, undocumented modifications, and security risks around API key management. Their fix was aggressive: they moved to a strict monorepo for all Terraform configurations, deployed Atlantis to tie every infrastructure change to a GitHub pull request, and built a custom Zero Trust toggle that forces the Cloudflare UI into read-only mode. No one can make manual changes outside of the codebase. Period.</p>

<p>The result: 100% peer-reviewed accountability for all infrastructure changes, API tokens managed via service accounts instead of individual credentials, and a daily <code class="language-plaintext highlighter-rouge">terraform apply</code> pipeline that catches and overwrites unauthorized drift automatically.</p>

<p>Benchling tells a similar story from a different angle. Their engineers were running Terraform locally and, to minimize the pain of slow execution, they crammed everything into massive monolithic workspaces (some with over 4,000 resources). The blast radius was the entire infrastructure. A single typo could lock the state for the whole company. When they migrated to HCP Terraform and broke their monolith into granular, microservice-scoped workspaces, they saved an estimated 8,000 hours of engineering time that had been wasted on manual coordination and troubleshooting.</p>

<p>The pattern here is clear: tooling friction drives architectural decisions. When running Terraform is painful, engineers consolidate to minimize runs. When you automate execution, you can afford to partition aggressively.</p>

<h3 id="module-design-determines-long-term-maintainability">Module Design Determines Long-Term Maintainability</h3>

<p>Here is the thing about Terraform modules: they are easy to write and incredibly hard to write <em>well</em>.</p>

<p>The most common failure is what I call the “God module,” a single module that tries to do everything for everyone by exposing every possible provider attribute as a variable. It starts with good intentions (DRY principles, reuse) and ends with a module that has 200 variables, is impossible to test comprehensively, and breaks downstream consumers every time you refactor.</p>

<p>Rene Schach, speaking at HashiDays 2025, put it well: “Every module should serve a specific purpose and audience. Modules should follow functional separation, performing one function well. A kubernetes_cluster module should only handle cluster configuration, while networking belongs in its own module.”</p>

<p>The second critical design principle is aligning module boundaries with resource lifecycles. Short-lived infrastructure (ephemeral dev environments, stateless compute) must never share a module with long-lived foundational components (core VPC routing, DNS zones). Mixing volatility profiles guarantees that a routine update to a dev environment will eventually trigger the accidental destruction of your core networking stack.</p>

<p>Shopify demonstrates what good module design enables at scale. They built highly abstracted modules for DNS routing and Snowflake data pipelines. Application owners can now update complex, traffic-managed domains by adjusting lightweight <code class="language-plaintext highlighter-rouge">domain.tf</code> files, which automatically triggers failover logic underneath. The developer does not need deep infrastructure expertise. The module encapsulates it. This, combined with a hybrid AI-human automation model (LLMs trained on company-specific patterns, with mandatory human oversight for critical infrastructure), allowed Shopify to reach a 90% automation rate for standard deployments.</p>

<figure class="figure">
  <img src="/assets/img/terraform-at-scale/02.webp" alt="Two module designs side by side. Left, labelled anti-pattern: one God module with dozens of input variables. Right, labelled pattern: right-sized network, compute, database and monitoring modules, each with a few inputs, passing outputs such as vpc_id, subnet_ids and endpoint to the next." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>One module that does everything, against small modules wired together through their outputs.</figcaption>
</figure>

<h3 id="policy-as-code-is-no-longer-optional">Policy-as-Code is No Longer Optional</h3>

<p>If you are running Terraform at scale without automated policy enforcement, you are one bad pull request away from a public S3 bucket or an unencrypted database.</p>

<p>Fannie Mae, operating in one of the most regulated environments imaginable, solved this by integrating HashiCorp Sentinel into a 5-stage Policy-as-Code pipeline. Every infrastructure policy is peer-reviewed, tested against Terraform plan mocks, and initially deployed in “advisory” mode before being promoted to “hard mandatory” enforcement. The key insight from their implementation: you must ensure backward compatibility. If you enforce new rules instantly and aggressively, you will break older infrastructure pipelines that were provisioned before the policy existed.</p>

<p>DoorDash took a vendor-neutral approach, coupling Terraform with Open Policy Agent (OPA) to enforce cloud-native infrastructure rules, authorization limits, and network routing constraints via a GitOps workflow on GitHub. Their CI/CD pipeline catches breaking changes, overlapping CIDR blocks, and security vulnerabilities before any code hits production.</p>

<p>The choice between Sentinel and OPA is less important than having <em>something</em> automated in the path. Manual security reviews do not scale, and relying on human vigilance for every <code class="language-plaintext highlighter-rouge">terraform plan</code> output is a recipe for missed misconfigurations.</p>

<h3 id="ai-is-a-multiplier-not-a-replacement">AI is a Multiplier, Not a Replacement</h3>

<p>The numbers on AI-assisted Terraform are genuinely impressive when done right. Uber deployed context-aware LLMs trained on their internal architectural patterns and compliance policies, reducing infrastructure configuration errors by 85%. Stripe built autonomous coding agents (“Minions”) that author end-to-end pull requests for infrastructure changes, cutting provisioning time by 70%. Both companies share a critical detail: the AI is tightly coupled with organizational context and strict policy guardrails.</p>

<p>What this actually means in practice is that generic AI (pointing ChatGPT at your Terraform codebase) produces mediocre results. Practitioners report that generic LLMs hallucinate provider attributes, create circular dependencies, and generate configurations that pass <code class="language-plaintext highlighter-rouge">terraform validate</code> but fail spectacularly on <code class="language-plaintext highlighter-rouge">terraform apply</code>. The emerging solution is grounding LLMs in your organization’s specific state files and provider schemas via Model Context Protocols (MCP).</p>

<p>But the DORA data should give everyone pause. The 7.2% drop in delivery stability correlated with AI adoption is not a minor footnote. As RedMonk analyst R. Stephens framed it through the Theory of Constraints: “Writing code isn’t the bottleneck to deploying reliable applications.” Generating Terraform faster via AI solves a problem that was not the actual constraint, while actively worsening the real bottlenecks of security review, state management, and integration testing.</p>

<figure class="figure">
  <img src="/assets/img/terraform-at-scale/03.webp" alt="AI in Terraform and infrastructure automation, promise versus risk. Company results: Stripe, 70% reduction in provisioning time. Uber, 85% reduction in configuration errors. Shopify, 90% automation rate for standard deployments. Industry average, 55% productivity boost from AI-assisted IaC. Below, DORA 2024 industry-wide impact of AI adoption: delivery stability down 7.2%, throughput down 1.5%." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>Company wins next to the DORA 2024 industry numbers. AI helps most when guardrails, policies and tests are in place.</figcaption>
</figure>

<h2 id="the-pitfalls-you-will-recognize">The Pitfalls You Will Recognize</h2>

<h3 id="the-monolithic-state-file">The Monolithic State File</h3>

<p>What it looks like: One <code class="language-plaintext highlighter-rouge">terraform.tfstate</code> file managing your entire cloud environment. Plan times measured in minutes. A minor syntax error locks state for the whole organization.</p>

<p>Why it happens: Small teams start with one repo and one state file. It works. They never revisit the decision as the company grows to thousands of resources.</p>

<p>What to do instead: Partition aggressively into logical workspaces (core-network, app-compute, databases). Use <code class="language-plaintext highlighter-rouge">terraform_remote_state</code> data sources or dedicated remote outputs to pass variables between isolated states. Benchling’s 4,000-resource monoliths are the cautionary tale here.</p>

<h3 id="hardcoded-secrets-and-environment-ids">Hardcoded Secrets and Environment IDs</h3>

<p>What it looks like: API keys, database passwords, or exact AMI IDs sitting in <code class="language-plaintext highlighter-rouge">.tf</code> or <code class="language-plaintext highlighter-rouge">.tfvars</code> files committed to Git. Code that cannot be promoted from dev to staging to production without manual edits.</p>

<p>Why it happens: Setting up secure secret injection feels like overhead early on. Developers underestimate how Terraform stores sensitive data in plain text within the state file.</p>

<p>What to do instead: Use native data sources (AWS Secrets Manager, HashiCorp Vault) to fetch secrets at runtime. Mark all sensitive variables with <code class="language-plaintext highlighter-rouge">sensitive = true</code>. Never commit <code class="language-plaintext highlighter-rouge">.tfvars</code> files containing real credentials.</p>

<h3 id="ignoring-drift">Ignoring Drift</h3>

<p>What it looks like: Someone makes a “quick fix” in the AWS console during an incident. The next automated <code class="language-plaintext highlighter-rouge">terraform apply</code> detects the discrepancy and destroys the manual change, potentially causing a second outage.</p>

<p>Why it happens: Panicked incident response. Operational convenience. The cloud console is right there.</p>

<p>What to do instead: Run automated drift detection (a <code class="language-plaintext highlighter-rouge">terraform plan</code> on a cron job every 6 hours, alerting on discrepancies). At the advanced end, follow Cloudflare’s approach and make the console read-only.</p>

<h3 id="misusing-count-for-resource-iteration">Misusing <code class="language-plaintext highlighter-rouge">count</code> for Resource Iteration</h3>

<p>What it looks like: You remove item 2 from a list of 5 resources managed with <code class="language-plaintext highlighter-rouge">count</code>. Terraform shifts all subsequent indices and destroys/recreates resources 3, 4, and 5. One of them is a production database.</p>

<p>Why it happens: <code class="language-plaintext highlighter-rouge">count</code> was the original iteration mechanism in older Terraform versions. Many engineers are unaware of <code class="language-plaintext highlighter-rouge">for_each</code>.</p>

<p>What to do instead: Always use <code class="language-plaintext highlighter-rouge">for_each</code> with a map or set of strings. Resource identifiers tie to immutable string keys instead of shifting numerical indices.</p>

<h2 id="what-you-can-do-next-week">What You Can Do Next Week</h2>

<figure class="figure">
  <img src="/assets/img/terraform-at-scale/04.webp" alt="Terraform execution at scale, as a flow: a developer writes Terraform code and opens a GitHub pull request. Atlantis or CI runs terraform plan. Policy as code checks it with OPA and Sentinel. A human reviews and approves. A CI runner runs terraform apply, assuming a role in the target account. State lives in a remote S3 backend with a DynamoDB lock. Manual console changes are crossed out." width="1376" height="768" loading="lazy" decoding="async" />
  <figcaption>Every change goes through a pull request, a policy check and a CI runner. Nobody applies from a laptop or the console.</figcaption>
</figure>

<p>Here is a concrete checklist, ordered by impact:</p>

<ul>
  <li><strong>Audit your execution model.</strong> If anyone on your team runs <code class="language-plaintext highlighter-rouge">terraform apply</code> from a laptop against production, fix that first. Set up Atlantis, GitHub Actions, or HCP Terraform. Deny manual provisioning via IAM policies.</li>
  <li><strong>Measure your blast radius.</strong> Count the resources in your largest state file. If it is over 200, start planning a partition. Use the directory structure below as a reference:</li>
</ul>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform-monorepo/
├── modules/                  # Reusable, zero provider config
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── compute/
├── live/                     # Minimal code, env-specific .tfvars
│   ├── production/
│   │   └── us-east-1/
│   │       ├── terragrunt.hcl
│   │       └── main.tf
│   └── development/
</code></pre></div></div>

<ul>
  <li><strong>Add drift detection.</strong> Even a simple cron job running <code class="language-plaintext highlighter-rouge">terraform plan</code> and sending Slack alerts on discrepancies is better than nothing. Less than one-third of organizations continuously monitor for drift.</li>
  <li><strong>Implement Policy-as-Code.</strong> Start with 3-5 critical rules (no public S3 buckets, no unencrypted storage, mandatory tagging). Deploy them in advisory mode first.</li>
  <li><strong>Use cross-account IAM with assume_role.</strong> Stop hardcoding credentials per environment:</li>
</ul>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># providers.tf</span>
<span class="nx">provider</span> <span class="s2">"aws"</span> <span class="p">{</span>
  <span class="nx">region</span> <span class="p">=</span> <span class="s2">"us-east-1"</span>
  <span class="nx">assume_role</span> <span class="p">{</span>
    <span class="nx">role_arn</span>     <span class="p">=</span> <span class="s2">"arn:aws:iam::111111111111:role/TerraformExecutionRole"</span>
    <span class="nx">session_name</span> <span class="p">=</span> <span class="s2">"terraform-ci"</span>
    <span class="nx">external_id</span>  <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">external_id</span>
  <span class="p">}</span>
  <span class="nx">max_retries</span> <span class="p">=</span> <span class="mi">25</span>  <span class="c1"># Critical for cross-account API throttling</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li><strong>Replace <code class="language-plaintext highlighter-rouge">count</code> with <code class="language-plaintext highlighter-rouge">for_each</code>.</strong> Grep your codebase for <code class="language-plaintext highlighter-rouge">count =</code> on non-trivial resources. Migrate the stateful ones first.</li>
</ul>

<h2 id="the-uncomfortable-trade-offs">The Uncomfortable Trade-offs</h2>

<p>No honest discussion of Terraform at scale is complete without acknowledging where the popular advice breaks down.</p>

<p>The OpenTofu fork is real and growing. HashiCorp’s 2023 switch from MPL 2.0 to BSL 1.1 created genuine strategic risk for Managed Service Providers and platform companies. If you are building a product on top of Terraform, you need to evaluate this carefully. If you are using Terraform internally to deploy your own infrastructure, the license change likely does not affect you at all. But the community split means ecosystem fragmentation is a long-term concern.</p>

<p>Native Terraform workspaces (the <code class="language-plaintext highlighter-rouge">terraform workspace new prod</code> kind) are, in my experience, an operational trap. They use the same directory and same <code class="language-plaintext highlighter-rouge">.tf</code> files but silently switch the underlying state file. An engineer reviewing the Git repo cannot visually see what environments exist. There is no physical <code class="language-plaintext highlighter-rouge">prod</code> folder to inspect. This violates basic GitOps visibility principles and increases the risk of accidentally applying to the wrong environment. Use directory-based separation instead.</p>

<p>And then there is the “You’re Not Netflix” argument, which is valid. Netflix runs over 50,000 individual Terraform files and needs 100+ engineers just to maintain the IaC tooling. Applying that level of infrastructure complexity to a mid-sized application with modest traffic creates technical debt that will outlive the application itself. Right-size your Terraform architecture to your actual scale, not your aspirational scale.</p>

<h2 id="closing-thought">Closing Thought</h2>

<p>The Terraform AWS provider just crossed 5 billion downloads. The tool is not going anywhere. But the gap between “we use Terraform” and “we use Terraform well” is widening, not narrowing. The organizations that get this right treat infrastructure code with the exact same rigor as application code: strict modularity, automated testing, zero-trust execution, and continuous validation.</p>

<p>That 94% of companies still stuck in partial codification? They are not lacking a tool. They are lacking the operational discipline to use the tool they already have. The patterns in this post are not theoretical. They are what Cloudflare, Stripe, Uber, Benchling, Fannie Mae, and others built after learning the hard way.</p>

<p>The question is whether you will learn from their experience or repeat it.</p>]]></content><author><name>Rajat Gupta</name></author><category term="terraform" /><category term="iac" /><category term="sre" /><summary type="html"><![CDATA[What separates teams that run Terraform smoothly at scale from those fighting state corruption, slow plans and drift, drawn from Cloudflare, Stripe, Uber and Benchling.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Three Pillars of Observability: Logs, Metrics, Traces Done Right</title><link href="https://rajatgupta.work/posts/three-pillars-of-observability/" rel="alternate" type="text/html" title="The Three Pillars of Observability: Logs, Metrics, Traces Done Right" /><published>2026-02-05T04:30:00+05:30</published><updated>2026-02-05T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/three-pillars-of-observability</id><content type="html" xml:base="https://rajatgupta.work/posts/three-pillars-of-observability/"><![CDATA[<p>Monitoring tells you when something breaks. Observability tells you why, even when you did not know the failure mode existed. In modern microservice systems, that difference is the line between a fast fix and an all-hands war room.</p>

<p>This post breaks down the three pillars of observability - <strong>logs, metrics, and traces</strong> - and shows how to implement them in a way that actually helps during incidents.</p>

<h2 id="the-shift-from-known-unknowns-to-unknown-unknowns">The Shift from “Known Unknowns” to “Unknown Unknowns”</h2>

<p>Traditional monitoring is built around anticipated failures: disk full, CPU pegged, server down. These are known unknowns. Microservices introduce failure chains you have never seen before. Observability is what lets you debug those novel scenarios by slicing high-dimensional data without shipping new code or adding new instrumentation mid-incident.</p>

<h2 id="metrics-more-than-just-the-average">Metrics: More Than Just the Average</h2>

<p>Metrics are the bedrock of alerting. They are cheap to store and fast to query, but they trade detail for speed. Use the right primitives and avoid misleading aggregates.</p>

<h3 id="pick-the-right-instrument">Pick the Right Instrument</h3>

<ul>
  <li><strong>Counters</strong> are monotonic values that only increase (example: <code class="language-plaintext highlighter-rouge">http_requests_total</code>). Never graph raw counters; use the rate of change.</li>
  <li><strong>Gauges</strong> are point-in-time snapshots (memory usage, queue depth). If your scrape interval is too long, you will miss the spike that actually caused the failure.</li>
  <li><strong>Histograms</strong> track distributions (latency, payload size). They enable server-side quantiles without storing every sample.</li>
</ul>

<h3 id="the-fallacy-of-averages">The Fallacy of Averages</h3>

<p>Averages lie. If 99 requests take 10ms and 1 request takes 10s, the average looks fine, but your p99 is 10,000ms. For user-facing SLIs, use p95, p99, and p99.9. That is what correlates with churn.</p>

<h3 id="the-high-cardinality-trap">The High Cardinality Trap</h3>

<p>Time-series databases create a new series for every unique label set. Adding <code class="language-plaintext highlighter-rouge">user_id</code> or email as a label creates millions of series and can exhaust RAM. This is the fastest way to crash Prometheus.</p>

<p><strong>Solution:</strong> For high-cardinality data, store it in columnar analytics databases like ClickHouse. They compress well, scan only relevant columns, and can handle millions of unique values without index explosions.</p>

<h3 id="the-four-golden-signals">The Four Golden Signals</h3>

<p>To avoid dashboard fatigue, focus on the four signals that matter:</p>

<ul>
  <li><strong>Latency</strong>: Time to service a request. Separate success and failure.</li>
  <li><strong>Traffic</strong>: Demand on the system. Requests per second, I/O rate.</li>
  <li><strong>Errors</strong>: Failed request rate, explicit and implicit.</li>
  <li><strong>Saturation</strong>: The most constrained resource (thread pool, memory). This is a leading indicator.</li>
</ul>

<p>Alert on symptoms, not causes. Users do not care if CPU is at 100% if their request is fast. They do care if their request is slow even when CPU is at 10%.</p>

<h2 id="logs-structure-or-suffer">Logs: Structure or Suffer</h2>

<p>Logs are immutable records of events. In distributed systems, raw text logs turn into noise. The fix is structured logging.</p>

<h3 id="the-non-negotiable-structured-logs">The Non-Negotiable: Structured Logs</h3>

<p>Unstructured logs are expensive to query and fragile to parse. Structured logs make analysis reliable and fast.</p>

<p>Unstructured:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2023-10-27 10:00:00 ERROR Payment failed for user bob due to timeout
</code></pre></div></div>

<p>Structured:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-10-27T10:00:00Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"level"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ERROR"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"event"</span><span class="p">:</span><span class="w"> </span><span class="s2">"payment_failed"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bob"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"reason"</span><span class="p">:</span><span class="w"> </span><span class="s2">"timeout"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"service"</span><span class="p">:</span><span class="w"> </span><span class="s2">"payment-service"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"request_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"12345-abcde"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"trace_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"..."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>With structured logs, you can run SQL-like queries and index specific fields. It forces teams to agree on log schemas and guarantees metadata like <code class="language-plaintext highlighter-rouge">trace_id</code> is always present.</p>

<h3 id="canonical-log-lines-stripe-pattern">Canonical Log Lines (Stripe Pattern)</h3>

<p>Instead of emitting 20 log lines per request, aggregate context in memory and emit a single, wide JSON log line when the request finishes. This reduces log volume 10x to 20x and makes debugging a single query.</p>

<h3 id="the-log-trace-bridge">The Log-Trace Bridge</h3>

<p>Every log line generated within a request should include <code class="language-plaintext highlighter-rouge">trace_id</code> and <code class="language-plaintext highlighter-rouge">span_id</code>. When you are in a trace view, you should be able to click a span and see the exact logs for that operation. This is one of the biggest MTTR reducers in practice.</p>

<h2 id="traces-sample-smarter-not-harder">Traces: Sample Smarter, Not Harder</h2>

<p>Distributed tracing builds a causal graph of requests across service boundaries. Metrics show that something is slow. Traces show why.</p>

<h3 id="the-data-model">The Data Model</h3>

<ul>
  <li><strong>Trace</strong>: End-to-end workflow (“Purchase Item”).</li>
  <li><strong>Span</strong>: Named, timed operation (“SQL SELECT”, “HTTP POST /cart”).</li>
  <li><strong>Context</strong>: Metadata that links spans across services.</li>
</ul>

<h3 id="sampling-the-economics-of-tracing">Sampling: The Economics of Tracing</h3>

<p>Tracing every request is expensive. Sampling decides which traces to keep.</p>

<p><strong>Head-based sampling</strong> (decide at the start):</p>
<ul>
  <li>Pros: Easy, low overhead.</li>
  <li>Cons: Statistically blind. You will miss rare errors.</li>
</ul>

<p><strong>Tail-based sampling</strong> (decide at the end):</p>
<ul>
  <li>Pros: Keep 100% of anomalous traces (errors, high latency).</li>
  <li>Cons: Requires a stateful collector and more memory.</li>
</ul>

<p>OpenTelemetry Collector example:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">processors</span><span class="pi">:</span>
  <span class="na">tail_sampling</span><span class="pi">:</span>
    <span class="na">decision_wait</span><span class="pi">:</span> <span class="s">10s</span>
    <span class="na">num_traces</span><span class="pi">:</span> <span class="m">50000</span>
    <span class="na">policies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">error-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">status_code</span>
        <span class="na">status_code</span><span class="pi">:</span>
          <span class="na">status_codes</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">ERROR</span><span class="pi">]</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">latency-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">latency</span>
        <span class="na">latency</span><span class="pi">:</span>
          <span class="na">threshold_ms</span><span class="pi">:</span> <span class="m">500</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">probabilistic-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">probabilistic</span>
        <span class="na">probabilistic</span><span class="pi">:</span>
          <span class="na">sampling_percentage</span><span class="pi">:</span> <span class="m">1</span>
</code></pre></div></div>

<h3 id="context-propagation-across-languages">Context Propagation Across Languages</h3>

<p>Consistent propagation ensures spans link across polyglot services. OpenTelemetry makes this possible across Python, Go, and more with standardized <code class="language-plaintext highlighter-rouge">traceparent</code> headers.</p>

<h2 id="opentelemetry-the-unification-layer">OpenTelemetry: The Unification Layer</h2>

<p>OpenTelemetry provides a vendor-neutral API and protocol for logs, metrics, and traces. You instrument once and export to any backend. If you switch vendors, you do not rewrite your application code.</p>

<p>Architecture:</p>

<ul>
  <li><strong>OTel API/SDK</strong>: Instrumentation in your app.</li>
  <li><strong>OTel Collector</strong>: Vendor-agnostic proxy for sampling, batching, and exporting.</li>
  <li><strong>OTLP</strong>: High-performance gRPC protocol for telemetry.</li>
</ul>

<h2 id="the-fourth-pillar-continuous-profiling">The Fourth Pillar: Continuous Profiling</h2>

<p>Logs, metrics, and traces often cannot pinpoint performance problems at the code level. Continuous profiling fills that gap.</p>

<p>Modern eBPF-based profilers sample production systems with very low overhead and produce flame graphs that reveal where CPU time is spent. This directly ties observability to FinOps by showing which hot paths drive cost.</p>

<h2 id="the-roi-conversation">The ROI Conversation</h2>

<p>Observability can consume 20% to 30% of infrastructure spend. To justify it, quantify outcomes:</p>

<ul>
  <li><strong>MTTR Reduction</strong>: Faster incident resolution saves revenue.</li>
  <li><strong>Developer Velocity</strong>: Less time investigating means more feature work.</li>
  <li><strong>Infrastructure Optimization</strong>: Profiling reduces compute waste.</li>
</ul>

<h2 id="build-vs-buy">Build vs Buy</h2>

<ul>
  <li><strong>Buy</strong> if the team is small and you want to focus on product.</li>
  <li><strong>Build</strong> if scale makes SaaS costs massive and you can staff a dedicated observability team.</li>
  <li><strong>Hybrid</strong>: Use OpenTelemetry for collection and a vendor backend to avoid lock-in.</li>
</ul>

<h2 id="the-checklist-observability-done-right">The Checklist: Observability Done Right</h2>

<ul>
  <li>All logs are structured JSON.</li>
  <li>Logs include <code class="language-plaintext highlighter-rouge">trace_id</code> and <code class="language-plaintext highlighter-rouge">span_id</code>.</li>
  <li>High-cardinality data stays out of TSDBs (or moves to columnar stores).</li>
  <li>Critical flows use tail-based sampling.</li>
  <li>OpenTelemetry is the standard for instrumentation and collection.</li>
  <li>Alerts are based on SLO burn rates, not static thresholds.</li>
  <li>Continuous profiling is enabled.</li>
  <li>Weekly ops reviews and monthly game days are standard practice.</li>
</ul>

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/the-three-pillars-of-observability-logs-metrics-traces-done-right-114597ebdcf1">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="observability" /><category term="sre" /><category term="platform" /><summary type="html"><![CDATA[Monitoring tells you when something breaks; observability tells you why. How to get logs, metrics and traces right, with OpenTelemetry, smarter sampling and a checklist.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SRE in 2026: What’s Changed and What’s Next</title><link href="https://rajatgupta.work/posts/state-of-sre/" rel="alternate" type="text/html" title="SRE in 2026: What’s Changed and What’s Next" /><published>2026-01-02T04:30:00+05:30</published><updated>2026-01-02T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/state-of-sre</id><content type="html" xml:base="https://rajatgupta.work/posts/state-of-sre/"><![CDATA[<p>Remember the panicked LinkedIn threads from early 2023? The ones predicting that by 2026, Site Reliability Engineering would be automated out of existence?</p>

<p>Well, it is 2026. We are still here.</p>

<p>But the job looks nothing like it did three years ago. We are no longer just “operators of servers.” We have become “architects of reliability” in a landscape that is significantly more fragile than the DevOps era ever was.</p>

<p>I have been digging into the data from the 2025 State of SRE reports, and the picture it paints is fascinating. We are living through a massive contradiction. We have radically accelerated software delivery using GenAI, but the friction of managing that complexity has skyrocketed.</p>

<p>Here is the reality of SRE in 2026.</p>

<hr />

<h2 id="the-trust-paradox-and-rising-toil">The “Trust Paradox” and Rising Toil</h2>

<p>We were promised that AI would handle the grunt work. The theory was that Generative AI would eliminate toil, freeing us up for “high-value creative work.”</p>

<p>The reality? Toil actually went up by 6% in 2024 and kept climbing through 2025.</p>

<p>We call this the <strong>Trust Paradox</strong>. While 90% of us use AI daily, nearly a third of us do not trust the code it generates. This lack of trust has created a new category of work: cleaning up the “Day 2” mess created by high-velocity “Day 1” AI development.</p>

<p>We are spending our time in a defensive posture. We are writing additional tests and creating granular guardrails because we cannot blindly trust configuration files hallucinated by an LLM.</p>

<blockquote>
  <p>Key Stat: Junior engineers and automated agents can’t handle the complexity AI creates. This leaves management and lead SREs acting as the “human backstop,” handling 6 to 20 critical incidents a month.</p>
</blockquote>

<p>AI accelerates creation, but human verification creates a new bottleneck.</p>

<hr />

<h2 id="slow-is-the-new-down">Slow is the New Down</h2>

<p>The binary definition of reliability (Up vs. Down) is dead. In 2026, <strong>Slow is the New Down</strong>.</p>

<p>Think about the user expectations today. We are dealing with real-time AI agents and voice interfaces. If a service returns a <code class="language-plaintext highlighter-rouge">200 OK</code> but takes 2 seconds to respond, the conversation breaks. The user thinks it is broken.</p>

<p>This has forced us to abandon simple availability targets. Over 53% of organizations now treat latency spikes or UI sluggishness as Sev-1 incidents.</p>

<p>This shift kills traditional monitoring. You cannot rely on 1-minute averages anymore, as they mask tail latency spikes (P99) that undermine user trust. We have moved to <strong>High-Resolution Observability</strong>, utilizing tail-based sampling to keep 100% of the traces where things go wrong, rather than randomly sampling the happy path.</p>

<hr />

<h2 id="the-myth-of-the-autonomous-sre">The Myth of the “Autonomous SRE”</h2>

<p>Let’s talk about the elephant in the room. Why isn’t AI fixing incidents for us yet?</p>

<p>A seminal study by ClickHouse in 2025 gave us the answer. They pitted models like Claude Sonnet 4, OpenAI o3, and Gemini 2.5 Pro against a distributed microservices failure.</p>

<p>The setup was tricky but realistic: a logic bug caused a cache to append data rather than replace it, leading to exponential memory growth and an OOM Kill.</p>

<p>The results were humbling:</p>

<ul>
  <li><strong>Zero-Shot Failure:</strong> When asked “What is wrong?”, none of the models found the root cause. They hallucinated standard issues like network latency.</li>
  <li><strong>The “Locking On” Problem:</strong> Some models fabricated theories (like a “poisoned product ID”) and wasted their token budget trying to prove a falsehood.</li>
</ul>

<p>The AI only succeeded when a human operator acted as the investigator, feeding it hypotheses. We provide the intuition; the AI provides the data retrieval.</p>

<p><strong>The takeaway:</strong> The SRE workflow of 2026 is Collaborative, not Autonomous.</p>

<hr />

<h2 id="the-new-stack-rust-ebpf-and-the-kernel">The New Stack: Rust, eBPF, and the Kernel</h2>

<p>Because we can’t touch the application code for every legacy service, we have moved our observability and security down to the kernel using <strong>eBPF</strong> (Extended Berkeley Packet Filter).</p>

<p>In 2026, we are using eBPF to drop malicious traffic at the network card driver level — before it even hits the TCP/IP stack. This allows a single server to withstand massive packet floods.</p>

<p>And yes, we are writing these filters in <strong>Rust</strong> for memory safety. Here is what a typical XDP packet filter looks like today:</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// eBPF program utilizing XDP to drop packets from a blocklist</span>
<span class="c1">// Written in Rust using the Aya framework</span>
<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>
<span class="k">use</span>
<span class="nn">aya_bpf</span><span class="p">::{</span>
<span class="nn">bindings</span><span class="p">::</span><span class="n">xdp_action</span><span class="p">,</span>
<span class="nn">macros</span><span class="p">::</span><span class="n">xdp</span><span class="p">,</span>
<span class="nn">programs</span><span class="p">::</span><span class="n">XdpContext</span><span class="p">,</span>
<span class="p">};</span>
<span class="nd">#[xdp(name=</span>
<span class="s">"firewall"</span>
<span class="nd">)]</span>
<span class="k">pub</span>

<span class="k">fn</span>

<span class="nf">firewall</span>
<span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">XdpContext</span><span class="p">)</span>
<span class="k">-&gt;</span>

<span class="nb">u32</span>
<span class="p">{</span>

<span class="k">match</span>

<span class="nf">try_firewall</span>
<span class="p">(</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>

<span class="nf">Ok</span>
<span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">ret</span><span class="p">,</span>

<span class="nf">Err</span>
<span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="nn">xdp_action</span><span class="p">::</span><span class="n">XDP_ABORTED</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">fn</span>

<span class="nf">try_firewall</span>
<span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">XdpContext</span><span class="p">)</span>
<span class="k">-&gt;</span>

<span class="nb">Result</span><span class="p">,</span> <span class="p">()</span><span class="o">&gt;</span> <span class="p">{</span>

<span class="c1">// Check source IP against a map of blocked IPs</span>

<span class="k">if</span>

<span class="k">unsafe</span>
<span class="p">{</span> <span class="n">BLOCKLIST</span><span class="nf">.
get</span>
<span class="p">(</span><span class="o">&amp;</span><span class="n">src_ip</span><span class="p">)</span><span class="nf">.
is_some</span>
<span class="p">()</span> <span class="p">}</span> <span class="p">{</span>

<span class="c1">// Drop the packet immediately.</span>

<span class="c1">// This happens before the OS allocates an sk_buff.</span>

<span class="k">return</span>

<span class="nf">Ok</span>
<span class="p">(</span><span class="nn">xdp_action</span><span class="p">::</span><span class="n">XDP_DROP</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Pass legitimate traffic up the stack</span>

<span class="nf">Ok</span>
<span class="p">(</span><span class="nn">xdp_action</span><span class="p">::</span><span class="n">XDP_PASS</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is the level of technical depth required now. We aren’t just configuring Nginx; we are programming the kernel.</p>

<p>eBPF allows us to filter noise and attacks at the hardware level, saving CPU for what matters.</p>

<hr />

<h2 id="greenops-the-regulatory-mandate">GreenOps: The Regulatory Mandate</h2>

<p>Finally, we have a new boss: The environment.</p>

<p>Thanks to frameworks like the EU’s CSRD, sustainability is no longer optional — it is a compliance requirement. This has birthed <strong>GreenOps</strong>, joining FinOps and Reliability in our “Golden Triangle”.</p>

<p>We are now tracking metrics like <strong>SCI (Software Carbon Intensity)</strong> alongside our error rates.</p>

<p>The coolest implementation of this? <strong>Carbon-Aware Autoscaling</strong>.</p>

<p>We connect our Kubernetes Horizontal Pod Autoscalers to grid data APIs. If the grid is dirty (coal/gas), we restrict scaling for non-critical background jobs. If the grid is green (wind/solar), we scale out aggressively to clear the queue.</p>

<hr />

<h2 id="the-road-ahead">The Road Ahead</h2>

<p>If you are an Engineering Manager or a Lead SRE, your strategy needs to shift.</p>

<ol>
  <li><strong>Invest in “Vibe Debugging”:</strong> Use LLMs to analyze the qualitative noise — Slack channels and support tickets. Often, the users feel the outage before the metrics catch it.</li>
  <li><strong>Platform as a Product:</strong> Don’t just build a portal. Ensure you have a paved road. If developers hate your platform, they will bypass it, leading to shadow IT.</li>
  <li><strong>Human + AI:</strong> Stop trying to replace your SREs with agents. Train your SREs to direct the agents.</li>
</ol>

<p>The SRE role hasn’t disappeared. It just got a promotion.</p>

<hr />

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/sre-in-2026-whats-changed-and-what-s-next-e73757276921">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="sre" /><category term="genai" /><category term="greenops" /><summary type="html"><![CDATA[SRE was not automated away. What changed by 2026: rising toil, slow as the new down, the limits of the autonomous SRE, Rust and eBPF, and GreenOps.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SRE &amp;amp; Security Lessons from the 2025 Trenches</title><link href="https://rajatgupta.work/posts/sre-lessons/" rel="alternate" type="text/html" title="SRE &amp;amp; Security Lessons from the 2025 Trenches" /><published>2025-12-30T04:30:00+05:30</published><updated>2025-12-30T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/sre-lessons</id><content type="html" xml:base="https://rajatgupta.work/posts/sre-lessons/"><![CDATA[<p>If you look back at the engineering landscape of 2025, it wasn’t just another year of iteration. It was a phase shift. We saw a collision between two massive tectonic forces: the chaotic complexity of hyperscale systems and the ungoverned integration of AI agents.</p>

<p>Here is the thing. We have better observability and automation tools than ever before. Yet, the systemic fragility of our infrastructure has never been more acute. I’ve spent the last few weeks digging through the 2025 State of DevOps reports, RCAs from AWS and Google, and the Catchpoint SRE survey. The data paints a picture of a “Configuration Cascade” replacing hardware failure as our main antagonist.</p>

<p>We need to talk about what actually broke, why our zonal redundancy was an illusion, and the specific code patterns we need to survive the second half of the Cloud Native era.</p>

<h2 id="the-anatomy-of-a-hyperscale-crash">The Anatomy of a Hyperscale Crash</h2>

<p>The biggest lesson from 2025 is that the promise of “global control planes” has become our greatest liability. We optimized for velocity, building pipelines that push config to thousands of nodes in seconds. But when that config is malformed, the speed of propagation becomes the speed of destruction.</p>

<h3 id="1-the-aws-dns-race-condition">1. The AWS DNS Race Condition</h3>

<p>On October 20, 2025, us-east-1 went dark. It wasn’t a cyberattack. It was a concurrency bug in the DynamoDB internal DNS system.</p>

<p>Here is what happened. The system has a “Planner” that makes a DNS plan and an “Enactor” that applies it. They run redundant Enactors for safety. But they hit a race condition. One Enactor lagged, held a “stale” plan, and then woke up to overwrite the “fresh” plan from a healthy Enactor.</p>

<p>Because they lacked a robust optimistic locking mechanism at the write commit point, the stale data won. The cleanup workflow saw this inconsistent state, assumed no valid endpoints existed, and deleted the DNS records.</p>

<p>The kick in the teeth? The recovery tools depended on the very system (DynamoDB) that was down.</p>

<p><strong>The Takeaway:</strong> If your ability to fix the network depends on the network being up, you have designed a fragility trap.</p>

<h3 id="2-the-safe-change-that-broke-google">2. The “Safe” Change that Broke Google</h3>

<p>Months earlier, GCP took a hit from a null pointer exception. A code change for a new policy feature was deployed behind a feature flag. Safe, right? Except the configuration update that enabled it contained unintended blank fields.</p>

<p>This config was pushed globally to ensure financial controls were enforced in real-time. The binary read the null value, crashed, and because it sits in the critical path of every API request, global traffic tanked.</p>

<p><strong>The Takeaway:</strong> Configuration is code. We canary binaries, but we often blast configuration changes globally. A “canary config” push would have caught this in one region.</p>

<h2 id="identity-is-the-new-edge-and-its-leaking">Identity is the New Edge (and it’s Leaking)</h2>

<p>While SREs were fighting race conditions, security teams were dealing with a shift from perimeter defense to identity defense. The firewall is dead. The Identity Provider is the new edge.</p>

<h3 id="the-salesloftdrift-breach">The Salesloft/Drift Breach</h3>

<p>The breach of Salesloft’s Drift platform didn’t involve a zero-day or a phished password. It was an OAuth token exfiltration. Attackers stole valid tokens and “became” the application. Since they had bearer access, MFA didn’t matter. They accessed Salesforce environments via API, bypassing the UI entirely.</p>

<p>Most defenses failed because they looked for login events. API calls don’t generate login events. The only thing that worked was IP restrictions on Connected Apps.</p>

<h3 id="promptlock-ransomware-with-a-brain">PromptLock: Ransomware with a Brain</h3>

<p>We also saw “PromptLock.” This ransomware doesn’t have hardcoded encryption logic. It carries a local LLM (like gpt-oss:20b) and asks it to write encryption scripts in Lua dynamically at runtime. Every execution is different. It is polymorphic by design, rendering signature-based detection useless.</p>

<h2 id="the-code-fighting-back">The Code: Fighting Back</h2>

<p>Philosophy is great, but let’s look at the Python patterns that prevent these specific failures.</p>

<h3 id="1-distributed-locking-the-aws-fix">1. Distributed Locking (The AWS Fix)</h3>

<p>The AWS outage proved in-memory locks don’t work in distributed systems. You need a distributed lock with a fencing token or lease. Here is a robust implementation using Redis that handles the “Zombie Release” problem (where a process wakes up and deletes a lock it no longer owns).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">uuid</span>
<span class="kn">import</span> <span class="nn">redis</span>

<span class="k">class</span> <span class="nc">RedisDistributedLock</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">redis_client</span><span class="p">,</span> <span class="n">lock_key</span><span class="p">,</span> <span class="n">lease_time_ms</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">redis</span> <span class="o">=</span> <span class="n">redis_client</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">lock_key</span> <span class="o">=</span> <span class="n">lock_key</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">lease_time_ms</span> <span class="o">=</span> <span class="n">lease_time_ms</span>
        <span class="c1"># Unique ID for this specific process instance is CRITICAL
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">owner_id</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">uuid</span><span class="p">.</span><span class="n">uuid4</span><span class="p">())</span>

    <span class="k">def</span> <span class="nf">acquire</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">wait_time_ms</span><span class="o">=</span><span class="mi">5000</span><span class="p">):</span>
        <span class="n">end_time</span> <span class="o">=</span> <span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">+</span> <span class="n">wait_time_ms</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">end_time</span><span class="p">:</span>
            <span class="c1"># NX: Set only if not exists (Atomic)
</span>            <span class="c1"># PX: Set expiration in milliseconds (Lease)
</span>            <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">lock_key</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">owner_id</span><span class="p">,</span> <span class="n">nx</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">px</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">lease_time_ms</span><span class="p">):</span>
                <span class="k">return</span> <span class="bp">True</span>
            <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># Retry backoff
</span>        <span class="k">return</span> <span class="bp">False</span>

    <span class="k">def</span> <span class="nf">release</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="c1"># LUA Script to ensure atomicity:
</span>        <span class="c1"># "Check if I own the lock. If yes, delete it. If no, do nothing."
</span>        <span class="c1"># This prevents the "Zombie Release" problem.
</span>        <span class="n">lua_script</span> <span class="o">=</span> <span class="s">"""
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="nb">eval</span><span class="p">(</span><span class="n">lua_script</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">lock_key</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">owner_id</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="2-the-circuit-breaker-the-cascading-failure-fix">2. The Circuit Breaker (The Cascading Failure Fix)</h3>

<p>To stop the bleeding when a dependency like GCP’s Service Control fails, you need a circuit breaker. Stop retrying. Fail fast.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">botocore.exceptions</span> <span class="kn">import</span> <span class="n">ClientError</span><span class="p">,</span> <span class="n">BotoCoreError</span>

<span class="k">class</span> <span class="nc">CircuitBreaker</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">failure_threshold</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">recovery_timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">failure_threshold</span> <span class="o">=</span> <span class="n">failure_threshold</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">recovery_timeout</span> <span class="o">=</span> <span class="n">recovery_timeout</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">failures</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="s">"CLOSED"</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">last_failure_time</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="c1"># If circuit is OPEN, check if we should try a reset (Half-Open)
</span>        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">==</span> <span class="s">"OPEN"</span><span class="p">:</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="p">.</span><span class="n">last_failure_time</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="p">.</span><span class="n">recovery_timeout</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="s">"HALF_OPEN"</span>   <span class="c1"># Probation period
</span>            <span class="k">else</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nb">Exception</span><span class="p">(</span><span class="s">"Circuit is OPEN. Call rejected fast."</span><span class="p">)</span>
        
        <span class="k">try</span><span class="p">:</span>
            <span class="n">result</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="c1"># If successful in Half-Open, reset to Closed
</span>            <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">==</span> <span class="s">"HALF_OPEN"</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">reset</span><span class="p">()</span>
            <span class="k">return</span> <span class="n">result</span>
        <span class="k">except</span> <span class="p">(</span><span class="n">ClientError</span><span class="p">,</span> <span class="n">BotoCoreError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">record_failure</span><span class="p">()</span>
            <span class="k">raise</span> <span class="n">e</span>

    <span class="k">def</span> <span class="nf">record_failure</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">failures</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">last_failure_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">()</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">failures</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="p">.</span><span class="n">failure_threshold</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="s">"OPEN"</span>
            <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Circuit Breaker tripped! State is now OPEN."</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">reset</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">failures</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="s">"CLOSED"</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Circuit recovered. State is CLOSED."</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="the-path-forward">The Path Forward</h2>

<p>We are entering an era of interconnected fragility. We are abstracting away infrastructure with Serverless and AI Agents, but we are introducing invisible layers of logical dependency.</p>

<p>The SRE of 2026 isn’t just a Linux sysadmin. They are a distributed systems theorist who understands Paxos to prevent race conditions. They are an Identity Architect who treats OAuth tokens like nuclear launch codes. And they are an AI Guardian who builds cages for models.</p>

<p>We have to stop trusting global control planes. We have to stop trusting internal traffic. And honestly, we have to stop trusting AI code blindly.</p>

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/sre-security-lessons-from-the-2025-trenches-8ea01b0c03a8">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="sre" /><category term="security" /><category term="cloud" /><summary type="html"><![CDATA[What broke at hyperscale in 2025, why zonal redundancy was an illusion, and the code patterns that help when a configuration change, not hardware, takes systems down.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Beyond the Breach: A playbook for Cloud Security</title><link href="https://rajatgupta.work/posts/beyond-the-breach/" rel="alternate" type="text/html" title="Beyond the Breach: A playbook for Cloud Security" /><published>2025-11-25T04:30:00+05:30</published><updated>2025-11-25T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/beyond-the-breach</id><content type="html" xml:base="https://rajatgupta.work/posts/beyond-the-breach/"><![CDATA[<p>Let’s focus on cloud security for engineering teams, dissecting “silent killers” (misconfigurations, IAM flaws) with real-world examples. We will cover Zero Trust, CSPM/CNAPP, DevSecOps, a practical example for secure S3 buckets, and the importance of the Shared Responsibility Model and security culture.</p>

<h2 id="the-evolving-cloud-security-landscape">The Evolving Cloud Security Landscape</h2>

<ul>
  <li>The cloud offers innovation and scale but also expands the “digital battlefield.”</li>
  <li>Common pitfalls like misconfigured services and overprivileged roles can lead to significant data breaches.</li>
  <li>Examples of breaches due to common errors include:
    <ul>
      <li><strong>Capital One:</strong> Misconfigured web application firewall exposed over 100 million customer records.</li>
      <li><strong>Toyota:</strong> A simple cloud misconfiguration exposed customer data for eight years.</li>
    </ul>
  </li>
  <li>Reactive, perimeter-based security is outdated; modern cloud security requires building it into the engineering culture and code.</li>
</ul>

<h2 id="the-two-pitfalls-behind-most-cloud-breaches">The Two Pitfalls Behind Most Cloud Breaches</h2>

<p>The most common threats are not sophisticated attacks but basic, preventable failures.</p>

<h3 id="the-misconfiguration-minefield">The Misconfiguration Minefield</h3>

<ul>
  <li>Defined as a “ticking time bomb,” these include public S3 buckets, unprotected databases (e.g., Elasticsearch without a password), and overly permissive security group ingress rules.</li>
  <li>The <strong>CAM4 breach</strong> exposed 7 terabytes of data due to an unprotected database.</li>
  <li>Solution: Codify security using Infrastructure as Code (IaC) tools like Terraform to define a repeatable, auditable, and secure baseline.</li>
</ul>

<h3 id="the-iam-identity-crisis">The IAM Identity Crisis</h3>

<ul>
  <li>Flawed Identity and Access Management (IAM) is akin to distributing master keys.</li>
  <li><strong>Overprivileged accounts</strong> are a major liability, allowing attackers to move laterally within an environment.</li>
  <li>Example: Granting a developer <code class="language-plaintext highlighter-rouge">dynamodb:*</code> permissions when only read access is needed, or an EC2 instance role access to all S3 buckets instead of a specific one.</li>
  <li>The <strong>2024 Snowflake attacks</strong> involved stolen credentials with broad access leading to significant data breaches.</li>
  <li>Solution: Implement the <strong>Principle of Least Privilege (PoLP)</strong>, granting only the minimum necessary permissions.</li>
</ul>

<h2 id="from-reactive-firefighting-to-proactive-defense">From Reactive Firefighting to Proactive Defense</h2>

<p>A modern security strategy is built on three pillars that embed security into the development lifecycle:</p>

<h3 id="embrace-a-zero-trust-mindset">Embrace a Zero Trust Mindset</h3>

<ul>
  <li>Replaces “trust but verify” with “never trust, always verify.”</li>
  <li>Requires authentication and authorization for every user, device, and service for every request, regardless of network location.</li>
  <li>Involves enforcing strict Multi-Factor Authentication (MFA), implementing micro-segmentation, and continuous monitoring for anomalous behavior.</li>
</ul>

<h3 id="automate-your-posture-with-cspm--cnapp">Automate Your Posture with CSPM &amp; CNAPP</h3>

<ul>
  <li><strong>Cloud Security Posture Management (CSPM)</strong> tools act as automated security auditors, scanning cloud environments for misconfigurations, compliance violations, and vulnerabilities.</li>
  <li><strong>Cloud-Native Application Protection Platforms (CNAPP)</strong> integrate CSPM with runtime protection for workloads (VMs, containers, serverless functions), providing a unified view.</li>
</ul>

<h3 id="shift-left-with-devsecops-and-policy-as-code">“Shift Left” with DevSecOps and Policy-as-Code</h3>

<ul>
  <li>Fixing vulnerabilities before deployment is most effective.</li>
  <li>Embedding security into the CI/CD pipeline makes it part of the developer workflow.</li>
</ul>

<h4 id="key-practices-include">Key practices include</h4>

<ul>
  <li><strong>Static Application Security Testing (SAST):</strong> Scans code for vulnerabilities.</li>
  <li><strong>Software Composition Analysis (SCA):</strong> Checks open-source dependencies for known issues.</li>
  <li><strong>Policy-as-Code (PaC):</strong> Uses tools like Open Policy Agent or native cloud services (e.g., Azure Policy) to enforce rules automatically. Example: A policy can fail a build if a Terraform module attempts to create a public S3 bucket.</li>
</ul>

<h2 id="deep-dive-codifying-security-for-an-s3-bucket-terraform-example">Deep Dive: Codifying Security for an S3 Bucket (Terraform Example)</h2>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"secure_bucket"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="p">=</span> <span class="s2">"my-secure-application-data"</span>
  <span class="nx">tags</span> <span class="p">=</span> <span class="p">{</span>
    <span class="nx">Environment</span> <span class="p">=</span> <span class="s2">"Production"</span>
    <span class="nx">ManagedBy</span>   <span class="p">=</span> <span class="s2">"Terraform"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_public_access_block"</span> <span class="s2">"secure_bucket_public_access_block"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="p">=</span> <span class="nx">aws_s3_bucket</span><span class="err">.</span><span class="nx">secure_bucket</span><span class="err">.</span><span class="nx">id</span>
  <span class="nx">block_public_acls</span>       <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">block_public_policy</span>     <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">ignore_public_acls</span>      <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">restrict_public_buckets</span> <span class="p">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_server_side_encryption_configuration"</span> <span class="s2">"secure_bucket_encryption"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="p">=</span> <span class="nx">aws_s3_bucket</span><span class="err">.</span><span class="nx">secure_bucket</span><span class="err">.</span><span class="nx">id</span>
  <span class="nx">rule</span> <span class="p">{</span>
    <span class="nx">apply_server_side_encryption_by_default</span> <span class="p">{</span>
      <span class="nx">sse_algorithm</span> <span class="p">=</span> <span class="s2">"AES256"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_versioning"</span> <span class="s2">"secure_bucket_versioning"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="p">=</span> <span class="nx">aws_s3_bucket</span><span class="err">.</span><span class="nx">secure_bucket</span><span class="err">.</span><span class="nx">id</span>
  <span class="nx">versioning_configuration</span> <span class="p">{</span>
    <span class="nx">status</span> <span class="p">=</span> <span class="s2">"Enabled"</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_s3_bucket_policy"</span> <span class="s2">"secure_bucket_policy"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="p">=</span> <span class="nx">aws_s3_bucket</span><span class="err">.</span><span class="nx">secure_bucket</span><span class="err">.</span><span class="nx">id</span>
  <span class="nx">policy</span> <span class="p">=</span> <span class="nx">jsonencode</span><span class="err">(</span><span class="p">{</span>
    <span class="nx">Version</span> <span class="p">=</span> <span class="s2">"2012-10-17"</span><span class="err">,</span>
    <span class="nx">Statement</span> <span class="p">=</span> <span class="p">[</span>
      <span class="p">{</span>
        <span class="nx">Sid</span>       <span class="p">=</span> <span class="s2">"DenyUnencryptedTraffic"</span><span class="err">,</span>
        <span class="nx">Effect</span>    <span class="p">=</span> <span class="s2">"Deny"</span><span class="err">,</span>
        <span class="nx">Principal</span> <span class="p">=</span> <span class="s2">"*"</span><span class="err">,</span>
        <span class="nx">Action</span>    <span class="p">=</span> <span class="s2">"s3:*"</span><span class="err">,</span>
        <span class="nx">Resource</span>  <span class="p">=</span> <span class="p">[</span>
          <span class="s2">"${aws_s3_bucket.secure_bucket.arn}"</span><span class="p">,</span>
          <span class="s2">"${aws_s3_bucket.secure_bucket.arn}/*"</span>
        <span class="p">]</span><span class="err">,</span>
        <span class="nx">Condition</span> <span class="p">=</span> <span class="p">{</span>
          <span class="nx">Bool</span> <span class="p">=</span> <span class="p">{</span>
            <span class="s2">"aws:SecureTransport"</span> <span class="p">=</span> <span class="s2">"false"</span>
          <span class="p">}</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">}</span><span class="err">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code creates a secure S3 bucket by enforcing encryption, blocking public access, and enabling versioning before data is uploaded.</p>

<h2 id="the-elephant-in-the-room-the-shared-responsibility-model">The Elephant in the Room: The Shared Responsibility Model</h2>

<ul>
  <li>
    <p>A critical myth to dispel is that “the cloud provider handles security.”</p>
  </li>
  <li>
    <p>Gartner predicts that through 2025, 99% of cloud security failures will be the customer’s fault.</p>
  </li>
  <li>
    <p>Cloud providers (Amazon, Google, Microsoft) are responsible for securing the cloud (physical infrastructure, hardware, network).</p>
  </li>
  <li>
    <p>Customers are responsible for security in the cloud (data, IAM policies, network configurations, application code).</p>
  </li>
  <li>
    <p>Leaders must educate teams on this model to avoid abdication of duty.</p>
  </li>
</ul>

<h2 id="building-a-culture-of-security">Building a Culture of Security</h2>

<ul>
  <li>
    <p>Technology and tools are only part of the solution; a cultural shift is paramount.</p>
  </li>
  <li>
    <p>Foster an environment where security is a shared responsibility, not solely the domain of a separate team.</p>
  </li>
  <li>
    <p>Recognize and celebrate engineers who find and fix vulnerabilities.</p>
  </li>
  <li>
    <p>Invest in continuous security training.</p>
  </li>
  <li>
    <p>Make security a fundamental quality metric alongside performance and reliability.</p>
  </li>
</ul>

<p>By understanding pitfalls, adopting proactive strategies, and codifying security, organizations can build faster, more resilient, and innovative products with a foundation of robust security.</p>

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/beyond-the-breach-a-playbook-for-cloud-security-a2823317a2ff">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="cloud" /><category term="security" /><summary type="html"><![CDATA[A playbook for the misconfigurations and IAM flaws behind most cloud breaches, covering Zero Trust, CSPM, DevSecOps, a secure S3 bucket in Terraform, and security culture.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Proving the ROI of Your SRE Program</title><link href="https://rajatgupta.work/posts/sre-roi/" rel="alternate" type="text/html" title="Proving the ROI of Your SRE Program" /><published>2025-11-18T04:30:00+05:30</published><updated>2025-11-18T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/sre-roi</id><content type="html" xml:base="https://rajatgupta.work/posts/sre-roi/"><![CDATA[<p>Leading the SRE Teams, one of the hardest challenges is how to prove the Return on Investment (ROI) of a Site Reliability Engineering (SRE) program, addressing the “translation gap” between technical metrics and business outcomes.</p>

<h2 id="key-problem">Key Problem</h2>
<p>Leadership often asks for the ROI of SRE, but technical metrics like availability (e.g., 99.99%) and Mean Time To Resolution (MTTR) are not directly understood in business terms like revenue, customer churn, and operational costs. Bridging this gap is crucial for continued investment.</p>

<h2 id="sre-program-roi">SRE Program ROI</h2>
<blockquote>
  <p>Organizations successfully implementing SRE can see an average ROI of 200%. The value lies not just in reliability but in articulating that value in financial terms.</p>
</blockquote>

<h2 id="core-sre-concepts">Core SRE Concepts</h2>
<ul>
  <li>Service Level Indicators (SLIs): Raw measurements (e.g., request latency, error rate, throughput).</li>
  <li>Service Level Objectives (SLOs): Targets for SLIs (e.g., “99.9% of requests served under 300ms”).</li>
  <li>Error Budgets: The inverse of SLO (100% — SLO), representing acceptable unreliability to balance innovation and stability.</li>
</ul>

<p>These are valuable for engineering teams but need reframing for business stakeholders.</p>

<h2 id="sre-roi-formula">SRE ROI Formula</h2>
<p>The classic ROI formula is: <code class="language-plaintext highlighter-rouge">ROI = (Net Benefit / Cost of Investment) * 100</code>.</p>

<h3 id="step-1-calculate-the-cost-of-investment-denominator">Step 1: Calculate the Cost of Investment (Denominator)</h3>
<p>This includes:</p>
<ul>
  <li>Salaries: Compensation for the SRE team.</li>
  <li>Tools &amp; Technology: Licensing for observability (Datadog, Splunk), monitoring (Prometheus), and incident management software (PagerDuty).</li>
  <li>Training &amp; Development: Costs for certifications, courses, and conferences.</li>
  <li>Infrastructure: Costs for running monitoring and automation platforms.</li>
</ul>

<h3 id="step-2-quantify-the-net-benefit-the-return--numerator">Step 2: Quantify the Net Benefit (The Return -Numerator)</h3>
<p>Benefits fall into revenue saved/gained and costs reduced:</p>

<h4 id="1-reduced-cost-of-downtime-revenue-saved">1. Reduced Cost of Downtime (Revenue Saved)</h4>
<p><strong>Calculation:</strong> <code class="language-plaintext highlighter-rouge">(Hours of Downtime Reduced Annually) x (Revenue Loss per Hour)</code></p>

<p><strong>Impact:</strong> Over 60% of outages cost businesses over $100,000.</p>

<p><strong>Case Study:</strong> A global industrial manufacturer reduced downtime by 90%, saving millions.</p>

<h4 id="2-increased-operational-efficiency-costs-reduced">2. Increased Operational Efficiency (Costs Reduced)</h4>
<p><strong>Concept:</strong> Replacing manual, repetitive work (“toil”) with automation.</p>

<p><strong>Calculation:</strong> <code class="language-plaintext highlighter-rouge">(Hours of Manual Toil Automated per Week) x 52 x (Average Engineer Hourly Cost)</code></p>

<p><strong>Example:</strong> Automating 4 hours of manual deployment checks per week at $75/hour saves $15,600 annually.</p>

<h4 id="3-faster-incident-resolution-costs-reduced">3. Faster Incident Resolution (Costs Reduced)</h4>
<p><strong>Concept:</strong> Lower Mean Time To Resolution (MTTR) reduces customer impact and engineer time.</p>

<p><strong>Calculation:</strong> <code class="language-plaintext highlighter-rouge">(Average Incidents per Year) x (Time Saved per Incident) x (Engineer Hourly Cost)</code></p>

<p><strong>Case Study:</strong> The same manufacturer accelerated incident resolution by 75%.</p>

<h4 id="4-optimized-cloud-spend-costs-reduced">4. Optimized Cloud Spend (Costs Reduced)</h4>
<p><strong>Concept:</strong> Data-driven capacity planning and saturation monitoring prevent over-provisioning.</p>

<p><strong>Calculation:</strong> Directly measure the reduction in cloud bills due to SRE optimization.</p>

<h4 id="5-improved-customer-retention-revenue-gained">5. Improved Customer Retention (Revenue Gained)</h4>
<p><strong>Concept:</strong> Reliable and performant services lead to happier customers and reduced churn.</p>

<p><strong>Calculation:</strong> (Reduction in Churn Rate %) x (Number of Customers) x (Average Customer Lifetime Value)</p>

<p><strong>Industry Stat:</strong> Companies with strong reliability practices see customer churn rates fall by 20–30%.</p>

<h2 id="from-code-to-cash">From Code to Cash</h2>
<ul>
  <li>
    <p>Instrumentation: Using tools like Prometheus to track SLIs such as latency and errors.</p>
  </li>
  <li>Example (Python):
    <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">REQUEST_LATENCY</span> <span class="o">=</span> <span class="n">Histogram</span><span class="p">(...)</span> <span class="c1"># tracks request duration 
</span><span class="n">ERROR_COUNT</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(...)</span>    <span class="c1"># tracks failed requests 
</span><span class="n">SUCCESS_COUNT</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(...)</span>  <span class="c1"># tracks successful requests
</span></code></pre></div>    </div>
  </li>
  <li>
    <p>Scenario: SRE team notices increased latency and approaching SLO for /api/v1/checkout.</p>
  </li>
  <li>
    <p>Action: Fix an unoptimized database query.</p>
  </li>
  <li>
    <p>Result: p99 latency drops from 800ms to 200ms; error rate drops from 4% to 0.1%.</p>
  </li>
  <li>Tying to ROI: A 600ms latency improvement increases conversion rate by 3% (0.5% per 100ms). For a $50,000 daily transaction value, this is $1,500/day or $547,500/year.</li>
</ul>

<p>Reduced errors protect revenue by preventing customer churn (10% churn chance per failed checkout).</p>

<h2 id="common-pitfalls-and-anti-patterns">Common Pitfalls and Anti-Patterns</h2>
<ul>
  <li>“SRE in Name Only” Team: Rebranding an old Ops team without a cultural shift, engineering focus, or authority.</li>
  <li>Focusing on Vanity Metrics: Tracking metrics like “alerts closed” or “99.999% uptime” on non-critical services that don’t impact users or the bottom line.</li>
  <li>Ignoring the Intangibles: Dismissing benefits like improved developer morale or enhanced brand reputation. Proxy metrics like employee retention or NPS can represent these.</li>
  <li>Short-Term Focus: Underestimating the compounding benefits of reliability culture and scalable systems. Measure continuously and show trends.</li>
</ul>

<h2 id="the-future-of-sre-roi">The Future of SRE ROI</h2>
<ul>
  <li>Predictive Reliability: AI/ML will enable predicting failures and automating preventative actions, measuring the value of incidents averted.</li>
  <li>Business-Aware Metrics: Alerts will evolve from technical thresholds (e.g., “CPU at 95%”) to business-risk indicators (e.g., “High-value customer checkout flow at risk of SLO breach”).</li>
</ul>

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/proving-the-roi-of-your-sre-program-b96d094d0f09">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="sre" /><category term="roi" /><category term="management" /><summary type="html"><![CDATA[How to translate availability, SLOs and MTTR into revenue, churn and operating cost, so leadership can see the return on an SRE program.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Threat Modeling: The Ultimate Guide</title><link href="https://rajatgupta.work/posts/threat-modeling/" rel="alternate" type="text/html" title="Threat Modeling: The Ultimate Guide" /><published>2025-11-16T04:30:00+05:30</published><updated>2025-11-16T04:30:00+05:30</updated><id>https://rajatgupta.work/posts/threat-modeling</id><content type="html" xml:base="https://rajatgupta.work/posts/threat-modeling/"><![CDATA[<p>A proactive approach to building secure software in the DevSecOps era.</p>

<p>This guide emphasizes the critical need for <strong>threat modeling</strong> as a proactive security practice integrated into modern software development, moving away from the outdated model of security as a final gate. It highlights the astronomical costs of breaches and the importance of building security into the development process, aligning with the “shift left” movement.</p>

<h2 id="why-threat-modeling-is-crucial-now-the-devsecops-imperative">Why Threat Modeling Is Crucial Now: The DevSecOps Imperative</h2>

<ul>
  <li>
    <p><strong>Modern relevance</strong><br />
Although its roots are in 20th century military planning, the relevance of threat modeling is amplified by DevSecOps, which seeks to integrate security seamlessly into the development lifecycle.</p>
  </li>
  <li>
    <p><strong>Continuous conversation</strong><br />
Modern threat modeling is an ongoing, collaborative process that involves developers, security professionals, architects, SREs, and product owners throughout the Software Development Lifecycle (SDLC).</p>
  </li>
  <li>
    <p><strong>Collaborative advantage</strong><br />
This approach leverages the deep context of coders and the broad view of attack patterns from security experts.</p>
  </li>
</ul>

<h2 id="core-threat-modeling-methodologies">Core Threat Modeling Methodologies</h2>

<p>The guide introduces <strong>STRIDE</strong> as a practical, intuitive framework for categorizing threats. Developed at Microsoft in 1999, STRIDE is broken down here with a real-world example of a user profile service.</p>

<h3 id="s---spoofing-impersonating-another-entity">S - Spoofing: Impersonating another entity</h3>

<p><strong>Threat example:</strong><br />
An attacker uses stolen session cookies to impersonate a legitimate user.</p>

<p><strong>Mitigation:</strong><br />
Strict session validation, short-lived tokens, and multi-factor authentication.</p>

<h3 id="t---tampering-modifying-data-or-code">T - Tampering: Modifying data or code</h3>

<p><strong>Threat example:</strong><br />
A malicious user intercepts an API call to change their role from user to admin.</p>

<p><strong>Mitigation:</strong><br />
Digital signatures (for example HMAC), server-side validation, and never trusting client-side data.</p>

<h3 id="r---repudiation-denying-that-an-action-was-performed">R - Repudiation: Denying that an action was performed</h3>

<p><strong>Threat example:</strong><br />
A user deletes data and later denies it without proof.</p>

<p><strong>Mitigation:</strong><br />
Maintain comprehensive, immutable audit logs that detail who did what and when.</p>

<h3 id="i---information-disclosure-exposing-data-to-unauthorized-parties">I - Information Disclosure: Exposing data to unauthorized parties</h3>

<p><strong>Threat example:</strong><br />
An API endpoint accidentally leaks sensitive PII.</p>

<p><strong>Mitigation:</strong><br />
Principle of least privilege, returning only necessary data, encrypting data in transit (TLS) and at rest.</p>

<h3 id="d---denial-of-service-dos-making-a-system-unavailable">D - Denial of Service (DoS): Making a system unavailable</h3>

<p><strong>Threat example:</strong><br />
An unauthenticated, computationally expensive endpoint is spammed, overwhelming the server.</p>

<p><strong>Mitigation:</strong><br />
Rate limiting, circuit breakers, and scalable infrastructure.</p>

<h3 id="e---elevation-of-privilege-gaining-unauthorized-capabilities">E - Elevation of Privilege: Gaining unauthorized capabilities</h3>

<p><strong>Threat example:</strong><br />
A standard user accesses administrative functions through predictable URLs.</p>

<p><strong>Mitigation:</strong><br />
Robust RBAC enforced server-side for every request.</p>

<p>Other methodologies mentioned include <strong>PASTA</strong> (risk-centric, business-focused) and <strong>Attack Trees</strong> (visualizing attack paths). The recommendation is to consistently apply one chosen framework.</p>

<h2 id="threat-modeling-as-code">Threat Modeling as Code</h2>

<p>To keep threat models relevant in agile environments, <strong>Threat Modeling as Code</strong> embeds threat model information directly into source code as annotations.</p>

<p><strong>Workflow:</strong><br />
Security context is kept alongside the implementation, enabling automated generation of up-to-date models in the CI/CD pipeline.</p>

<p><strong>Tools:</strong><br />
OWASP pytm and threatspec.</p>

<p><strong>Example (Go with threatspec):</strong></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// @exposes Web:Form to #XSS due to lack of output encoding</span>
<span class="c">// @mitigates MyService:Database against #SQLInjection by using prepared stat</span>
<span class="k">func</span> <span class="n">handler</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
    <span class="c">// 1. Get user input from the request</span>
    <span class="n">userInput</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">URL</span><span class="o">.</span><span class="n">Query</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"q"</span><span class="p">)</span>

    <span class="c">// 2. Query the database using a prepared statement (GOOD)</span>
    <span class="n">rows</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">db</span><span class="o">.</span><span class="n">Query</span><span class="p">(</span><span class="s">"SELECT name FROM users WHERE name = ?"</span><span class="p">,</span> <span class="n">userInput</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="c">// handle error</span>
    <span class="p">}</span>
    <span class="k">defer</span> <span class="n">rows</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

    <span class="c">// ... process rows</span>

    <span class="c">// 3. Render the input back to the user without encoding (BAD)</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"You searched for: %s"</span><span class="p">,</span> <span class="n">userInput</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="output">Output:</h3>
<p>Running tools like threatspec can automatically generate threatmodel.json reports, making threat modeling continuous and developer-driven.</p>

<h2 id="the-engineering-managers-playbook-avoiding-pitfalls">The Engineering Manager’s Playbook: Avoiding Pitfalls</h2>

<p>Engineering managers shape the culture and success of threat modeling. Common traps and how to avoid them:</p>

<h3 id="trap-the-hero-threat-modeler">Trap: The “Hero Threat Modeler”</h3>

<p>Over-reliance on a single security expert.</p>

<p><strong>Playbook:</strong>
Democratize it. Train the entire team on STRIDE, encourage anyone to raise security concerns, and spread security knowledge.</p>

<h3 id="trap-focusing-only-on-finding-threats">Trap: Focusing Only on Finding Threats</h3>

<p>Teams generate long lists of vulnerabilities without mitigation plans.</p>

<p><strong>Playbook:</strong>
Prioritize and fix. Turn findings into actionable backlog items using DREAD or High/Medium/Low rating.</p>

<h3 id="trap-over-complicating-the-process">Trap: Over-complicating the Process</h3>

<p>Heavy processes slow teams down.</p>

<p><strong>Playbook:</strong>
Start simple. Run short threat modeling sessions (about 30 minutes) for new features, focusing on data flow diagrams, trust boundaries, and STRIDE.</p>

<h3 id="trap-treating-it-as-a-tick-box-exercise">Trap: Treating It as a Tick-Box Exercise</h3>

<p>Doing threat modeling only for compliance.</p>

<p><strong>Playbook:</strong>
Connect it to quality and ownership. Treat it like performance or scalability work. Celebrate proactive teams.</p>

<h2 id="the-future-of-threat-modeling">The Future of Threat Modeling</h2>

<p>Threat modeling will become more integrated and automated.</p>

<h3 id="ai-and-ml-powered-tooling">AI and ML-powered tooling</h3>
<p>Predicting vulnerabilities from code patterns, suggesting mitigations, generating models from infrastructure as code.</p>

<h3 id="core-principle">Core principle</h3>
<p>Technology will augment, but not replace, the fundamental need for curious engineers to collaborate in building resilient systems. Embedding threat modeling into a team’s DNA transforms software development for the better.</p>

<p>Read the full post on Medium <a href="https://pulse.rajatgupta.work/threat-modeling-the-ultimate-guide-for-modern-engineering-teams-c1e95d4b1605">here</a>.</p>]]></content><author><name>Rajat Gupta</name></author><category term="security" /><category term="threat-modeling" /><category term="devsecops" /><summary type="html"><![CDATA[Why threat modeling belongs inside delivery instead of at the end: the main methodologies, threat modeling as code, and the pitfalls engineering managers should avoid.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://rajatgupta.work/assets/img/social-card.png" /><media:content medium="image" url="https://rajatgupta.work/assets/img/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>