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. Hosted by CNCF as a Sandbox Project
These crates build upon Kubernetes apimachinery + api concepts to enable generic abstractions. These abstractions allow Rust reinterpretations of reflectors, controllers, and custom resource interfaces, so that you can write applications easily.
Select a version of kube
along with the generated k8s-openapi structs at your chosen Kubernetes version:
[dependencies]
kube = { version = "0.91.0", features = ["runtime", "derive"] }
k8s-openapi = { version = "0.22.0", features = ["latest"] }
See features for a quick overview of default-enabled / opt-in functionality.
See kube.rs/upgrading. Noteworthy changes are highlighted in releases, and archived in the changelog.
See the examples directory for how to use any of these crates.
Official examples:
- version-rs: lightweight deployment
reflector
using axum - controller-rs:
Controller
of a crd inside actix
For real world projects see ADOPTERS.
The Api
is what interacts with Kubernetes resources, and is generic over Resource
:
use k8s_openapi::api::core::v1::Pod;
let pods: Api<Pod> = Api::default_namespaced(client);
let pod = pods.get("blog").await?;
println!("Got pod: {pod:?}");
let patch = json!({"spec": {
"activeDeadlineSeconds": 5
}});
let pp = PatchParams::apply("kube");
let patched = pods.patch("blog", &pp, &Patch::Apply(patch)).await?;
assert_eq!(patched.spec.active_deadline_seconds, Some(5));
pods.delete("blog", &DeleteParams::default()).await?;
See the examples ending in _api
examples for more detail.
Working with custom resources uses automatic code-generation via proc_macros in kube-derive.
You need to add #[derive(CustomResource, JsonSchema)]
and some #[kube(attrs..)]
on a spec struct:
#[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
#[kube(group = "kube.rs", version = "v1", kind = "Document", namespaced)]
pub struct DocumentSpec {
title: String,
content: String,
}
Then you can use the generated wrapper struct Document
as a kube::Resource
:
let docs: Api<Document> = Api::default_namespaced(client);
let d = Document::new("guide", DocumentSpec::default());
println!("doc: {:?}", d);
println!("crd: {:?}", serde_yaml::to_string(&Document::crd()));
There are a ton of kubebuilder-like instructions that you can annotate with here. See the documentation or the crd_
prefixed examples for more.
NB: #[derive(CustomResource)]
requires the derive
feature enabled on kube
.
The runtime
module exports the kube_runtime
crate and contains higher level abstractions on top of the Api
and Resource
types so that you don't have to do all the watch
/resourceVersion
/storage book-keeping yourself.
A low level streaming interface (similar to informers) that presents Applied
, Deleted
or Restarted
events.
let api = Api::<Pod>::default_namespaced(client);
let stream = watcher(api, Config::default()).applied_objects();
This now gives a continual stream of events and you do not need to care about the watch having to restart, or connections dropping.
while let Some(event) = stream.try_next().await? {
println!("Applied: {}", event.name_any());
}
NB: the plain items in a watcher
stream are different from WatchEvent
. If you are following along to "see what changed", you should flatten it with one of the utilities from WatchStreamExt
, such as applied_objects
.
A reflector
is a watcher
with Store
on K
. It acts on all the Event<K>
exposed by watcher
to ensure that the state in the Store
is as accurate as possible.
let nodes: Api<Node> = Api::all(client);
let lp = Config::default().labels("kubernetes.io/arch=amd64");
let (reader, writer) = reflector::store();
let rf = reflector(writer, watcher(nodes, lp));
At this point you can listen to the reflector
as if it was a watcher
, but you can also query the reader
at any point.
A Controller
is a reflector
along with an arbitrary number of watchers that schedule events internally to send events through a reconciler:
Controller::new(root_kind_api, Config::default())
.owns(child_kind_api, Config::default())
.run(reconcile, error_policy, context)
.for_each(|res| async move {
match res {
Ok(o) => info!("reconciled {:?}", o),
Err(e) => warn!("reconcile failed: {}", Report::from(e)),
}
})
.await;
Here reconcile
and error_policy
refer to functions you define. The first will be called when the root or child elements change, and the second when the reconciler
returns an Err
.
See the controller guide for how to write these.
By default rustls is used for TLS, but openssl
is supported. To switch, turn off default-features
, and enable the openssl-tls
feature:
[dependencies]
kube = { version = "0.91.0", default-features = false, features = ["client", "openssl-tls"] }
k8s-openapi = { version = "0.22.0", features = ["latest"] }
This will pull in openssl
and hyper-openssl
. If default-features
is left enabled, you will pull in two TLS stacks, and the default will remain as rustls
.
Kube will work with distroless, scratch, and alpine
(it's also possible to use alpine as a builder with some caveats).
Apache 2.0 licensed. See LICENSE for details.