Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,23 @@ license.workspace = true
release = false

[features]
default = ["rustls-tls", "kubederive", "ws", "latest", "socks5", "runtime", "refresh"]
default = [
"rustls-tls",
"kubederive",
"ws",
"latest",
"socks5",
"runtime",
"refresh",
]
kubederive = ["kube/derive"]
openssl-tls = ["kube/client", "kube/openssl-tls", "kube/unstable-client"]
rustls-tls = ["kube/client", "kube/rustls-tls", "kube/ring", "kube/unstable-client"]
rustls-tls = [
"kube/client",
"kube/rustls-tls",
"kube/ring",
"kube/unstable-client",
]
runtime = ["kube/runtime", "kube/unstable-runtime"]
schemars-preserve-order = ["schemars/preserve_order"]
socks5 = ["kube/socks5"]
Expand All @@ -29,7 +42,9 @@ garde = { version = "0.22.0", default-features = false, features = ["derive"] }
anyhow.workspace = true
futures = { workspace = true, features = ["async-await"] }
jsonpath-rust.workspace = true
kube = { path = "../kube", version = "^2.0.1", default-features = false, features = ["admission"] }
kube = { path = "../kube", version = "^2.0.1", default-features = false, features = [
"admission",
] }
kube-derive = { path = "../kube-derive", version = "^2.0.1", default-features = false } # only needed to opt out of schema
k8s-openapi.workspace = true
serde = { workspace = true, features = ["derive"] }
Expand All @@ -50,10 +65,19 @@ json-patch.workspace = true
tower = { workspace = true, features = ["limit"] }
tower-http = { workspace = true, features = ["trace", "decompression-gzip"] }
hyper = { workspace = true, features = ["client", "http1"] }
hyper-util = { workspace = true, features = ["client-legacy", "http1", "tokio", "tracing"] }
hyper-util = { workspace = true, features = [
"client-legacy",
"http1",
"tokio",
"tracing",
] }
thiserror.workspace = true
backon.workspace = true
clap = { version = "4.0", default-features = false, features = ["std", "cargo", "derive"] }
clap = { version = "4.0", default-features = false, features = [
"std",
"cargo",
"derive",
] }
edit = "0.1.3"
tokio-stream = { version = "0.1.9", features = ["net"] }
crossterm = "0.29.0"
Expand Down Expand Up @@ -175,6 +199,10 @@ path = "pod_portforward_hyper_http.rs"
name = "pod_portforward_bind"
path = "pod_portforward_bind.rs"

[[example]]
name = "pod_resize"
path = "pod_resize.rs"

[[example]]
name = "pod_reflector"
path = "pod_reflector.rs"
Expand Down
131 changes: 131 additions & 0 deletions examples/pod_resize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use k8s_openapi::api::core::v1::Pod;
use kube::{
api::{Api, DeleteParams, Patch, PatchParams, PostParams, ResourceExt},
runtime::wait::{await_condition, conditions::is_pod_running},
Client,
};
use tracing::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let client = Client::try_default().await?;

let pods: Api<Pod> = Api::default_namespaced(client);

// Create a sample pod with resource limits
info!("Creating pod with initial resource requirements");
let pod: Pod = serde_json::from_value(serde_json::json!({
"apiVersion": "v1",
"kind": "Pod",
"metadata": { "name": "resize-demo" },
"spec": {
"containers": [{
"name": "app",
"image": "nginx:1.14.2",
"resources": {
"requests": {
"cpu": "100m",
"memory": "128Mi"
},
"limits": {
"cpu": "200m",
"memory": "256Mi"
}
}
}]
}
}))?;

let pp = PostParams::default();
pods.create(&pp, &pod).await?;

// Wait for pod to be running
info!("Waiting for pod to be running...");
let running = await_condition(pods.clone(), "resize-demo", is_pod_running());
let _ = tokio::time::timeout(std::time::Duration::from_secs(30), running).await?;

