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

推荐订阅源

量子位
Vercel News
Vercel News
Google DeepMind News
Google DeepMind News
罗磊的独立博客
WordPress大学
WordPress大学
The Cloudflare Blog
GbyAI
GbyAI
The Register - Security
The Register - Security
L
LangChain Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Microsoft Security Blog
Microsoft Security Blog
MyScale Blog
MyScale Blog
A
About on SuperTechFans
U
Unit 42
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
T
Tor Project blog
A
Arctic Wolf
H
Hacker News: Front Page
NISL@THU
NISL@THU
F
Full Disclosure
雷峰网
雷峰网
L
LINUX DO - 热门话题
Recent Announcements
Recent Announcements
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
Google Online Security Blog
Google Online Security Blog
I
InfoQ
Webroot Blog
Webroot Blog
S
Security Affairs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News | PayPal Newsroom
Forbes - Security
Forbes - Security
博客园 - Franky
V
Vulnerabilities – Threatpost
博客园 - 【当耐特】
Latest news
Latest news

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
client-go Deep Dive: Reflector — How Kubernetes Syncs Resources from the API Server
James Lee · 2026-05-19 · via DEV Community

In the previous article we saw that Informer's core mechanism is List/Watch. The component responsible for executing that mechanism is Reflector. Every time an Informer starts up, it's Reflector that connects to the API Server, fetches the full resource snapshot, and then keeps watching for changes.

Source: k8s.io/client-go/tools/cache/reflector.go


1. Where Reflector Fits

Informer starts
     │
     ▼
┌──────────────────────────────────────────────────┐
│                   Reflector                      │
│                                                  │
│  Phase 1: LIST                                   │
│  ├── call listerWatcher.List()                   │
│  ├── get ResourceVersion                         │
│  ├── extract object list                         │
│  └── syncWith() → store all objects in DeltaFIFO │
│                                                  │
│  Phase 2: WATCH                                  │
│  ├── call listerWatcher.Watch()                  │
│  ├── receive incremental events (HTTP chunked)   │
│  └── watchHandler() → push events to DeltaFIFO  │
└──────────────────────────────────────────────────┘
     │
     ▼
DeltaFIFO (event queue)

Enter fullscreen mode Exit fullscreen mode


2. The Reflector Struct

// k8s.io/client-go/tools/cache/reflector.go
type Reflector struct {
    name             string
    expectedTypeName string
    expectedType     reflect.Type          // the resource type being watched (e.g. *v1.Pod)
    expectedGVK      *schema.GroupVersionKind

    store            Store                 // DeltaFIFO — where events are written
    listerWatcher    ListerWatcher         // the actual List/Watch implementation

    backoffManager         wait.BackoffManager  // retry backoff for Watch reconnects
    initConnBackoffManager wait.BackoffManager  // backoff for initial connection

    resyncPeriod     time.Duration         // how often to force a full resync (0 = never)
    ShouldResync     func() bool           // optional: custom resync decision function

    clock            clock.Clock
    paginatedResult  bool

    // ResourceVersion tracking — critical for Watch correctness
    lastSyncResourceVersion              string
    isLastSyncResourceVersionUnavailable bool
    lastSyncResourceVersionMutex         sync.RWMutex

    WatchListPageSize  int64
    watchErrorHandler  WatchErrorHandler
}

Enter fullscreen mode Exit fullscreen mode

Key fields explained:

Field Purpose
store The DeltaFIFO queue — Reflector writes all events here
listerWatcher Interface that provides the actual List() and Watch() calls
lastSyncResourceVersion Tracks the latest version seen — Watch resumes from this point after reconnect
resyncPeriod If > 0, Reflector periodically forces a full re-List to catch any missed events
backoffManager Exponential backoff when Watch connection drops

3. The ListerWatcher Interface

listerWatcher is an interface — Reflector doesn't know or care which resource type it's watching. The concrete implementation is injected per resource type.

type ListerWatcher interface {
    Lister
    Watcher
}

type Lister interface {
    List(options metav1.ListOptions) (runtime.Object, error)
}

type Watcher interface {
    Watch(options metav1.ListOptions) (watch.Interface, error)
}

Enter fullscreen mode Exit fullscreen mode

For a Pod Informer, the concrete implementation looks like this:

// k8s.io/client-go/informers/core/v1/pods.go
func NewFilteredPodInformer(
    client kubernetes.Interface,
    namespace string,
    resyncPeriod time.Duration,
    indexers cache.Indexers,
    tweakListOptions internalinterfaces.TweakListOptionsFunc,
) cache.SharedIndexInformer {
    return cache.NewSharedIndexInformer(
        &cache.ListWatch{
            // List: calls the real Kubernetes API
            ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                if tweakListOptions != nil {
                    tweakListOptions(&options)
                }
                return client.CoreV1().Pods(namespace).List(context.TODO(), options)
            },
            // Watch: opens a long-lived HTTP connection to the API Server
            WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                if tweakListOptions != nil {
                    tweakListOptions(&options)
                }
                return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
            },
        },
        &corev1.Pod{},
        resyncPeriod,
        indexers,
    )
}

