mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
Add check_mode support for k8s_drain module (#1086)
SUMMARY Closes #1037 added support for check_mode Converted warnings into informational display when user has explicitly requested to delete daemontset-managed pods, unmanaged pods or pods with local storage ISSUE TYPE Feature Pull Request COMPONENT NAME k8s_drain Reviewed-by: Bianca Henderson <beeankha@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
minor_changes:
|
||||||
|
- k8s_drain - Add support for `check_mode` (https://github.com/ansible-collections/kubernetes.core/pull/1086).
|
||||||
|
- k8s_drain - Convert module warnings into informational displays when users explicitly request the deletion of
|
||||||
|
unmanaged pods, pods with local storage, or those managed by a `DaemonSet` (https://github.com/ansible-collections/kubernetes.core/issues/1037).
|
||||||
@@ -230,7 +230,7 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
else:
|
else:
|
||||||
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
to_delete.append((pod.metadata.namespace, pod.metadata.name))
|
||||||
|
|
||||||
warnings, errors = [], []
|
warnings, errors, info = [], [], []
|
||||||
if unmanaged:
|
if unmanaged:
|
||||||
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in unmanaged])
|
pod_names = ",".join([pod[0] + "/" + pod[1] for pod in unmanaged])
|
||||||
if not force:
|
if not force:
|
||||||
@@ -242,7 +242,7 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Pod not managed will be deleted as 'force' is true
|
# Pod not managed will be deleted as 'force' is true
|
||||||
warnings.append(
|
info.append(
|
||||||
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
|
"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: {0}.".format(
|
||||||
pod_names
|
pod_names
|
||||||
)
|
)
|
||||||
@@ -264,7 +264,7 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
"cannot delete Pods with local storage: {0}.".format(pod_names)
|
"cannot delete Pods with local storage: {0}.".format(pod_names)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
warnings.append("Deleting Pods with local storage: {0}.".format(pod_names))
|
info.append("Deleting Pods with local storage: {0}.".format(pod_names))
|
||||||
for pod in localStorage:
|
for pod in localStorage:
|
||||||
to_delete.append((pod[0], pod[1]))
|
to_delete.append((pod[0], pod[1]))
|
||||||
|
|
||||||
@@ -278,8 +278,8 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
warnings.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
info.append("Ignoring DaemonSet-managed Pods: {0}.".format(pod_names))
|
||||||
return to_delete, warnings, errors
|
return to_delete, warnings, errors, info
|
||||||
|
|
||||||
|
|
||||||
class K8sDrainAnsible(object):
|
class K8sDrainAnsible(object):
|
||||||
@@ -334,18 +334,19 @@ class K8sDrainAnsible(object):
|
|||||||
def evict_pods(self, pods):
|
def evict_pods(self, pods):
|
||||||
for namespace, name in pods:
|
for namespace, name in pods:
|
||||||
try:
|
try:
|
||||||
if self._drain_options.get("disable_eviction"):
|
if not self._module.check_mode:
|
||||||
self._api_instance.delete_namespaced_pod(
|
if self._drain_options.get("disable_eviction"):
|
||||||
name=name, namespace=namespace, body=self._delete_options
|
self._api_instance.delete_namespaced_pod(
|
||||||
)
|
name=name, namespace=namespace, body=self._delete_options
|
||||||
else:
|
)
|
||||||
body = v1_eviction(
|
else:
|
||||||
delete_options=self._delete_options,
|
body = v1_eviction(
|
||||||
metadata=V1ObjectMeta(name=name, namespace=namespace),
|
delete_options=self._delete_options,
|
||||||
)
|
metadata=V1ObjectMeta(name=name, namespace=namespace),
|
||||||
self._api_instance.create_namespaced_pod_eviction(
|
)
|
||||||
name=name, namespace=namespace, body=body
|
self._api_instance.create_namespaced_pod_eviction(
|
||||||
)
|
name=name, namespace=namespace, body=body
|
||||||
|
)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
except ApiException as exc:
|
except ApiException as exc:
|
||||||
if exc.reason != "Not Found":
|
if exc.reason != "Not Found":
|
||||||
@@ -362,11 +363,7 @@ class K8sDrainAnsible(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def list_pods(self):
|
def list_pods(self):
|
||||||
params = {
|
params = {"field_selector": "spec.nodeName=" + self._module.params.get("name")}
|
||||||
"field_selector": "spec.nodeName={name}".format(
|
|
||||||
name=self._module.params.get("name")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pod_selectors = self._module.params.get("pod_selectors")
|
pod_selectors = self._module.params.get("pod_selectors")
|
||||||
if pod_selectors:
|
if pod_selectors:
|
||||||
params["label_selector"] = ",".join(pod_selectors)
|
params["label_selector"] = ",".join(pod_selectors)
|
||||||
@@ -376,7 +373,8 @@ class K8sDrainAnsible(object):
|
|||||||
# Mark node as unschedulable
|
# Mark node as unschedulable
|
||||||
result = []
|
result = []
|
||||||
if not node_unschedulable:
|
if not node_unschedulable:
|
||||||
self.patch_node(unschedulable=True)
|
if not self._module.check_mode:
|
||||||
|
self.patch_node(unschedulable=True)
|
||||||
result.append(
|
result.append(
|
||||||
"node {0} marked unschedulable.".format(self._module.params.get("name"))
|
"node {0} marked unschedulable.".format(self._module.params.get("name"))
|
||||||
)
|
)
|
||||||
@@ -391,7 +389,8 @@ class K8sDrainAnsible(object):
|
|||||||
def _revert_node_patch():
|
def _revert_node_patch():
|
||||||
if self._changed:
|
if self._changed:
|
||||||
self._changed = False
|
self._changed = False
|
||||||
self.patch_node(unschedulable=False)
|
if not self._module.check_mode:
|
||||||
|
self.patch_node(unschedulable=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pod_list = self.list_pods()
|
pod_list = self.list_pods()
|
||||||
@@ -401,7 +400,7 @@ class K8sDrainAnsible(object):
|
|||||||
delete_emptydir_data = self._drain_options.get(
|
delete_emptydir_data = self._drain_options.get(
|
||||||
"delete_emptydir_data", False
|
"delete_emptydir_data", False
|
||||||
)
|
)
|
||||||
pods, warnings, errors = filter_pods(
|
pods, warnings, errors, info = filter_pods(
|
||||||
pod_list.items, force, ignore_daemonset, delete_emptydir_data
|
pod_list.items, force, ignore_daemonset, delete_emptydir_data
|
||||||
)
|
)
|
||||||
if errors:
|
if errors:
|
||||||
@@ -431,18 +430,25 @@ class K8sDrainAnsible(object):
|
|||||||
if pods:
|
if pods:
|
||||||
self.evict_pods(pods)
|
self.evict_pods(pods)
|
||||||
number_pod = len(pods)
|
number_pod = len(pods)
|
||||||
if self._drain_options.get("wait_timeout") is not None:
|
if self._module.check_mode:
|
||||||
warn = self.wait_for_pod_deletion(
|
result.append(
|
||||||
pods,
|
"Would have deleted {0} Pod(s) from node if not in check mode.".format(
|
||||||
self._drain_options.get("wait_timeout"),
|
number_pod
|
||||||
self._drain_options.get("wait_sleep"),
|
)
|
||||||
)
|
)
|
||||||
if warn:
|
else:
|
||||||
warnings.append(warn)
|
wait_timeout = self._drain_options.get("wait_timeout")
|
||||||
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
wait_sleep = self._drain_options.get("wait_sleep")
|
||||||
|
if wait_timeout is not None:
|
||||||
|
warn = self.wait_for_pod_deletion(pods, wait_timeout, wait_sleep)
|
||||||
|
if warn:
|
||||||
|
warnings.append(warn)
|
||||||
|
result.append("{0} Pod(s) deleted from node.".format(number_pod))
|
||||||
if warnings:
|
if warnings:
|
||||||
for warning in warnings:
|
for warning in warnings:
|
||||||
self._module.warn(warning)
|
self._module.warn(warning)
|
||||||
|
for line in info:
|
||||||
|
self._module.debug(line)
|
||||||
return dict(result=" ".join(result))
|
return dict(result=" ".join(result))
|
||||||
|
|
||||||
def patch_node(self, unschedulable):
|
def patch_node(self, unschedulable):
|
||||||
@@ -483,7 +489,8 @@ class K8sDrainAnsible(object):
|
|||||||
self._module.exit_json(
|
self._module.exit_json(
|
||||||
result="node {0} already marked unschedulable.".format(name)
|
result="node {0} already marked unschedulable.".format(name)
|
||||||
)
|
)
|
||||||
self.patch_node(unschedulable=True)
|
if not self._module.check_mode:
|
||||||
|
self.patch_node(unschedulable=True)
|
||||||
result["result"] = "node {0} marked unschedulable.".format(name)
|
result["result"] = "node {0} marked unschedulable.".format(name)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
|
|
||||||
@@ -492,7 +499,8 @@ class K8sDrainAnsible(object):
|
|||||||
self._module.exit_json(
|
self._module.exit_json(
|
||||||
result="node {0} already marked schedulable.".format(name)
|
result="node {0} already marked schedulable.".format(name)
|
||||||
)
|
)
|
||||||
self.patch_node(unschedulable=False)
|
if not self._module.check_mode:
|
||||||
|
self.patch_node(unschedulable=False)
|
||||||
result["result"] = "node {0} marked schedulable.".format(name)
|
result["result"] = "node {0} marked schedulable.".format(name)
|
||||||
self._changed = True
|
self._changed = True
|
||||||
|
|
||||||
@@ -535,7 +543,9 @@ def argspec():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleK8SModule(module_class=AnsibleModule, argument_spec=argspec())
|
module = AnsibleK8SModule(
|
||||||
|
module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True
|
||||||
|
)
|
||||||
|
|
||||||
if not HAS_EVICTION_API:
|
if not HAS_EVICTION_API:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
---
|
---
|
||||||
test_namespace: "drain"
|
test_namespace: "drain"
|
||||||
k8s_wait_timeout: 400
|
k8s_wait_timeout: 400
|
||||||
|
daemonset_name: promotheus
|
||||||
|
deployment_name: busybox-emptydir
|
||||||
|
pod1_name: "busybox-1"
|
||||||
|
pod2_name: "busybox-2"
|
||||||
|
|||||||
65
tests/integration/targets/k8s_drain/tasks/cordon.yml
Normal file
65
tests/integration/targets/k8s_drain/tasks/cordon.yml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
- name: Cordon node (check mode)
|
||||||
|
k8s_drain:
|
||||||
|
state: cordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: cordon_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: assert that module reported change while running in check_mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cordon_check_mode is changed
|
||||||
|
|
||||||
|
- name: Ensure the node remain schedulable (cordon run on check mode)
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Cordon node
|
||||||
|
k8s_drain:
|
||||||
|
state: cordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: cordon
|
||||||
|
|
||||||
|
- name: assert that cordon is changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cordon is changed
|
||||||
|
|
||||||
|
- name: Ensure the node is unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Test cordon idempotency (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: cordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: cordon_checkmode_idempotency
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that module is idempotent while running in check mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cordon_checkmode_idempotency is not changed
|
||||||
|
|
||||||
|
- name: Test cordon idempotency
|
||||||
|
k8s_drain:
|
||||||
|
state: cordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: cordon
|
||||||
|
|
||||||
|
- name: assert that cordon is not changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cordon is not changed
|
||||||
|
|
||||||
|
- name: Get pods
|
||||||
|
k8s_info:
|
||||||
|
kind: Pod
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
register: Pod
|
||||||
|
|
||||||
|
- name: assert that pods are running on cordoned node
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- Pod.resources | selectattr('status.phase', 'equalto', 'Running') | selectattr('spec.nodeName', 'equalto', node_to_drain) | list | length > 0
|
||||||
389
tests/integration/targets/k8s_drain/tasks/drain.yml
Normal file
389
tests/integration/targets/k8s_drain/tasks/drain.yml
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
---
|
||||||
|
# Drain the node (Should failed)
|
||||||
|
- name: Drain node with expected failure (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
ignore_errors: true
|
||||||
|
register: drain_failed_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that drain failed due to DaemonSet managed Pods
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_failed_check_mode is failed
|
||||||
|
- '"cannot delete DaemonSet-managed Pods" in drain_failed_check_mode.msg'
|
||||||
|
- '"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet" in drain_failed_check_mode.msg'
|
||||||
|
- '"cannot delete Pods with local storage" in drain_failed_check_mode.msg'
|
||||||
|
|
||||||
|
- name: Ensure that the node remains schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Drain node with expected failure
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
ignore_errors: true
|
||||||
|
register: drain_failed
|
||||||
|
|
||||||
|
- name: Assert that drain failed due to DaemonSet managed Pods
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_failed is failed
|
||||||
|
- '"cannot delete DaemonSet-managed Pods" in drain_failed.msg'
|
||||||
|
- '"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet" in drain_failed.msg'
|
||||||
|
- '"cannot delete Pods with local storage" in drain_failed.msg'
|
||||||
|
|
||||||
|
- name: Ensure that the node remains schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
# Drain the node ignoring non-candidate Pods
|
||||||
|
# check_mode
|
||||||
|
- name: Drain node using ignore_daemonsets, force, and delete_emptydir_data options (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
delete_options:
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
delete_emptydir_data: true
|
||||||
|
wait_timeout: 0
|
||||||
|
register: drain_force_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that module reported changed while node was not drained
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_force_check_mode is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_force_check_mode.result'
|
||||||
|
|
||||||
|
- name: Ensure node remains schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Assert that running with check_mode did not delete any Pod
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- "{{ item }}"
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
loop:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
- drain=daemonset-pod
|
||||||
|
- drain=emptyDir
|
||||||
|
|
||||||
|
# Apply
|
||||||
|
- 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_force
|
||||||
|
|
||||||
|
- name: Assert that module reported changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_force is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_force.result'
|
||||||
|
|
||||||
|
- name: Ensure node is now unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Assert that unmanaged Pod were deleted
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length > 0
|
||||||
|
|
||||||
|
- name: Assert that Pod with local storage are not Pending
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=emptyDir
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | map(attribute='status.phase') | unique != ['Pending']
|
||||||
|
|
||||||
|
- name: Assert that DaemonSet-managed pod were not deleted
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=daemonset-pod
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# Idempotency
|
||||||
|
- name: Test drain idempotency (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
delete_options:
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
delete_emptydir_data: true
|
||||||
|
register: drain_force_idempotency_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Validate idempotency with check_mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_force_idempotency_check_mode is not changed
|
||||||
|
|
||||||
|
- name: Ensure node remains unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Assert that DaemonSet-managed pod were not deleted
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=daemonset-pod
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# Drain with disable_eviction = true
|
||||||
|
# check_mode
|
||||||
|
- name: Uncordon node
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
|
||||||
|
- name: Create once again the Pod deleted before
|
||||||
|
k8s:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
wait: true
|
||||||
|
wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
|
||||||
|
template: pod1.yml.j2
|
||||||
|
|
||||||
|
- name: Drain node using disable_eviction (check_mode)
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
delete_options:
|
||||||
|
force: true
|
||||||
|
disable_eviction: true
|
||||||
|
terminate_grace_period: 0
|
||||||
|
ignore_daemonsets: true
|
||||||
|
wait_timeout: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
register: disable_evict_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that node has been drained
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- disable_evict_check_mode is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in disable_evict_check_mode.result'
|
||||||
|
|
||||||
|
- name: Ensure node remains schedulable (check_mode)
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Assert that unmanaged Pod were not deleted
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# apply
|
||||||
|
- name: Drain node using disable_eviction
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
delete_options:
|
||||||
|
force: true
|
||||||
|
disable_eviction: true
|
||||||
|
terminate_grace_period: 0
|
||||||
|
ignore_daemonsets: true
|
||||||
|
wait_timeout: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
register: disable_evict
|
||||||
|
|
||||||
|
- name: Assert that node has been drained
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- disable_evict is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in disable_evict_check_mode.result'
|
||||||
|
|
||||||
|
- name: Ensure the node is unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
# Drain using pod_selectors
|
||||||
|
- name: Uncordon node
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
|
||||||
|
- name: Create Pod with label selector
|
||||||
|
k8s:
|
||||||
|
namespace: "{{ test_namespace }}"
|
||||||
|
wait: true
|
||||||
|
template: pod1.yml.j2
|
||||||
|
|
||||||
|
# check_mode
|
||||||
|
- name: Drain the node using pod_selectors matching no Pod
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
pod_selectors:
|
||||||
|
- drain=no_match_selector
|
||||||
|
delete_options:
|
||||||
|
terminate_grace_period: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
register: drain_pod_selector_no_match_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that module reported change while running in check_mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_pod_selector_no_match_check_mode is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector_no_match_check_mode.result'
|
||||||
|
|
||||||
|
- name: Ensure that the node remains schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Validate that Pod are still running
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# apply
|
||||||
|
- name: Drain the node using pod_selectors matching no Pod
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
pod_selectors:
|
||||||
|
- drain=no_match_selector
|
||||||
|
delete_options:
|
||||||
|
terminate_grace_period: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
register: drain_pod_selector_no_match
|
||||||
|
|
||||||
|
- name: Assert that node has been drained
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_pod_selector_no_match is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector_no_match.result'
|
||||||
|
|
||||||
|
- name: Ensure the node is unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Validate that Pod are still running
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# Drain the node using matching pod_selector
|
||||||
|
- name: Uncordon node
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
|
||||||
|
# check_mode
|
||||||
|
- name: Drain the node using matching pod_selectors
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
pod_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
delete_options:
|
||||||
|
terminate_grace_period: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
register: drain_pod_selector_match_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that module reported change while running in check_mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_pod_selector_match_check_mode is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector_match_check_mode.result'
|
||||||
|
|
||||||
|
- name: Ensure that the node remains schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Validate that Pod are still running
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length == 0
|
||||||
|
|
||||||
|
# apply
|
||||||
|
- name: Drain the node using matching pod_selectors
|
||||||
|
k8s_drain:
|
||||||
|
state: drain
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
pod_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
delete_options:
|
||||||
|
terminate_grace_period: 0
|
||||||
|
delete_emptydir_data: true
|
||||||
|
force: true
|
||||||
|
ignore_daemonsets: true
|
||||||
|
wait_timeout: 0
|
||||||
|
register: drain_pod_selector_match
|
||||||
|
|
||||||
|
- name: Assert that node has been drained
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- drain_pod_selector_match is changed
|
||||||
|
- '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector_match.result'
|
||||||
|
|
||||||
|
- name: Ensure the node is unschedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Validate that Pod are not running
|
||||||
|
k8s_info:
|
||||||
|
namespace: '{{ test_namespace }}'
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- drain=unmanaged-pod
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: pods
|
||||||
|
failed_when: pods.resources | length > 0
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
---
|
---
|
||||||
- block:
|
- block:
|
||||||
- name: Set common facts
|
|
||||||
set_fact:
|
|
||||||
drain_daemonset_name: "promotheus-dset"
|
|
||||||
drain_pod_name: "pod-drain"
|
|
||||||
drain_deployment_emptydir_name: "deployment-emptydir-drain"
|
|
||||||
|
|
||||||
# It seems that the default ServiceAccount can take a bit to be created
|
# It seems that the default ServiceAccount can take a bit to be created
|
||||||
# right after a cluster is brought up. This can lead to the ServiceAccount
|
# right after a cluster is brought up. This can lead to the ServiceAccount
|
||||||
# admission controller rejecting a Pod creation request because the
|
# admission controller rejecting a Pod creation request because the
|
||||||
@@ -35,407 +29,23 @@
|
|||||||
set_fact:
|
set_fact:
|
||||||
node_to_drain: '{{ uncordoned_nodes[0] }}'
|
node_to_drain: '{{ uncordoned_nodes[0] }}'
|
||||||
|
|
||||||
- name: Deploy daemonset on cluster
|
- name: Create resources
|
||||||
k8s:
|
k8s:
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
definition:
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: DaemonSet
|
|
||||||
metadata:
|
|
||||||
name: '{{ drain_daemonset_name }}'
|
|
||||||
spec:
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
requiredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
nodeSelectorTerms:
|
|
||||||
- matchFields:
|
|
||||||
- key: metadata.name
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- '{{ node_to_drain }}'
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
name: prometheus-exporter
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
name: prometheus-exporter
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: prometheus
|
|
||||||
image: prom/node-exporter
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
|
|
||||||
- name: Create Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet.
|
|
||||||
k8s:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
wait: yes
|
|
||||||
wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
|
|
||||||
definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: '{{ drain_pod_name }}'
|
|
||||||
spec:
|
|
||||||
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
|
|
||||||
|
|
||||||
- name: Create Deployment with an emptyDir volume.
|
|
||||||
k8s:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
wait: yes
|
|
||||||
wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
|
|
||||||
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: '{{ test_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
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
register: cordon
|
|
||||||
|
|
||||||
- name: assert that cordon is changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cordon is changed
|
|
||||||
|
|
||||||
- name: Test cordon idempotency
|
|
||||||
k8s_drain:
|
|
||||||
state: cordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
register: cordon
|
|
||||||
|
|
||||||
- name: assert that cordon is not changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cordon is not changed
|
|
||||||
|
|
||||||
- name: Get pods
|
|
||||||
k8s_info:
|
|
||||||
kind: Pod
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
register: Pod
|
|
||||||
|
|
||||||
- name: assert that pods are running on cordoned node
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- Pod.resources | selectattr('status.phase', 'equalto', 'Running') | selectattr('spec.nodeName', 'equalto', node_to_drain) | list | length > 0
|
|
||||||
|
|
||||||
- name: Uncordon node
|
|
||||||
k8s_drain:
|
|
||||||
state: uncordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
register: uncordon
|
|
||||||
|
|
||||||
- name: assert that uncordon is changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- uncordon is changed
|
|
||||||
|
|
||||||
- name: Test uncordon idempotency
|
|
||||||
k8s_drain:
|
|
||||||
state: uncordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
register: uncordon
|
|
||||||
|
|
||||||
- name: assert that uncordon is not changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- uncordon is not changed
|
|
||||||
|
|
||||||
- name: Drain node
|
|
||||||
k8s_drain:
|
|
||||||
state: drain
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
ignore_errors: true
|
|
||||||
register: drain_result
|
|
||||||
|
|
||||||
- name: assert that drain failed due to DaemonSet managed Pods
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- 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, 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
|
|
||||||
|
|
||||||
- name: assert that node has been drained
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- drain_result is changed
|
|
||||||
- '"node "+node_to_drain+" marked unschedulable." in drain_result.result'
|
|
||||||
|
|
||||||
- name: assert that unmanaged pod were deleted
|
|
||||||
k8s_info:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
kind: Pod
|
|
||||||
name: '{{ drain_pod_name }}'
|
|
||||||
register: _result
|
|
||||||
failed_when: _result.resources | length > 0
|
|
||||||
|
|
||||||
- name: assert that emptyDir pod was deleted
|
|
||||||
k8s_info:
|
|
||||||
namespace: '{{ test_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
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- drain_result is not changed
|
|
||||||
|
|
||||||
- name: Get DaemonSet
|
|
||||||
k8s_info:
|
|
||||||
kind: DaemonSet
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
name: '{{ drain_daemonset_name }}'
|
|
||||||
register: dset_result
|
|
||||||
|
|
||||||
- name: assert that daemonset managed pods were not removed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- dset_result.resources | list | length > 0
|
|
||||||
|
|
||||||
# test: drain using disable_eviction=true
|
|
||||||
- name: Uncordon node
|
|
||||||
k8s_drain:
|
|
||||||
state: uncordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
|
|
||||||
- name: Create another Pod
|
|
||||||
k8s:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
wait: yes
|
|
||||||
wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
|
|
||||||
definition:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: '{{ drain_pod_name }}-01'
|
|
||||||
spec:
|
|
||||||
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: Drain node using disable_eviction set to yes
|
|
||||||
k8s_drain:
|
|
||||||
state: drain
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
delete_options:
|
|
||||||
force: true
|
|
||||||
disable_eviction: yes
|
|
||||||
terminate_grace_period: 0
|
|
||||||
ignore_daemonsets: yes
|
|
||||||
wait_timeout: 0
|
|
||||||
delete_emptydir_data: true
|
|
||||||
register: disable_evict
|
|
||||||
|
|
||||||
- name: assert that node has been drained
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- disable_evict is changed
|
|
||||||
- '"node "+node_to_drain+" marked unschedulable." in disable_evict.result'
|
|
||||||
|
|
||||||
- name: assert that unmanaged pod were deleted
|
|
||||||
k8s_info:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
kind: Pod
|
|
||||||
name: '{{ drain_pod_name }}-01'
|
|
||||||
register: _result
|
|
||||||
failed_when: _result.resources | length > 0
|
|
||||||
|
|
||||||
# test: drain using pod_selectors
|
|
||||||
- name: Uncordon node
|
|
||||||
k8s_drain:
|
|
||||||
state: uncordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
|
|
||||||
- name: create a Pod for test
|
|
||||||
k8s:
|
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
wait: true
|
wait: true
|
||||||
wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
|
namespace: "{{ test_namespace }}"
|
||||||
definition:
|
template:
|
||||||
apiVersion: v1
|
- daemonset.yml.j2
|
||||||
kind: Pod
|
- deployment.yml.j2
|
||||||
metadata:
|
- pod1.yml.j2
|
||||||
name: 'ansible-drain-pod'
|
|
||||||
labels:
|
|
||||||
app: ansible-drain
|
|
||||||
spec:
|
|
||||||
affinity:
|
|
||||||
nodeAffinity:
|
|
||||||
requiredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
nodeSelectorTerms:
|
|
||||||
- matchFields:
|
|
||||||
- key: metadata.name
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- '{{ node_to_drain }}'
|
|
||||||
containers:
|
|
||||||
- name: ansible-container
|
|
||||||
image: busybox
|
|
||||||
command:
|
|
||||||
- '/bin/sh'
|
|
||||||
- '-c'
|
|
||||||
- 'while true; do echo $(date); sleep 10; done'
|
|
||||||
|
|
||||||
- name: Drain node using pod_selectors 'app!=ansible-drain'
|
- name: Test Cordon node
|
||||||
k8s_drain:
|
ansible.builtin.include_tasks: tasks/cordon.yml
|
||||||
state: drain
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
pod_selectors:
|
|
||||||
- app!=ansible-drain
|
|
||||||
delete_options:
|
|
||||||
terminate_grace_period: 0
|
|
||||||
delete_emptydir_data: true
|
|
||||||
force: true
|
|
||||||
ignore_daemonsets: true
|
|
||||||
register: drain_pod_selector
|
|
||||||
|
|
||||||
- name: assert that node has been drained
|
- name: Test Uncordon node
|
||||||
assert:
|
ansible.builtin.include_tasks: tasks/uncordon.yml
|
||||||
that:
|
|
||||||
- drain_pod_selector is changed
|
|
||||||
- '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector.result'
|
|
||||||
|
|
||||||
- name: assert that pod created before is still running
|
- name: Test drain node
|
||||||
k8s_info:
|
ansible.builtin.include_tasks: tasks/drain.yml
|
||||||
namespace: '{{ test_namespace }}'
|
|
||||||
kind: Pod
|
|
||||||
label_selectors:
|
|
||||||
- app=ansible-drain
|
|
||||||
field_selectors:
|
|
||||||
- status.phase=Running
|
|
||||||
register: pods
|
|
||||||
failed_when: pods.resources == []
|
|
||||||
|
|
||||||
- name: Drain node using pod_selectors 'app=ansible-drain'
|
|
||||||
k8s_drain:
|
|
||||||
state: drain
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
pod_selectors:
|
|
||||||
- app=ansible-drain
|
|
||||||
delete_options:
|
|
||||||
terminate_grace_period: 0
|
|
||||||
force: true
|
|
||||||
register: drain_pod_selector_equal
|
|
||||||
|
|
||||||
- name: assert that node was not drained
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- drain_pod_selector_equal is changed
|
|
||||||
- '"node "+node_to_drain+" already marked unschedulable." in drain_pod_selector_equal.result'
|
|
||||||
- '"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: "+test_namespace+"/ansible-drain-pod." in drain_pod_selector_equal.warnings'
|
|
||||||
|
|
||||||
- name: Uncordon node
|
|
||||||
k8s_drain:
|
|
||||||
state: uncordon
|
|
||||||
name: '{{ node_to_drain }}'
|
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Uncordon node
|
- name: Uncordon node
|
||||||
|
|||||||
54
tests/integration/targets/k8s_drain/tasks/uncordon.yml
Normal file
54
tests/integration/targets/k8s_drain/tasks/uncordon.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
- name: Uncordon node (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: uncordon_check_mode
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Assert that module reported change while running in check_mode
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- uncordon_check_mode is changed
|
||||||
|
|
||||||
|
- name: Ensure the node is still unschedulable (uncordon run in check_mode)
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
|
||||||
|
- name: Uncordon node
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: uncordon
|
||||||
|
|
||||||
|
- name: Assert that module reported change
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- uncordon is changed
|
||||||
|
|
||||||
|
- name: Ensure the node is now schedulable
|
||||||
|
ansible.builtin.include_tasks: tasks/validate_node_status.yml
|
||||||
|
vars:
|
||||||
|
schedulable: true
|
||||||
|
|
||||||
|
- name: Test uncordon idempotency (check_mode=true)
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: uncordon_checkmode_idempotency
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: assert that uncordon is not changed (idempotency with check mode)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- uncordon_checkmode_idempotency is not changed
|
||||||
|
|
||||||
|
- name: Test uncordon idempotency
|
||||||
|
k8s_drain:
|
||||||
|
state: uncordon
|
||||||
|
name: '{{ node_to_drain }}'
|
||||||
|
register: uncordon
|
||||||
|
|
||||||
|
- name: assert that uncordon is not changed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- uncordon is not changed
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
- name: Retrieve node information
|
||||||
|
k8s_info:
|
||||||
|
kind: Node
|
||||||
|
name: "{{ node_to_drain }}"
|
||||||
|
register: node_info
|
||||||
|
|
||||||
|
- name: Validate that node is schedulable
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- node_info.resources.0.spec.unschedulable is undefined
|
||||||
|
when: schedulable | default('false') | bool
|
||||||
|
|
||||||
|
- name: Validate that node is unschedulable
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- node_info.resources.0.spec.unschedulable | bool
|
||||||
|
when: not (schedulable | default('false') | bool)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: '{{ daemonset_name }}'
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchFields:
|
||||||
|
- key: metadata.name
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- '{{ node_to_drain }}'
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
drain: daemonset-pod
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
drain: daemonset-pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: prometheus
|
||||||
|
image: prom/node-exporter
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: '{{ deployment_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: busybox-container
|
||||||
|
image: busybox
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- while true;do date;sleep 5; done
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /emptydir
|
||||||
|
name: emptydir
|
||||||
|
volumes:
|
||||||
|
- name: emptydir
|
||||||
|
emptyDir: {}
|
||||||
24
tests/integration/targets/k8s_drain/templates/pod1.yml.j2
Normal file
24
tests/integration/targets/k8s_drain/templates/pod1.yml.j2
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: '{{ pod1_name }}'
|
||||||
|
labels:
|
||||||
|
drain: unmanaged-pod
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchFields:
|
||||||
|
- key: metadata.name
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- '{{ node_to_drain }}'
|
||||||
|
containers:
|
||||||
|
- name: busybox-container
|
||||||
|
image: busybox
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- while true;do date;sleep 5; done
|
||||||
Reference in New Issue
Block a user