惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
C
Cisco Blogs
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
S
Security Affairs
PCI Perspectives
PCI Perspectives
The Last Watchdog
The Last Watchdog
AWS News Blog
AWS News Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
News and Events Feed by Topic
W
WeLiveSecurity
T
Tenable Blog
L
LINUX DO - 最新话题
T
Tor Project blog
Help Net Security
Help Net Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
爱范儿
爱范儿
O
OpenAI News
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
I
Intezer
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
S
Securelist
P
Privacy International News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
Vulnerabilities – Threatpost
Schneier on Security
Schneier on Security
量子位
SecWiki News
SecWiki News
L
Lohrmann on Cybersecurity
T
Threat Research - Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Scott Helme
Scott Helme
H
Help Net Security
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
Spread Privacy
Spread Privacy
Know Your Adversary
Know Your Adversary
I
InfoQ
TaoSecurity Blog
TaoSecurity Blog
Blog — PlanetScale
Blog — PlanetScale
N
News | PayPal Newsroom
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Git as source of truth is a property, not a slogan
Arthur · 2026-06-19 · via DEV Community

The most useful insight in the Kubernetes-drift postmortem I want to walk through here is the one the team writes near the end, almost in passing: production didn't break at the moment of the deploy. It broke six months earlier, when somebody ran kubectl edit on a ConfigMap and didn't put the change in Git. The deploy was the moment that fact became visible.

That sentence reframes a whole class of cluster outage. It's worth taking seriously. The team I'm retelling here ran a perfectly normal release pipeline (GitLab CI; tests; image build; helm upgrade --atomic as the last step) into an afternoon production environment, watched the green checkmark appear, and then watched the alerts fire. New replicas couldn't reach the database. The image rollback to the previous tag, the one that had been running fine for months, did not help. Same CrashLoopBackOff. Same authentication failure against PostgreSQL. The version of the application that had been alive at lunchtime would not boot from clean state at 3 PM.

The reason was that the running pods at lunchtime had been initialised at some forgotten point in the past, when somebody had kubectl edit-ed the users-api-env ConfigMap to fix an unrelated PgBouncer issue. The fix had never made it into the chart's values-prod.yaml. The ConfigMap in the cluster diverged from the ConfigMap that Helm thought it was managing. The pods kept running, holding their open connections, perfectly content. The Helm release was only consulted when something rendered new pods. Then the chart's "correct" version of DB_HOST was the one that got applied — and it was the one that had been wrong for half a year.

This is the load-bearing observation. The release didn't break the service. It synchronised a divergence that had been sitting there for months. Reading it that way, the question stops being "what went wrong with this deploy" and becomes "why did the cluster have a state that didn't exist in Git in the first place, and how had it been allowed to live there for so long."

Why the rollback didn't help, and why it never could have

The reflex when a deploy goes red is to roll the image back. That works when the regression is in the image: the new code does something the old code didn't, the old code doesn't do that thing, the symptoms go away. It does not work when the regression is in the environment and the deploy was the trigger for the environment to be re-rendered.

In this incident the image was a red herring. Both the new image and the old image were starting up against the same chart-derived config, because both were starting from a clean pod, and the clean pod's config was the config in the chart, and the config in the chart was wrong. Image rollbacks are an answer for one shape of regression. They are silent on another shape, and the cluster's current state quietly determines which shape you are looking at.

This is also where the team realised --atomic had been doing less than they thought. The Helm --atomic flag rolls back the upgrade if the operation itself fails. It does not protect against an upgrade that succeeds operationally and breaks behaviourally a few minutes later. Kubernetes saw replicas come up, declared rollout done, and the pipeline turned green. The 5xx surge happened after that, on real traffic, against real bugs, in a cluster that thought everything had gone fine.

Five things the team had been carrying without seeing them

Re-reading the team's own list of weak points, what's striking is how much of it is invisible until something pulls on it. They wrote down five:

  1. CI had direct access to the production cluster. Kubeconfig in GitLab CI variables, protected and masked, but a credential whose presence was assumed and forgotten. An attacker who lands inside CI is one yaml away from production state.
  2. The runner had inbound access to the Kubernetes API server. The model required it; nobody loved it.
  3. Git was a description of what the team thought was running, not what was running. The two had been allowed to disagree.
  4. There was no continuous drift check. A divergence could persist indefinitely, and only the next pod scheduling event would surface it.
  5. Rollback covered the image, not the environment. When the regression lived in the environment, the rollback control was pointing at the wrong layer.

