kube-rs/kube: a Rust client and controller runtime for Kubernetes
Rust Kubernetes client and controller runtime
At a glance
- What is it?
- kube-rs is a Rust client for Kubernetes plus a runtime layer for watchers, reflectors and controllers. It suits Rust teams writing operators, and it asks you to keep three crate versions aligned.
- Who is it for?
- Adopt kube-rs if your operator or cluster tooling is already Rust, or if you want the derive macro and watcher machinery instead of hand-rolled watch bookkeeping. Do not adopt it to avoid learning the Kubernetes API, and do not adopt it if your team cannot carry a Rust toolchain on the build.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What kube-rs/kube is for, and who ends up using it
The crate exists to let Rust programs talk to the Kubernetes API without reimplementing the API machinery. The README describes kube as a Rust client for Kubernetes in the style of a more generic client-go, a runtime abstraction inspired by controller-runtime, and a derive macro for CRDs inspired by kubebuilder. Those three pieces map onto three jobs: reading and writing resources, keeping a local cache of a resource type in sync, and declaring a custom resource in Rust types.
The audience is narrow and specific. If you are writing an operator, a controller, or a piece of cluster tooling in Rust, this is the layer you would otherwise build yourself. The README points at two official examples for shape: version-rs, described as a lightweight deployment reflector using axum, and controller-rs, a Controller of a CRD inside actix. Both are web frameworks, which tells you something about the intended deployment model: a kube-rs program is usually a long-running process with an HTTP surface, not a one-shot script.
The project is hosted by CNCF as a Sandbox Project, per the README, and the workspace is Apache-2.0. It is not archived, and the last push was on 2026-09-23. The most recent release listed is 4.2.0 on 2026-07-22, after 4.0.0 on 2026-06-16 and 3.1.0 on 2026-03-17. That cadence matters more than any single feature: a major version landed in June and a minor two months later, so upgrade notes are part of the operating cost, not an edge case.
Api, Resource and the generic client underneath
Everything starts from Api, which the README says interacts with Kubernetes resources and is generic over Resource. For built-in types, Resource is already implemented, so an Api<Pod> is a thin typed handle over the cluster. The README's example creates a default-namespaced Api, gets a pod by name, patches it with a server-side apply, and deletes it. The patch is built from a json! value and wrapped in Patch::Apply with PatchParams::apply("kube"), which is the client-side spelling of a server-side apply field manager.
This is where kube-rs differs from a hand-written HTTP client. You do not construct URLs or parse JSON into ad hoc structs. The types come from k8s-openapi, and the README's installation block pins that dependency alongside kube and schemars. That coupling is the design: kube-core builds on Kubernetes apimachinery and api concepts, and k8s-openapi supplies the generated structs. Version skew between those crates is the failure mode you will actually hit, which is why the README tells you to select a version of kube along matching versions of k8s-openapi and schemars.
The workspace layout reflects the split. kube-client, kube-core, kube-derive and kube-runtime are separate members, with kube as the facade that re-exports them behind features. The runtime module, for instance, is described as exporting the kube_runtime crate. So the feature flags in your Cargo.toml are not cosmetic; they decide which of those members gets compiled.
Installing kube and writing a first watcher
The README gives the dependency block directly. Note that kube 4.2.0, k8s-openapi 0.28.0 and schemars 1 are the matching set it names; if you do not need the derive macro you can drop schemars, and the README says you can remove the schemars parts when the kube/derive feature is not needed.
[dependencies]
kube = { version = "4.2.0", features = ["runtime", "derive"] }
k8s-openapi = { version = "0.28.0", features = ["latest", "schemars"] }
schemars = { version = "1" }The runtime feature is what brings in watchers, reflectors and controllers. A watcher is described as a streaming interface similar to informers that emits watcher::Event values and does automatic relists. The README's construction takes an Api, a Config, and chains default_backoff and applied_objects:
let api = Api::<Pod>::default_namespaced(client);
let stream = watcher(api, Config::default()).default_backoff().applied_objects();From there the README loops over the stream and prints event.name_any() for each applied object. What you should expect to see is a continuous stream that survives the watch restarting or the connection dropping, because the relist logic is inside the watcher rather than in your loop. One detail worth reading twice: the base items from a watcher are an abstraction above the native WatchEvent, and applied_objects is the utility that narrows it back to a conventional stream. If your code assumes raw WatchEvent values from the start, it will not compile against this signature.
For a custom resource, the derive macro generates the wrapper type. You annotate a spec struct with #[derive(CustomResource, JsonSchema)] and #[kube(group = ..., version = ..., kind = ..., namespaced)], and the generated type implements kube::Resource. The README's example uses group "kube.rs", version "v1", kind "Document" and a namespaced DocumentSpec with title and content. It then builds an Api<Document>, constructs one with Document::new, and serializes Document::crd() to get the CRD manifest. The README states plainly that this derive requires the derive feature enabled on kube, which is the most common first compile error.
Reflectors and controllers: where the state actually lives
A reflector is defined in the README as a watcher with a Store on K. It consumes the events the watcher exposes and keeps the store as accurate as it can. The construction is a pair: reflector::store() returns a reader and a writer, and the writer is handed to reflector along with the watcher. You then treat the reflector as a watcher for streaming purposes while querying the reader whenever you want the current cached set. The README's example watches nodes with the label selector kubernetes.io/arch=amd64, built through Config::default().labels(...).
A Controller goes one step further. The README describes it as a reflector plus an arbitrary number of watchers that schedule events internally into a reconciler. The example chains Controller::new on a root kind, then .owns(child_kind_api, Config::default()), then .run(reconcile, error_policy, context) and .for_each over the results. The owns call is the part that carries operational weight: it is how a reconciler gets triggered when a child object changes, without you wiring a second watcher and correlating identities by hand.
The trade-off is that the store is a cache, and caches are eventually consistent with respect to the API server. A reconciler that reads from the store instead of issuing a live get is faster and gentler on the API server, but it can act on state that is a moment behind. The README does not document a staleness guarantee for the reflector, so if your reconcile logic depends on reading its own writes immediately, that is a design question the documentation leaves to you.
Version coupling is the real adoption cost
The most likely reason a kube-rs build breaks is not a bug in kube-rs. It is a mismatch among kube, k8s-openapi and schemars. The README's installation section is explicit that you select a version of kube along matching versions of k8s-openapi and schemars, and links to a Kubernetes versions page for the historical mapping. The workspace pins k8s-openapi 0.28.0 and schemars 1.0.0, which is the pair the 4.2.0 line expects.
There is a second constraint in the same file: the workspace sets rust-version to 1.89.0, and the README's badge states MSRV 1.89. Edition is 2024. If your CI image is on an older toolchain, nothing else in this article matters until that is fixed.
The cluster side has a floor too. The README badge says the project is tested against Kubernetes v1.32 and above. That is a statement about what is tested, not a promise about older clusters. Running against a cluster below that line is unsupported territory, and the failure may appear as a missing API field rather than a clean error.
Finally, the project forbids unsafe code and denies missing docs at the workspace lint level. That is a real quality signal for a library you will link into your binary, but it also means the public surface is documented and therefore changes to it are visible and intentional. Expect to read release notes. The README points at kube.rs/upgrading for the procedure and at the releases page and changelog for the noteworthy changes, and given that 4.0.0 shipped in June 2026, a major-version migration is recent enough to still be in people's working memory.
When kube-rs is the wrong tool, and what to use instead
kube-rs is the wrong choice if your team does not write Rust. The client-go and controller-runtime lineage is the obvious alternative, and the difference is not stylistic. controller-runtime gives you the same reflector and reconciler model with the manager, cache and client abstractions already assembled, and client-go gives you the typed client and informer machinery. In Go, the generated clientset and the scheme registration are the standard path; in kube-rs you get a generic Api over Resource plus k8s-openapi structs, and you opt into runtime pieces through feature flags. If your operators are already Go, adding a Rust operator means a second toolchain, a second dependency graph and a second upgrade cadence for the same cluster.
A second case where kube-rs is the wrong tool is the one-off script. If you need to list pods and print something, the Api layer is pleasant, but you are pulling a workspace of crates and a Rust build for a task kubectl or a short Python script finishes in a few lines. The README's own framing points at long-running reflectors and controllers, not command-line utilities.
The third case is a team that wants a framework to hide Kubernetes from them. kube-rs does the opposite. It assumes you understand resourceVersion, watch semantics and the difference between a cached read and a live read. The watcher and reflector remove bookkeeping, not conceptual load. If nobody on the team can explain why a reconciler should be idempotent, no amount of type safety in the client will help.
Licence, maintenance and what an upgrade looks like
The workspace declares license = "Apache-2.0", and the repository carries both a LICENSE file and a deny.toml, which the justfile uses through a deny recipe that checks bans, licenses and sources. Apache-2.0 is permissive and includes an explicit patent grant, which is usually the reason projects in this space pick it over MIT. This is a description of the licence identifier, not legal advice; if your organisation has a policy list, check it against Apache-2.0 yourself.
The maintenance picture from the repository facts: the project is not archived, and the last push was on 2026-09-23. Releases are listed as 4.2.0 on 2026-07-22, 4.0.0 on 2026-06-16 and 3.1.0 on 2026-03-17. A major release followed by a minor six weeks later means the upgrade path is live and documented rather than theoretical. The README routes upgrades to kube.rs/upgrading, with noteworthy changes in the GitHub releases and an archived changelog.
The practical upgrade cost is the three-crate alignment. When you move kube, you move k8s-openapi and schemars with it, and any code touching generated Kubernetes structs recompiles against new types. The workspace's own tooling shows the shape of that work: the justfile runs clippy across the workspace with all features and all targets excluding e2e, then a second pass with default features to reach the cfg(not(feature = ...)) paths the first pass cannot. It also runs tests with no default features, with default features, and with all features. That is three configurations for one library, and your application sits on top of whichever combination of features you enabled.
Editorial conclusion
Adopt kube-rs if your operator or cluster tooling is already Rust, or if you want the derive macro and watcher machinery instead of hand-rolled watch bookkeeping. Do not adopt it to avoid learning the Kubernetes API, and do not adopt it if your team cannot carry a Rust toolchain on the build. Before committing, verify that your cluster meets the stated floor of Kubernetes v1.32, that your toolchain is at Rust 1.89 or newer, and that your kube, k8s-openapi and schemars versions are the matching set the README names.
Frequently asked questions
What is kube-rs/kube used for?
It is a Rust client for Kubernetes plus a runtime layer for watchers, reflectors and controllers, and a derive macro for custom resource definitions. The README frames it as a generic client-go equivalent, a controller-runtime-inspired runtime, and a kubebuilder-inspired CRD derive.
What is the purpose of kube-rs/kube?
The README says the crates build on Kubernetes apimachinery and api concepts to enable generic abstractions, so that reflectors, controllers and custom resource interfaces can be written in Rust. In practice that means you write an operator or cluster tool in Rust without reimplementing the watch and cache bookkeeping.
Which versions of kube, k8s-openapi and schemars go together?
The README's installation block pairs kube 4.2.0 with k8s-openapi 0.28.0 and schemars 1. It states that you should select a version of kube along matching versions of k8s-openapi and schemars, and links to a Kubernetes versions page for the historical mapping.
Does kube-rs/kube require a minimum Rust version or Kubernetes version?
The workspace sets rust-version to 1.89.0 and the README badge states MSRV 1.89. The README badge also states the project is tested against Kubernetes v1.32 and above.
How does kube-rs/kube handle a watch connection dropping?
The watcher does automatic relists under the hood, and the README says you do not need to care about the watch having to restart or connections dropping. The base stream items are an abstraction above the native WatchEvent, and applied_objects narrows it back to a conventional stream.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kube-rs-kube)