Skip to content

Commit

Permalink
Add delete_emptydir_data to drain delete_options (#322)
Browse files Browse the repository at this point in the history
Add delete_emptydir_data to drain delete_options

SUMMARY
Adds delete_emptydir_data option to k8s_drain.delete_options to evict pods with an emptyDir volume attached.
ISSUE TYPE

Feature Pull Request

COMPONENT NAME
k8s_drain
ADDITIONAL INFORMATION
Be gentle, this is my first pull request 😨 
Basically adds the kubectl drain <node> --delete-emptydir-data feature, including tests.

Reviewed-by: Abhijeet Kasurde <None>
Reviewed-by: Jorn Eilander <None>
Reviewed-by: None <None>
Reviewed-by: None <None>
  • Loading branch information
jorneilander authored Jan 13, 2022
1 parent 50a1bd9 commit c3ecb64
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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).
20 changes: 20 additions & 0 deletions docs/kubernetes.core.k8s_drain_module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,26 @@ Parameters
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>delete_emptydir_data</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li><div style="color: blue"><b>no</b>&nbsp;&larr;</div></li>
<li>yes</li>
</ul>
</td>
<td>
<div>Continue even if there are pods using emptyDir (local data that will be deleted when the node is drained)</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>disable_eviction</b>
Expand Down
69 changes: 68 additions & 1 deletion molecule/default/tasks/drain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -192,13 +250,22 @@
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
name: '{{ node_to_drain }}'
delete_options:
force: true
ignore_daemonsets: true
delete_emptydir_data: true
register: drain_result

- name: Check idempotency
Expand Down
24 changes: 20 additions & 4 deletions plugins/modules/k8s_drain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit c3ecb64

Please sign in to comment.