Skip to content

Commit e671b4f

Browse files
committed
feat: enable additional scheduling options for wasmCloud host pods
This refactors the WasmCloudHostConfig CRD so that it has a single field (`schedulingOptions`) for configuring how the underlying Pods are scheduled in Kubernetes. This includes: * Relocating the `daemonset` option to this new field * Relocating the `resources` option to this new field * Adding a new `pod_template_additions` field that allows you to set any valid option in a `PodSpec` Doing so allows cluster operators to do things like set node affinity and node selector rules, along with any other valid PodSpec option. The only thing that cannot be done is adding additional containers to the pod, since that is all handled by the controller. We could look at exposing that option if users want to be able to add additional sidecars. Signed-off-by: Dan Norris <[email protected]>
1 parent 0756cde commit e671b4f

File tree

6 files changed

+125
-42
lines changed

6 files changed

+125
-42
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wasmcloud-operator"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
edition = "2021"
55

66
[[bin]]

Dockerfile.local

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# syntax=docker/dockerfile:1
2-
FROM rust:1.75-bookworm as builder
2+
FROM rust:1.77-bookworm as builder
33

44
WORKDIR /app
55
COPY . .

crates/types/src/v1alpha1/wasmcloud_host_config.rs

+33-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use k8s_openapi::api::core::v1::ResourceRequirements;
1+
use k8s_openapi::api::core::v1::{PodSpec, ResourceRequirements};
22
use kube::CustomResource;
3-
use schemars::JsonSchema;
3+
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
44
use serde::{Deserialize, Serialize};
5-
use std::collections::HashMap;
5+
use std::collections::{BTreeSet, HashMap};
66

