mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-08 14:02:38 +00:00
Improve k8s Deployment and Daemonset wait conditions
Ensure that Deployments and Daemonsets properly await all replicas to be available. Correctly handles the subtle edge case when a service account no longer exists. Note that this will dramatically slow Daemonset updates
This commit is contained in:
@@ -146,7 +146,7 @@ After the version is published, verify it exists on the [Kubernetes Collection G
|
|||||||
|
|
||||||
## More Information
|
## More Information
|
||||||
|
|
||||||
For more information about Ansible's Kubernetes integration, join the `#ansible-community` channel on Freenode IRC, and browse the resources in the [Kubernetes Working Group](https://github.com/ansible/community/wiki/Kubernetes) Community wiki page.
|
For more information about Ansible's Kubernetes integration, join the `#ansible-kubernetes` channel on Freenode IRC, and browse the resources in the [Kubernetes Working Group](https://github.com/ansible/community/wiki/Kubernetes) Community wiki page.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ provisioner:
|
|||||||
ansible_python_interpreter: '{{ ansible_playbook_python }}'
|
ansible_python_interpreter: '{{ ansible_playbook_python }}'
|
||||||
env:
|
env:
|
||||||
ANSIBLE_FORCE_COLOR: 'true'
|
ANSIBLE_FORCE_COLOR: 'true'
|
||||||
|
options:
|
||||||
|
vvv: True
|
||||||
scenario:
|
scenario:
|
||||||
name: default
|
name: default
|
||||||
test_sequence:
|
test_sequence:
|
||||||
|
|||||||
@@ -179,6 +179,162 @@
|
|||||||
- k8s_service_3.result.spec.ports | length == 1
|
- k8s_service_3.result.spec.ports | length == 1
|
||||||
- k8s_service_3.result.spec.ports[0].port == 8081
|
- k8s_service_3.result.spec.ports[0].port == 8081
|
||||||
|
|
||||||
|
- name: Insert new service port
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: apply-svc
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: whatever
|
||||||
|
ports:
|
||||||
|
- name: mesh
|
||||||
|
port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
- name: http
|
||||||
|
port: 8081
|
||||||
|
targetPort: 8081
|
||||||
|
apply: yes
|
||||||
|
register: k8s_service_4
|
||||||
|
|
||||||
|
- name: Check ports are correct
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- k8s_service_4 is changed
|
||||||
|
- k8s_service_4.result.spec.ports | length == 2
|
||||||
|
- k8s_service_4.result.spec.ports[0].port == 8080
|
||||||
|
- k8s_service_4.result.spec.ports[1].port == 8081
|
||||||
|
|
||||||
|
- name: Remove new service port (check mode)
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: apply-svc
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: whatever
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8081
|
||||||
|
targetPort: 8081
|
||||||
|
apply: yes
|
||||||
|
check_mode: yes
|
||||||
|
register: k8s_service_check
|
||||||
|
|
||||||
|
- name: Check ports are correct
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- k8s_service_check is changed
|
||||||
|
- k8s_service_check.result.spec.ports | length == 1
|
||||||
|
- k8s_service_check.result.spec.ports[0].port == 8081
|
||||||
|
|
||||||
|
- name: Remove new service port
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: apply-svc
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: whatever
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8081
|
||||||
|
targetPort: 8081
|
||||||
|
apply: yes
|
||||||
|
register: k8s_service_5
|
||||||
|
|
||||||
|
- name: Check ports are correct
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- k8s_service_5 is changed
|
||||||
|
- k8s_service_5.result.spec.ports | length == 1
|
||||||
|
- k8s_service_5.result.spec.ports[0].port == 8081
|
||||||
|
|
||||||
|
- name: Add a serviceaccount
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: apply-deploy
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
|
||||||
|
- name: Add a deployment
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: apply-deploy
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: "{{ k8s_pod_name }}"
|
||||||
|
template: "{{ k8s_pod_template }}"
|
||||||
|
wait: yes
|
||||||
|
apply: yes
|
||||||
|
vars:
|
||||||
|
k8s_pod_name: apply-deploy
|
||||||
|
k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-green
|
||||||
|
k8s_pod_service_account: apply-deploy
|
||||||
|
k8s_pod_ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
- name: Remove the serviceaccount
|
||||||
|
k8s:
|
||||||
|
state: absent
|
||||||
|
definition:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: apply-deploy
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
|
||||||
|
- name: Update the earlier deployment
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: apply-deploy
|
||||||
|
namespace: "{{ apply_namespace }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: "{{ k8s_pod_name }}"
|
||||||
|
template: "{{ k8s_pod_template }}"
|
||||||
|
wait: yes
|
||||||
|
apply: yes
|
||||||
|
vars:
|
||||||
|
k8s_pod_name: apply-deploy
|
||||||
|
k8s_pod_image: gcr.io/kuar-demo/kuard-amd64:v0.10.0-purple
|
||||||
|
k8s_pod_service_account: apply-deploy
|
||||||
|
k8s_pod_ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
register: deploy_after_serviceaccount_removal
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Ensure that updating deployment after service account removal failed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- deploy_after_serviceaccount_removal is failed
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Remove namespace
|
- name: Remove namespace
|
||||||
k8s:
|
k8s:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ k8s_pod_metadata:
|
|||||||
app: "{{ k8s_pod_name }}"
|
app: "{{ k8s_pod_name }}"
|
||||||
|
|
||||||
k8s_pod_spec:
|
k8s_pod_spec:
|
||||||
|
serviceAccount: "{{ k8s_pod_service_account }}"
|
||||||
containers:
|
containers:
|
||||||
- image: "{{ k8s_pod_image }}"
|
- image: "{{ k8s_pod_image }}"
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
@@ -20,6 +21,8 @@ k8s_pod_spec:
|
|||||||
memory: "100Mi"
|
memory: "100Mi"
|
||||||
ports: "{{ k8s_pod_ports }}"
|
ports: "{{ k8s_pod_ports }}"
|
||||||
|
|
||||||
|
k8s_pod_service_account: default
|
||||||
|
|
||||||
k8s_pod_command: []
|
k8s_pod_command: []
|
||||||
|
|
||||||
k8s_pod_ports: []
|
k8s_pod_ports: []
|
||||||
|
|||||||
@@ -469,7 +469,8 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
|||||||
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
|
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
|
||||||
return (deployment.status and deployment.status.replicas is not None and
|
return (deployment.status and deployment.status.replicas is not None and
|
||||||
deployment.status.availableReplicas == deployment.status.replicas and
|
deployment.status.availableReplicas == deployment.status.replicas and
|
||||||
deployment.status.observedGeneration == deployment.metadata.generation)
|
deployment.status.observedGeneration == deployment.metadata.generation and
|
||||||
|
not deployment.status.unavailableReplicas)
|
||||||
|
|
||||||
def _pod_ready(pod):
|
def _pod_ready(pod):
|
||||||
return (pod.status and pod.status.containerStatuses is not None and
|
return (pod.status and pod.status.containerStatuses is not None and
|
||||||
@@ -478,7 +479,8 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
|||||||
def _daemonset_ready(daemonset):
|
def _daemonset_ready(daemonset):
|
||||||
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None and
|
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None and
|
||||||
daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and
|
daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and
|
||||||
daemonset.status.observedGeneration == daemonset.metadata.generation)
|
daemonset.status.observedGeneration == daemonset.metadata.generation and
|
||||||
|
not daemonset.status.unavailableReplicas)
|
||||||
|
|
||||||
def _custom_condition(resource):
|
def _custom_condition(resource):
|
||||||
if not resource.status or not resource.status.conditions:
|
if not resource.status or not resource.status.conditions:
|
||||||
|
|||||||
@@ -29,11 +29,16 @@ from ansible.module_utils.six import string_types
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
from openshift import watch
|
|
||||||
from openshift.dynamic.client import ResourceInstance
|
from openshift.dynamic.client import ResourceInstance
|
||||||
from openshift.helper.exceptions import KubernetesException
|
from openshift.dynamic.exceptions import NotFoundError
|
||||||
except ImportError as exc:
|
except ImportError:
|
||||||
class KubernetesException(Exception):
|
pass
|
||||||
|
try:
|
||||||
|
from openshift import watch
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
from openshift.dynamic.client import watch
|
||||||
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +119,7 @@ class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
|||||||
try:
|
try:
|
||||||
existing = resource.get(name=name, namespace=namespace)
|
existing = resource.get(name=name, namespace=namespace)
|
||||||
return_attributes['result'] = existing.to_dict()
|
return_attributes['result'] = existing.to_dict()
|
||||||
except KubernetesException as exc:
|
except NotFoundError as exc:
|
||||||
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
||||||
error=exc.value.get('status'))
|
error=exc.value.get('status'))
|
||||||
|
|
||||||
@@ -189,15 +194,12 @@ class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
|||||||
""" Create a stream of events for the object """
|
""" Create a stream of events for the object """
|
||||||
w = None
|
w = None
|
||||||
stream = None
|
stream = None
|
||||||
try:
|
w = watch.Watch()
|
||||||
w = watch.Watch()
|
w._api_client = self.client.client
|
||||||
w._api_client = self.client.client
|
if namespace:
|
||||||
if namespace:
|
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
||||||
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
else:
|
||||||
else:
|
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
||||||
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
|
||||||
except KubernetesException:
|
|
||||||
pass
|
|
||||||
return w, stream
|
return w, stream
|
||||||
|
|
||||||
def _read_stream(self, resource, watcher, stream, name, replicas):
|
def _read_stream(self, resource, watcher, stream, name, replicas):
|
||||||
|
|||||||
Reference in New Issue
Block a user