Getting Started with Rust and Kubernetes using kube-rs

Last updated: April 13, 2025

1. Introduction: Automating Kubernetes with Rust

Kubernetes (k8s) has become the standard for container orchestration, but managing its resources programmatically often involves interacting with its extensive REST API. While kubectl is excellent for manual operations, building controllers, operators, or custom automation tools requires a client library.

Rust, with its performance, safety, and strong typing, is a great choice for building reliable Kubernetes tooling. The kube-rs crate provides a powerful and idiomatic Rust client for the Kubernetes API.

This guide introduces kube-rs and demonstrates how to set up a client, list resources (like Pods), and understand the basics of interacting with the Kubernetes API from a Rust application.

2. The kube-rs Crate

kube-rs is the predominant Rust client for Kubernetes. Key features include:

  • Asynchronous API built on Tokio.
  • Strongly typed representations of Kubernetes API objects (Pods, Deployments, Services, etc.) generated from the official OpenAPI spec.
  • Helper functions for common operations (list, get, create, update, delete, watch).
  • Automatic discovery of cluster configuration (from ~/.kube/config or in-cluster service account).
  • Support for Custom Resource Definitions (CRDs).

It aims to provide a safe, ergonomic, and efficient way to interact with Kubernetes from Rust.

3. Prerequisites

  • Rust and Cargo installed (see Getting Started with Rust).
  • Access to a Kubernetes cluster. This can be:
    • A local cluster via Minikube, Kind, k3d, or Docker Desktop.
    • A managed cloud cluster (EKS, GKE, AKS).
  • kubectl configured to communicate with your cluster (kube-rs uses the same configuration). Verify with kubectl cluster-info.
  • Basic understanding of Kubernetes concepts (Pods, Deployments, etc. - see Getting Started with Kubernetes).
  • Familiarity with Rust's async/await and Tokio.

4. Project Setup

Create a new Rust binary project:

cargo new rust-k8s-client
cd rust-k8s-client

Add the necessary dependencies to your Cargo.toml:

[package]
name = "rust-k8s-client"
version = "0.1.0"
edition = "2021"

[dependencies]
kube = { version = "0.88", features = ["runtime", "derive"] } # Check crates.io for latest version
k8s-openapi = { version = "0.20.0", features = ["v1_28"] } # Match your cluster API version
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0" }
serde_json = "1.0"
anyhow = "1.0" # For easier error handling in examples

Important:

  • Adjust the kube version as needed.
  • The k8s-openapi version and feature flag (e.g., "v1_28") should ideally match the API version of the Kubernetes cluster you are targeting. Use kubectl version to check your cluster version.
  • We add anyhow for convenient error handling in these examples.

Alternatively, use cargo add:

cargo add kube --features runtime,derive
cargo add k8s-openapi --features v1_28 # Adjust version feature
cargo add tokio --features full
cargo add serde serde_json anyhow

5. Creating a Kubernetes Client

The first step in any interaction is creating a Client instance. kube-rs makes this easy by automatically inferring the configuration.

Modify src/main.rs:

use kube::{Client, Config};
use anyhow::Result; // Using anyhow for convenient Result type

#[tokio::main]
async fn main() -> Result<()> { // Return anyhow::Result
    println!("Attempting to create Kubernetes client...");

    // Infer the runtime environment and try to create a Kubernetes Client
    let client = Client::try_default().await?; // The '?' propagates errors

    println!("Successfully created Kubernetes client.");

    // We'll add more logic here later...

    Ok(())
}

Client::try_default().await attempts to load configuration from:

  1. In-cluster service account (if running inside a Kubernetes Pod).
  2. Your local kubeconfig file (typically ~/.kube/config).

Run this with cargo run. If your kubectl is configured correctly, it should print the success message.

6. Listing Resources (Pods Example)

To work with specific Kubernetes resources (like Pods, Deployments, etc.), you use the Api type from kube-rs, parameterized with the corresponding struct from k8s-openapi.

Let's list all Pods in the default namespace:

