Async kube-rs — LabelSelector strings and Fargate pod labels
Speak the selector string the API server speaks. Keep the objects typed.
<!-- hal:authoritative:yaml -->
The API server matches labels. Your Rust code must speak the same selector string, then stay strict on the objects that come back.
§I. Frame
This morning's Duha shipped TRPL chapter 17: async fn, .await, and a runtime that drives futures. On a K8s day that unlocks the live half of kube-rs. The Ops lesson in this trio calls AWS for Fargate profiles and kube-rs for pods. This lesson keeps the Rust spine offline and typed: encode a label selector, classify pods against Fargate profile rules, and prove the kubectl kind: List envelope still parses after 09-27.
It is not a drain preflight redo. No PDB math. No eviction verdicts. The object under study is the selector string and the eks.amazonaws.com/fargate-profile label.
The crate fargate-pod-census lives under pkg/.
§II. Encode Match Labels
Encode Match Labels (named technique). ListParams::labels wants a string the API server understands: key=value pairs joined by commas. Sort the keys so two maps with the same entries always produce the same string.
pub fn encode_match_labels(labels: &BTreeMap<String, String>) -> String {
let mut pairs: Vec<String> = labels.iter().map(|(k, v)| format!("{k}={v}")).collect();
pairs.sort();
pairs.join(",")
}
BTreeMap already iterates in key order. Sorting the rendered pairs still protects you if the input was a HashMap at the call site. A #[tokio::test] builds ListParams for profile batch-a through an async fn, so the async path is exercised even when no cluster is listening.
§III. Classify against profile selectors
A Fargate profile selector is a namespace plus optional AND labels. Classification has three outcomes:
- OnFargate — pod already carries
eks.amazonaws.com/fargate-profile. - SelectorHit — namespace and labels match, but the Fargate label is missing (EC2 today; Fargate after recycle).
- Unmatched — ignore for coverage.
When several profiles match and the pod has no explicit profile label, sort profile names and take the first. That is the same alphanumeric rule the EKS userguide documents for conflict resolution.
pub fn alphanumeric_first<'a>(
pod: &Pod,
profiles: &'a [(String, Vec<ProfileSelector>)],
) -> Option<&'a str> {
// explicit label short-circuits; else sort matching names and take first
...
}
The binary reads a kubectl-style pod list from disk and optional PROFILE:NS:k=v arguments, then prints one row per interesting pod. Exit 3 when any gap remains; exit 64 on bad usage.
§IV. The List envelope, again
kubectl get pods -A -o json still emits "kind": "List". k8s-openapi's typed PodList still rejects that. Keep the 09-27 envelope strip:
#[derive(Deserialize)]
struct KubectlList<T> { items: Vec<T> }
pub fn items<T: DeserializeOwned>(json: &str) -> Result<Vec<T>, serde_json::Error> {
Ok(serde_json::from_str::<KubectlList<T>>(json)?.items)
}
Items stay strict. A malformed pod fails the parse. The fixture fixture/pods.json holds three pods: one OnFargate, one SelectorHit, one Unmatched.
§V. What was checked
On the lab Mac after ship: cargo test in pkg/ (unit tests including one #[tokio::test]). cargo run -- fixture/pods.json beta-workload:batch:app=etl prod-workload:batch:app=etl prints the OnFargate and SelectorHit rows and exits 3. No live kube API call is required for the grade.
§VI. What not to do
- Re-implementing drain or PDB logic from 09-27.
- Calling
Client::try_defaultinside unit tests that must pass without a cluster. - Building selector strings with unsorted HashMap iteration and hoping the tests stay stable.
- Treating a SelectorHit Running pod as already on Fargate.
§VII. Close instruction
Add a fourth fixture pod in kube-system with no labels. Add profile kube-dns:kube-system: (namespace only). State the class for that pod, then state what changes if you also add profile aaa-dns:kube-system: and why.
Related
- Ops: EKS Fargate profiles (same trio)
- Cert: CKA HPA (same trio)
- Prior Dev: drain preflight envelope
- Duha: TRPL ch17 async/await