Skip to content
Open
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
32 changes: 32 additions & 0 deletions release-notes/unreleased/170-applyset-label-propagation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Release Notes for Issue #170: Do not propagate ApplySet bookkeeping labels

## Bug Fix

### What Changed

`RestateDeployment` no longer copies labels in the
`applyset.kubernetes.io/` namespace onto its operator-owned Services, Knative
Configurations, or Knative Routes. Other user labels continue to propagate.

### Why This Matters

kubectl uses `applyset.kubernetes.io/part-of` to decide which resources belong
to an ApplySet. Copying that label made operator-owned child resources look
like direct members of the ApplySet that contained the `RestateDeployment`.
A later `kubectl apply --prune --applyset=...` could consequently delete a
versioned Service before Restate had drained the corresponding deployment.

### Impact on Users

- Existing `RestateDeployment` resources need no manifest changes.
- The operator removes the bookkeeping labels from children on reconciliation.
- kubectl continues to manage and prune the `RestateDeployment` itself.

### Migration Guidance

Upgrade the operator. No CRD or workload configuration changes are required.

### Related Issues

- Issue #170: RestateDeployment propagates ApplySet membership to owned
resources
33 changes: 32 additions & 1 deletion src/controllers/restatedeployment/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ use super::reconcilers::replicaset::{
pub(super) const RESTATE_DEPLOYMENT_ID_ANNOTATION: &str = "restate.dev/deployment-id";
pub(super) const OWNED_BY_LABEL: &str = "restate.dev/owned-by";
pub(super) const APP_MANAGED_BY_LABEL: &str = "app.kubernetes.io/managed-by";
const APPLYSET_LABEL_PREFIX: &str = "applyset.kubernetes.io/";

/// Copy user labels to an operator-owned child without copying ApplySet
/// bookkeeping. A child that inherits `applyset.kubernetes.io/part-of` becomes
/// an accidental member of the parent's kubectl ApplySet and can be pruned even
/// though it was created and is still needed by this operator.
pub(super) fn propagated_labels(labels: &BTreeMap<String, String>) -> BTreeMap<String, String> {
let mut propagated = labels.clone();
propagated.retain(|key, _| !key.starts_with(APPLYSET_LABEL_PREFIX));
propagated
}

pub(super) struct Context {
/// Kubernetes client
Expand Down Expand Up @@ -391,7 +402,7 @@ impl RestateDeployment {
.await?;
}

let mut service_labels = self.labels().clone();
let mut service_labels = propagated_labels(self.labels());
service_labels.insert(
APP_MANAGED_BY_LABEL.to_string(),
"restate-operator".to_string(),
Expand Down Expand Up @@ -1546,4 +1557,24 @@ mod tests {
let s1_again = latest_version_label_selector(&v1, None).expect("v1 selector again");
assert_eq!(s1, s1_again, "selector should be deterministic");
}

#[test]
fn propagated_labels_exclude_applyset_bookkeeping() {
let labels = BTreeMap::from([
("app.kubernetes.io/name".to_string(), "greeter".to_string()),
(
"applyset.kubernetes.io/part-of".to_string(),
"applyset-parent-id".to_string(),
),
(
"applyset.kubernetes.io/id".to_string(),
"applyset-parent-id".to_string(),
),
]);

assert_eq!(
propagated_labels(&labels),
BTreeMap::from([("app.kubernetes.io/name".to_string(), "greeter".to_string())])
);
}
}
6 changes: 3 additions & 3 deletions src/controllers/restatedeployment/reconcilers/knative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::*;
use url::Url;

use crate::controllers::restatedeployment::controller::{
Context, RESTATE_DEPLOYMENT_ID_ANNOTATION,
Context, RESTATE_DEPLOYMENT_ID_ANNOTATION, propagated_labels,
};
use crate::controllers::restatedeployment::reconcilers::replicaset::generate_pod_template_hash;
use crate::resources::knative::{
Expand Down Expand Up @@ -338,7 +338,7 @@ fn build_configuration_spec(
config_annotations.insert(RESTATE_POD_TEMPLATE_ANNOTATION.to_string(), v.to_string());
}

let mut config_labels = rsd.labels().clone();
let mut config_labels = propagated_labels(rsd.labels());
config_labels.insert(
"app.kubernetes.io/managed-by".to_string(),
"restate-operator".to_string(),
Expand Down Expand Up @@ -516,7 +516,7 @@ fn build_route_spec(
route_annotations.insert(RESTATE_DEPLOYMENT_ANNOTATION.to_string(), rsd.name_any());

// Propagate RestateDeployment labels to Route
let mut route_labels = rsd.labels().clone();
let mut route_labels = propagated_labels(rsd.labels());
route_labels.insert(
"app.kubernetes.io/managed-by".to_string(),
"restate-operator".to_string(),
Expand Down
Loading