wip: connected the thanos-datasource to grafana, need to complete connecting the openshift-userworkload-monitoring as well
This commit is contained in:
parent
85bec66e58
commit
06a0c44c3c
@ -1,12 +1,20 @@
|
|||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
|
use http::StatusCode;
|
||||||
use k8s_openapi::{
|
use k8s_openapi::{
|
||||||
ClusterResourceScope, NamespaceResourceScope,
|
ClusterResourceScope, NamespaceResourceScope,
|
||||||
api::{apps::v1::Deployment, core::v1::Pod},
|
api::{
|
||||||
|
apps::v1::Deployment,
|
||||||
|
authentication::v1::{TokenRequest, TokenRequestSpec, TokenRequestStatus},
|
||||||
|
core::v1::{Pod, ServiceAccount},
|
||||||
|
},
|
||||||
apimachinery::pkg::version::Info,
|
apimachinery::pkg::version::Info,
|
||||||
};
|
};
|
||||||
use kube::{
|
use kube::{
|
||||||
Client, Config, Discovery, Error, Resource,
|
Client, Config, Discovery, Error, Resource,
|
||||||
api::{Api, AttachParams, DeleteParams, ListParams, Patch, PatchParams, ResourceExt},
|
api::{
|
||||||
|
Api, AttachParams, DeleteParams, ListParams, ObjectMeta, Patch, PatchParams, PostParams,
|
||||||
|
ResourceExt,
|
||||||
|
},
|
||||||
config::{KubeConfigOptions, Kubeconfig},
|
config::{KubeConfigOptions, Kubeconfig},
|
||||||
core::ErrorResponse,
|
core::ErrorResponse,
|
||||||
runtime::reflector::Lookup,
|
runtime::reflector::Lookup,
|
||||||
@ -54,6 +62,11 @@ impl K8sClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn service_account_api(&self, namespace: &str) -> Api<ServiceAccount> {
|
||||||
|
let api: Api<ServiceAccount> = Api::namespaced(self.client.clone(), namespace);
|
||||||
|
api
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_apiserver_version(&self) -> Result<Info, Error> {
|
pub async fn get_apiserver_version(&self) -> Result<Info, Error> {
|
||||||
let client: Client = self.client.clone();
|
let client: Client = self.client.clone();
|
||||||
let version_info: Info = client.apiserver_version().await?;
|
let version_info: Info = client.apiserver_version().await?;
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
use std::{collections::BTreeMap, process::Command, sync::Arc};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
process::Command,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use k8s_openapi::api::{
|
use k8s_openapi::api::{
|
||||||
authentication::v1::{TokenRequest, TokenRequestSpec},
|
authentication::v1::{TokenRequest, TokenRequestSpec, TokenRequestStatus},
|
||||||
core::v1::{Secret, ServiceAccount},
|
core::v1::{Secret, ServiceAccount},
|
||||||
rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
|
rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
|
||||||
};
|
};
|
||||||
@ -150,39 +154,90 @@ impl Grafana for K8sAnywhereTopology {
|
|||||||
match_labels: label.clone(),
|
match_labels: label.clone(),
|
||||||
match_expressions: vec![],
|
match_expressions: vec![],
|
||||||
};
|
};
|
||||||
|
debug!("getting client");
|
||||||
let client = self.k8s_client().await?;
|
let client = self.k8s_client().await?;
|
||||||
let url = format!("{}:9091", self.get_domain("thanos-querier").await.unwrap());
|
|
||||||
|
|
||||||
let sa = self.build_service_account();
|
|
||||||
//TODO finish this section
|
|
||||||
//needs apply Api<Secret> or something
|
|
||||||
client.apply(&sa, Some(ns)).await?;
|
|
||||||
|
|
||||||
let token_request =self.get_token_request();
|
|
||||||
//this wont work needs a new function for apply secret
|
|
||||||
client.apply(&token_request, Some(ns)).await?;
|
|
||||||
|
|
||||||
let clusterrolebinding = self.build_cluster_rolebinding();
|
|
||||||
|
|
||||||
client.apply(&clusterrolebinding, Some(ns)).await?;
|
|
||||||
|
|
||||||
let secret = self.build_token_secret();
|
|
||||||
|
|
||||||
client.apply(&secret, Some(ns)).await?;
|
|
||||||
|
|
||||||
let datasource = self.build_grafana_datasource(ns, &label_selector, &url);
|
|
||||||
|
|
||||||
client.apply(&datasource, Some(ns)).await?;
|
|
||||||
|
|
||||||
let dashboard = self.build_grafana_dashboard(ns, &label_selector);
|
|
||||||
|
|
||||||
client.apply(&dashboard, Some(ns)).await?;
|
|
||||||
|
|
||||||
|
info!("creating grafanas crd");
|
||||||
let grafana = self.build_grafana(ns, &label);
|
let grafana = self.build_grafana(ns, &label);
|
||||||
|
|
||||||
client.apply(&grafana, Some(ns)).await?;
|
client.apply(&grafana, Some(ns)).await?;
|
||||||
|
|
||||||
|
client
|
||||||
|
.wait_until_deployment_ready(
|
||||||
|
"grafana-grafana-deployment".to_string(),
|
||||||
|
Some("grafana"),
|
||||||
|
Some(15),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let sa_name = "grafana-grafana-sa";
|
||||||
|
|
||||||
|
debug!("creating token for sevice account {sa_name}");
|
||||||
|
let token = self.create_service_account_token(sa_name, ns).await?;
|
||||||
|
|
||||||
|
debug!("creating secret");
|
||||||
|
let secret_name = "grafana-sa-secret";
|
||||||
|
let secret = self.build_token_secret(secret_name, &token.token, ns).await;
|
||||||
|
|
||||||
|
client.apply(&secret, Some(ns)).await?;
|
||||||
|
|
||||||
|
debug!("creating grafana clusterrole binding");
|
||||||
|
let clusterrolebinding =
|
||||||
|
self.build_cluster_rolebinding(sa_name, "cluster-monitoring-view", ns);
|
||||||
|
|
||||||
|
client.apply(&clusterrolebinding, Some(ns)).await?;
|
||||||
|
|
||||||
|
debug!("creating grafana datasource crd");
|
||||||
|
|
||||||
|
let token_str = format!("Bearer {}", token.token);
|
||||||
|
|
||||||
|
let thanos_url = format!(
|
||||||
|
"https://{}",
|
||||||
|
self.get_domain("thanos-querier-openshift-monitoring")
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let thanos_openshift_datasource = self.build_grafana_datasource(
|
||||||
|
"thanos-openshift-monitoring",
|
||||||
|
ns,
|
||||||
|
&label_selector,
|
||||||
|
&thanos_url,
|
||||||
|
token_str.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.apply(&thanos_openshift_datasource, Some(ns)).await?;
|
||||||
|
|
||||||
|
//TODO user workload datasource returns 503 -> need to figure out how to correctly add the
|
||||||
|
//userworkload thanos-ruler or prometheus-federate to the grafana datasource
|
||||||
|
//it may alrady be included in the overall monitoring stack
|
||||||
|
|
||||||
|
let user_thanos_url = format!(
|
||||||
|
"https://{}",
|
||||||
|
self.get_domain(
|
||||||
|
"thanos-ruler-openshift-user-workload-monitoring.apps.ncd0.harmony.mcd"
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let thanos_openshift_userworkload_datasource = self.build_grafana_datasource(
|
||||||
|
"thanos-openshift-userworkload-monitoring",
|
||||||
|
ns,
|
||||||
|
&label_selector,
|
||||||
|
&user_thanos_url,
|
||||||
|
token_str.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client
|
||||||
|
.apply(&thanos_openshift_userworkload_datasource, Some(ns))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
debug!("creating grafana dashboard crd");
|
||||||
|
let dashboard = self.build_grafana_dashboard(ns, &label_selector);
|
||||||
|
|
||||||
|
client.apply(&dashboard, Some(ns)).await?;
|
||||||
|
debug!("creating grafana ingress");
|
||||||
let grafana_ingress = self.build_grafana_ingress(ns).await;
|
let grafana_ingress = self.build_grafana_ingress(ns).await;
|
||||||
|
|
||||||
grafana_ingress
|
grafana_ingress
|
||||||
@ -368,31 +423,36 @@ impl K8sAnywhereTopology {
|
|||||||
|
|
||||||
pub fn build_cluster_rolebinding(
|
pub fn build_cluster_rolebinding(
|
||||||
&self,
|
&self,
|
||||||
|
service_account_name: &str,
|
||||||
|
clusterrole_name: &str,
|
||||||
ns: &str,
|
ns: &str,
|
||||||
account_name: &str,
|
|
||||||
role: &str,
|
|
||||||
) -> ClusterRoleBinding {
|
) -> ClusterRoleBinding {
|
||||||
ClusterRoleBinding {
|
ClusterRoleBinding {
|
||||||
metadata: ObjectMeta {
|
metadata: ObjectMeta {
|
||||||
name: Some(format!("{}-view-binding", account_name)),
|
name: Some(format!("{}-view-binding", service_account_name)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
role_ref: RoleRef {
|
role_ref: RoleRef {
|
||||||
api_group: "rbac.authorization.k8s.io".into(),
|
api_group: "rbac.authorization.k8s.io".into(),
|
||||||
kind: "ClusterRole".into(),
|
kind: "ClusterRole".into(),
|
||||||
name: role.into(),
|
name: clusterrole_name.into(),
|
||||||
},
|
},
|
||||||
subjects: Some(vec![Subject {
|
subjects: Some(vec![Subject {
|
||||||
kind: "ServiceAccount".into(),
|
kind: "ServiceAccount".into(),
|
||||||
name: account_name.into(),
|
name: service_account_name.into(),
|
||||||
namespace: Some(ns.into()),
|
namespace: Some(ns.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}]),
|
}]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_token_request(&self) -> TokenRequest {
|
pub fn get_token_request(&self, ns: &str) -> TokenRequest {
|
||||||
|
debug!("building token request");
|
||||||
TokenRequest {
|
TokenRequest {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
namespace: Some(ns.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
spec: TokenRequestSpec {
|
spec: TokenRequestSpec {
|
||||||
audiences: vec!["https://kubernetes.default.svc".to_string()],
|
audiences: vec!["https://kubernetes.default.svc".to_string()],
|
||||||
expiration_seconds: Some(3600),
|
expiration_seconds: Some(3600),
|
||||||
@ -402,15 +462,39 @@ impl K8sAnywhereTopology {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_token_secret(&self, token: &str, ns: &str) -> Secret {
|
pub async fn create_service_account_token(
|
||||||
|
&self,
|
||||||
|
service_account_name: &str,
|
||||||
|
ns: &str,
|
||||||
|
) -> Result<TokenRequestStatus, PreparationError> {
|
||||||
|
debug!("creating service account token");
|
||||||
|
let token_request = self.get_token_request(ns);
|
||||||
|
let client = self.k8s_client().await?;
|
||||||
|
let pp = PostParams::default();
|
||||||
|
let token_requests_api = client.service_account_api(ns).await;
|
||||||
|
|
||||||
|
let data = serde_json::to_vec(&token_request).unwrap();
|
||||||
|
|
||||||
|
let created_token_request = token_requests_api
|
||||||
|
.create_subresource::<TokenRequest>("token", service_account_name, &pp, data)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let status = created_token_request
|
||||||
|
.status
|
||||||
|
.ok_or_else(|| PreparationError::new("missing token request status".to_string()))?;
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build_token_secret(&self, secret_name: &str, token: &str, ns: &str) -> Secret {
|
||||||
Secret {
|
Secret {
|
||||||
metadata: ObjectMeta {
|
metadata: ObjectMeta {
|
||||||
name: Some("grafana-credentials".into()),
|
name: Some(secret_name.into()),
|
||||||
namespace: Some(ns.into()),
|
namespace: Some(ns.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
string_data: Some(std::collections::BTreeMap::from([(
|
string_data: Some(std::collections::BTreeMap::from([(
|
||||||
"PROMETHEUS_TOKEN".into(),
|
secret_name.into(),
|
||||||
format!("Bearer {}", token),
|
format!("Bearer {}", token),
|
||||||
)])),
|
)])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -419,39 +503,18 @@ impl K8sAnywhereTopology {
|
|||||||
|
|
||||||
fn build_grafana_datasource(
|
fn build_grafana_datasource(
|
||||||
&self,
|
&self,
|
||||||
|
name: &str,
|
||||||
ns: &str,
|
ns: &str,
|
||||||
label_selector: &LabelSelector,
|
label_selector: &LabelSelector,
|
||||||
url: &str,
|
url: &str,
|
||||||
|
token: String,
|
||||||
) -> GrafanaDatasource {
|
) -> GrafanaDatasource {
|
||||||
let mut json_data = BTreeMap::new();
|
let mut json_data = BTreeMap::new();
|
||||||
json_data.insert("timeInterval".to_string(), "5s".to_string());
|
json_data.insert("timeInterval".to_string(), "5s".to_string());
|
||||||
//
|
|
||||||
// let graf_data_source = GrafanaDatasource {
|
|
||||||
// metadata: ObjectMeta {
|
|
||||||
// name: Some(format!("grafana-datasource-{}", ns)),
|
|
||||||
// namespace: Some(ns.to_string()),
|
|
||||||
// ..Default::default()
|
|
||||||
// },
|
|
||||||
// spec: GrafanaDatasourceSpec {
|
|
||||||
// instance_selector: label_selector.clone(),
|
|
||||||
// allow_cross_namespace_import: Some(false),
|
|
||||||
// datasource: GrafanaDatasourceConfig {
|
|
||||||
// access: "proxy".to_string(),
|
|
||||||
// database: Some("prometheus".to_string()),
|
|
||||||
// json_data: Some(json_data),
|
|
||||||
// //this is fragile
|
|
||||||
// name: format!("prometheus-{}-0", ns),
|
|
||||||
// r#type: "prometheus".to_string(),
|
|
||||||
// url: url.to_string(),
|
|
||||||
// //url: format!("http://prometheus-operated.{}.svc.cluster.local:9090", ns),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// graf_data_source
|
|
||||||
|
|
||||||
GrafanaDatasource {
|
GrafanaDatasource {
|
||||||
metadata: ObjectMeta {
|
metadata: ObjectMeta {
|
||||||
name: Some("thanos-prometheus".to_string()),
|
name: Some(name.to_string()),
|
||||||
namespace: Some(ns.to_string()),
|
namespace: Some(ns.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@ -460,20 +523,21 @@ impl K8sAnywhereTopology {
|
|||||||
allow_cross_namespace_import: Some(true),
|
allow_cross_namespace_import: Some(true),
|
||||||
datasource: GrafanaDatasourceConfig {
|
datasource: GrafanaDatasourceConfig {
|
||||||
access: "proxy".to_string(),
|
access: "proxy".to_string(),
|
||||||
name: "OpenShift-Thanos".to_string(),
|
name: name.to_string(),
|
||||||
r#type: "prometheus".to_string(),
|
r#type: "prometheus".to_string(),
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
database: None,
|
database: None,
|
||||||
json_data: Some(GrafanaDatasourceJsonData {
|
json_data: Some(GrafanaDatasourceJsonData {
|
||||||
time_interval: Some("60s".to_string()),
|
time_interval: Some("60s".to_string()),
|
||||||
http_header_name1: Some("Authorization".to_string()),
|
http_header_name1: Some("Authorization".to_string()),
|
||||||
|
tls_skip_verify: Some(true),
|
||||||
|
oauth_pass_thru: Some(true),
|
||||||
}),
|
}),
|
||||||
secure_json_data: Some(GrafanaDatasourceSecureJsonData {
|
secure_json_data: Some(GrafanaDatasourceSecureJsonData {
|
||||||
http_header_value1: Some("Bearer eyJhbGc...".to_string()),
|
http_header_value1: Some(token),
|
||||||
}),
|
}),
|
||||||
is_default: Some(false),
|
is_default: Some(false),
|
||||||
editable: Some(true),
|
editable: Some(true),
|
||||||
version: Some(1),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -132,6 +132,7 @@ pub struct GrafanaDatasourceSpec {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GrafanaDatasourceConfig {
|
pub struct GrafanaDatasourceConfig {
|
||||||
pub access: String,
|
pub access: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub database: Option<String>,
|
pub database: Option<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
@ -149,9 +150,6 @@ pub struct GrafanaDatasourceConfig {
|
|||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub editable: Option<bool>,
|
pub editable: Option<bool>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub version: Option<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||||
@ -162,6 +160,14 @@ pub struct GrafanaDatasourceJsonData {
|
|||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub http_header_name1: Option<String>,
|
pub http_header_name1: Option<String>,
|
||||||
|
|
||||||
|
/// Disable TLS skip verification (false = verify)
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tls_skip_verify: Option<bool>,
|
||||||
|
|
||||||
|
/// Auth type - set to "forward" for OpenShift OAuth identity
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub oauth_pass_thru: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||||
|
@ -12,7 +12,7 @@ use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::C
|
|||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
|
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
|
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
|
||||||
Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
|
Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
|
||||||
GrafanaDatasourceSpec, GrafanaSpec,
|
GrafanaDatasourceJsonData, GrafanaDatasourceSpec, GrafanaSpec,
|
||||||
};
|
};
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
|
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
|
||||||
PrometheusRule, PrometheusRuleSpec, RuleGroup,
|
PrometheusRule, PrometheusRuleSpec, RuleGroup,
|
||||||
@ -466,10 +466,15 @@ impl K8sPrometheusCRDAlertingInterpret {
|
|||||||
match_labels: label.clone(),
|
match_labels: label.clone(),
|
||||||
match_expressions: vec![],
|
match_expressions: vec![],
|
||||||
};
|
};
|
||||||
let mut json_data = BTreeMap::new();
|
// let mut json_data = BTreeMap::new();
|
||||||
json_data.insert("timeInterval".to_string(), "5s".to_string());
|
// json_data.insert("timeInterval".to_string(), "5s".to_string());
|
||||||
let namespace = self.sender.namespace.clone();
|
let namespace = self.sender.namespace.clone();
|
||||||
|
let json_data = GrafanaDatasourceJsonData {
|
||||||
|
time_interval: Some("5s".to_string()),
|
||||||
|
http_header_name1: None,
|
||||||
|
tls_skip_verify: Some(true),
|
||||||
|
oauth_pass_thru: Some(true),
|
||||||
|
};
|
||||||
let json = build_default_dashboard(&namespace);
|
let json = build_default_dashboard(&namespace);
|
||||||
|
|
||||||
let graf_data_source = GrafanaDatasource {
|
let graf_data_source = GrafanaDatasource {
|
||||||
@ -495,6 +500,9 @@ impl K8sPrometheusCRDAlertingInterpret {
|
|||||||
"http://prometheus-operated.{}.svc.cluster.local:9090",
|
"http://prometheus-operated.{}.svc.cluster.local:9090",
|
||||||
self.sender.namespace.clone()
|
self.sender.namespace.clone()
|
||||||
),
|
),
|
||||||
|
secure_json_data: None,
|
||||||
|
is_default: None,
|
||||||
|
editable: None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user