Added ownerRefs

This commit is contained in:
Michael Francis 2024-05-26 15:02:43 -04:00
parent 39bab0cb3a
commit 1a7dfa7acd
3 changed files with 109 additions and 39 deletions

View File

@ -12,14 +12,16 @@ use kube::runtime::wait::await_condition;
use kube::runtime::watcher::Config; use kube::runtime::watcher::Config;
use kube::Client; use kube::Client;
use kube::CustomResourceExt; use kube::CustomResourceExt;
use kube::Resource;
use kube::ResourceExt; use kube::ResourceExt;
use kube::{runtime::controller::Action, runtime::Controller}; use kube::{runtime::controller::Action, runtime::Controller};
use tracing::error; use tracing::error;
use tracing::info; use tracing::info;
use tracing::instrument; use tracing::instrument;
pub mod crd;
pub mod minio; mod crd;
pub mod neon; mod minio;
mod neon;
struct ContextData { struct ContextData {
pub kube_client: Client, pub kube_client: Client,
@ -40,9 +42,11 @@ pub enum Error {
#[from] #[from]
source: kube::Error, source: kube::Error,
}, },
/// Error in user input or Echo resource definition, typically missing fields. /// Error in user input or NeonDatabase resource definition, typically missing fields.
#[error("Invalid Neon CRD: {0}")] #[error("Invalid NeonDatabase CRD: {0}")]
UserInputError(String), UserInputError(String),
#[error("General error: {0}")]
GeneralError(String),
} }
#[tokio::main] #[tokio::main]
@ -55,7 +59,7 @@ async fn main() {
let crd_api: Api<NeonDatabase> = Api::all(kube_client.clone()); let crd_api: Api<NeonDatabase> = Api::all(kube_client.clone());
let context: Arc<ContextData> = Arc::new(ContextData::new(kube_client.clone())); let context: Arc<ContextData> = Arc::new(ContextData::new(kube_client.clone()));
let ssapply = PatchParams::apply("crd_apply_example").force(); let ssapply = PatchParams::apply("neon_operator").force();
let crds: Api<CustomResourceDefinition> = Api::all(kube_client.clone()); let crds: Api<CustomResourceDefinition> = Api::all(kube_client.clone());
info!( info!(
"Creating crd: {}", "Creating crd: {}",
@ -77,7 +81,7 @@ async fn main() {
); );
let _ = tokio::time::timeout(std::time::Duration::from_secs(10), establish) let _ = tokio::time::timeout(std::time::Duration::from_secs(10), establish)
.await .await
.expect("Waiting failed"); .expect("Timeout waiting for CRD to establish");
info!("CRD established, starting controller"); info!("CRD established, starting controller");
Controller::new(crd_api.clone(), Config::default()) Controller::new(crd_api.clone(), Config::default())
@ -100,16 +104,44 @@ async fn reconcile(neon: Arc<NeonDatabase>, context: Arc<ContextData>) -> Result
let client = context.kube_client.clone(); let client = context.kube_client.clone();
let namespace: String = match neon.namespace() { let namespace: String = match neon.namespace() {
Some(ns) => ns.to_string(), Some(ns) => ns,
None => return Err(Error::UserInputError("Missing namespace".to_string())), None => return Err(Error::UserInputError("Missing namespace".to_string())),
}; };
minio::create_deployment(client.clone(), &namespace).await?; let owner_ref = neon
minio::create_service(client.clone(), &namespace).await?; .controller_owner_ref(&())
neon::reconcile_storage_broker(client.clone(), &namespace).await?; .ok_or(Error::GeneralError("Missing owner ref".to_string()))?;
neon::reconcile_page_server(client.clone(), &namespace).await?;
neon::reconcile_safe_keepers(client.clone(), &namespace).await?; minio::create_deployment(client.clone(), &owner_ref, &namespace).await?;
neon::reconcile_compute(client.clone(), &namespace, &neon.spec.compute_image_ref).await?; minio::create_service(client.clone(), &owner_ref, &namespace).await?;
neon::reconcile_storage_broker(
client.clone(),
&owner_ref,
&namespace,
&neon.spec.neon_image_ref,
)
.await?;
neon::reconcile_page_server(
client.clone(),
&owner_ref,
&namespace,
&neon.spec.neon_image_ref,
)
.await?;
neon::reconcile_safe_keepers(
client.clone(),
&owner_ref,
&namespace,
&neon.spec.neon_image_ref,
)
.await?;
neon::reconcile_compute(
client.clone(),
&owner_ref,
&namespace,
&neon.spec.compute_image_ref,
)
.await?;
Ok(Action::requeue(Duration::from_secs(5))) Ok(Action::requeue(Duration::from_secs(5)))
} }

