Skip to content
GitHub stars

Policy Combinations - Practical Examples

Real-world examples showing how to combine CreationPolicy, DeletionPolicy, ConflictPolicy, and PatchStrategy for different resource types.

Overview

This guide demonstrates how policies work together through four common scenarios:

ExampleUse CaseKey PatternPolicies
Example 1Persistent DataImmutable + RetainedOnce + Retain + Stuck
Example 2One-time SetupRun Once + CleanupOnce + Delete + Force
Example 3Main ApplicationSync + CleanupWhenNeeded + Delete + Stuck
Example 4Shared ConfigSync + RetainedWhenNeeded + Retain + Force

Example 1: Stateful Data (PVC)

Use Case: Persistent storage that must survive node lifecycle changes and never lose data.

Configuration

yaml
persistentVolumeClaims:
  - id: data
    creationPolicy: Once        # Create only once
    deletionPolicy: Retain      # Keep data after node deletion
    conflictPolicy: Stuck       # Don't overwrite existing PVCs
    patchStrategy: apply        # Standard SSA
    nameTemplate: "{{ .uid }}-data"
    spec:
      apiVersion: v1
      kind: PersistentVolumeClaim
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi

Lifecycle Flow

Rationale

  • Once: PVC spec shouldn't change (size immutable in many storage classes)
  • Retain: Data survives node deletion - NO ownerReference set to prevent automatic deletion
  • Stuck: Safety - don't overwrite someone else's PVC on initial creation
  • apply: Standard SSA for declarative management

Key Behaviors

  • ✅ Created once, never updated (even if template changes)
  • ✅ Survives node deletion (label-based tracking)
  • ✅ Safe conflict detection on initial creation
  • 📊 Data persists indefinitely
  • ⚠️ Important: Once created-once annotation is set, ApplyResource is never called again

Delete and Recreate Scenario

CreationPolicy=Once Limitation

With CreationPolicy: Once, the operator SKIPS resources that have the created-once annotation. This means on Node recreation:

  • NO re-adoption occurs
  • Orphan markers remain on the resource
  • NO conflict detection (ApplyResource is not called)
  • Resource is counted as Ready but not actively managed

Scenario Timeline:

Manual Recovery:

bash
# Remove the created-once annotation to allow re-adoption
kubectl annotate pvc acme-data lynq.sh/created-once-

# Next reconciliation will call ApplyResource and remove orphan markers

Example 2: Init Job

Use Case: One-time initialization task that runs once per node and cleans up after node deletion.

Configuration

yaml
jobs:
  - id: init
    creationPolicy: Once        # Run only once
    deletionPolicy: Delete      # Clean up after node deletion
    conflictPolicy: Force       # Re-create if needed
    patchStrategy: replace      # Exact job spec
    nameTemplate: "{{ .uid }}-init"
    spec:
      apiVersion: batch/v1
      kind: Job
      spec:
        template:
          spec:
            containers:
            - name: init
              image: busybox
              command: ["sh", "-c", "echo Initializing {{ .uid }}"]
            restartPolicy: Never

Lifecycle Flow

Rationale

  • Once: Initialization runs only once - even if template changes, job won't re-run
  • Delete: No need to keep job history after node deletion
  • Force: Operator owns this resource exclusively - takes ownership if conflict
  • replace: Ensures exact job spec match

Key Behaviors

  • ✅ Runs once per node lifetime
  • ✅ Automatically cleaned up on node deletion
  • ✅ Force takes ownership from conflicts
  • 🔄 Re-creates if manually deleted (but still runs only once due to created-once annotation)

Example 3: Application Deployment

Use Case: Main application that should stay synchronized with template changes and clean up completely on deletion.

Configuration

