> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Replica Deployments Should Have A PodDisruptionBudget

### More Info:

Advisory: define a PodDisruptionBudget for each multi-replica Deployment so node drains and rollouts keep a minimum number of pods available.

### Risk Level

Informational

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. List multi-replica Deployments that currently lack a PodDisruptionBudget (run on any machine with kubectl access):
           ```bash theme={null}
           { kubectl get poddisruptionbudgets --all-namespaces -o json \
             kubectl get deployments --all-namespaces -o json; } | jq -rs '
             .[0] as $pdbs | .[1] |
             [ .items[]
               | select((.spec.replicas // 1) > 1)
               | .metadata as $m
               | (.spec.template.metadata.labels // {}) as $podLabels
               | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
               | ([ $pdbs.items[]
                    | select(.metadata.namespace == $m.namespace)
                    | select((.spec.selector.matchLabels // {}) | length > 0)
                    | select([ (.spec.selector.matchLabels | to_entries)[]
                               | $podLabels[.key] == .value ] | all)
                  ] | length) as $count
               | select($count == 0)
               | {name: $m.name, namespace: $m.namespace, labels: $podLabels}
             ]'
           ```

        2. For one non-compliant Deployment, inspect its spec to identify pod labels and replica count (replace NAMESPACE and DEPLOYMENT with real values from step 1; run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get deployment DEPLOYMENT -n NAMESPACE -o yaml
           ```
           Note the labels under `spec.template.metadata.labels` and confirm `spec.replicas > 1`.

        3. Create a PodDisruptionBudget manifest for that Deployment, using the pod template labels as `matchLabels` and setting an appropriate availability policy (run on any machine with kubectl access):
           ```bash theme={null}
           cat > pdb-DEPLOYMENT.yaml << 'EOF'
           apiVersion: policy/v1
           kind: PodDisruptionBudget
           metadata:
             name: DEPLOYMENT-pdb
             namespace: NAMESPACE
           spec:
             minAvailable: 1
             selector:
               matchLabels:
                 app: REPLACE_WITH_APP_LABEL_VALUE
           EOF
           ```
           Replace:
           * `NAMESPACE` with the Deployment’s namespace
           * `DEPLOYMENT` with the Deployment name
           * `app: REPLACE_WITH_APP_LABEL_VALUE` (and/or add more keys) so that the label set exactly matches `spec.template.metadata.labels` of the Deployment’s pods. Only `matchLabels` is evaluated by the check; do not use `matchExpressions` if you want it counted as compliant.

        4. Apply the PodDisruptionBudget to the cluster (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl apply -f pdb-DEPLOYMENT.yaml
           ```

        5. Repeat steps 2–4 for each remaining non-compliant multi-replica Deployment, ensuring each has a corresponding PodDisruptionBudget whose `spec.selector.matchLabels` selects that Deployment’s pods.

        6. Verify that all multi-replica Deployments now have at least one matching PodDisruptionBudget (run on any machine with kubectl access):
           ```bash theme={null}
           { kubectl get poddisruptionbudgets --all-namespaces -o json \
             kubectl get deployments --all-namespaces -o json; } | jq -rs '
             .[0] as $pdbs | .[1] |
             [ .items[]
             | select((.spec.replicas // 1) > 1)
             | .metadata as $m
             | (.spec.template.metadata.labels // {}) as $podLabels
             | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
             | ([ $pdbs.items[]
                  | select(.metadata.namespace == $m.namespace)
                  | select((.spec.selector.matchLabels // {}) | length > 0)
                  | select([ (.spec.selector.matchLabels | to_entries)[]
                             | $podLabels[.key] == .value ] | all)
                ] | length) as $count
             | "kind=Deployment ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=apps/v1"
               + " replicas=\(.spec.replicas) podDisruptionBudgets=\($count)"
               + " is_compliant=\(if $count > 0 then "true" else "false" end)"
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Confirm that every `kind=Deployment` line with `replicas=` greater than 1 now shows `is_compliant=true`.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1) Inspect a non-compliant multi-replica Deployment to get its labels
        # Run on: any machine with kubectl access
        kubectl get deployments -A \
          -o jsonpath='{"NAMESPACE NAME LABELS\n"}{range .items[?(@.spec.replicas>1)]}{.metadata.namespace}{" "}{.metadata.name}{" "}{range $k,$v := .spec.template.metadata.labels}{$k}={"$v"}, {end}{"\n"}{end}'

        # Example: assume deployment "my-app" in namespace "prod" with pod label app=my-app
        # Adjust namespace, name, and labels to match your Deployment(s).

        # 2) Create a PodDisruptionBudget manifest for that Deployment
        cat > pdb-my-app.yaml << 'EOF'
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: my-app-pdb
          namespace: prod
        spec:
          minAvailable: 1
          selector:
            matchLabels:
              app: my-app
        EOF

        # 3) Apply the PodDisruptionBudget
        kubectl apply -f pdb-my-app.yaml

        # Repeat steps 2–3 for each multi-replica Deployment, ensuring:
        # - metadata.namespace matches the Deployment namespace
        # - spec.selector.matchLabels exactly matches a stable subset of the pod template labels
        # - minAvailable or maxUnavailable reflect your availability/SLO requirements

        # 4) Verification: rerun the benchmark-style check
        { kubectl get poddisruptionbudgets --all-namespaces -o json \
          ; kubectl get deployments --all-namespaces -o json; } | jq -rs '
          .[0] as $pdbs | .[1] |
          [ .items[]
          | select((.spec.replicas // 1) > 1)
          | .metadata as $m
          | (.spec.template.metadata.labels // {}) as $podLabels
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ $pdbs.items[]
               | select(.metadata.namespace == $m.namespace)
               | select((.spec.selector.matchLabels // {}) | length > 0)
               | select([ (.spec.selector.matchLabels | to_entries)[]
                          | $podLabels[.key] == .value ] | all)
             ] | length) as $count
          | "kind=Deployment ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=apps/v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $labels == "" then "" else " labels=\($labels)" end)
            + " replicas=\(.spec.replicas) podDisruptionBudgets=\($count)"
            + " is_compliant=\(if $count > 0 then "true" else "false" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Automation: Ensure every multi-replica Deployment has a matching PodDisruptionBudget
        # Platform: AKS (or any cluster reachable via kubectl)
        #
        # Requirements:
        # - Run on any machine with kubectl access and jq installed.
        # - Uses kubectl current context.
        #
        # Behavior:
        # - For each Deployment with replicas > 1:
        #   - If no PDB exists whose selector.matchLabels is a subset of the pod template labels,
        #     create a PDB named "<deployment-name>-pdb" with:
        #       minAvailable: 1            if replicas == 2
        #       minAvailable: 50% (string) if replicas >= 3
        #   - PDB selector.matchLabels is set to the Deployment's pod template labels.
        # - Safe to re-run: existing matching PDBs are left untouched; created PDBs are stable.

        set -euo pipefail

        # --- Configurable defaults -----------------------------------------------------

        # Label key/value added to PDBs created by this script (optional but useful)
        PDB_MANAGED_LABEL_KEY="pdb.k8s-benchmark/managed-by"
        PDB_MANAGED_LABEL_VALUE="cbp-c5.1-automation"

        # ------------------------------------------------------------------------------
        command -v kubectl >/dev/null 2>&1 || { echo "kubectl is required in PATH" >&2; exit 1; }
        command -v jq >/dev/null 2>&1 || { echo "jq is required in PATH" >&2; exit 1; }

        echo "Discovering multi-replica Deployments (replicas > 1)..."
        DEPLOY_JSON="$(kubectl get deployments --all-namespaces -o json)"
        PDB_JSON="$(kubectl get poddisruptionbudgets --all-namespaces -o json || echo '{"items": []}')"

        # Function: check if any PDB in the namespace covers the deployment's pod labels.
        # Coverage rule: PDB.spec.selector.matchLabels is non-empty and is a subset of
        # the Deployment's pod template labels.
        deployment_needs_pdb() {
          local namespace="$1"
          local deploy_name="$2"

          # Extract pod template labels and replicas for this deployment
          local deploy_info
          deploy_info="$(jq -r \
            --arg ns "$namespace" \
            --arg name "$deploy_name" \
            '
            .items[]
            | select(.metadata.namespace == $ns and .metadata.name == $name)
            | {
                replicas: (.spec.replicas // 1),
                podLabels: (.spec.template.metadata.labels // {})
              }
            ' <<<"$DEPLOY_JSON")"

          if [[ -z "$deploy_info" ]]; then
            # Deployment disappeared between listing and processing
            return 1
          fi

          local pod_labels_json replicas
          pod_labels_json="$(jq -c '.podLabels' <<<"$deploy_info")"
          replicas="$(jq -r '.replicas' <<<"$deploy_info")"

          # Skip if not actually multi-replica
          if (( replicas <= 1 )); then
            return 1
          fi

          # Determine if any PDB matches
          local match_count
          match_count="$(jq -r \
            --arg ns "$namespace" \
            --argjson podLabels "$pod_labels_json" \
            '
            .items[]
            | select(.metadata.namespace == $ns)
            | select((.spec.selector.matchLabels // {}) | length > 0)
            | .spec.selector.matchLabels as $sel
            # check $sel is a subset of podLabels:
            | ( [ ($sel | to_entries)[]
                  | ($podLabels[.key] == .value)
                ] | all ) as $isSubset
            | select($isSubset)
            | 1
            ' <<<"$PDB_JSON" | wc -l | tr -d ' ')"

          if [[ "$match_count" -gt 0 ]]; then
            return 1  # already covered
          fi

          return 0  # needs PDB
        }

        # Function: create PDB manifest for a deployment
        create_pdb_for_deployment() {
          local namespace="$1"
          local deploy_name="$2"

          local deploy
          deploy="$(kubectl get deployment "$deploy_name" -n "$namespace" -o json 2>/dev/null || true)"
          if [[ -z "$deploy" ]]; then
            echo "  [WARN] Deployment $namespace/$deploy_name disappeared; skipping"
            return
          fi

          local replicas pod_labels_json pdb_name min_available_yaml

          replicas="$(jq -r '.spec.replicas // 1' <<<"$deploy")"
          pod_labels_json="$(jq -c '.spec.template.metadata.labels // {}' <<<"$deploy")"
          pdb_name="${deploy_name}-pdb"

          if (( replicas <= 1 )); then
            echo "  [INFO] $namespace/$deploy_name now has replicas <=1; skipping PDB creation"
            return
          fi

          # Set minAvailable based on replicas
          if (( replicas == 2 )); then
            min_available_yaml="minAvailable: 1"
          else
            min_available_yaml="minAvailable: \"50%\""
          fi

          # Build selector.matchLabels YAML from pod_labels_json
          local selector_yaml
          selector_yaml="$(jq -r '
            to_entries
            | map("      \(.key): \"\(.value)\"")
            | join("\n")
          ' <<<"$pod_labels_json")"

          if [[ -z "$selector_yaml" ]]; then
            echo "  [WARN] $namespace/$deploy_name has no pod template labels; cannot create selector-based PDB"
            return
          fi

          echo "  [ACTION] Creating PDB $namespace/$pdb_name for Deployment $deploy_name (replicas=$replicas)"

          cat <<EOF | kubectl apply -f -
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: ${pdb_name}
          namespace: ${namespace}
          labels:
            ${PDB_MANAGED_LABEL_KEY}: "${PDB_MANAGED_LABEL_VALUE}"
        spec:
          ${min_available_yaml}
          selector:
            matchLabels:
        $(echo "${selector_yaml}")
        EOF
        }

        # Iterate deployments and create PDBs where needed
        echo "Processing Deployments..."
        while IFS=$'\t' read -r ns name replicas; do
          # Filter to replicas > 1 to reduce work early
          if [[ "$replicas" -le 1 ]]; then
            continue
          fi

          echo "Checking Deployment $ns/$name (replicas=$replicas)..."
          if deployment_needs_pdb "$ns" "$name"; then
            create_pdb_for_deployment "$ns" "$name"
            # Refresh PDB_JSON after potential creation to keep checks consistent
            PDB_JSON="$(kubectl get poddisruptionbudgets --all-namespaces -o json || echo '{"items": []}')"
          else
            echo "  [OK] Covered by existing PodDisruptionBudget"
          fi
        done < <(echo "$DEPLOY_JSON" | jq -r '
          .items[]
          | {ns: .metadata.namespace, name: .metadata.name, replicas: (.spec.replicas // 1)}
          | select(.replicas > 1)
          | "\(.ns)\t\(.name)\t\(.replicas)"
        ')

        # --- Verification -------------------------------------------------------------
        echo
        echo "Verification: multi-replica Deployments and their PodDisruptionBudgets"

        {
          kubectl get poddisruptionbudgets --all-namespaces -o json || echo '{"items": []}'
          kubectl get deployments --all-namespaces -o json
        } | jq -rs '
          .[0] as $pdbs | .[1] |
          [ .items[]
          | select((.spec.replicas // 1) > 1)
          | .metadata as $m
          | (.spec.template.metadata.labels // {}) as $podLabels
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ $pdbs.items[]
               | select(.metadata.namespace == $m.namespace)
               | select((.spec.selector.matchLabels // {}) | length > 0)
               | select([ (.spec.selector.matchLabels | to_entries)[]
                          | $podLabels[.key] == .value ] | all)
             ] | length) as $count
          | "kind=Deployment ns=\($m.namespace) name=\($m.name) replicas=\(.spec.replicas) podDisruptionBudgets=\($count)"
            + " is_compliant=\(if $count > 0 then "true" else "false" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end
        '
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
