This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/superset-kubernetes-operator.git
The following commit(s) were added to refs/heads/main by this push:
new 4b997ec chore: address probes, docs, RBAC, samples (#49)
4b997ec is described below
commit 4b997ec5c138138650abdae85a8a308c1bddd747
Author: Ville Brofeldt <[email protected]>
AuthorDate: Tue May 12 11:22:15 2026 -0700
chore: address probes, docs, RBAC, samples (#49)
---
.../superset-operator/templates/clusterrole.yaml | 15 +++-----
.../superset_v1alpha1_supersetcelerybeat.yaml | 2 +-
docs/user-guide/configuration.md | 24 +++++++++---
internal/controller/child_controllers.go | 43 +++++++++++++++++++++-
internal/controller/deployment_builder.go | 29 +++++++++++++--
internal/controller/superset_controller.go | 1 +
6 files changed, 94 insertions(+), 20 deletions(-)
diff --git a/charts/superset-operator/templates/clusterrole.yaml
b/charts/superset-operator/templates/clusterrole.yaml
index ebc05d2..0e6cea1 100644
--- a/charts/superset-operator/templates/clusterrole.yaml
+++ b/charts/superset-operator/templates/clusterrole.yaml
@@ -23,9 +23,6 @@ rules:
- apiGroups: [""]
resources: [configmaps, serviceaccounts, services]
verbs: [create, delete, get, list, patch, update, watch]
- - apiGroups: [""]
- resources: [events]
- verbs: [create, patch]
- apiGroups: [""]
resources: [pods]
verbs: [create, delete, get, list, watch]
@@ -35,6 +32,9 @@ rules:
- apiGroups: [autoscaling]
resources: [horizontalpodautoscalers]
verbs: [create, delete, get, list, patch, update, watch]
+ - apiGroups: [events.k8s.io]
+ resources: [events]
+ verbs: [create, patch, update]
- apiGroups: [gateway.networking.k8s.io]
resources: [httproutes]
verbs: [create, delete, get, list, patch, update, watch]
@@ -47,15 +47,12 @@ rules:
- apiGroups: [policy]
resources: [poddisruptionbudgets]
verbs: [create, delete, get, list, patch, update, watch]
- - apiGroups: [superset.apache.org]
- resources: [supersets]
- verbs: [get, list, watch]
- apiGroups: [superset.apache.org]
resources: [supersetcelerybeats, supersetceleryflowers,
supersetceleryworkers, supersetlifecycletasks, supersetmcpservers,
supersetwebservers, supersetwebsocketservers]
verbs: [create, delete, get, list, patch, update, watch]
- apiGroups: [superset.apache.org]
- resources: [supersetcelerybeats/status, supersetceleryflowers/status,
supersetceleryworkers/status, supersetlifecycletasks/status,
supersetmcpservers/status, supersetwebservers/status,
supersetwebsocketservers/status]
+ resources: [supersetcelerybeats/status, supersetceleryflowers/status,
supersetceleryworkers/status, supersetlifecycletasks/status,
supersetmcpservers/status, supersets/status, supersetwebservers/status,
supersetwebsocketservers/status]
verbs: [get, patch, update]
- apiGroups: [superset.apache.org]
- resources: [supersets/status]
- verbs: [get, patch, update]
+ resources: [supersets]
+ verbs: [get, list, watch]
diff --git a/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
b/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
index daab42b..ad55dcd 100644
--- a/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
+++ b/config/samples/superset_v1alpha1_supersetcelerybeat.yaml
@@ -5,7 +5,7 @@ metadata:
app.kubernetes.io/name: superset
app.kubernetes.io/component: celery-beat
app.kubernetes.io/instance: superset-sample
- name: superset-sample-celery-beat
+ name: superset-sample
spec:
image:
tag: "latest"
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
index 964cc24..4779680 100644
--- a/docs/user-guide/configuration.md
+++ b/docs/user-guide/configuration.md
@@ -28,8 +28,9 @@ For lifecycle (migrations, upgrades), see
[Lifecycle](lifecycle.md).
The `environment` field controls validation strictness (enforced by
[CEL](https://kubernetes.io/docs/reference/using-api/cel/) rules in the CRD
schema):
-- **`prod`** (default) — inline `secretKey`, `metastore.uri`,
`metastore.password`, and `valkey.password` are rejected by CRD validation. Use
`secretKeyFrom`, `metastore.uriFrom`, `metastore.passwordFrom`, or
`valkey.passwordFrom` to reference Kubernetes Secrets.
-- **`dev`** — allows plain-text `secretKey`, `metastore.uri`,
`metastore.password`, and `valkey.password` directly in the CR for quick local
development.
+- **`Production`** (default) — inline `secretKey`, `metastore.uri`,
`metastore.password`, and `valkey.password` are rejected by CRD validation. Use
`secretKeyFrom`, `metastore.uriFrom`, `metastore.passwordFrom`, or
`valkey.passwordFrom` to reference Kubernetes Secrets.
+- **`Staging`** — same secret restrictions as Production, but allows
`lifecycle.clone` for database cloning from an external source.
+- **`Development`** — allows plain-text `secretKey`, `metastore.uri`,
`metastore.password`, and `valkey.password` directly in the CR for quick local
development. Also permits `lifecycle.clone`, `lifecycle.init.adminUser`, and
`lifecycle.init.loadExamples`.
### Dev Mode Example
@@ -406,11 +407,21 @@ spec:
Enable Superset's async event streaming by setting `websocketServer`. This
deploys a **Node.js** application (not Python) that pushes real-time updates to
-dashboards via WebSocket connections:
+dashboards via WebSocket connections.
+
+!!! warning "Requires a dedicated image"
+ The websocket server is a separate Node.js application and **does not run
+ from the default Superset image**. You must provide an image that contains
+ `websocket_server.js`. A community-maintained image is available at
+
[`oneacrefund/superset-websocket`](https://hub.docker.com/r/oneacrefund/superset-websocket)
+ (experimental, not officially supported by Apache Superset).
```yaml
spec:
- websocketServer: {}
+ websocketServer:
+ image:
+ repository: oneacrefund/superset-websocket
+ tag: "latest"
```
Because the websocket server is Node.js-based, it does **not** receive a
@@ -421,6 +432,9 @@ on the container template:
```yaml
spec:
websocketServer:
+ image:
+ repository: oneacrefund/superset-websocket
+ tag: "latest"
podTemplate:
container:
env:
@@ -428,7 +442,7 @@ spec:
value: "http://my-superset-web-server:8088"
```
-The websocket server creates a Service (default port 8088) and supports the
+The websocket server creates a Service (default port 8080) and supports the
same scaling, deployment template, and pod template fields as other scalable
components.
diff --git a/internal/controller/child_controllers.go
b/internal/controller/child_controllers.go
index fab62fe..eb1cadf 100644
--- a/internal/controller/child_controllers.go
+++ b/internal/controller/child_controllers.go
@@ -21,6 +21,7 @@ package controller
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/tools/events"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -28,6 +29,37 @@ import (
"github.com/apache/superset-kubernetes-operator/internal/common"
)
+var celeryBeatSingletonReplica = int32(1)
+
+func httpProbe(path string, port int32, initialDelay int32) *corev1.Probe {
+ return &corev1.Probe{
+ ProbeHandler: corev1.ProbeHandler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: path,
+ Port: intstr.FromInt32(port),
+ },
+ },
+ InitialDelaySeconds: initialDelay,
+ PeriodSeconds: 10,
+ TimeoutSeconds: 5,
+ FailureThreshold: 3,
+ }
+}
+
+func tcpProbe(port int32, initialDelay int32) *corev1.Probe {
+ return &corev1.Probe{
+ ProbeHandler: corev1.ProbeHandler{
+ TCPSocket: &corev1.TCPSocketAction{
+ Port: intstr.FromInt32(port),
+ },
+ },
+ InitialDelaySeconds: initialDelay,
+ PeriodSeconds: 10,
+ TimeoutSeconds: 5,
+ FailureThreshold: 3,
+ }
+}
+
//
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetwebservers,verbs=get;list;watch;create;update;patch;delete
//
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetwebservers/status,verbs=get;update;patch
//
+kubebuilder:rbac:groups=superset.apache.org,resources=supersetceleryworkers,verbs=get;list;watch;create;update;patch;delete
@@ -47,8 +79,6 @@ import (
//
+kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch;create;update;patch;delete
//
+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete
-var celeryBeatSingletonReplica = int32(1)
-
// ChildControllerDef defines the data needed to register a child controller.
type ChildControllerDef struct {
Name string
@@ -69,6 +99,9 @@ func ChildControllerDefs() []ChildControllerDef {
DefaultPorts: []corev1.ContainerPort{
{Name: common.PortNameHTTP,
ContainerPort: common.PortWebServer, Protocol: corev1.ProtocolTCP},
},
+ DefaultLivenessProbe:
httpProbe("/health", common.PortWebServer, 15),
+ DefaultReadinessProbe:
httpProbe("/health", common.PortWebServer, 5),
+ DefaultStartupProbe:
httpProbe("/health", common.PortWebServer, 15),
},
defaultPort: 0,
hasConfig: true,
@@ -115,6 +148,8 @@ func ChildControllerDefs() []ChildControllerDef {
DefaultPorts: []corev1.ContainerPort{
{Name: common.PortNameHTTP,
ContainerPort: common.PortCeleryFlower, Protocol: corev1.ProtocolTCP},
},
+ DefaultLivenessProbe:
httpProbe("/api/workers", common.PortCeleryFlower, 15),
+ DefaultReadinessProbe:
httpProbe("/api/workers", common.PortCeleryFlower, 5),
},
defaultPort: common.PortCeleryFlower,
hasConfig: true,
@@ -132,6 +167,8 @@ func ChildControllerDefs() []ChildControllerDef {
DefaultPorts: []corev1.ContainerPort{
{Name:
common.PortNameWebsocket, ContainerPort: common.PortWebsocket, Protocol:
corev1.ProtocolTCP},
},
+ DefaultLivenessProbe:
httpProbe("/health", common.PortWebsocket, 15),
+ DefaultReadinessProbe:
httpProbe("/health", common.PortWebsocket, 5),
},
defaultPort: common.PortWebsocket,
hasConfig: false,
@@ -149,6 +186,8 @@ func ChildControllerDefs() []ChildControllerDef {
DefaultPorts: []corev1.ContainerPort{
{Name: common.PortNameMcp,
ContainerPort: common.PortMcpServer, Protocol: corev1.ProtocolTCP},
},
+ DefaultLivenessProbe:
tcpProbe(common.PortMcpServer, 15),
+ DefaultReadinessProbe:
tcpProbe(common.PortMcpServer, 5),
},
defaultPort: common.PortMcpServer,
hasConfig: true,
diff --git a/internal/controller/deployment_builder.go
b/internal/controller/deployment_builder.go
index d35d0df..3522a76 100644
--- a/internal/controller/deployment_builder.go
+++ b/internal/controller/deployment_builder.go
@@ -54,6 +54,15 @@ type DeploymentConfig struct {
// ForceReplicas, when non-nil, overrides the spec replicas (used for
singletons like beat).
ForceReplicas *int32
+
+ // DefaultLivenessProbe is used when the spec LivenessProbe is nil.
+ DefaultLivenessProbe *corev1.Probe
+
+ // DefaultReadinessProbe is used when the spec ReadinessProbe is nil.
+ DefaultReadinessProbe *corev1.Probe
+
+ // DefaultStartupProbe is used when the spec StartupProbe is nil.
+ DefaultStartupProbe *corev1.Probe
}
// buildDeploymentSpec constructs a complete DeploymentSpec from the flat
child spec,
@@ -96,6 +105,20 @@ func buildDeploymentSpec(
ports = ct.Ports
}
+ // Resolve probes: user-provided takes precedence over component
defaults.
+ livenessProbe := ct.LivenessProbe
+ if livenessProbe == nil {
+ livenessProbe = cfg.DefaultLivenessProbe
+ }
+ readinessProbe := ct.ReadinessProbe
+ if readinessProbe == nil {
+ readinessProbe = cfg.DefaultReadinessProbe
+ }
+ startupProbe := ct.StartupProbe
+ if startupProbe == nil {
+ startupProbe = cfg.DefaultStartupProbe
+ }
+
// Build the main container from ContainerTemplate.
mainContainer := corev1.Container{
Name: cfg.ContainerName,
@@ -106,9 +129,9 @@ func buildDeploymentSpec(
EnvFrom: ct.EnvFrom,
VolumeMounts: ct.VolumeMounts,
Ports: ports,
- LivenessProbe: ct.LivenessProbe,
- ReadinessProbe: ct.ReadinessProbe,
- StartupProbe: ct.StartupProbe,
+ LivenessProbe: livenessProbe,
+ ReadinessProbe: readinessProbe,
+ StartupProbe: startupProbe,
SecurityContext: ct.SecurityContext,
Lifecycle: ct.Lifecycle,
}
diff --git a/internal/controller/superset_controller.go
b/internal/controller/superset_controller.go
index eceedb1..3d6863a 100644
--- a/internal/controller/superset_controller.go
+++ b/internal/controller/superset_controller.go
@@ -208,6 +208,7 @@ func (r *SupersetReconciler) Reconcile(ctx context.Context,
req ctrl.Request) (c
}
// Phase 5: Update aggregate status.
+ superset.Status.ConfigChecksum = configChecksum
if err := r.updateStatus(ctx, superset); err != nil {
return ctrl.Result{}, fmt.Errorf("updating status: %w", err)
}