From d2d7256b7dd3cf53ff17aef0983eba23c5519a2d Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Tue, 13 Jan 2026 10:49:52 +0100 Subject: [PATCH 1/2] fix: Prevent unnecessary Pod restarts by applying StatefulSet last --- CHANGELOG.md | 3 + .../operator-binary/src/airflow_controller.rs | 98 ++++++++++--------- tests/templates/kuttl/smoke/40-assert.yaml.j2 | 5 + 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b328ee9f..4b4e1303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,13 @@ ### Fixed - Default `API_WORKERS` to 1 (instead of letting Airflow default to 4) to prevent crashloop and update/correct docs to reflect this ([#727]). +- Prevent unnecessary Pod restarts when initially creating an AirflowCluster. + This is achieved by applying the StatefulSet after all ConfigMaps and Secrets that it mounts ([#XXX]). [#726]: https://github.com/stackabletech/airflow-operator/pull/726 [#727]: https://github.com/stackabletech/airflow-operator/pull/727 [#733]: https://github.com/stackabletech/airflow-operator/pull/733 +[#XXX]: https://github.com/stackabletech/airflow-operator/pull/XXX ## [25.11.0] - 2025-11-07 diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index c2d89a13..a58a0d0c 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -519,6 +519,36 @@ pub async fn reconcile_airflow( role: role_name.to_string(), })?; + if let Some(GenericRoleConfig { + pod_disruption_budget: pdb, + }) = airflow.role_config(&airflow_role) + { + add_pdbs(&pdb, airflow, &airflow_role, client, &mut cluster_resources) + .await + .context(FailedToCreatePdbSnafu)?; + } + + if let Some(listener_class) = airflow_role.listener_class_name(airflow) { + if let Some(listener_group_name) = airflow.group_listener_name(&airflow_role) { + let rg_group_listener = build_group_listener( + airflow, + build_recommended_labels( + airflow, + AIRFLOW_CONTROLLER_NAME, + &resolved_product_image.app_version_label_value, + role_name, + "none", + ), + listener_class.to_string(), + listener_group_name, + )?; + cluster_resources + .add(client, rg_group_listener) + .await + .context(ApplyGroupListenerSnafu)?; + } + } + for (rolegroup_name, rolegroup_config) in role_config.iter() { let rolegroup = RoleGroupRef { cluster: ObjectRef::from_obj(airflow), @@ -587,6 +617,26 @@ pub async fn reconcile_airflow( rolegroup: rolegroup.clone(), })?; + let rg_configmap = build_rolegroup_config_map( + airflow, + &resolved_product_image, + &rolegroup, + rolegroup_config, + &authentication_config, + &authorization_config, + &merged_airflow_config.logging, + &Container::Airflow, + )?; + cluster_resources + .add(client, rg_configmap) + .await + .with_context(|_| ApplyRoleGroupConfigSnafu { + rolegroup: rolegroup.clone(), + })?; + + // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts + // to prevent unnecessary Pod restarts. + // See https://github.com/stackabletech/commons-operator/issues/111 for details. let rg_statefulset = build_server_rolegroup_statefulset( airflow, &resolved_product_image, @@ -609,54 +659,6 @@ pub async fn reconcile_airflow( rolegroup: rolegroup.clone(), })?, ); - - let rg_configmap = build_rolegroup_config_map( - airflow, - &resolved_product_image, - &rolegroup, - rolegroup_config, - &authentication_config, - &authorization_config, - &merged_airflow_config.logging, - &Container::Airflow, - )?; - cluster_resources - .add(client, rg_configmap) - .await - .with_context(|_| ApplyRoleGroupConfigSnafu { - rolegroup: rolegroup.clone(), - })?; - } - - let role_config = airflow.role_config(&airflow_role); - if let Some(GenericRoleConfig { - pod_disruption_budget: pdb, - }) = role_config - { - add_pdbs(&pdb, airflow, &airflow_role, client, &mut cluster_resources) - .await - .context(FailedToCreatePdbSnafu)?; - } - - if let Some(listener_class) = airflow_role.listener_class_name(airflow) { - if let Some(listener_group_name) = airflow.group_listener_name(&airflow_role) { - let rg_group_listener = build_group_listener( - airflow, - build_recommended_labels( - airflow, - AIRFLOW_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - role_name, - "none", - ), - listener_class.to_string(), - listener_group_name, - )?; - cluster_resources - .add(client, rg_group_listener) - .await - .context(ApplyGroupListenerSnafu)?; - } } } diff --git a/tests/templates/kuttl/smoke/40-assert.yaml.j2 b/tests/templates/kuttl/smoke/40-assert.yaml.j2 index 0a2aed84..b1d5eaa9 100644 --- a/tests/templates/kuttl/smoke/40-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/40-assert.yaml.j2 @@ -17,6 +17,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: airflow-webserver-default + generation: 1 # There should be no unneeded Pod restarts spec: template: spec: @@ -30,6 +31,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: airflow-worker-default + generation: 1 # There should be no unneeded Pod restarts spec: template: spec: @@ -43,6 +45,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: airflow-scheduler-default + generation: 1 # There should be no unneeded Pod restarts spec: template: spec: @@ -55,6 +58,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: airflow-dagprocessor-default + generation: 1 # There should be no unneeded Pod restarts spec: template: spec: @@ -67,6 +71,7 @@ apiVersion: apps/v1 kind: StatefulSet metadata: name: airflow-triggerer-default + generation: 1 # There should be no unneeded Pod restarts spec: template: spec: From 4e1a191de05e3fb34d120d2fdcbd2f82dd51b237 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Tue, 13 Jan 2026 11:04:17 +0100 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4e1303..85444bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,12 @@ - Default `API_WORKERS` to 1 (instead of letting Airflow default to 4) to prevent crashloop and update/correct docs to reflect this ([#727]). - Prevent unnecessary Pod restarts when initially creating an AirflowCluster. - This is achieved by applying the StatefulSet after all ConfigMaps and Secrets that it mounts ([#XXX]). + This is achieved by applying the StatefulSet after all ConfigMaps and Secrets that it mounts ([#734]). [#726]: https://github.com/stackabletech/airflow-operator/pull/726 [#727]: https://github.com/stackabletech/airflow-operator/pull/727 [#733]: https://github.com/stackabletech/airflow-operator/pull/733 -[#XXX]: https://github.com/stackabletech/airflow-operator/pull/XXX +[#734]: https://github.com/stackabletech/airflow-operator/pull/734 ## [25.11.0] - 2025-11-07