View File

@ -1,16 +1,33 @@
use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec}; use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec};
use k8s_openapi::api::core::v1::{ use k8s_openapi::api::core::v1::{
Container, ContainerPort, EnvVar, PodSpec, PodTemplateSpec, Service, ServicePort, ServiceSpec, Container, ContainerPort, EnvVar, HTTPGetAction, PodSpec, PodTemplateSpec, Probe, Service,
ServicePort, ServiceSpec,
}; };
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, OwnerReference};
use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;
use kube::api::{ListParams, ObjectMeta, Patch, PatchParams}; use kube::api::{ListParams, ObjectMeta, Patch, PatchParams};
use kube::{Api, Client, Error}; use kube::{Api, Client, Error};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use tracing::{info, instrument}; use tracing::{info, instrument};
const NAME: &str = "minio"; // Minio settings to lift out
const NAME: &str = "minio"; // In a real world scenario, we should support multiple minio instances
const MINIO_ROOT_USER: &str = "minio";
const MINIO_ROOT_PASSWORD: &str = "password";
const MINIO_IMAGE: &str = "quay.io/minio/minio:RELEASE.2022-10-20T00-55-09Z";
const MINIO_PORT: i32 = 9000;
const MINIO_CONSOLE_PORT: i32 = 9001;
pub async fn create_deployment(client: Client, namespace: &str) -> Result<(), Error> { // Workflow:
// 1. Create minio deployment
// 2. Create minio service
// 3. Ensure bucket exists
pub async fn create_deployment(
client: Client,
owner_ref: &OwnerReference,
namespace: &str,
) -> Result<(), Error> {
let minio_container = Container { let minio_container = Container {
name: NAME.to_string(), name: NAME.to_string(),
env: Some(vec![ env: Some(vec![
@ -49,20 +66,20 @@ pub async fn create_deployment(client: Client, namespace: &str) -> Result<(), Er
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
), ),
liveness_probe: Some(k8s_openapi::api::core::v1::Probe { liveness_probe: Some(Probe {
http_get: Some(k8s_openapi::api::core::v1::HTTPGetAction { http_get: Some(HTTPGetAction {
path: Some("/minio/health/live".to_string()), path: Some("/minio/health/live".to_string()),
port: k8s_openapi::apimachinery::pkg::util::intstr::IntOrString::Int(9000), port: IntOrString::Int(9000),
..Default::default() ..Default::default()
}), }),
initial_delay_seconds: Some(10), initial_delay_seconds: Some(10),
period_seconds: Some(30), period_seconds: Some(30),
..Default::default() ..Default::default()
}), }),
readiness_probe: Some(k8s_openapi::api::core::v1::Probe { readiness_probe: Some(Probe {
http_get: Some(k8s_openapi::api::core::v1::HTTPGetAction { http_get: Some(HTTPGetAction {
path: Some("/minio/health/ready".to_string()), path: Some("/minio/health/ready".to_string()),
port: k8s_openapi::apimachinery::pkg::util::intstr::IntOrString::Int(9000), port: IntOrString::Int(9000),
..Default::default() ..Default::default()
}), }),
initial_delay_seconds: Some(10), initial_delay_seconds: Some(10),
@ -100,6 +117,7 @@ pub async fn create_deployment(client: Client, namespace: &str) -> Result<(), Er
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some(NAME.to_string()), name: Some(NAME.to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
owner_references: Some(vec![owner_ref.clone()]),
labels: Some(BTreeMap::from([("app".to_string(), NAME.to_string())])), labels: Some(BTreeMap::from([("app".to_string(), NAME.to_string())])),
..Default::default() ..Default::default()
}, },
@ -131,7 +149,7 @@ pub async fn create_deployment(client: Client, namespace: &str) -> Result<(), Er
deployment_api deployment_api
.patch( .patch(
NAME, NAME,
&PatchParams::apply("neon-operator"), &PatchParams::apply("neon_operator"),
&Patch::Apply(deployment), &Patch::Apply(deployment),
) )
.await?; .await?;
@ -139,12 +157,17 @@ pub async fn create_deployment(client: Client, namespace: &str) -> Result<(), Er
Ok(()) Ok(())
} }
pub async fn create_service(client: Client, namespace: &str) -> Result<(), Error> { pub async fn create_service(
client: Client,
owner_ref: &OwnerReference,
namespace: &str,
) -> Result<(), Error> {
let service = Service { let service = Service {
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some(NAME.to_string()), name: Some(NAME.to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
labels: Some(BTreeMap::from([("app".to_string(), NAME.to_string())])), labels: Some(BTreeMap::from([("app".to_string(), NAME.to_string())])),
owner_references: Some(vec![owner_ref.clone()]),
..Default::default() ..Default::default()
}, },
spec: Some(ServiceSpec { spec: Some(ServiceSpec {
@ -162,7 +185,7 @@ pub async fn create_service(client: Client, namespace: &str) -> Result<(), Error
Api::<Service>::namespaced(client, namespace) Api::<Service>::namespaced(client, namespace)
.patch( .patch(
NAME, NAME,
&PatchParams::apply("neon-operator"), &PatchParams::apply("neon_operator"),
&Patch::Apply(service), &Patch::Apply(service),
) )
.await?; .await?;

View File

@ -2,21 +2,22 @@ use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec, StatefulSet};
use k8s_openapi::api::core::v1::{ use k8s_openapi::api::core::v1::{
Container, ContainerPort, EnvVar, PodSpec, PodTemplateSpec, Service, ServicePort, ServiceSpec, Container, ContainerPort, EnvVar, PodSpec, PodTemplateSpec, Service, ServicePort, ServiceSpec,
}; };
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, OwnerReference};
use kube::api::{ObjectMeta, Patch, PatchParams}; use kube::api::{ObjectMeta, Patch, PatchParams};
use kube::{Api, Client, Error}; use kube::{Api, Client, Error};
use std::collections::BTreeMap; use std::collections::BTreeMap;
// Workflow: pub async fn reconcile_storage_broker(
// 1. Create minio deployment client: Client,
// 2. Create minio service owner_ref: &OwnerReference,
// 3. Ensure bucket exists namespace: &str,
neon_image_ref: &str,
pub async fn reconcile_storage_broker(client: Client, namespace: &str) -> Result<(), Error> { ) -> Result<(), Error> {
let storage_broker = Deployment { let storage_broker = Deployment {
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some("storage-broker".to_string()), name: Some("storage-broker".to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
owner_references: Some(vec![owner_ref.clone()]),
..Default::default() ..Default::default()
}, },
spec: Some(DeploymentSpec { spec: Some(DeploymentSpec {
@ -39,7 +40,7 @@ pub async fn reconcile_storage_broker(client: Client, namespace: &str) -> Result
spec: Some(PodSpec { spec: Some(PodSpec {
containers: vec![Container { containers: vec![Container {
name: "storage-broker".to_string(), name: "storage-broker".to_string(),
image: Some("neondatabase/neon".to_string()), image: Some(neon_image_ref.to_string()),
ports: Some(vec![ContainerPort { ports: Some(vec![ContainerPort {
container_port: 50051, container_port: 50051,
..Default::default() ..Default::default()
@ -104,11 +105,17 @@ pub async fn reconcile_storage_broker(client: Client, namespace: &str) -> Result
Ok(()) Ok(())
} }
pub async fn reconcile_page_server(client: Client, namespace: &str) -> Result<(), Error> { pub async fn reconcile_page_server(
client: Client,
owner_ref: &OwnerReference,
namespace: &str,
neon_image_ref: &str,
) -> Result<(), Error> {
let deployment = Deployment { let deployment = Deployment {
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some("pageserver".to_string()), name: Some("pageserver".to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
owner_references: Some(vec![owner_ref.clone()]),
..Default::default() ..Default::default()
}, },
spec: Some(DeploymentSpec { spec: Some(DeploymentSpec {
@ -131,7 +138,7 @@ pub async fn reconcile_page_server(client: Client, namespace: &str) -> Result<()
spec: Some(PodSpec { spec: Some(PodSpec {
containers: vec![Container { containers: vec![Container {
name: "pageserver".to_string(), name: "pageserver".to_string(),
image: Some("neondatabase/neon".to_string()), image: Some(neon_image_ref.to_string()),
ports: Some(vec![ContainerPort { ports: Some(vec![ContainerPort {
container_port: 9898, container_port: 9898,
name: Some("http".to_string()), name: Some("http".to_string()),
@ -235,11 +242,17 @@ pub async fn reconcile_page_server(client: Client, namespace: &str) -> Result<()
Ok(()) Ok(())
} }
pub async fn reconcile_safe_keepers(client: Client, namespace: &str) -> Result<(), Error> { pub async fn reconcile_safe_keepers(
client: Client,
owner_ref: &OwnerReference,
namespace: &str,
neon_image_ref: &str,
) -> Result<(), Error> {
let stateful_set = StatefulSet { let stateful_set = StatefulSet {
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some("safe-keeper".to_string()), name: Some("safe-keeper".to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
owner_references: Some(vec![owner_ref.clone()]),
..Default::default() ..Default::default()
}, },
spec: Some(k8s_openapi::api::apps::v1::StatefulSetSpec { spec: Some(k8s_openapi::api::apps::v1::StatefulSetSpec {
@ -263,7 +276,7 @@ pub async fn reconcile_safe_keepers(client: Client, namespace: &str) -> Result<(
spec: Some(PodSpec { spec: Some(PodSpec {
containers: vec![Container { containers: vec![Container {
name: "safe-keeper".to_string(), name: "safe-keeper".to_string(),
image: Some("neondatabase/neon".to_string()), image: Some(neon_image_ref.to_string()),
ports: Some(vec![ContainerPort { ports: Some(vec![ContainerPort {
container_port: 7676, container_port: 7676,
..Default::default() ..Default::default()
@ -375,6 +388,7 @@ pub async fn reconcile_safe_keepers(client: Client, namespace: &str) -> Result<(
pub async fn reconcile_compute( pub async fn reconcile_compute(
client: Client, client: Client,
owner_ref: &OwnerReference,
namespace: &str, namespace: &str,
compute_image: &str, compute_image: &str,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -382,6 +396,7 @@ pub async fn reconcile_compute(
metadata: ObjectMeta { metadata: ObjectMeta {
name: Some("compute".to_string()), name: Some("compute".to_string()),
namespace: Some(namespace.to_string()), namespace: Some(namespace.to_string()),
owner_references: Some(vec![owner_ref.clone()]),
..Default::default() ..Default::default()
}, },
spec: Some(DeploymentSpec { spec: Some(DeploymentSpec {