feat/node-health-score #242
33
Cargo.lock
generated
33
Cargo.lock
generated
@@ -1981,6 +1981,19 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "example-node-health"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"harmony_macros",
|
||||||
|
"harmony_types",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-ntfy"
|
name = "example-ntfy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3687,6 +3700,26 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json-prompt"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
|
"cidr",
|
||||||
|
"env_logger",
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"harmony_macros",
|
||||||
|
"harmony_secret",
|
||||||
|
"harmony_secret_derive",
|
||||||
|
"harmony_types",
|
||||||
|
"log",
|
||||||
|
"schemars 0.8.22",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonpath-rust"
|
name = "jsonpath-rust"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|||||||
16
examples/node_health/Cargo.toml
Normal file
16
examples/node_health/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-node-health"
|
||||||
|
edition = "2024"
|
||||||
|
version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
harmony = { path = "../../harmony" }
|
||||||
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
log = { workspace = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
17
examples/node_health/src/main.rs
Normal file
17
examples/node_health/src/main.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use harmony::{
|
||||||
|
inventory::Inventory, modules::node_health::NodeHealthScore, topology::K8sAnywhereTopology,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let node_health = NodeHealthScore {};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(node_health)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -983,7 +983,6 @@ impl K8sClient {
|
|||||||
pub async fn apply_many<K>(&self, resource: &[K], ns: Option<&str>) -> Result<Vec<K>, Error>
|
pub async fn apply_many<K>(&self, resource: &[K], ns: Option<&str>) -> Result<Vec<K>, Error>
|
||||||
where
|
where
|
||||||
K: Resource + Clone + std::fmt::Debug + DeserializeOwned + serde::Serialize,
|
K: Resource + Clone + std::fmt::Debug + DeserializeOwned + serde::Serialize,
|
||||||
<K as Resource>::Scope: ApplyStrategy<K>,
|
|
||||||
<K as Resource>::DynamicType: Default,
|
<K as Resource>::DynamicType: Default,
|
||||||
{
|
{
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use k8s_openapi::NamespaceResourceScope;
|
use k8s_openapi::{NamespaceResourceScope, ResourceScope};
|
||||||
use kube::Resource;
|
use kube::Resource;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
@@ -29,7 +29,7 @@ impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
K: Resource<Scope = NamespaceResourceScope>
|
K: Resource<Scope: ResourceScope>
|
||||||
+ std::fmt::Debug
|
+ std::fmt::Debug
|
||||||
+ Sync
|
+ Sync
|
||||||
+ DeserializeOwned
|
+ DeserializeOwned
|
||||||
@@ -61,7 +61,7 @@ pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync + Send> {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<
|
impl<
|
||||||
K: Resource<Scope = NamespaceResourceScope>
|
K: Resource<Scope: ResourceScope>
|
||||||
+ Clone
|
+ Clone
|
||||||
+ std::fmt::Debug
|
+ std::fmt::Debug
|
||||||
+ DeserializeOwned
|
+ DeserializeOwned
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub mod load_balancer;
|
|||||||
pub mod monitoring;
|
pub mod monitoring;
|
||||||
pub mod nats;
|
pub mod nats;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
pub mod node_health;
|
||||||
pub mod okd;
|
pub mod okd;
|
||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
pub mod postgresql;
|
pub mod postgresql;
|
||||||
|
|||||||
260
harmony/src/modules/node_health/mod.rs
Normal file
260
harmony/src/modules/node_health/mod.rs
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
use k8s_openapi::api::{
|
||||||
|
apps::v1::{DaemonSet, DaemonSetSpec},
|
||||||
|
core::v1::{
|
||||||
|
Container, ContainerPort, EnvVar, EnvVarSource, Namespace, ObjectFieldSelector, PodSpec,
|
||||||
|
PodTemplateSpec, ResourceRequirements, ServiceAccount, Toleration,
|
||||||
|
},
|
||||||
|
rbac::v1::{ClusterRole, ClusterRoleBinding, PolicyRule, Role, RoleBinding, RoleRef, Subject},
|
||||||
|
};
|
||||||
|
use k8s_openapi::apimachinery::pkg::api::resource::Quantity;
|
||||||
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
|
||||||
|
use kube::api::ObjectMeta;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::Version,
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::k8s::resource::K8sResourceScore,
|
||||||
|
score::Score,
|
||||||
|
topology::{K8sclient, Topology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct NodeHealthScore {}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for NodeHealthScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("NodeHealthScore")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(NodeHealthInterpret {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NodeHealthInterpret {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + K8sclient> Interpret<T> for NodeHealthInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let namespace_name = "harmony-node-healthcheck".to_string();
|
||||||
|
|
||||||
|
// Namespace
|
||||||
|
let mut labels = BTreeMap::new();
|
||||||
|
labels.insert("name".to_string(), namespace_name.clone());
|
||||||
|
|
||||||
|
let namespace = Namespace {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some(namespace_name.clone()),
|
||||||
|
labels: Some(labels),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
..Namespace::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// ServiceAccount
|
||||||
|
let service_account_name = "node-healthcheck-sa".to_string();
|
||||||
|
let service_account = ServiceAccount {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some(service_account_name.clone()),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
..ServiceAccount::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// ClusterRole
|
||||||
|
let cluster_role = ClusterRole {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("node-healthcheck-role".to_string()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
rules: Some(vec![PolicyRule {
|
||||||
|
api_groups: Some(vec!["".to_string()]),
|
||||||
|
resources: Some(vec!["nodes".to_string()]),
|
||||||
|
verbs: vec!["get".to_string(), "list".to_string()],
|
||||||
|
..PolicyRule::default()
|
||||||
|
}]),
|
||||||
|
..ClusterRole::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Role
|
||||||
|
let role = Role {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("allow-hostnetwork-scc".to_string()),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
rules: Some(vec![PolicyRule {
|
||||||
|
api_groups: Some(vec!["security.openshift.io".to_string()]),
|
||||||
|
resources: Some(vec!["securitycontextconstraints".to_string()]),
|
||||||
|
resource_names: Some(vec!["hostnetwork".to_string()]),
|
||||||
|
verbs: vec!["use".to_string()],
|
||||||
|
..PolicyRule::default()
|
||||||
|
}]),
|
||||||
|
..Role::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// RoleBinding
|
||||||
|
let role_binding = RoleBinding {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("node-status-querier-scc-binding".to_string()),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
subjects: Some(vec![Subject {
|
||||||
|
kind: "ServiceAccount".to_string(),
|
||||||
|
name: service_account_name.clone(),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..Subject::default()
|
||||||
|
}]),
|
||||||
|
role_ref: RoleRef {
|
||||||
|
api_group: "rbac.authorization.k8s.io".to_string(),
|
||||||
|
kind: "Role".to_string(),
|
||||||
|
name: "allow-hostnetwork-scc".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ClusterRoleBinding
|
||||||
|
let cluster_role_binding = ClusterRoleBinding {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("read-nodes-binding".to_string()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
subjects: Some(vec![Subject {
|
||||||
|
kind: "ServiceAccount".to_string(),
|
||||||
|
name: service_account_name.clone(),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..Subject::default()
|
||||||
|
}]),
|
||||||
|
role_ref: RoleRef {
|
||||||
|
api_group: "rbac.authorization.k8s.io".to_string(),
|
||||||
|
kind: "ClusterRole".to_string(),
|
||||||
|
name: "node-healthcheck-role".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// DaemonSet
|
||||||
|
let mut daemonset_labels = BTreeMap::new();
|
||||||
|
daemonset_labels.insert("app".to_string(), "node-healthcheck".to_string());
|
||||||
|
|
||||||
|
let daemon_set = DaemonSet {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("node-healthcheck".to_string()),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
labels: Some(daemonset_labels.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
spec: Some(DaemonSetSpec {
|
||||||
|
selector: LabelSelector {
|
||||||
|
match_labels: Some(daemonset_labels.clone()),
|
||||||
|
..LabelSelector::default()
|
||||||
|
},
|
||||||
|
template: PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
labels: Some(daemonset_labels),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
service_account_name: Some(service_account_name.clone()),
|
||||||
|
host_network: Some(true),
|
||||||
|
tolerations: Some(vec![Toleration {
|
||||||
|
operator: Some("Exists".to_string()),
|
||||||
|
..Toleration::default()
|
||||||
|
}]),
|
||||||
|
containers: vec![Container {
|
||||||
|
name: "checker".to_string(),
|
||||||
|
image: Some(
|
||||||
|
"hub.nationtech.io/harmony/harmony-node-readiness-endpoint:latest"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
env: Some(vec![EnvVar {
|
||||||
|
name: "NODE_NAME".to_string(),
|
||||||
|
value_from: Some(EnvVarSource {
|
||||||
|
field_ref: Some(ObjectFieldSelector {
|
||||||
|
field_path: "spec.nodeName".to_string(),
|
||||||
|
..ObjectFieldSelector::default()
|
||||||
|
}),
|
||||||
|
..EnvVarSource::default()
|
||||||
|
}),
|
||||||
|
..EnvVar::default()
|
||||||
|
}]),
|
||||||
|
ports: Some(vec![ContainerPort {
|
||||||
|
container_port: 25001,
|
||||||
|
host_port: Some(25001),
|
||||||
|
name: Some("health-port".to_string()),
|
||||||
|
..ContainerPort::default()
|
||||||
|
}]),
|
||||||
|
resources: Some(ResourceRequirements {
|
||||||
|
requests: Some({
|
||||||
|
let mut requests = BTreeMap::new();
|
||||||
|
requests.insert("cpu".to_string(), Quantity("10m".to_string()));
|
||||||
|
requests
|
||||||
|
.insert("memory".to_string(), Quantity("50Mi".to_string()));
|
||||||
|
requests
|
||||||
|
}),
|
||||||
|
..ResourceRequirements::default()
|
||||||
|
}),
|
||||||
|
..Container::default()
|
||||||
|
}],
|
||||||
|
..PodSpec::default()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
..DaemonSetSpec::default()
|
||||||
|
}),
|
||||||
|
..DaemonSet::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
K8sResourceScore::single(namespace, None)
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(service_account, Some(namespace_name.clone()))
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(cluster_role, None)
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(role, Some(namespace_name.clone()))
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(role_binding, Some(namespace_name.clone()))
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(cluster_role_binding, None)
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
K8sResourceScore::single(daemon_set, Some(namespace_name.clone()))
|
||||||
|
.interpret(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(
|
||||||
|
"Harmony node health successfully deployed".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("NodeHealth")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user