1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
use failure::Error; use kube::{ api::{Api, Object}, client::APIClient, config }; use k8s_openapi::api::core::v1::{ContainerStatus, PodSpec, PodStatus}; use failure::_core::fmt::Formatter; pub enum SuspiciousContainerReason { ContainerWaiting(Option<String>), Restarted { count: i32, exit_code: Option<i32>, reason: Option<String> }, TerminatedWithError(i32) } impl std::fmt::Display for SuspiciousContainerReason { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { SuspiciousContainerReason::ContainerWaiting(reason) => { write!(f, "Waiting")?; if let Some(r) = reason { write!(f, ": {}", r)?; } }, SuspiciousContainerReason::Restarted { count, exit_code, reason} => { if *count == 1 { write!(f, "Restarted {} time", count)?; } else { write!(f, "Restarted {} times", count)?; } if let Some(e) = exit_code { write!(f, ". Last exit code: {}", e)?; } if let Some(r) = reason { write!(f, ". ({})", r)?; } }, SuspiciousContainerReason::TerminatedWithError(exit_code) => { write!(f, "Terminated with error. Exit code {}.", exit_code)?; } } Ok(()) } } pub struct SuspiciousContainer { pub name: String, pub reason: SuspiciousContainerReason } pub enum SuspiciousPodReason { StuckOnInitContainer(String), SuspiciousContainers(Vec<SuspiciousContainer>) } pub struct SuspiciousPod { pub name: String, pub reason: SuspiciousPodReason } pub type Result<T> = std::result::Result<T, Error>; fn is_suspicious(p: Object<PodSpec, PodStatus>) -> Option<SuspiciousPod> { let pod_name = p.metadata.name; let status = p.status .expect(format!("Cannot get status for pod {}", pod_name).as_str()); if let Some(init_containers) = status.init_container_statuses { if let Some(stuck_init) = init_containers.into_iter().find(|c| !c.ready) { return Some(SuspiciousPod { name: pod_name, reason: SuspiciousPodReason::StuckOnInitContainer(stuck_init.name) }) } } let statuses: Vec<ContainerStatus> = status.container_statuses .expect(format!("Cannot get container statuses for pod {}", pod_name).as_str()); let suspicious_containers: Vec<_> = statuses.into_iter().filter_map(|status: ContainerStatus| { let container_name = status.name; let state = status.state .expect(format!("Cannot get state for container {} in pod {}", container_name, pod_name).as_str()); let reason = if status.restart_count > 0 { let last_state = status.last_state .expect(format!("Cannot get last state for container {} in pod {}", container_name, pod_name).as_str()) .terminated; Some(SuspiciousContainerReason::Restarted { count: status.restart_count, exit_code: last_state.as_ref().map(|s| s.exit_code), reason: last_state.and_then(|s| s.reason) }) } else if let Some(waiting_state) = state.waiting { let msg: Option<String> = waiting_state.reason.or(waiting_state.message); Some(SuspiciousContainerReason::ContainerWaiting(msg)) } else if state.terminated.is_some() && state.terminated.as_ref().unwrap().exit_code != 0 { Some(SuspiciousContainerReason::TerminatedWithError(state.terminated.unwrap().exit_code)) } else { None }; reason.map(|reason| SuspiciousContainer { name: container_name, reason }) }).collect(); if suspicious_containers.is_empty() { None } else { Some(SuspiciousPod { name: pod_name, reason: SuspiciousPodReason::SuspiciousContainers(suspicious_containers) }) } } pub fn get_suspicious_pods(namespace: &str) -> Result<Vec<SuspiciousPod>> { let config = config::load_kube_config()?; let client = APIClient::new(config); let pods = Api::v1Pod(client).within(namespace).list(&Default::default())?; Ok(pods.items.into_iter() .filter_map(is_suspicious) .collect()) }