> ## 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. **Identify multi-replica Deployments and their labels** (run on any machine with kubectl access)
           ```sh theme={null}
           kubectl get deployments --all-namespaces -o wide
           # For each multi-replica deployment, inspect pod template labels:
           kubectl get deployment <deployment-name> -n <namespace> -o jsonpath='{.spec.template.metadata.labels}{"\n"}'
           ```

        2. **Decide the minimum available pods for each Deployment** (planning step)\
           For each multi-replica Deployment, choose either:
           * `minAvailable`: minimum number of pods that must stay available during disruptions, or
           * `maxUnavailable`: maximum number or percentage of pods that can be disrupted at once.\
             Ensure the value makes sense for the workload’s availability/SLO requirements.

        3. **Create a PodDisruptionBudget manifest that matches the pod labels** (run on any machine with kubectl access)\
           Create a file such as `pdb-<deployment-name>.yaml` with **matchLabels exactly matching the pod template labels** from step 1 (do not use `matchExpressions`, as the audit ignores them). Example structure:
           ```yaml theme={null}
           apiVersion: policy/v1
           kind: PodDisruptionBudget
           metadata:
             name: <deployment-name>-pdb
             namespace: <namespace>
           spec:
             minAvailable: 1         # or use maxUnavailable: 1 / "25%"
             selector:
               matchLabels:
                 app: my-app         # replace with the exact key: value pairs from the pod template
                 tier: backend       # add/remove keys to match the Deployment’s pod labels
           ```

        4. **Apply the PodDisruptionBudget to the cluster** (run on any machine with kubectl access)
           ```sh theme={null}
           kubectl apply -f pdb-<deployment-name>.yaml
           ```

        5. **Confirm that each PDB selector correctly matches pods from its Deployment** (run on any machine with kubectl access)
           ```sh theme={null}
           kubectl get poddisruptionbudgets -n <namespace>
           kubectl get poddisruptionbudget <deployment-name>-pdb -n <namespace> -o yaml | sed -n '1,80p'
           # Verify the selector.matchLabels exactly corresponds to the Deployment’s pod template labels:
           kubectl get deployment <deployment-name> -n <namespace> -o jsonpath='{.spec.template.metadata.labels}{"\n"}'
           ```

        6. **Verify compliance using the benchmark audit logic** (run on any machine with kubectl access)
           ```sh 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"
               + (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'
           ```
           Ensure every multi-replica Deployment reports `is_compliant=true`.
      </Accordion>

      <Accordion title="Using kubectl">
        On any machine with kubectl access:

        1. Identify multi‑replica Deployments and their labels

        ```bash theme={null}
        kubectl get deployments --all-namespaces -o wide
        kubectl get deployment -n YOUR_NAMESPACE YOUR_DEPLOYMENT -o jsonpath='{.spec.template.metadata.labels}{"\n"}'
        ```

        2. Create a PodDisruptionBudget manifest matching the Deployment’s pod labels

        Example (edit namespace, name, and labels to match your Deployment):

        ```yaml theme={null}
        # pdb-multi-replica-example.yaml
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: my-app-pdb
          namespace: my-namespace
        spec:
          minAvailable: 1
          selector:
            matchLabels:
              app: my-app
              tier: backend
        ```

        Apply it:

        ```bash theme={null}
        kubectl apply -f pdb-multi-replica-example.yaml
        ```

        Repeat as needed so each Deployment with `.spec.replicas > 1` has at least one PDB whose `spec.selector.matchLabels` is a subset of the pod template labels for that Deployment.

        3. Verification

        Re‑run the audit (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"
            + (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
        #
        # Ensure every multi-replica Deployment has a matching PodDisruptionBudget
        # that uses only matchLabels in its selector (per CBP C5.1).
        #
        # Run on: any machine with kubectl access and jq installed.
        # Idempotent: safe to re-run; only creates PDBs that are missing.

        set -euo pipefail

        # --- prerequisites check ---
        if ! command -v kubectl >/dev/null 2>&1; then
          echo "ERROR: kubectl not found in PATH" >&2
          exit 1
        fi

        if ! command -v jq >/dev/null 2>&1; then
          echo "ERROR: jq not found in PATH" >&2
          exit 1
        fi

        KCTX="$(kubectl config current-context 2>/dev/null || true)"
        if [ -z "$KCTX" ]; then
          echo "ERROR: kubectl has no current-context configured" >&2
          exit 1
        fi

        echo "Using kubectl context: $KCTX"
        echo

        # --- discovery: find non-compliant Deployments (replicas > 1 without covering PDB) ---
        echo "Discovering multi-replica Deployments without a matching PodDisruptionBudget..."

        NONCOMPLIANT_JSON="$(
          {
            kubectl get poddisruptionbudgets --all-namespaces -o json 2>/dev/null || 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
              | select($count == 0)
              | {
                  namespace: $m.namespace,
                  name: $m.name,
                  replicas: (.spec.replicas // 1),
                  podLabels: $podLabels
                }
            ]'
        )"

        if [ "$(echo "$NONCOMPLIANT_JSON" | jq 'length')" -eq 0 ]; then
          echo "All multi-replica Deployments already have a matching PodDisruptionBudget."
          echo
          echo "Verification output (benchmark-style):"
          {
            kubectl get poddisruptionbudgets --all-namespaces -o json 2>/dev/null || 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) 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'
          exit 0
        fi

        echo "$NONCOMPLIANT_JSON" | jq -r '.[] | "- Namespace: \(.namespace), Deployment: \(.name), replicas: \(.replicas)"'
        echo

        # --- create PDBs for each non-compliant Deployment ---
        # Strategy: for each Deployment, create a PDB:
        # - name: "<deployment-name>-pdb"
        # - selector.matchLabels = pod template labels
        # - use a conservative minAvailable of 1 (can be tuned per-Deployment later)
        #
        # If a PDB with that name already exists in the namespace, it is left as-is
        # (to avoid overwriting manual settings), and we still verify compliance.

        TMPDIR="$(mktemp -d)"
        trap 'rm -rf "$TMPDIR"' EXIT

        echo "Creating PodDisruptionBudgets where missing..."

        echo "$NONCOMPLIANT_JSON" | jq -c '.[]' | while read -r item; do
          ns="$(echo "$item" | jq -r '.namespace')"
          dep="$(echo "$item" | jq -r '.name')"
          replicas="$(echo "$item" | jq -r '.replicas')"
          pdb_name="${dep}-pdb"

          # Build matchLabels yaml fragment from podLabels
          labels_yaml="$(
            echo "$item" | jq -r '.podLabels | to_entries[]? | "      \(.key): \"\(.value)\""' || true
          )"

          if [ -z "$labels_yaml" ]; then
            echo "WARNING: Deployment ${ns}/${dep} has no pod template labels; skipping PDB creation." >&2
            continue
          fi

          # Skip if a PDB with this name already exists
          if kubectl get poddisruptionbudget "$pdb_name" -n "$ns" >/dev/null 2>&1; then
            echo "PDB ${ns}/${pdb_name} already exists; not modifying it."
            continue
          fi

          cat > "${TMPDIR}/${ns}-${dep}-pdb.yaml" <<EOF
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: ${pdb_name}
          namespace: ${ns}
        spec:
          minAvailable: 1
          selector:
            matchLabels:
        $(echo "$labels_yaml")
        EOF

          echo "Applying PDB for ${ns}/${dep} (replicas=${replicas})..."
          kubectl apply -f "${TMPDIR}/${ns}-${dep}-pdb.yaml"
        done

        echo
        echo "PDB creation complete."
        echo

        # --- verification (benchmark-style) ---
        echo "Verification output (benchmark-style):"
        {
          kubectl get poddisruptionbudgets --all-namespaces -o json 2>/dev/null || 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) 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>
    </AccordionGroup>
  </Tab>
</Tabs>