yaml
deployments:
  - id: app
    creationPolicy: WhenNeeded  # Keep updated
    deletionPolicy: Delete      # Clean up on deletion
    conflictPolicy: Stuck       # Safe default
    patchStrategy: apply        # Kubernetes best practice
    nameTemplate: "{{ .uid }}-app"
    spec:
      apiVersion: apps/v1
      kind: Deployment
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: "{{ .uid }}"
        template:
          metadata:
            labels:
              app: "{{ .uid }}"
          spec:
            containers:
            - name: app
              image: "nginx:latest"

Lifecycle Flow

Rationale

  • WhenNeeded: Always keep deployment in sync with template and database
  • Delete: Standard cleanup via ownerReference
  • Stuck: Safe default - investigate conflicts rather than force override
  • apply: SSA best practice - preserves fields from other controllers (e.g., HPA)

Key Behaviors

  • ✅ Continuously synchronized with template
  • ✅ Auto-corrects manual drift
  • ✅ Plays well with other controllers (HPA, VPA)
  • ✅ Complete cleanup on deletion
  • ⚠️ Stops on conflicts for safety

Example 4: Shared Infrastructure

Use Case: Configuration data that should stay updated but survive node deletion for debugging or shared resource references.

Configuration

yaml
configMaps:
  - id: shared-config
    creationPolicy: WhenNeeded  # Maintain config
    deletionPolicy: Retain      # Keep config for investigation
    conflictPolicy: Force       # Operator manages configs
    patchStrategy: apply        # SSA
    nameTemplate: "{{ .uid }}-shared-config"
    spec:
      apiVersion: v1
      kind: ConfigMap
      data:
        config.json: |
          {
            "tenantId": "{{ .uid }}",
            "environment": "production",
            "version": "1.0"
          }

Lifecycle Flow

Rationale

  • WhenNeeded: Keep configmap data updated as template/database changes
  • Retain: ConfigMap might be referenced by other resources or needed for debugging - NO ownerReference to prevent deletion
  • Force: Operator is authoritative for this config - takes ownership if conflict exists
  • apply: SSA for declarative configuration management

Key Behaviors

  • ✅ Continuously synchronized with changes
  • ✅ Force takes ownership from conflicts
  • ✅ Survives node deletion (label-based tracking)
  • 📊 Available for investigation post-deletion
  • 🔗 Can be referenced by non-node resources

Delete and Recreate with WhenNeeded

Unlike Example 1 (PVC with Once), resources with WhenNeeded automatically re-adopt on recreation:

Key Differences from Example 1:

AspectExample 1 (PVC)
Once + Retain
Example 4 (ConfigMap)
WhenNeeded + Retain
Updates🚫 Never (frozen after creation)✅ Always (syncs with template)
Retention✅ Yes (orphaned on delete)✅ Yes (orphaned on delete)
Re-adoption❌ No (skipped due to created-once)✅ Yes (automatic on recreate)
Force Ownership❌ No (Stuck policy)✅ Yes (Force policy)

Policy Combinations Summary

Quick reference comparing all four examples:

AspectPVC (Stateful)Init JobApp DeploymentShared Config
CreationPolicyOnceOnceWhenNeededWhenNeeded
DeletionPolicyRetainDeleteDeleteRetain
ConflictPolicyStuckForceStuckForce
PatchStrategyapplyreplaceapplyapply
ownerReference❌ No✅ Yes✅ Yes❌ No
Updates🚫 Never🚫 Never✅ Always✅ Always
Survives Deletion✅ Yes❌ No❌ No✅ Yes
Auto-Cleanup❌ Manual✅ Auto (GC)✅ Auto (GC)❌ Manual
Drift CorrectionN/A (Once)N/A (Once)✅ Yes✅ Yes
Conflict Handling⚠️ Stop💪 Force⚠️ Stop💪 Force

Legend:

  • ✅ Enabled / Yes
  • ❌ Disabled / No
  • 🚫 Never updates
  • ⚠️ Safe mode (stops on conflict)
  • 💪 Aggressive (forces ownership)
  • N/A: Not applicable

Decision Tree

Choose the right policy combination for your use case:

See Also