77
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
88
#[cfg_attr(test, derive(Default))]
@@ -36,10 +36,6 @@ pub struct WasmCloudHostConfigSpec {
3636
pub enable_structured_logging: Option<bool>,
3737
/// Name of a secret containing the registry credentials
3838
pub registry_credentials_secret: Option<String>,
39-
/// Kubernetes resources to allocate for the host. See
40-
/// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for valid
41-
/// values to use here.
42-
pub resources: Option<WasmCloudHostConfigResources>,
4339
/// The control topic prefix to use for the host.
4440
pub control_topic_prefix: Option<String>,
4541
/// The leaf node domain to use for the NATS sidecar. Defaults to "leaf".
@@ -57,9 +53,39 @@ pub struct WasmCloudHostConfigSpec {
5753
/// The log level to use for the host. Defaults to "INFO".
5854
#[serde(default = "default_log_level")]
5955
pub log_level: String,
56+
/// Kubernetes scheduling options for the wasmCloud host.
57+
pub scheduling_options: Option<KubernetesSchedulingOptions>,
58+
}
59+
60+
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
61+
pub struct KubernetesSchedulingOptions {
6062
/// Run hosts as a DaemonSet instead of a Deployment.
6163
#[serde(default)]
6264
pub daemonset: bool,
65+
/// Kubernetes resources to allocate for the host. See
66+
/// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for valid
67+
/// values to use here.
68+
pub resources: Option<WasmCloudHostConfigResources>,
69+
#[schemars(schema_with = "pod_schema")]
70+
/// Any other pod template spec options to set for the underlying wasmCloud host pods.
71+
pub pod_template_additions: Option<PodSpec>,
72+
}
73+
74+
/// This is a workaround for the fact that we can't override the PodSpec schema to make containers
75+
/// an optional field. It generates the OpenAPI schema for the PodSpec type the same way that
76+
/// kube.rs does while dropping any required fields.
77+
fn pod_schema(_gen: &mut SchemaGenerator) -> Schema {
78+
let gen = schemars::gen::SchemaSettings::openapi3()
79+
.with(|s| {
80+
s.inline_subschemas = true;
81+
s.meta_schema = None;
82+
})
83+
.with_visitor(kube::core::schema::StructuralSchemaRewriter)
84+
.into_generator();
85+
let mut val = gen.into_root_schema_for::<PodSpec>();
86+
// Drop `containers` as a required field, along with any others.
87+
val.schema.object.as_mut().unwrap().required = BTreeSet::new();
88+
val.schema.into()
6389
}
6490

6591
fn default_host_replicas() -> u32 {

sample.yaml

+20-2
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,23 @@ spec:
1818
secretName: cluster-secrets
1919
logLevel: INFO
2020
natsAddress: nats://nats-cluster.default.svc.cluster.local
21-
# Enable the following to run the wasmCloud hosts as a DaemonSet
22-
#daemonset: true
21+
# Additional options to control how the underlying wasmCloud hosts are scheduled in Kubernetes.
22+
# This includes setting resource requirements for the nats and wasmCloud host
23+
# containers along with any additional pot template settings.
24+
#schedulingOptions:
25+
# Enable the following to run the wasmCloud hosts as a DaemonSet
26+
#daemonset: true
27+
# Set the resource requirements for the nats and wasmCloud host containers.
28+
#resources:
29+
# nats:
30+
# requests:
31+
# cpu: 100m
32+
# wasmCloudHost:
33+
# requests:
34+
# cpu: 100m
35+
# Any additional pod template settings to apply to the wasmCloud host pods.
36+
# See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#podspec-v1-core for all valid options.
37+
# Note that you *cannot* set the `containers` field here as it is managed by the controller.
38+
#pod_template_additions:
39+
# nodeSelector:
40+
# kubernetes.io/os: linux

src/controller.rs

+69-30
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,11 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
315315

316316
let mut nats_resources: Option<k8s_openapi::api::core::v1::ResourceRequirements> = None;
317317
let mut wasmcloud_resources: Option<k8s_openapi::api::core::v1::ResourceRequirements> = None;
318-
if let Some(resources) = &config.spec.resources {
319-
nats_resources = resources.nats.clone();
320-
wasmcloud_resources = resources.wasmcloud.clone();
318+
if let Some(scheduling_options) = &config.spec.scheduling_options {
319+
if let Some(resources) = &scheduling_options.resources {
320+
nats_resources = resources.nats.clone();
321+
wasmcloud_resources = resources.wasmcloud.clone();
322+
}
321323
}
322324

323325
let containers = vec![
@@ -371,14 +373,34 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
371373
..Default::default()
372374
},
373375
];
374-
PodTemplateSpec {
376+
377+
let mut volumes = vec![
378+
Volume {
379+
name: "nats-config".to_string(),
380+
config_map: Some(ConfigMapVolumeSource {
381+
name: Some(config.name_any()),
382+
..Default::default()
383+
}),
384+
..Default::default()
385+
},
386+
Volume {
387+
name: "nats-creds".to_string(),
388+
secret: Some(SecretVolumeSource {
389+
secret_name: Some(config.spec.secret_name.clone()),
390+
..Default::default()
391+
}),
392+
..Default::default()
393+
},
394+
];
395+
let service_account = config.name_any();
396+
let mut template = PodTemplateSpec {
375397
metadata: Some(ObjectMeta {
376398
labels: Some(labels),
377399
..Default::default()
378400
}),
379401
spec: Some(PodSpec {
380402
service_account: Some(config.name_any()),
381-
containers,
403+
containers: containers.clone(),
382404
volumes: Some(vec![
383405
Volume {
384406
name: "nats-config".to_string(),
@@ -399,7 +421,21 @@ fn pod_template(config: &WasmCloudHostConfig, _ctx: Arc<Context>) -> PodTemplate
399421
]),
400422
..Default::default()
401423
}),
402-
}
424+
};
425+
426+
if let Some(scheduling_options) = &config.spec.scheduling_options {
427+
if let Some(pod_overrides) = &scheduling_options.pod_template_additions {
428+
let mut overrides = pod_overrides.clone();
429+
overrides.service_account_name = Some(service_account);
430+
overrides.containers = containers.clone();
431+
if let Some(vols) = overrides.volumes {
432+
volumes.extend(vols);
433+
}
434+
overrides.volumes = Some(volumes);
435+
template.spec = Some(overrides);
436+
}
437+
};
438+
template
403439
}
404440

405441
fn deployment_spec(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> DeploymentSpec {
@@ -504,31 +540,34 @@ async fn configure_hosts(config: &WasmCloudHostConfig, ctx: Arc<Context>) -> Res
504540
];
505541
}
506542

507-
if config.spec.daemonset {
508-
let mut spec = daemonset_spec(config, ctx.clone());
509-
spec.template.spec.as_mut().unwrap().containers[1]
510-
.env
511-
.as_mut()
512-
.unwrap()
513-
.append(&mut env_vars);
514-
let ds = DaemonSet {
515-
metadata: ObjectMeta {
516-
name: Some(config.name_any()),
517-
namespace: Some(config.namespace().unwrap()),
518-
owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]),
543+
if let Some(scheduling_options) = &config.spec.scheduling_options {
544+
if scheduling_options.daemonset {
545+
let mut spec = daemonset_spec(config, ctx.clone());
546+
spec.template.spec.as_mut().unwrap().containers[1]
547+
.env
548+
.as_mut()
549+
.unwrap()
550+
.append(&mut env_vars);
551+
let ds = DaemonSet {
552+
metadata: ObjectMeta {
553+
name: Some(config.name_any()),
554+
namespace: Some(config.namespace().unwrap()),
555+
owner_references: Some(vec![config.controller_owner_ref(&()).unwrap()]),
556+
..Default::default()
557+
},
558+
spec: Some(spec),
519559
..Default::default()
520-
},
521-
spec: Some(spec),
522-
..Default::default()
523-
};
524-
525-
let api = Api::<DaemonSet>::namespaced(ctx.client.clone(), &config.namespace().unwrap());
526-
api.patch(
527-
&config.name_any(),
528-
&PatchParams::apply(CLUSTER_CONFIG_FINALIZER),
529-
&Patch::Apply(ds),
530-
)
531-
.await?;
560+
};
561+
562+
let api =
563+
Api::<DaemonSet>::namespaced(ctx.client.clone(), &config.namespace().unwrap());
564+
api.patch(
565+
&config.name_any(),
566+
&PatchParams::apply(CLUSTER_CONFIG_FINALIZER),
567+
&Patch::Apply(ds),
568+
)
569+
.await?;
570+
}
532571
} else {
533572
let mut spec = deployment_spec(config, ctx.clone());
534573
spec.template.spec.as_mut().unwrap().containers[1]

0 commit comments

Comments
 (0)