// Example 1: Using get_resize to view current state
info!("Example 1: Getting pod resize subresource");
let current = pods.get_resize("resize-demo").await?;
if let Some(spec) = &current.spec {
if let Some(container) = spec.containers.first() {
info!("Current resources: {:?}", container.resources);
}
}

// Example 2: Using patch_resize to update resources
info!("Example 2: Patching pod resources using resize subresource");
let patch = serde_json::json!({
"spec": {
"containers": [{
"name": "app",
"resources": {
"requests": {
"cpu": "150m",
"memory": "256Mi"
},
"limits": {
"cpu": "300m",
"memory": "512Mi"
}
}
}]
}
});

let patch_params = PatchParams::default();
match pods
.patch_resize("resize-demo", &patch_params, &Patch::Strategic(patch))
.await
{
Ok(resized) => {
info!("Successfully patched pod: {}", resized.name_any());
if let Some(spec) = resized.spec {
if let Some(container) = spec.containers.first() {
info!("Updated resources via patch: {:?}", container.resources);
}
}
}
Err(e) => {
error!("Failed to patch resize pod: {}", e);
}
}

// Example 3: Using replace_resize
info!("Example 3: Using replace_resize method");
let mut current_pod = pods.get_resize("resize-demo").await?;
Comment on lines +96 to +97
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think it's possible and/or worth extending the conditions module to be able to wait for the PodResizePending or / PodResizeInProgress condition to complete?

i feel like if you are patching things like this you might want to "wait for it to complete", and if an await_condition hasn't succeeded in like 10s then maybe people would look into whether the request was "Infeasible" / "Deferred" themselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to implement this. Is it possible to create an issue from this comment ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure thing, feel free to copy paste it before or after merge (or i can write one after merge). just leave this comment unresolved until we write one. 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened #1854 so we don't forget


if let Some(spec) = &mut current_pod.spec {
if let Some(container) = spec.containers.get_mut(0) {
if let Some(resources) = &mut container.resources {
// Update memory request
if let Some(requests) = &mut resources.requests {
requests.insert(
"memory".to_string(),
k8s_openapi::apimachinery::pkg::api::resource::Quantity("384Mi".to_string()),
);
}
}
}
}

match pods.replace_resize("resize-demo", &pp, &current_pod).await {
Ok(resized) => {
info!("Pod resized via replace: {}", resized.name_any());
if let Some(spec) = resized.spec {
if let Some(container) = spec.containers.first() {
info!("Final resources via replace: {:?}", container.resources);
}
}
}
Err(e) => error!("Failed to replace_resize: {}", e),
}

// Cleanup
info!("Cleaning up");
let dp = DeleteParams::default();
pods.delete("resize-demo", &dp).await?;

Ok(())
}
2 changes: 2 additions & 0 deletions kube-client/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use std::fmt::Debug;
#[cfg(feature = "ws")] pub use portforward::Portforwarder;

mod subresource;
#[cfg_attr(docsrs, doc(cfg(feature = "k8s_if_ge_1_33")))]
pub use subresource::Resize;
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub use subresource::{Attach, AttachParams, Ephemeral, Execute, Portforward};
Expand Down
164 changes: 164 additions & 0 deletions kube-client/src/api/subresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,154 @@ where
}
}

// ----------------------------------------------------------------------------
// Resize subresource
// ----------------------------------------------------------------------------

/// Marker trait for objects that support the resize sub resource.
///
/// The resize subresource allows updating container resource requests/limits
/// without restarting the pod. This is available in Kubernetes 1.33+.
///
/// See [`Api::get_resize`], [`Api::patch_resize`], and [`Api::replace_resize`].
///
/// See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/)
/// and [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
/// for more details.
#[cfg_attr(docsrs, doc(cfg(feature = "k8s_if_ge_1_33")))]
pub trait Resize {}

k8s_openapi::k8s_if_ge_1_33! {
impl Resize for k8s_openapi::api::core::v1::Pod {}
}

