|
| 1 | +use k8s_openapi::api::core::v1::Node; |
| 2 | +use kube::{ |
| 3 | + Api, |
| 4 | + api::{ListParams, ResourceExt}, |
| 5 | + client::Client, |
| 6 | +}; |
| 7 | +use serde::Deserialize; |
| 8 | +use http; |
| 9 | +use snafu::{Snafu, ResultExt, OptionExt}; |
| 10 | + |
| 11 | +#[derive(Debug, Snafu)] |
| 12 | +pub enum Error { |
| 13 | + |
| 14 | + #[snafu(display("failed to list nodes"))] |
| 15 | + ListNodes { |
| 16 | + source: kube::Error, |
| 17 | + }, |
| 18 | + #[snafu(display("failed to build proxy/configz request"))] |
| 19 | + ConfigzRequest { |
| 20 | + source: http::Error, |
| 21 | + }, |
| 22 | + |
| 23 | + #[snafu(display("failed to fetch kubelet config from node {node}"))] |
| 24 | + FetchNodeKubeletConfig { |
| 25 | + source: kube::Error, |
| 26 | + node: String, |
| 27 | + }, |
| 28 | + |
| 29 | + #[snafu(display("failed to fetch `kubeletconfig` JSON key from configz response"))] |
| 30 | + KubeletConfigJsonKey, |
| 31 | + |
| 32 | + #[snafu(display("failed to deserialize kubelet config JSON"))] |
| 33 | + KubeletConfigJson { |
| 34 | + source: serde_json::Error, |
| 35 | + }, |
| 36 | + |
| 37 | + #[snafu(display("empty Kubernetes nodes list"))] |
| 38 | + EmptyKubernetesNodesList, |
| 39 | +} |
| 40 | + |
| 41 | +#[derive(Debug, Deserialize)] |
| 42 | +#[serde(rename_all = "camelCase")] |
| 43 | +struct KubeletConfig { |
| 44 | + cluster_domain: String, |
| 45 | +} |
| 46 | + |
| 47 | +impl KubeletConfig { |
| 48 | + pub async fn fetch(client: &Client) -> Result<Self, Error> { |
| 49 | + let api: Api<Node> = Api::all(client.clone()); |
| 50 | + let nodes = api.list(&ListParams::default()).await.context(ListNodesSnafu)?; |
| 51 | + let node = nodes.iter().next().context(EmptyKubernetesNodesListSnafu)?; |
| 52 | + |
| 53 | + let name = node.name_any(); |
| 54 | + |
| 55 | + // Query node stats by issuing a request to the admin endpoint. |
| 56 | + // See https://kubernetes.io/docs/reference/instrumentation/node-metrics/ |
| 57 | + let url = format!("/api/v1/nodes/{}/proxy/configz", name); |
| 58 | + let req = http::Request::get(url).body(Default::default()).context(ConfigzRequestSnafu)?; |
| 59 | + |
| 60 | + // Deserialize JSON response as a JSON value. Alternatively, a type that |
| 61 | + // implements `Deserialize` can be used. |
| 62 | + let resp = client.request::<serde_json::Value>(req).await.context(FetchNodeKubeletConfigSnafu { node: name })?; |
| 63 | + |
| 64 | + // Our JSON value is an object so we can treat it like a dictionary. |
| 65 | + let summary = resp |
| 66 | + .get("kubeletconfig") |
| 67 | + .context(KubeletConfigJsonKeySnafu)?; |
| 68 | + |
| 69 | + // The base JSON representation includes a lot of metrics, including |
| 70 | + // container metrics. Use a `NodeMetrics` type to deserialize only the |
| 71 | + // values we care about. |
| 72 | + serde_json::from_value::<KubeletConfig>(summary.to_owned()).context(KubeletConfigJsonSnafu) |
| 73 | + } |
| 74 | + |
| 75 | +} |
0 commit comments