Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement resource requests and limits for Superset pods #273

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased]

### Added

- CPU and memory limits are now configurable ([#273]).

[#273]: https://github.com/stackabletech/superset-operator/pull/273

## [0.6.0] - 2022-09-07

### Added
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

246 changes: 246 additions & 0 deletions deploy/crd/supersetcluster.crd.yaml

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions deploy/helm/superset-operator/crds/crds.yaml

Large diffs are not rendered by default.

246 changes: 246 additions & 0 deletions deploy/manifests/crds.yaml

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions docs/modules/ROOT/pages/usage.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,64 @@ nodes:
----

// cliOverrides don't make sense for this operator, so the feature is omitted for now

=== Storage for data volumes

The Superset operator currently does not support using https://kubernetes.io/docs/concepts/storage/persistent-volumes[PersistentVolumeClaims] for internal storage.

=== Memory requests

You can request a certain amount of memory for each individual role group as shown below:

[source,yaml]
----
nodes:
roleGroups:
default:
config:
resources:
memory:
limit: '2Gi'
----

In this example, each Superset container in the `default` role group will have a maximum of 2 gigabytes of memory. To be more precise, these memory limits apply to the container running Superset but not to any sidecar containers (e.g. the metrics container) that are part of the pod.

For more details regarding Kubernetes memory requests and limits see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/[Assign Memory Resources to Containers and Pods].

=== CPU requests

Similarly to memory resources, you can also configure CPU limits, as shown below:

[source,yaml]
----
nodes:
roleGroups:
default:
config:
resources:
cpu:
max: '500m'
min: '250m'
----

=== Defaults

If nothing is specified, the operator will automatically set the following default values for resources:

[source,yaml]
----
nodes:
roleGroups:
default:
config:
resources:
cpu:
min: '200m'
max: "4"
memory:
limit: '2Gi'
----

WARNING: The default values are _most likely_ not sufficient to run a proper cluster in production. Please adapt according to your requirements.

For more details regarding Kubernetes CPU limits see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/[Assign CPU Resources to Containers and Pods].
1 change: 1 addition & 0 deletions rust/crd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ serde_json = "1.0"
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.25.0" }
strum = { version = "0.24", features = ["derive"] }
snafu = "0.7"
tracing = "0.1"
78 changes: 67 additions & 11 deletions rust/crd/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
pub mod druidconnection;
pub mod supersetdb;

use std::collections::BTreeMap;
use std::num::ParseIntError;

use serde::{Deserialize, Serialize};
use snafu::Snafu;
use stackable_operator::kube::runtime::reflector::ObjectRef;
use stackable_operator::kube::CustomResource;
use stackable_operator::product_config::flask_app_config_writer::{
FlaskAppConfigOptions, PythonType,
use stackable_operator::{
commons::resources::{CpuLimits, MemoryLimits, NoRuntimeLimits, Resources},
config::merge::Merge,
k8s_openapi::apimachinery::pkg::api::resource::Quantity,
kube::{runtime::reflector::ObjectRef, CustomResource},
product_config::flask_app_config_writer::{FlaskAppConfigOptions, PythonType},
product_config_utils::{ConfigError, Configuration},
role_utils::{Role, RoleGroupRef},
schemars::{self, JsonSchema},
};
use stackable_operator::product_config_utils::{ConfigError, Configuration};
use stackable_operator::role_utils::{Role, RoleGroupRef};
use stackable_operator::schemars::{self, JsonSchema};
use std::{collections::BTreeMap, num::ParseIntError};
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};

pub const APP_NAME: &str = "superset";
Expand Down Expand Up @@ -236,7 +236,11 @@ pub enum SupersetRole {
Node,
}

#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, Eq, Merge, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SupersetStorageConfig {}

#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SupersetConfig {
/// Row limit when requesting chart data.
Expand All @@ -247,11 +251,27 @@ pub struct SupersetConfig {
/// If you get timeout errors before your query returns the result you may need to increase this timeout.
/// Corresponds to SUPERSET_WEBSERVER_TIMEOUT
pub webserver_timeout: Option<u32>,
/// CPU and memory limits for Superset pods
pub resources: Option<Resources<SupersetStorageConfig, NoRuntimeLimits>>,
}

impl SupersetConfig {
pub const CREDENTIALS_SECRET_PROPERTY: &'static str = "credentialsSecret";
pub const MAPBOX_SECRET_PROPERTY: &'static str = "mapboxSecret";

fn default_resources() -> Resources<SupersetStorageConfig, NoRuntimeLimits> {
Resources {
cpu: CpuLimits {
min: Some(Quantity("200m".to_owned())),
max: Some(Quantity("4".to_owned())),
},
memory: MemoryLimits {
limit: Some(Quantity("2Gi".to_owned())),
runtime_limits: NoRuntimeLimits {},
},
storage: SupersetStorageConfig {},
}
}
}

impl Configuration for SupersetConfig {
Expand Down Expand Up @@ -323,6 +343,42 @@ impl SupersetCluster {
role_group: group_name.into(),
}
}

/// Retrieve and merge resource configs for role and role groups
pub fn resolve_resource_config_for_role_and_rolegroup(
&self,
role: &SupersetRole,
rolegroup_ref: &RoleGroupRef<SupersetCluster>,
) -> Option<Resources<SupersetStorageConfig, NoRuntimeLimits>> {
// Initialize the result with all default values as baseline
let conf_defaults = SupersetConfig::default_resources();

let role = match role {
SupersetRole::Node => self.spec.nodes.as_ref()?,
};

// Retrieve role resource config
let mut conf_role: Resources<SupersetStorageConfig, NoRuntimeLimits> =
role.config.config.resources.clone().unwrap_or_default();

// Retrieve rolegroup specific resource config
let mut conf_rolegroup: Resources<SupersetStorageConfig, NoRuntimeLimits> = role
.role_groups
.get(&rolegroup_ref.role_group)
.and_then(|rg| rg.config.config.resources.clone())
.unwrap_or_default();

// Merge more specific configs into default config
// Hierarchy is:
// 1. RoleGroup
// 2. Role
// 3. Default
conf_role.merge(&conf_defaults);
conf_rolegroup.merge(&conf_role);

tracing::debug!("Merged resource config: {:?}", conf_rolegroup);
Some(conf_rolegroup)
}
}

/// A reference to a [`SupersetCluster`]
Expand Down
14 changes: 12 additions & 2 deletions rust/operator-binary/src/superset_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use stackable_operator::{
cluster_resources::ClusterResources,
commons::{
authentication::{AuthenticationClass, AuthenticationClassProvider},
resources::{NoRuntimeLimits, Resources},
secret_class::SecretClassVolumeScope,
tls::{CaCert, TlsServerVerification, TlsVerification},
},
Expand Down Expand Up @@ -43,8 +44,8 @@ use stackable_operator::{
};
use stackable_superset_crd::{
supersetdb::{SupersetDB, SupersetDBStatusCondition},
SupersetCluster, SupersetConfig, SupersetConfigOptions, SupersetRole, PYTHONPATH,
SUPERSET_CONFIG_FILENAME,
SupersetCluster, SupersetConfig, SupersetConfigOptions, SupersetRole, SupersetStorageConfig,
PYTHONPATH, SUPERSET_CONFIG_FILENAME,
};
use std::{
borrow::Cow,
Expand Down Expand Up @@ -160,6 +161,8 @@ pub enum Error {
MissingSupersetConfigInNodeConfig,
#[snafu(display("failed to get {timeout} from {SUPERSET_CONFIG_FILENAME} file. It should be set in the product config or by user input", timeout = SupersetConfigOptions::SupersetWebserverTimeout))]
MissingWebServerTimeoutInSupersetConfig,
#[snafu(display("failed to resolve and merge resource config for role and role group"))]
FailedToResolveResourceConfig,
}

type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -269,6 +272,10 @@ pub async fn reconcile_superset(superset: Arc<SupersetCluster>, ctx: Arc<Ctx>) -
for (rolegroup_name, rolegroup_config) in role_node_config.iter() {
let rolegroup = superset.node_rolegroup_ref(rolegroup_name);

let resources = superset
.resolve_resource_config_for_role_and_rolegroup(&SupersetRole::Node, &rolegroup)
.context(FailedToResolveResourceConfigSnafu)?;

let rg_service = build_node_rolegroup_service(&rolegroup, &superset)?;
let rg_configmap = build_rolegroup_config_map(
&superset,
Expand All @@ -281,6 +288,7 @@ pub async fn reconcile_superset(superset: Arc<SupersetCluster>, ctx: Arc<Ctx>) -
&superset,
rolegroup_config,
authentication_class.as_ref(),
&resources,
)?;
cluster_resources
.add(client, &rg_service)
Expand Down Expand Up @@ -479,6 +487,7 @@ fn build_server_rolegroup_statefulset(
superset: &SupersetCluster,
node_config: &HashMap<PropertyNameKind, BTreeMap<String, String>>,
authentication_class: Option<&AuthenticationClass>,
resources: &Resources<SupersetStorageConfig, NoRuntimeLimits>,
) -> Result<StatefulSet> {
let rolegroup = superset
.spec
Expand Down Expand Up @@ -551,6 +560,7 @@ fn build_server_rolegroup_statefulset(
'superset.app:create_app()'
"},
])
.resources(resources.clone().into())
.build();
let metrics_container = ContainerBuilder::new("metrics")
.expect("ContainerBuilder not created")
Expand Down
14 changes: 14 additions & 0 deletions tests/templates/kuttl/resources/00-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
metadata:
name: test-superset-postgresql
timeout: 480
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: superset-postgresql
status:
readyReplicas: 1
replicas: 1
12 changes: 12 additions & 0 deletions tests/templates/kuttl/resources/00-install-postgresql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: >-
helm install superset-postgresql
--namespace $NAMESPACE
--version 11.0.0
--set auth.username=superset
--set auth.password=superset
--set auth.database=superset
--repo https://charts.bitnami.com/bitnami postgresql
48 changes: 48 additions & 0 deletions tests/templates/kuttl/resources/01-assert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
metadata:
name: install-superset
timeout: 300
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: superset-node-resources-from-role
spec:
template:
spec:
containers:
- name: superset
resources:
requests:
cpu: 100m
memory: 1Gi
limits:
cpu: "1"
memory: 1Gi
- name: metrics
status:
readyReplicas: 1
replicas: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: superset-node-resources-from-role-group
spec:
template:
spec:
containers:
- name: superset
resources:
requests:
cpu: 300m
memory: 3Gi
limits:
cpu: "3"
memory: 3Gi
- name: metrics
status:
readyReplicas: 1
replicas: 1
48 changes: 48 additions & 0 deletions tests/templates/kuttl/resources/01-install-superset.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
timeout: 300
---
apiVersion: v1
kind: Secret
metadata:
name: superset-credentials
type: Opaque
stringData:
adminUser.username: admin
adminUser.firstname: Superset
adminUser.lastname: Admin
adminUser.email: admin@superset.com
adminUser.password: admin
connections.secretKey: thisISaSECRET_1234
connections.sqlalchemyDatabaseUri: postgresql://superset:superset@superset-postgresql/superset
---
apiVersion: superset.stackable.tech/v1alpha1
kind: SupersetCluster
metadata:
name: superset
spec:
version: {{ test_scenario['values']['superset-latest'] }}
statsdExporterVersion: v0.22.4
credentialsSecret: superset-credentials
loadExamplesOnInit: false
nodes:
config:
resources:
cpu:
min: 100m
max: "1"
memory:
limit: 1Gi
roleGroups:
resources-from-role:
replicas: 1
resources-from-role-group:
replicas: 1
config:
resources:
cpu:
min: 300m
max: "3"
memory:
limit: 3Gi
Loading