wip: public postgres many fixes and refactoring to have a more cohesive routing management
Some checks failed
Run Check Script / check (pull_request) Failing after 41s
Some checks failed
Run Check Script / check (pull_request) Failing after 41s
This commit is contained in:
18
examples/public_postgres/Cargo.toml
Normal file
18
examples/public_postgres/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-public-postgres"
|
||||||
|
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" }
|
||||||
|
cidr = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
log = { workspace = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
34
examples/public_postgres/src/main.rs
Normal file
34
examples/public_postgres/src/main.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::{network::TlsPassthroughScore, postgresql::PostgreSQLScore},
|
||||||
|
topology::{K8sAnywhereTopology, TlsRoute},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let namespace = "harmony-postgres-example".to_string();
|
||||||
|
let postgresql = PostgreSQLScore {
|
||||||
|
name: "harmony-postgres-example".to_string(), // Override default name
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
..Default::default() // Use harmony defaults, they are based on CNPG's default values :
|
||||||
|
// "default" namespace, 1 instance, 1Gi storage
|
||||||
|
};
|
||||||
|
|
||||||
|
let tls_passthrough = TlsPassthroughScore {
|
||||||
|
route: TlsRoute {
|
||||||
|
hostname: "postgres.example.com".to_string(), // CNPG creates a -rw service for read-write endpoint
|
||||||
|
backend: format!("{}-rw", postgresql.name), // Public hostname for TLS SNI
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
target_port: 5432, // PostgreSQL default port
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(postgresql), Box::new(tls_passthrough)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ use std::{collections::BTreeMap, process::Command, sync::Arc, time::Duration};
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use base64::{Engine, engine::general_purpose};
|
use base64::{Engine, engine::general_purpose};
|
||||||
|
use harmony_types::rfc1123::Rfc1123Name;
|
||||||
use k8s_openapi::api::{
|
use k8s_openapi::api::{
|
||||||
core::v1::Secret,
|
core::v1::Secret,
|
||||||
rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
|
rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
|
||||||
@@ -35,6 +36,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
network::TlsPassthroughScore,
|
network::TlsPassthroughScore,
|
||||||
|
okd::route::OKDTlsPassthroughScore,
|
||||||
prometheus::{
|
prometheus::{
|
||||||
k8s_prometheus_alerting_score::K8sPrometheusCRDAlertingScore,
|
k8s_prometheus_alerting_score::K8sPrometheusCRDAlertingScore,
|
||||||
prometheus::PrometheusMonitoring, rhob_alerting_score::RHOBAlertingScore,
|
prometheus::PrometheusMonitoring, rhob_alerting_score::RHOBAlertingScore,
|
||||||
@@ -109,9 +111,12 @@ impl TlsRouter for K8sAnywhereTopology {
|
|||||||
if let Some(distro) = self.k8s_distribution.get() {
|
if let Some(distro) = self.k8s_distribution.get() {
|
||||||
match distro {
|
match distro {
|
||||||
KubernetesDistribution::OpenshiftFamily => {
|
KubernetesDistribution::OpenshiftFamily => {
|
||||||
TlsPassthroughScore { route }
|
OKDTlsPassthroughScore {
|
||||||
.interpret(&Inventory::empty(), self)
|
name: Rfc1123Name::try_from(route.to_string_short().as_str())?,
|
||||||
.await?;
|
route,
|
||||||
|
}
|
||||||
|
.interpret(&Inventory::empty(), self)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
KubernetesDistribution::K3sFamily | KubernetesDistribution::Default => Err(
|
KubernetesDistribution::K3sFamily | KubernetesDistribution::Default => Err(
|
||||||
|
|||||||
@@ -73,8 +73,19 @@ pub struct TlsRoute {
|
|||||||
|
|
||||||
/// Backend TCP port (Postgres: 5432).
|
/// Backend TCP port (Postgres: 5432).
|
||||||
pub target_port: u16,
|
pub target_port: u16,
|
||||||
|
|
||||||
|
/// The environment in which it lives.
|
||||||
|
/// TODO clarify how we handle this in higher level abstractions. The namespace name is a
|
||||||
|
/// direct mapping to k8s but that could be misleading for other implementations.
|
||||||
|
pub namespace: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TlsRoute {
|
||||||
|
pub fn to_string_short(&self) -> String {
|
||||||
|
format!("{}-{}:{}", self.hostname, self.backend, self.target_port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Installs and queries TLS passthrough routes (L4 TCP/SNI forwarding, no TLS termination).
|
/// Installs and queries TLS passthrough routes (L4 TCP/SNI forwarding, no TLS termination).
|
||||||
/// Agnostic to impl: OKD Route, AWS NLB+HAProxy, k3s Envoy Gateway, Apache ProxyPass.
|
/// Agnostic to impl: OKD Route, AWS NLB+HAProxy, k3s Envoy Gateway, Apache ProxyPass.
|
||||||
/// Used by PostgreSQL capability to expose CNPG clusters multisite (site1 → site2 replication).
|
/// Used by PostgreSQL capability to expose CNPG clusters multisite (site1 → site2 replication).
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ impl Default for RouteTargetReference {
|
|||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RoutePort {
|
pub struct RoutePort {
|
||||||
pub target_port: IntOrString,
|
pub target_port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
// always be dealing only with okd/openshift compatible topologies and is ready to manage the
|
// always be dealing only with okd/openshift compatible topologies and is ready to manage the
|
||||||
// additional maintenance burden that comes with a lower level functionnality.
|
// additional maintenance burden that comes with a lower level functionnality.
|
||||||
|
|
||||||
use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;
|
use harmony_types::rfc1123::Rfc1123Name;
|
||||||
use kube::api::ObjectMeta;
|
use kube::api::ObjectMeta;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@@ -24,21 +24,7 @@ use crate::modules::okd::crd::route::{
|
|||||||
Route, RoutePort, RouteSpec, RouteTargetReference, TLSConfig,
|
Route, RoutePort, RouteSpec, RouteTargetReference, TLSConfig,
|
||||||
};
|
};
|
||||||
use crate::score::Score;
|
use crate::score::Score;
|
||||||
use crate::topology::{K8sclient, Topology};
|
use crate::topology::{K8sclient, TlsRoute, Topology};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
struct OKDRoutePort {
|
|
||||||
#[serde(rename = "targetPort")]
|
|
||||||
target_port: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
struct OKDTLSConfig {
|
|
||||||
#[serde(rename = "termination")]
|
|
||||||
termination: String,
|
|
||||||
#[serde(rename = "insecureEdgeTerminationPolicy")]
|
|
||||||
insecure_edge_termination_policy: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct OKDRouteScore {
|
pub struct OKDRouteScore {
|
||||||
@@ -78,43 +64,22 @@ impl<T: Topology + K8sclient> Score<T> for OKDRouteScore {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct OKDTlsPassthroughScore {
|
pub struct OKDTlsPassthroughScore {
|
||||||
pub name: String,
|
pub route: TlsRoute,
|
||||||
pub namespace: String,
|
pub name: Rfc1123Name,
|
||||||
pub backend: String,
|
|
||||||
pub hostname: String,
|
|
||||||
pub target_port: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OKDTlsPassthroughScore {
|
|
||||||
pub fn new(
|
|
||||||
name: &str,
|
|
||||||
namespace: &str,
|
|
||||||
backend: &str,
|
|
||||||
hostname: &str,
|
|
||||||
target_port: &str,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.to_string(),
|
|
||||||
namespace: namespace.to_string(),
|
|
||||||
backend: backend.to_string(),
|
|
||||||
hostname: hostname.to_string(),
|
|
||||||
target_port: target_port.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient> Score<T> for OKDTlsPassthroughScore {
|
impl<T: Topology + K8sclient> Score<T> for OKDTlsPassthroughScore {
|
||||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||||
let passthrough_spec = RouteSpec {
|
let passthrough_spec = RouteSpec {
|
||||||
host: Some(self.hostname.clone()),
|
host: Some(self.route.hostname.clone()),
|
||||||
wildcard_policy: Some("None".to_string()),
|
wildcard_policy: Some("None".to_string()),
|
||||||
to: RouteTargetReference {
|
to: RouteTargetReference {
|
||||||
kind: "Service".to_string(),
|
kind: "Service".to_string(),
|
||||||
name: self.backend.clone(),
|
name: self.route.backend.clone(),
|
||||||
weight: Some(100),
|
weight: Some(100),
|
||||||
},
|
},
|
||||||
port: Some(RoutePort {
|
port: Some(RoutePort {
|
||||||
target_port: IntOrString::String(self.target_port.clone()),
|
target_port: self.route.target_port,
|
||||||
}),
|
}),
|
||||||
tls: Some(TLSConfig {
|
tls: Some(TLSConfig {
|
||||||
termination: "passthrough".to_string(),
|
termination: "passthrough".to_string(),
|
||||||
@@ -123,14 +88,14 @@ impl<T: Topology + K8sclient> Score<T> for OKDTlsPassthroughScore {
|
|||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let route_score = OKDRouteScore::new(&self.name, &self.namespace, passthrough_spec);
|
let route_score = OKDRouteScore::new(&self.name.to_string(), &self.route.namespace, passthrough_spec);
|
||||||
route_score.create_interpret()
|
route_score.create_interpret()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"OKDTlsPassthroughScore({}:{}/{} → {})",
|
"OKDTlsPassthroughScore({}:{}/{} → {})",
|
||||||
self.backend, self.target_port, self.namespace, self.hostname
|
self.route.backend, self.route.target_port, self.route.namespace, self.route.hostname
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ use crate::data::Version;
|
|||||||
use crate::domain::topology::router::{TlsRoute, TlsRouter};
|
use crate::domain::topology::router::{TlsRoute, TlsRouter};
|
||||||
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
||||||
use crate::inventory::Inventory;
|
use crate::inventory::Inventory;
|
||||||
use crate::modules::k8s::resource::K8sResourceScore;
|
|
||||||
use crate::modules::postgresql::PostgreSQLScore;
|
use crate::modules::postgresql::PostgreSQLScore;
|
||||||
use crate::modules::postgresql::cnpg::{Bootstrap, Cluster, ClusterSpec, Initdb, Storage};
|
|
||||||
use crate::score::Score;
|
use crate::score::Score;
|
||||||
use crate::topology::{K8sclient, Topology};
|
use crate::topology::{K8sclient, Topology};
|
||||||
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
|
||||||
|
|
||||||
/// Deploys a public PostgreSQL cluster: CNPG + TLS passthrough route for RW endpoint.
|
/// Deploys a public PostgreSQL cluster: CNPG + TLS passthrough route for RW endpoint.
|
||||||
/// For failover/multisite: exposes single-instance or small HA Postgres publicly.
|
/// For failover/multisite: exposes single-instance or small HA Postgres publicly.
|
||||||
@@ -26,24 +23,15 @@ use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct PublicPostgreSQLScore {
|
pub struct PublicPostgreSQLScore {
|
||||||
/// Inner non-public Postgres cluster config.
|
/// Inner non-public Postgres cluster config.
|
||||||
pub inner: PostgreSQLScore,
|
pub postgres_score: PostgreSQLScore,
|
||||||
/// Public hostname for RW TLS passthrough (port 443 → cluster-rw:5432).
|
/// Public hostname for RW TLS passthrough (port 443 → cluster-rw:5432).
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PublicPostgreSQLScore {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: PostgreSQLScore::default(),
|
|
||||||
hostname: "postgres.default.public".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PublicPostgreSQLScore {
|
impl PublicPostgreSQLScore {
|
||||||
pub fn new(namespace: &str, hostname: &str) -> Self {
|
pub fn new(namespace: &str, hostname: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: PostgreSQLScore::new(namespace),
|
postgres_score: PostgreSQLScore::new(namespace),
|
||||||
hostname: hostname.to_string(),
|
hostname: hostname.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,10 +60,7 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Interpret<T> for PublicP
|
|||||||
}
|
}
|
||||||
async fn execute(&self, inventory: &Inventory, topo: &T) -> Result<Outcome, InterpretError> {
|
async fn execute(&self, inventory: &Inventory, topo: &T) -> Result<Outcome, InterpretError> {
|
||||||
// Deploy CNPG cluster first (creates -rw service)
|
// Deploy CNPG cluster first (creates -rw service)
|
||||||
self.postgres_score
|
self.postgres_score.interpret(inventory, topo).await?;
|
||||||
.create_interpret()
|
|
||||||
.execute(inventory, topo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Expose RW publicly via TLS passthrough
|
// Expose RW publicly via TLS passthrough
|
||||||
topo.install_route(self.tls_route.clone())
|
topo.install_route(self.tls_route.clone())
|
||||||
@@ -92,15 +77,16 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Interpret<T> for PublicP
|
|||||||
|
|
||||||
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostgreSQLScore {
|
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostgreSQLScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
let rw_backend = format!("{}-rw", self.inner.name);
|
let rw_backend = format!("{}-rw", self.postgres_score.name);
|
||||||
let tls_route = TlsRoute {
|
let tls_route = TlsRoute {
|
||||||
|
namespace: self.postgres_score.namespace.clone(),
|
||||||
hostname: self.hostname.clone(),
|
hostname: self.hostname.clone(),
|
||||||
backend: rw_backend,
|
backend: rw_backend,
|
||||||
target_port: 5432,
|
target_port: 5432,
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(PublicPostgreSQLInterpret {
|
Box::new(PublicPostgreSQLInterpret {
|
||||||
postgres_score: self.inner.clone(),
|
postgres_score: self.postgres_score.clone(),
|
||||||
tls_route,
|
tls_route,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -108,9 +94,7 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostg
|
|||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"PublicPostgreSQLScore({}:{})",
|
"PublicPostgreSQLScore({}:{})",
|
||||||
self.inner.namespace, self.hostname
|
self.postgres_score.namespace, self.hostname
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add RO route (separate hostname/backend="cluster-ro"), backups, failover logic.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user