k8s_openapi::k8s_if_ge_1_33! {
impl<K> Api<K>
where
K: Clone + DeserializeOwned + Resize,
{
/// Get the named resource with the resize subresource.
///
/// This returns the whole Pod object with current resource allocations.
///
/// See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/)
/// and [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
/// for more details.
pub async fn get_resize(&self, name: &str) -> Result<K> {
let mut req = self
.request
.get_subresource("resize", name)
.map_err(Error::BuildRequest)?;
req.extensions_mut().insert("get_resize");
self.client.request::<K>(req).await
}

/// Patch the resize sub resource.
///
/// This allows you to update specific container resource requirements
/// without fetching the entire Pod object first.
///
/// Note that only certain container resource fields can be modified. See the
/// [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
/// for details on what can be changed.
///
/// # Example
///
/// ```no_run
/// use kube::api::{Api, PatchParams, Patch};
/// use k8s_openapi::api::core::v1::Pod;
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "default");
/// let pp = PatchParams::default();
///
/// let patch = serde_json::json!({
/// "spec": {
/// "containers": [{
/// "name": "mycontainer",
/// "resources": {
/// "requests": {
/// "cpu": "200m",
/// "memory": "512Mi"
/// }
/// }
/// }]
/// }
/// });
///
/// pods.patch_resize("mypod", &pp, &Patch::Strategic(patch)).await?;
/// # Ok(())
/// # }
/// ```
pub async fn patch_resize<P: serde::Serialize>(
&self,
name: &str,
pp: &PatchParams,
patch: &Patch<P>,
) -> Result<K> {
let mut req = self
.request
.patch_subresource("resize", name, pp, patch)
.map_err(Error::BuildRequest)?;
req.extensions_mut().insert("patch_resize");
self.client.request::<K>(req).await
}

/// Replace the resize sub resource entirely.
///
/// This works similarly to [`Api::replace`] but uses the resize subresource.
/// Takes a full Pod object with updated container resource requirements.
///
/// Note that only certain container resource fields can be modified. See the
/// [limitations](https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/#limitations)
/// for details on what can be changed.
///
/// # Example
///
/// ```no_run
/// use k8s_openapi::api::core::v1::Pod;
/// use kube::{Api, api::PostParams};
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "default");
/// let pp = PostParams::default();
///
/// // Get current pod
/// let mut pod = pods.get("mypod").await?;
///
/// // Modify resource requirements
/// if let Some(spec) = &mut pod.spec {
/// if let Some(container) = spec.containers.get_mut(0) {
/// if let Some(resources) = &mut container.resources {
/// // Update CPU/memory limits or requests
/// // ...
/// }
/// }
/// }
///
/// pods.replace_resize("mypod", &pp, &pod).await?;
/// # Ok(())
/// # }
/// ```
pub async fn replace_resize(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
where
K: Serialize,
{
let mut req = self
.request
.replace_subresource(
"resize",
name,
pp,
serde_json::to_vec(data).map_err(Error::SerdeError)?,
)
.map_err(Error::BuildRequest)?;
req.extensions_mut().insert("replace_resize");
self.client.request::<K>(req).await
}
}
}

// ----------------------------------------------------------------------------

// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
Expand Down Expand Up @@ -610,3 +758,19 @@ where
Ok(Portforwarder::new(connection.into_stream(), ports))
}
}

// ----------------------------------------------------------------------------
// Resize subresource tests
// ----------------------------------------------------------------------------

#[test]
fn resize_path() {
use crate::api::{Request, Resource};
use k8s_openapi::api::core::v1 as corev1;

k8s_openapi::k8s_if_ge_1_33! {
let url = corev1::Pod::url_path(&(), Some("ns"));
let req = Request::new(url).get_subresource("resize", "mypod").unwrap();
assert_eq!(req.uri(), "/api/v1/namespaces/ns/pods/mypod/resize");
}
}
Loading