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 00000000..279e8bff
--- /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 8b482333..f719e04b 100644
--- a/docs/kubernetes.core.k8s_drain_module.rst
+++ b/docs/kubernetes.core.k8s_drain_module.rst
@@ -140,6 +140,26 @@ Parameters
+ |
disable_eviction
diff --git a/molecule/default/tasks/drain.yml b/molecule/default/tasks/drain.yml
index 6bc2428b..7b504441 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 03aee07f..ecf0fba7 100644
--- a/plugins/modules/k8s_drain.py
+++ b/plugins/modules/k8s_drain.py
@@ -64,6 +64,12 @@ options:
- 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 @@ except ImportError:
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 @@ class K8sDrainAnsible(object):
# 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),
|