NAME
Net::Async::Kubernetes::Watcher - Auto-reconnecting Kubernetes watch as IO::Async::Notifier
VERSION
version 0.009
SYNOPSIS
my $watcher = $kube->watcher('Pod',
namespace => 'default',
label_selector => 'app=web',
on_added => sub { my ($pod) = @_; say "Added: " . $pod->metadata->name },
on_modified => sub { my ($pod) = @_; say "Modified: " . $pod->metadata->name },
on_deleted => sub { my ($pod) = @_; say "Deleted: " . $pod->metadata->name },
on_error => sub { my ($status) = @_; warn "Error: $status->{message}" },
);
# Client-side filtering by name and event type
$kube->watcher('Pod',
namespace => 'default',
names => [qr/^nginx/, qr/^redis/], # only matching names
event_types => ['ADDED', 'DELETED'], # skip MODIFIED
on_added => sub { ... },
on_deleted => sub { ... },
);
# Watch multiple resources concurrently
$kube->watcher('Deployment', namespace => 'production', on_modified => sub { ... });
$kube->watcher('Service', namespace => 'production', on_added => sub { ... });
# Stop watching
$watcher->stop;
# Restart
$watcher->start;
DESCRIPTION
An IO::Async::Notifier that watches a Kubernetes resource for changes. Created via "watcher" in Net::Async::Kubernetes.
The watcher automatically:
Reconnects when the server-side timeout expires
Resumes from the last
resourceVersionto avoid missing eventsHandles 410 Gone by clearing the
resourceVersionand restartingRetries a failed watch attempt -- a transport error, a rejection such as
401or403, or a stream that closes at once without an event or right after anERRORevent -- with an exponential backoff (1s, 2s, 4s, ... up to 30s by default, see "reconnect_delay", each shortened at random by up to a fifth, see "reconnect_jitter"), reports every such failure to "on_error" or as a warning, and gives up after "max_retries" consecutive failures when a limit is setFilters events client-side by name patterns (
names) and event types (event_types)
configure
Internal IO::Async::Notifier configuration method. Handles initialization of kube, resource, namespace, timeout, label_selector, field_selector, names, event_types, reconnect_delay, max_reconnect_delay, reconnect_jitter, max_retries, min_watch_duration, and all event callbacks (on_added, on_modified, on_deleted, on_error, on_event). Croaks on a reconnect_delay, max_reconnect_delay or min_watch_duration that is not a non-negative number, on a reconnect_jitter that is not a number from 0 to 1, and on a max_retries that is neither a non-negative integer nor undef.
kube
Returns the parent Net::Async::Kubernetes instance.
resource
Required. The Kubernetes resource kind to watch (e.g., 'Pod', 'Deployment'), or a qualified 'group/version/Kind' name to watch a specific API version -- see "expand_class" in Net::Async::Kubernetes.
namespace
Optional. Namespace to watch. Omit for cluster-scoped resources or to watch all namespaces.
timeout
Server-side timeout per watch cycle in seconds. Default: 300.
reconnect_delay
Seconds to wait before reconnecting after a failed watch attempt (see "on_error" for what counts as one). Default: 1. Each further consecutive failure doubles the delay, up to "max_reconnect_delay", and every delay is shortened at random by up to "reconnect_jitter". A reconnect that gets through -- an event arrives on the stream (an ERROR event does not count), or the watch cycle ends cleanly -- starts the next failure at reconnect_delay again. A watch cycle that ends cleanly (the server-side "timeout") is not a failure and reconnects at once; one that closes within "min_watch_duration" without an event, or right after an ERROR event, did not end cleanly.
max_reconnect_delay
Upper bound in seconds for the reconnect delay. Default: 30, the same cap client-go's reflector puts on its watch backoff: a cluster that comes back is noticed within half a minute, and one that stays away costs one request and one report per half minute.
reconnect_jitter
The share, from 0 to 1, by which each reconnect delay is shortened at random. Default: 0.2 -- a delay of 4 seconds becomes one between 3.2 and 4 seconds, rounded to the millisecond. Without it, watchers that fail together (every watch of a process, or of a fleet, on an API server that restarts) would send their reconnects together again on every step of the backoff.
The delay is only ever shortened: "reconnect_delay" and "max_reconnect_delay" stay upper bounds, and retryAfterSeconds in the report (see "on_error") is the delay the watcher actually waits. With the default each step of the backoff still lies above the one before it (0.8 times double the delay is more than the delay). 0 turns jitter off, for delays that are exactly the documented ones.
max_retries
How many consecutive failed watch requests are retried before the watcher gives up. Default: undef, retry for as long as the watcher runs. When the limit is exceeded the watcher stops and reports that it gave up (see "on_error"); start() begins again with the full count. 0 gives up on the first failure.
min_watch_duration
Seconds a watch stream has to stay open before it may end without an event. Default: 1. A stream that closes sooner without delivering a single event is no watch cycle that ran its course but a failed attempt (see "on_error"): reconnecting at once would send the API server one watch request after another in a tight loop -- a proxy or API server that accepts the watch and closes it straight away. The default is the threshold client-go's reflector uses for the same case (a "very short watch"): a real watch cycle runs until the server-side "timeout", minutes, so one second tells the two apart with room for a slow connection. 0 turns the check off.
label_selector
Optional label selector (e.g., 'app=web,env=prod').
field_selector
Optional field selector (e.g., 'status.phase=Running').
names
Optional client-side filter for resource names. Accepts a single regex, a string (exact match), or an arrayref of regexes/strings. Events whose resource name does not match any of the patterns are silently dropped before callbacks fire.
# Single regex
names => qr/^nginx/
# Multiple patterns (any match passes)
names => [qr/^nginx/, qr/^redis/]
# Exact string match
names => 'my-pod'
event_types
Optional client-side filter for event types. Accepts an arrayref of type strings (ADDED, MODIFIED, DELETED, ERROR). Events whose type is not in the list are silently dropped.
# Only ADDED and DELETED events
event_types => ['ADDED', 'DELETED']
When not set, event types are automatically derived from which callbacks are registered. If only on_added is set, only ADDED events are dispatched. If on_event is set (catch-all), all types pass through.
on_added
Callback for ADDED events. Receives the inflated IO::K8s object.
on_modified
Callback for MODIFIED events. Receives the inflated IO::K8s object.
on_deleted
Callback for DELETED events. Receives the inflated IO::K8s object.
on_error
Callback for errors of the watch. Receives a hashref shaped like a Kubernetes Status, in two cases:
An
ERRORevent on the stream, for example a403arriving mid-stream: the rawStatushashref the API server sent.410 Goneis handled internally and never reaches it.A failed watch attempt: the request fails outright (TLS support missing, a TLS or connection error, an unreachable API server), the API server rejects it with an HTTP error status (
401,403,5xx), or the stream it opens ends badly -- within "min_watch_duration" without a single event, or right after anERRORevent other than410 Gone(that event is reported first, as above). The watcher builds thisStatusitself.reasonisWatchFailed,codethe HTTP status of a rejected request, thecodeof theERRORevent the stream ended on, or0when neither is there (no response arrived, or the stream closed without an event),messagenames the resource, what the watcher does next and the cause (for a rejected request its HTTP status, reason and message, as inHTTP 403 Forbidden: pods is forbidden: ...), anddetailscarrieskind(the watched resource) and, while the watcher retries,retryAfterSeconds:{ kind => 'Status', apiVersion => 'v1', status => 'Failure', reason => 'WatchFailed', code => 0, message => 'watch Pod failed, retrying in 4s: Connection refused', details => { kind => 'Pod', retryAfterSeconds => 4 }, }Once "max_retries" is exceeded, the message says
giving up after N retriesinstead andretryAfterSecondsis absent; the watcher has stopped by then.
A failed watch attempt is reported whatever "event_types" says. Without an on_error it is passed to warn instead, so a watch that cannot reach its cluster never fails silently. ERROR events without an on_error are dropped, as before. The callback may call stop(), which also cancels the reconnect just announced.
on_event
Catch-all callback. Receives the Kubernetes::REST::WatchEvent object. Called in addition to the type-specific callbacks.
start
Start (or restart) the watch stream. Called automatically when the watcher is added to the event loop. Safe to call multiple times (idempotent): a watcher waiting to reconnect after a failed request is already running. After stop(), or after giving up on "max_retries", it starts over with the full retry count.
stop
Stop the watch stream and cancel the current HTTP request, or a reconnect that is waiting out its delay. The watcher will not automatically reconnect until start() is called again.
SEE ALSO
Net::Async::Kubernetes, Kubernetes::REST::WatchEvent, IO::Async::Notifier
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-net-async-kubernetes/issues.
IRC
Join #kubernetes on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.