From 9942d84f7d85a0b4981a36ccdd2431aeaf4312bf Mon Sep 17 00:00:00 2001 From: Jorn Eilander Date: Wed, 5 Jan 2022 21:55:04 +0100 Subject: [PATCH] Add delete_emptydir_data to drain delete_options --- ...emptydir_data-to-drain-delete_options.yaml | 3 + docs/kubernetes.core.k8s_drain_module.rst | 20 ++++++ molecule/default/tasks/drain.yml | 69 ++++++++++++++++++- plugins/modules/k8s_drain.py | 24 +++++-- 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/322-Add-delete_emptydir_data-to-drain-delete_options.yaml diff --git a/changelogs/fragments/322-Add-delete_emptydir_data-to-drain-delete_options.yaml b/changelogs/fragments/322-Add-delete_emptydir_data-to-drain-delete_options.yaml new file mode 100644 index 0000000000..279e8bff4f --- /dev/null +++ b/changelogs/fragments/322-Add-delete_emptydir_data-to-drain-delete_options.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - k8s_drain - Adds ``delete_emptydir_data`` option to ``k8s_drain.delete_options`` to evict pods with an ``emptyDir`` volume attached (https://github.com/ansible-collections/kubernetes.core/pull/322). diff --git a/docs/kubernetes.core.k8s_drain_module.rst b/docs/kubernetes.core.k8s_drain_module.rst index 8b482333bd..f719e04b0e 100644 --- a/docs/kubernetes.core.k8s_drain_module.rst +++ b/docs/kubernetes.core.k8s_drain_module.rst @@ -140,6 +140,26 @@ Parameters + +
+ delete_emptydir_data + +
+ boolean +
+ + + + + +
Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained)
+ + + +
disable_eviction diff --git a/molecule/default/tasks/drain.yml b/molecule/default/tasks/drain.yml index 6bc2428b81..7b50444170 100644 --- a/molecule/default/tasks/drain.yml +++ b/molecule/default/tasks/drain.yml @@ -5,6 +5,7 @@ drain_namespace: "drain" drain_daemonset_name: "promotheus-dset" drain_pod_name: "pod-drain" + drain_deployment_emptydir_name: "deployment-emptydir-drain" - name: Create {{ drain_namespace }} namespace k8s: @@ -99,6 +100,61 @@ - -c - while true;do date;sleep 5; done + - name: Create Deployment with an emptyDir volume. + k8s: + namespace: '{{ drain_namespace }}' + wait: yes + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: '{{ drain_deployment_emptydir_name }}' + spec: + replicas: 1 + selector: + matchLabels: + drain: emptyDir + template: + metadata: + labels: + drain: emptyDir + spec: + metadata: + labels: + drain: emptyDir + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchFields: + - key: metadata.name + operator: In + values: + - '{{ node_to_drain }}' + containers: + - name: c0 + image: busybox + command: + - /bin/sh + - -c + - while true;do date;sleep 5; done + volumeMounts: + - mountPath: /emptydir + name: emptydir + volumes: + - name: emptydir + emptyDir: {} + + - name: Register emptyDir Pod name + k8s_info: + namespace: '{{ drain_namespace }}' + kind: Pod + label_selectors: + - "drain = emptyDir" + register: emptydir_pod_result + failed_when: + - emptydir_pod_result.resources | length != 1 + - name: Cordon node k8s_drain: state: cordon @@ -167,14 +223,16 @@ - drain_result is failed - '"cannot delete DaemonSet-managed Pods" in drain_result.msg' - '"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet" in drain_result.msg' + - '"cannot delete Pods with local storage" in drain_result.msg' - - name: Drain node using ignore_daemonsets and force options + - name: Drain node using ignore_daemonsets, force, and delete_emptydir_data options k8s_drain: state: drain name: '{{ node_to_drain }}' delete_options: force: true ignore_daemonsets: true + delete_emptydir_data: true wait_timeout: 0 register: drain_result @@ -192,6 +250,14 @@ register: _result failed_when: _result.resources + - name: assert that emptyDir pod was deleted + k8s_info: + namespace: '{{ drain_namespace }}' + kind: Pod + name: "{{ emptydir_pod_result.resources[0].metadata.name }}" + register: _result + failed_when: _result.resources | length != 0 + - name: Test drain idempotency k8s_drain: state: drain @@ -199,6 +265,7 @@ delete_options: force: true ignore_daemonsets: true + delete_emptydir_data: true register: drain_result - name: Check idempotency diff --git a/plugins/modules/k8s_drain.py b/plugins/modules/k8s_drain.py index 03aee07f04..ecf0fba70b 100644 --- a/plugins/modules/k8s_drain.py +++ b/plugins/modules/k8s_drain.py @@ -64,6 +64,12 @@ - Ignore DaemonSet-managed pods. type: bool default: False + delete_emptydir_data: + description: + - Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained). + type: bool + default: False + version_added: 2.3.0 disable_eviction: description: - Forces drain to use delete rather than evict. @@ -138,7 +144,7 @@ pass -def filter_pods(pods, force, ignore_daemonset): +def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data): k8s_kind_mirror = "kubernetes.io/config.mirror" daemonSet, unmanaged, mirror, localStorage, to_delete = [], [], [], [], [] for pod in pods: @@ -153,7 +159,6 @@ def filter_pods(pods, force, ignore_daemonset): continue # Pod with local storage cannot be deleted - # TODO: support new option delete-emptydatadir in order to allow deletion of such pod if pod.spec.volumes and any(vol.empty_dir for vol in pod.spec.volumes): localStorage.append((pod.metadata.namespace, pod.metadata.name)) continue @@ -198,7 +203,14 @@ def filter_pods(pods, force, ignore_daemonset): # local storage if localStorage: pod_names = ",".join([pod[0] + "/" + pod[1] for pod in localStorage]) - errors.append("cannot delete Pods with local storage: {0}.".format(pod_names)) + if not delete_emptydir_data: + errors.append( + "cannot delete Pods with local storage: {0}.".format(pod_names) + ) + else: + warnings.append("Deleting Pods with local storage: {0}.".format(pod_names)) + for pod in localStorage: + to_delete.append((pod[0], pod[1])) # DaemonSet managed Pods if daemonSet: @@ -349,8 +361,11 @@ def _revert_node_patch(): # Filter pods force = self._drain_options.get("force", False) ignore_daemonset = self._drain_options.get("ignore_daemonsets", False) + delete_emptydir_data = self._drain_options.get( + "delete_emptydir_data", False + ) pods, warnings, errors = filter_pods( - pod_list.items, force, ignore_daemonset + pod_list.items, force, ignore_daemonset, delete_emptydir_data ) if errors: _revert_node_patch() @@ -467,6 +482,7 @@ def argspec(): terminate_grace_period=dict(type="int"), force=dict(type="bool", default=False), ignore_daemonsets=dict(type="bool", default=False), + delete_emptydir_data=dict(type="bool", default=False), disable_eviction=dict(type="bool", default=False), wait_timeout=dict(type="int"), wait_sleep=dict(type="int", default=5),