use kube::{Client, Api};
use k8s_openapi::api::core::v1::Pod; // Import the Pod struct definition
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::try_default().await?;
    println!("Created Kubernetes client.");

    // Create an API handle for Pods in the default namespace
    let pods: Api = Api::default_namespaced(client.clone()); // Use client.clone() if client is used later

    println!("\nListing Pods in default namespace:");

    // List pods using ListParams (can be customized for label selectors etc.)
    let pod_list = pods.list(&Default::default()).await?;

    if pod_list.items.is_empty() {
         println!("No pods found in the default namespace.");
    } else {
        for p in pod_list.items {
            let pod_name = p.metadata.name.as_deref().unwrap_or("");
            let phase = p.status.and_then(|s| s.phase).unwrap_or_else(|| "Unknown".to_string());
            println!(" - Pod: {}, Status: {}", pod_name, phase);
        }
    }

    Ok(())
}

Key points:

  • use k8s_openapi::api::core::v1::Pod;: Imports the struct representing a Pod.
  • Api::::default_namespaced(client): Creates an API handle specifically for Pod objects within the client's default namespace (as configured in your kubeconfig). Use Api::namespaced(client, "my-namespace") for specific namespaces or Api::all(client) for cluster-scoped resources.
  • pods.list(&Default::default()).await?: Lists the resources. &Default::default() provides default listing parameters (no filters).
  • The result is an ObjectList which contains a vector items of Pod structs.
  • We access pod metadata (like name) and status (like phase). Note the use of .as_deref().unwrap_or(...) and .and_then(...).unwrap_or_else(...) for safe handling of optional fields.

Run cargo run. It should list the pods running in your default namespace (if any).

7. Basic Operations (Conceptual)

kube-rs provides methods on the Api handle for common CRUD (Create, Read, Update, Delete) operations.

7.1 Getting a Specific Resource

// Assuming 'pods' is Api for the correct namespace
let pod_name = "my-specific-pod";
match pods.get(pod_name).await {
    Ok(pod) => println!("Found pod: {:?}", pod.spec),
    Err(e) => eprintln!("Error getting pod {}: {}", pod_name, e),
}

7.2 Creating Resources

You typically define the resource using structs from k8s-openapi and then use api.create().

// Conceptual - Requires more setup with PodSpec, metadata etc.
use k8s_openapi::api::core::v1::PodSpec;
let new_pod: Pod = Pod {
    metadata: ObjectMeta { name: Some("my-new-rust-pod".to_string()), ..Default::default() },
    spec: Some(PodSpec {
        containers: vec![Container {
            name: "nginx".to_string(),
            image: Some("nginx:alpine".to_string()),
            ..Default::default()
        }],
        ..Default::default()
    }),
    ..Default::default()
};
let pp = PostParams::default();
match pods.create(&pp, &new_pod).await {
    Ok(created_pod) => println!("Created pod: {}", created_pod.metadata.name.unwrap()),
    Err(e) => eprintln!("Error creating pod: {}", e),
}

7.3 Updating Resources

Usually involves getting the resource, modifying its spec, and using api.replace() or api.patch().

7.4 Deleting Resources

// Assuming 'pods' is Api for the correct namespace
let pod_to_delete = "my-new-rust-pod";
let dp = DeleteParams::default();
match pods.delete(pod_to_delete, &dp).await {
    Ok(_) => println!("Pod {} deleted (or deletion started)", pod_to_delete),
    Err(e) => eprintln!("Error deleting pod {}: {}", pod_to_delete, e),
}

8. Error Handling

Most kube-rs API methods return kube::Result (which is an alias for Result). You should handle these results appropriately using match, ?, or libraries like anyhow.

kube::Error provides detailed information about API errors, including HTTP status codes and Kubernetes API error responses.

9. Conclusion

kube-rs provides a type-safe, asynchronous, and idiomatic way to interact with the Kubernetes API from Rust applications. By leveraging the Client for connection and the generic Api<T> handle for specific resource types defined in k8s-openapi, you can build powerful controllers, operators, CLIs, and other tools that manage Kubernetes resources reliably.

This guide covered the essentials of setting up a client and listing resources. Exploring creating, updating, deleting, and watching resources are the next logical steps in building more sophisticated Kubernetes integrations with Rust.

External Resources