Rust client for Kubernetes in the style of a more generic client-go plus a runtime abstraction inspired by controller-runtime.
These crate makes certain assumptions about the kubernetes api to allow writing generic abstractions, and as such contains rust reinterpretations of reflectors, informers, and controller so that you can writing kubernetes controllers/watchers/operators more easily.
NB: This library is currently undergoing a lot of changes with async/await stabilizing. Please check the CHANGELOG when upgrading.
Select a version of kube
along with the generated k8s api types that corresponds to your cluster version:
[dependencies]
kube = "0.37.0"
kube-derive = "0.37.0"
kube-runtime = "0.37.0"
k8s-openapi = { version = "0.9.0", default-features = false, features = ["v1_17"] }
Note that turning off default-features
for k8s-openapi
is recommended to speed up your compilation (and we provide an api anyway).
See the examples directory for how to watch over resources in a simplistic way.
Some real world examples:
-
version-rs: super lightweight reflector deployment with actix 2 and prometheus metrics
-
controller-rs:
Controller
owned by aManager
inside actix -
krustlet: a complete
WASM
runningkubelet
The direct Api
type takes a client, and is constructed with either the ::global
or ::namespaced
functions:
use k8s_openapi::api::core::v1::Pod;
let pods: Api<Pod> = Api::namespaced(client, "default");
let p = pods.get("blog").await?;
println!("Got blog pod with containers: {:?}", p.spec.unwrap().containers);
let patch = json!({"spec": {
"activeDeadlineSeconds": 5
}});
let patched = pods.patch("blog", &pp, serde_json::to_vec(&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 #[derive(CustomResource)]
and some #[kube(attrs..)]
on a spec struct:
#[derive(CustomResource, Serialize, Deserialize, Default, Clone)]
#[kube(group = "clux.dev", version = "v1", namespaced)]
pub struct FooSpec {
name: String,
info: String,
}
Then you can use a lot of generated code as:
println!("kind = {}", Foo::KIND); // impl k8s_openapi::Resource
let foos: Api<Foo> = Api::namespaced(client, "default");
let f = Foo::new("my-foo");
println!("foo: {:?}", f)
println!("crd: {}", serde_yaml::to_string(Foo::crd());
There are a ton of kubebuilder like instructions that you can annotate with here. See the crd_
prefixed examples for more.
The kube_runtime
create contains sets of 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>::namespaced(client, "default");
let watcher = watcher(api, ListParams::default());
This now gives a continual stream of events and you do not need to care about the watch having to restart, or connections dropping.
let apply_events = try_flatten_applied(watcher).boxed_local()
while let Some(event) = watcher.try_next().await? {
println!("Applied: {}", Meta::name(&event));
}
NB: the plain stream items a watcher
returns are different from WatchEvent
. If you are following along to "see what changed", you should flatten it with one of the utilities like try_flatten_applied
or try_flatten_touched
.
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::namespaced(client, &namespace);
let lp = ListParams::default()
.labels("beta.kubernetes.io/instance-type=m4.2xlarge");
let store = reflector::store::Writer::<Node>::default();
let reader = store.as_reader();
let rf = reflector(store, 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, ListParams::default())
.owns(child_kind_api, ListParams::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
.
Examples that show a little common flows. These all have logging of this library set up to debug
, and where possible pick up on the NAMESPACE
evar.
NB: not all examples have been migrated to the new runtime yet. If it uses kube-runtime
it's new.
# watch configmap events
cargo run --example configmap_watcher
# watch pod events
cargo run --example pod_informer
# watch event events
cargo run --example event_informer
# watch for broken nodes
cargo run --example node_informer
or for the reflectors:
cargo run --example pod_reflector
cargo run --example node_reflector
cargo run --example deployment_reflector
cargo run --example secret_reflector
cargo run --example configmap_reflector
for one based on a CRD, you need to create the CRD first:
kubectl apply -f examples/foo.yaml
cargo run --example crd_reflector
then you can kubectl apply -f crd-baz.yaml -n default
, or kubectl delete -f crd-baz.yaml -n default
, or kubectl edit foos baz -n default
to verify that the events are being picked up.
ditto for a controller:
kubectl apply -f kube/examples/configmapgen_controller_crd.yaml
cargo run --example configmapgen_controller &
kubectl apply -f kube/examples/configmapgen_controller_object.yaml
For straight API use examples, try:
cargo run --example crd_api
cargo run --example job_api
cargo run --example log_stream
cargo run --example pod_api
NAMESPACE=dev cargo run --example log_stream -- kafka-manager-7d4f4bd8dc-f6c44
Kube has basic support (with caveats) for rustls as a replacement for the openssl
dependency. To use this, turn off default features, and enable rustls-tls
:
cargo run --example pod_informer --no-default-features --features=rustls-tls
or in Cargo.toml
:
[dependencies]
kube = { version = "0.37.0", default-features = false, features = ["rustls-tls"] }
kube-runtime = { version = "0.37.0", default-features = false, features = ["rustls-tls"] }
k8s-openapi = { version = "0.9.0", default-features = false, features = ["v1_17"] }
This will pull in the variant of reqwest
that also uses its rustls-tls
feature.
Apache 2.0 licensed. See LICENSE for details.