Add delete_emptydir_data to drain delete_options (#322)

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>
This commit is contained in:
Jorn Eilander
2022-01-13 14:40:06 +01:00
committed by GitHub
parent 50a1bd9db0
commit c3ecb64b72
4 changed files with 111 additions and 5 deletions

View File

@@ -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).

View File

@@ -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>

View File

@@ -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

View File

@@ -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),