Enter fullscreen mode Exit fullscreen mode

This is why Clientset must be created before SharedInformerFactory. The ListFunc and WatchFunc are closures over the Clientset — without it, Reflector has no way to talk to the API Server.

func NewKubeController(...) *KubeController {
    kc := &KubeController{clientset: clientset}

    // Clientset is passed in here — it flows down into every ListFunc/WatchFunc
    kc.factory = informers.NewSharedInformerFactory(clientset, defaultResync)

    kc.podInformer      = kc.factory.Core().V1().Pods()
    kc.podsLister       = kc.podInformer.Lister()
    kc.podsSynced       = kc.podInformer.Informer().HasSynced
    kc.deploymentInformer   = kc.factory.Apps().V1().Deployments()
    kc.deploymentsLister    = kc.deploymentInformer.Lister()
    kc.deploymentsSynced    = kc.deploymentInformer.Informer().HasSynced
    return kc
}

Enter fullscreen mode Exit fullscreen mode


4. ListAndWatch: Phase 1 — Full List

The ListAndWatch method is the heart of Reflector. Phase 1 fetches the complete current state:

func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
    var resourceVersion string
    options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}

    if err := func() error {
        // ① Fetch all resources (with pagination support)
        go func() {
            pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
                return r.listerWatcher.List(opts)   // → calls ListFunc → Clientset.CoreV1().Pods().List()
            }))
            list, paginatedResult, err = pager.List(context.Background(), options)
            // handle expired ResourceVersion: retry with fresh version
            if isExpiredError(err) || isTooLargeResourceVersionError(err) {
                list, paginatedResult, err = pager.List(context.Background(),
                    metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
            }
            close(listCh)
        }()

        // wait for list to complete or stop signal
        select {
        case <-stopCh:   return nil
        case r := <-panicCh: panic(r)
        case <-listCh:
        }

        // ② Extract ResourceVersion from the list response
        listMetaInterface, _ := meta.ListAccessor(list)
        resourceVersion = listMetaInterface.GetResourceVersion()

        // ③ Convert list result into a slice of runtime.Object
        items, _ := meta.ExtractList(list)

        // ④ Store all objects + ResourceVersion into DeltaFIFO
        r.syncWith(items, resourceVersion)

        return nil
    }(); err != nil {
        return err
    }

    // ⑤ Record the latest ResourceVersion — Watch will resume from here
    r.setLastSyncResourceVersion(resourceVersion)
    ...
}

Enter fullscreen mode Exit fullscreen mode

Phase 1 call chain:

ListAndWatch()
  │
  ├── ① r.listerWatcher.List()        → fetch all objects from API Server
  ├── ② listMetaInterface.GetResourceVersion()  → extract version stamp
  ├── ③ meta.ExtractList()            → convert to []runtime.Object
  ├── ④ r.syncWith()                  → write all objects into DeltaFIFO
  └── ⑤ r.setLastSyncResourceVersion() → save version for Watch resume point

Enter fullscreen mode Exit fullscreen mode


5. ListAndWatch: Phase 2 — Continuous Watch

After the full List completes, Reflector enters an infinite loop watching for incremental changes:

    // Periodic resync goroutine (if resyncPeriod > 0)
    go func() {
        for {
            select {
            case <-resyncCh:
                if r.ShouldResync == nil || r.ShouldResync() {
                    r.store.Resync()   // force re-sync of Indexer from DeltaFIFO
                }
            case <-stopCh:
                return
            }
        }
    }()

    // Main Watch loop
    for {
        select {
        case <-stopCh:
            return nil
        default:
        }

        // Add randomized timeout to prevent thundering herd on reconnect
        timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
        options = metav1.ListOptions{
            ResourceVersion:     resourceVersion,  // resume from last known version
            TimeoutSeconds:      &timeoutSeconds,
            AllowWatchBookmarks: true,
        }

        // ① Open a long-lived Watch connection to the API Server
        w, err := r.listerWatcher.Watch(options)
        if err != nil {
            if utilnet.IsConnectionRefused(err) {
                <-r.initConnBackoffManager.Backoff().C()  // backoff and retry
                continue
            }
            return err
        }

        // ② Process incoming events: write to DeltaFIFO + update ResourceVersion
        if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
            if !isExpiredError(err) {
                // log unexpected errors
            }
            return nil
        }
    }

Enter fullscreen mode Exit fullscreen mode

Phase 2 call chain:

ListAndWatch() — Watch loop
  │
  ├── ① r.listerWatcher.Watch()    → open HTTP long-poll to API Server
  │                                   (passes ResourceVersion to resume from)
  └── ② r.watchHandler()           → for each incoming event:
                                       write object to DeltaFIFO
                                       update lastSyncResourceVersion

Enter fullscreen mode Exit fullscreen mode


6. ResourceVersion: The Key to Reliable Watch

ResourceVersion is a monotonically increasing version stamp that etcd assigns to every resource object. It's the mechanism that makes Watch reliable:

List response:  ResourceVersion = "1000"
                (all objects as of version 1000)
     │
     ▼
Watch request:  ResourceVersion = "1000"
                (give me all events AFTER version 1000)
     │
     ▼
API Server streams:
  - Pod "nginx" Updated  (RV=1001)
  - Pod "redis" Deleted  (RV=1002)
  - ...

If Watch connection drops:
     │
     ▼
Reflector reconnects with:  ResourceVersion = "1002"
(resumes exactly where it left off — no events missed, no duplicates)

Enter fullscreen mode Exit fullscreen mode

Scenario ResourceVersion behavior
Fresh start "" or "0" — get latest snapshot
After List Set to the version returned by List response
After each Watch event Updated to the event's ResourceVersion
Watch reconnect Resumes from lastSyncResourceVersion
Expired version (too old) Triggers a fresh List from scratch

7. Under the Hood: How Watch Works (HTTP Chunked Transfer)

The Watch connection is not a WebSocket or gRPC stream — it's a plain HTTP/1.1 long-lived connection using Chunked Transfer Encoding.

Client (Reflector)                    API Server
     │                                     │
     │  GET /api/v1/pods?watch=true        │
     │  ResourceVersion=1000               │
     │ ──────────────────────────────────► │
     │                                     │
     │  HTTP/1.1 200 OK                    │
     │  Transfer-Encoding: chunked         │
     │ ◄────────────────────────────────── │
     │                                     │
     │  chunk: {"type":"ADDED","object":…} │
     │ ◄────────────────────────────────── │
     │                                     │
     │  chunk: {"type":"MODIFIED","object":…}
     │ ◄────────────────────────────────── │
     │                                     │
     │  (connection stays open…)           │
     │  (new chunks arrive as events occur)│

Enter fullscreen mode Exit fullscreen mode

Why Chunked Transfer Encoding?

In standard HTTP, the Content-Length header tells the client how many bytes to expect. But for a Watch stream, the server doesn't know in advance how many events will occur — the stream is open-ended.

Chunked Transfer Encoding solves this:

  • The response body is split into variable-size chunks
  • Each chunk is prefixed with its size in hex
  • The server sends chunks as events arrive — no pre-declared content length needed
  • A zero-length chunk signals the end of the stream
HTTP/1.1 200 OK
Transfer-Encoding: chunked

1a\r\n                          ← chunk size (hex)
{"type":"ADDED","object":…}\r\n ← chunk data
\r\n
2f\r\n
{"type":"MODIFIED","object":…}\r\n
\r\n
0\r\n                           ← end of stream
\r\n

Enter fullscreen mode Exit fullscreen mode

This is only available in HTTP/1.1 and later. It's what makes Kubernetes Watch efficient — a single persistent connection replaces thousands of polling requests.


8. Summary

Reflector.Run()
     │
     └── ListAndWatch()
           │
           ├── Phase 1: LIST
           │     ├── listerWatcher.List()          → full snapshot from API Server
           │     ├── GetResourceVersion()           → stamp the snapshot version
           │     ├── meta.ExtractList()             → parse into objects
           │     └── syncWith() → DeltaFIFO         → seed the local cache
           │
           └── Phase 2: WATCH (infinite loop)
                 ├── listerWatcher.Watch(RV)        → HTTP chunked long-poll
                 ├── watchHandler() → DeltaFIFO     → stream events to queue
                 └── on disconnect: backoff + retry
                     on expired RV: re-List from scratch

Enter fullscreen mode Exit fullscreen mode

Concept Detail
ListerWatcher Interface injected per resource type; backed by Clientset API calls
ResourceVersion etcd version stamp; enables Watch to resume without missing events
DeltaFIFO The downstream queue; Reflector is its sole producer
Chunked Transfer HTTP/1.1 mechanism that keeps the Watch connection open indefinitely
Backoff Exponential retry when Watch connection drops or API Server is unavailable

Reflector is the bridge between the Kubernetes API Server and the local Informer cache. Once you understand its List → syncWith → Watch → watchHandler pipeline, the reliability guarantees of the entire Informer framework become clear.


Next in this series: DeltaFIFO: The Event Queue Behind Informer (Part 3)


Follow the series for more deep dives into Kubernetes development.