The combination is what made the incident long. Each item alone is fixable; the team was running on the assumption that the items were all small enough not to matter, and the cluster's response was the standard one: state things you don't watch will drift.

Why a "no manual edits" rule would not have been enough

The reasonable response to an incident like this is to add a rule. Don't kubectl edit production. Pull a deploy job. File a change request. Rules of this kind are useful and people sometimes follow them. But rules don't change the system's properties. They change the population of paper-trail incidents. Somebody at three in the morning will still reach for the fastest available control surface, and the fastest available control surface will still be kubectl edit. The cluster will accept the edit. The cluster does not know the difference between an edit committed to Git and one performed in anger.

What the team wanted was a property of the system rather than a property of the operators: production should converge to the state in Git, automatically and continuously, as a structural fact. That is the substance of GitOps as the OpenGitOps project (overseen by CNCF TAG App Delivery's GitOps Working Group) defines it: declarative desired state, versioned and immutable, pulled automatically by an in-cluster agent, and continuously reconciled against the live system. Rules sit on top of operators. Reconciliation sits on top of the cluster. They are not the same engineering target.

The team picked Argo CD. They could have picked Flux, and the difference for what they needed came down largely to UI ergonomics — visible diffs, Synced / OutOfSync statuses readable by people who weren't platform engineers. What mattered was that the cluster now contained an agent whose entire job was to look at the manifests in a Git repository, look at the state of the cluster, and complain about the difference.

How they rolled it out, and why the rollout itself is the lesson

If you've been near GitOps adoption you've seen the failure mode: somebody enables auto-sync, prune, and self-heal on a long-lived production cluster all at once, and a controller starts deleting things the cluster needed but Git never knew about. The team avoided that by deliberately walking through the gradient.

They started with a pure observation mode. Argo CD pointed at the manifests, watched the cluster, and reported OutOfSync on every divergence. It did not act. The output was a list. The list was what mattered. Some of the differences were legitimate runtime fields (status, certain annotations added by admission controllers, replica counts under HPA). Some of them were real drift exactly of the kind that had caused the incident: forgotten ConfigMaps, ad-hoc Service objects, role bindings left over from experiments, Ingress annotations from a deprecated workaround. Each line in the list got classified before any control was turned on.

Only after the classification did selfHeal go on, app by app. Only after that did prune go on, project by project. The whole thing was a phased rollout, not a flag flip:

Phase Argo CD setting What it gives you What it can break
Observe-only automated: absent or all false A diff list and a classification exercise: drift, runtime field, or owned-elsewhere Nothing — pure read
Auto-sync (subset) automated.prune: false, automated.selfHeal: false per app Convergence to Git for the chosen apps; no retroactive cleanup yet A wrong commit hits prod faster than a human would catch it
Self-heal automated.selfHeal: true A manual kubectl edit no longer survives reconciliation Operator field-ownership fights surface (HPA vs Git on replicas, etc.)
Prune (per project) automated.prune: true Resources removed from Git actually leave the cluster Anything you forgot to commit gets deleted
RBAC narrowing Argo CD AppProject whitelists Bounded blast radius per project; CD agent isn't cluster-admin Initial whitelist mis-config breaks deploys until corrected

The fix for the incident itself, before any of this, was a one-line MR. It's worth showing. This is the entirety of the change that brought production back:

 env:
-  DB_HOST: "pgbouncer.users.svc.cluster.local"
+  DB_HOST: "pgbouncer-primary.users.svc.cluster.local"

Eight characters of suffix, after a kubectl describe-and-verify on the live pgbouncer-primary Service to confirm there were healthy endpoints behind it. The team did the merge through the existing pipeline rather than re-issuing a kubectl edit on the ConfigMap, which would have been faster by perhaps two minutes and would have re-introduced the original mistake on the same afternoon they were trying to learn from it.

The Argo CD Application they ended up with for that service, after all phases were on, looks roughly like this:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: users-api
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://git.example.com/platform/prod-manifests.git
    targetRevision: main
    path: services/users-api
    helm:
      valueFiles:
        - values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: users
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=false
      - ApplyOutOfSyncOnly=true

What changed around the Application is at least as important as the Application itself. Four pieces:

The CI runner lost its kubeconfig. It builds images. It pushes images. It commits a tag bump into the infra repository through a bot account whose only right is to write to that repo. That is the entire deploy authority CI has. Argo CD does the rest, from inside the cluster, by pulling state, which is the architectural inversion that gives GitOps its name.

Argo CD itself got a reasonable RBAC. The team broke applications into Argo CD AppProjects — production, platform, system — and the production project has an empty clusterResourceWhitelist. Application repositories cannot create ClusterRole, cannot create MutatingWebhookConfiguration, cannot define new CRDs. Anything cluster-scoped is somebody else's repository and somebody else's review.

Secrets moved out of Git. The Kubernetes docs are explicit about why: "Kubernetes Secrets are, by default, stored unencrypted in the API server's underlying data store (etcd). Anyone with API access can retrieve or modify a Secret, and so can anyone with access to etcd." Base64 is encoding, not protection. The team adopted External Secrets Operator. Git holds an ExternalSecret resource that says "this app needs the secret at prod/users-api/db"; the actual value lives in the configured store (Vault, AWS Secrets Manager, Doppler, whatever). The split is the point: declaration in Git, value in a secrets system that knows how to do rotation and audit.

There is an emergency path. Not a workaround, an explicit procedure: pause auto-sync for the affected Application, do the manual change, restore service, immediately commit the change, re-enable auto-sync. You don't pretend manual changes never happen in incidents. You make sure they don't survive into normality.

What stops being mysterious afterwards

The change that surprised the team, and the one I find most interesting to read about, is not in the controller. It's in the language people use during incidents.

Before, an outage would generate a familiar list of questions: what's actually in the cluster right now? Did this even apply? Who changed the ConfigMap? Why does Git say one thing and the namespace say another? Those questions weren't trivia. They were the inability of the team to answer them quickly enough that mattered. Now Synced means the live state matches Git within the configured comparison rules. OutOfSync means there's a diff and the diff is visible. The questions don't go away — Kubernetes is still Kubernetes — but the gap between asking and answering has collapsed into something an Argo CD UI can show in a tab.

Rollback works at the right layer. To roll a tag, revert the tag in Git. To roll a config change, revert the config commit. The control surface and the failure surface line up. This does not extend to data — GitOps does not roll back database migrations, queues, or external contracts, and pretending it does is a separate kind of mistake — but it does extend, cleanly, to everything Kubernetes considers its own.

Manual edits stop being invisible. This is the cultural change the technology forces. A six-month-old kubectl edit could survive every previous deploy because nothing was looking. Now it becomes OutOfSync within minutes, or, after self-heal goes on, gets quietly reverted. The team writes that some people didn't love this. Through a couple of incidents the consensus settled: GitOps doesn't stop you from fixing production, it stops you from forgetting what you fixed.

The CI attack surface contracts. The compromise of a CI runner used to mean potential write access to production. Now an attacker who lands in CI gets a registry credential and a write-to-infra-repo bot. To reach production they need to land a malicious change in an infra-repo branch, get it past review, and hope nobody catches it before reconciliation. That's not nothing, but it's a longer chain than "exec a kubectl with the credential we already have."

And there's a quieter outcome, which I think is the most honest one in the postmortem: the team got better at noticing where GitOps shouldn't be the only mechanism. Schema migrations don't belong in syncWaves for arbitrarily heavy changes. Expand-and-contract migrations, feature flags, runtime guards, and proper observability sit in a different layer. GitOps applies declarative Kubernetes state well; it doesn't apply data-shape changes in PostgreSQL well. Knowing the boundary makes both layers cheaper.

Git as source of truth is a property, not a slogan

The line I want to leave here is the one the team uses to close their own piece. Git as source of truth has become unremarkable from repetition, and it's possible to deploy software for years without testing whether it's actually true in your environment. After this incident the team rephrases it as a set of falsifiable questions: can you take a namespace, delete the managed resources, and rebuild it from Git? Can you roll a change by reverting a commit, not by piecing together kubectl history from Slack? Can you remove CI's access to the cluster API and still ship? If any answer is no, Git isn't your source of truth yet — it's a partial description the cluster sometimes consults.

That's the framing I'm taking from the postmortem. The deploy didn't break the service. The team had been running on a production state that existed only because somebody had once fixed something quickly and forgotten. The release pulled the curtain back, and the work after that was to build a system whose normal operation makes that kind of forgetting structurally impossible.