mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-07 21:42:38 +00:00
k8s_rollback: new module (#26)
This module allows user to rollback deployment and daemonsets. Signed-off-by: Julien Huon <julien@huon.email>
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
- include_tasks: tasks/log.yml
|
- include_tasks: tasks/log.yml
|
||||||
- include_tasks: tasks/cluster_info.yml
|
- include_tasks: tasks/cluster_info.yml
|
||||||
- include_tasks: tasks/access_review.yml
|
- include_tasks: tasks/access_review.yml
|
||||||
|
- include_tasks: tasks/rollback.yml
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- helm
|
- helm
|
||||||
|
|||||||
217
molecule/default/tasks/rollback.yml
Normal file
217
molecule/default/tasks/rollback.yml
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
---
|
||||||
|
- block:
|
||||||
|
- name: Set variables
|
||||||
|
set_fact:
|
||||||
|
namespace: "testingrollback"
|
||||||
|
|
||||||
|
- name: Create a namespace
|
||||||
|
k8s:
|
||||||
|
name: "{{ namespace }}"
|
||||||
|
kind: Namespace
|
||||||
|
api_version: v1
|
||||||
|
apply: no
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: show output
|
||||||
|
debug:
|
||||||
|
var: output
|
||||||
|
|
||||||
|
- name: Create a deployment
|
||||||
|
k8s:
|
||||||
|
state: present
|
||||||
|
wait: yes
|
||||||
|
inline: &deploy
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deploy
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.17
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
debug:
|
||||||
|
var: output
|
||||||
|
|
||||||
|
- name: Crash the existing deployment
|
||||||
|
k8s:
|
||||||
|
state: present
|
||||||
|
wait: yes
|
||||||
|
definition:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deploy
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.0.23449928384992872784
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
ignore_errors: yes
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Rolling Back the crashed deployment
|
||||||
|
k8s_rollback:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: nginx-deploy
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
when: output.failed
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
debug:
|
||||||
|
var: output
|
||||||
|
|
||||||
|
- name: Create a DaemonSet
|
||||||
|
k8s:
|
||||||
|
state: present
|
||||||
|
wait: yes
|
||||||
|
definition:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
labels:
|
||||||
|
k8s-app: fluentd-logging
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
spec:
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: fluentd-elasticsearch
|
||||||
|
image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 200Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 200Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: varlog
|
||||||
|
mountPath: /var/log
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
mountPath: /var/lib/docker/containers
|
||||||
|
readOnly: true
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: varlog
|
||||||
|
hostPath:
|
||||||
|
path: /var/log
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/docker/containers
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
debug:
|
||||||
|
var: output
|
||||||
|
|
||||||
|
- name: Crash the existing DaemonSet
|
||||||
|
k8s:
|
||||||
|
state: present
|
||||||
|
wait: yes
|
||||||
|
definition:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
labels:
|
||||||
|
k8s-app: fluentd-logging
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
spec:
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: fluentd-elasticsearch
|
||||||
|
image: quay.io/fluentd_elasticsearch/fluentd:v2734894949
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 200Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 200Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: varlog
|
||||||
|
mountPath: /var/log
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
mountPath: /var/lib/docker/containers
|
||||||
|
readOnly: true
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: varlog
|
||||||
|
hostPath:
|
||||||
|
path: /var/log
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/docker/containers
|
||||||
|
ignore_errors: yes
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Rolling Back the crashed DaemonSet
|
||||||
|
k8s_rollback:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
when: output.failed
|
||||||
|
register: output
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
debug:
|
||||||
|
var: output
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Delete {{ namespace }} namespace
|
||||||
|
k8s:
|
||||||
|
name: "{{ namespace }}"
|
||||||
|
kind: Namespace
|
||||||
|
api_version: v1
|
||||||
|
state: absent
|
||||||
224
plugins/modules/k8s_rollback.py
Normal file
224
plugins/modules/k8s_rollback.py
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2020, Julien Huon <@julienhuon> Institut National de l'Audiovisuel
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
module: k8s_rollback
|
||||||
|
short_description: Rollback Kubernetes (K8S) Deployments and DaemonSets
|
||||||
|
version_added: "1.0.0"
|
||||||
|
author:
|
||||||
|
- "Julien Huon (@julienhuon)"
|
||||||
|
description:
|
||||||
|
- Use the OpenShift Python client to perform the Rollback.
|
||||||
|
- Authenticate using either a config file, certificates, password or token.
|
||||||
|
- Similar to the kubectl rollout undo command.
|
||||||
|
options:
|
||||||
|
label_selectors:
|
||||||
|
description: List of label selectors to use to filter results.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
field_selectors:
|
||||||
|
description: List of field selectors to use to filter results.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.kubernetes.k8s_auth_options
|
||||||
|
- community.kubernetes.k8s_name_options
|
||||||
|
requirements:
|
||||||
|
- "python >= 2.7"
|
||||||
|
- "openshift >= 0.6"
|
||||||
|
- "PyYAML >= 3.11"
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Rollback a failed deployment
|
||||||
|
community.kubernetes.k8s_rollback:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: web
|
||||||
|
namespace: testing
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
rollback_info:
|
||||||
|
description:
|
||||||
|
- The rollbacked object.
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
api_version:
|
||||||
|
description: The versioned schema of this representation of an object.
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
code:
|
||||||
|
description: The HTTP Code of the response
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
kind:
|
||||||
|
description: Status
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
metadata:
|
||||||
|
description:
|
||||||
|
- Standard object metadata.
|
||||||
|
- Includes name, namespace, annotations, labels, etc.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
status:
|
||||||
|
description: Current status details for the object.
|
||||||
|
returned: success
|
||||||
|
type: dict
|
||||||
|
'''
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||||
|
K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||||
|
|
||||||
|
|
||||||
|
class KubernetesRollbackModule(K8sAnsibleMixin):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=self.argspec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
self.module = module
|
||||||
|
self.params = self.module.params
|
||||||
|
self.fail_json = self.module.fail_json
|
||||||
|
self.fail = self.module.fail_json
|
||||||
|
self.exit_json = self.module.exit_json
|
||||||
|
super(KubernetesRollbackModule, self).__init__()
|
||||||
|
|
||||||
|
self.kind = self.params['kind']
|
||||||
|
self.api_version = self.params['api_version']
|
||||||
|
self.name = self.params['name']
|
||||||
|
self.namespace = self.params['namespace']
|
||||||
|
self.managed_resource = {}
|
||||||
|
|
||||||
|
if self.kind == "DaemonSet":
|
||||||
|
self.managed_resource['kind'] = "ControllerRevision"
|
||||||
|
self.managed_resource['api_version'] = "apps/v1"
|
||||||
|
elif self.kind == "Deployment":
|
||||||
|
self.managed_resource['kind'] = "ReplicaSet"
|
||||||
|
self.managed_resource['api_version'] = "apps/v1"
|
||||||
|
else:
|
||||||
|
self.fail(msg="Cannot perform rollback on resource of kind {0}".format(self.kind))
|
||||||
|
|
||||||
|
def execute_module(self):
|
||||||
|
results = []
|
||||||
|
self.client = self.get_api_client()
|
||||||
|
|
||||||
|
resources = self.kubernetes_facts(self.kind,
|
||||||
|
self.api_version,
|
||||||
|
self.name,
|
||||||
|
self.namespace,
|
||||||
|
self.params['label_selectors'],
|
||||||
|
self.params['field_selectors'])
|
||||||
|
|
||||||
|
for resource in resources['resources']:
|
||||||
|
result = self.perform_action(resource)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
self.exit_json(**{
|
||||||
|
'changed': True,
|
||||||
|
'rollback_info': results
|
||||||
|
})
|
||||||
|
|
||||||
|
def perform_action(self, resource):
|
||||||
|
if self.kind == "DaemonSet":
|
||||||
|
current_revision = resource['metadata']['generation']
|
||||||
|
elif self.kind == "Deployment":
|
||||||
|
current_revision = resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
||||||
|
|
||||||
|
managed_resources = self.kubernetes_facts(self.managed_resource['kind'],
|
||||||
|
self.managed_resource['api_version'],
|
||||||
|
'',
|
||||||
|
self.namespace,
|
||||||
|
resource['spec']
|
||||||
|
['selector']
|
||||||
|
['matchLabels'],
|
||||||
|
'')
|
||||||
|
|
||||||
|
prev_managed_resource = get_previous_revision(managed_resources['resources'],
|
||||||
|
current_revision)
|
||||||
|
|
||||||
|
if self.kind == "Deployment":
|
||||||
|
del prev_managed_resource['spec']['template']['metadata']['labels']['pod-template-hash']
|
||||||
|
|
||||||
|
resource_patch = [{
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/spec/template",
|
||||||
|
"value": prev_managed_resource['spec']['template']
|
||||||
|
}, {
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/metadata/annotations",
|
||||||
|
"value": {
|
||||||
|
"deployment.kubernetes.io/revision": prev_managed_resource['metadata']['annotations']['deployment.kubernetes.io/revision']
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
api_target = 'deployments'
|
||||||
|
content_type = 'application/json-patch+json'
|
||||||
|
elif self.kind == "DaemonSet":
|
||||||
|
resource_patch = prev_managed_resource["data"]
|
||||||
|
|
||||||
|
api_target = 'daemonsets'
|
||||||
|
content_type = 'application/strategic-merge-patch+json'
|
||||||
|
|
||||||
|
rollback = self.client.request("PATCH",
|
||||||
|
"/apis/{0}/namespaces/{1}/{2}/{3}"
|
||||||
|
.format(self.api_version,
|
||||||
|
self.namespace,
|
||||||
|
api_target,
|
||||||
|
self.name),
|
||||||
|
body=resource_patch,
|
||||||
|
content_type=content_type)
|
||||||
|
|
||||||
|
result = {'changed': True}
|
||||||
|
result['method'] = 'patch'
|
||||||
|
result['body'] = resource_patch
|
||||||
|
result['resources'] = rollback.to_dict()
|
||||||
|
return result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def argspec(self):
|
||||||
|
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||||
|
args.update(NAME_ARG_SPEC)
|
||||||
|
args.update(
|
||||||
|
dict(
|
||||||
|
label_selectors=dict(type='list', elements='str', default=[]),
|
||||||
|
field_selectors=dict(type='list', elements='str', default=[]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def get_previous_revision(all_resources, current_revision):
|
||||||
|
for resource in all_resources:
|
||||||
|
if resource['kind'] == 'ReplicaSet':
|
||||||
|
if int(resource['metadata']
|
||||||
|
['annotations']
|
||||||
|
['deployment.kubernetes.io/revision']) == int(current_revision) - 1:
|
||||||
|
return resource
|
||||||
|
elif resource['kind'] == 'ControllerRevision':
|
||||||
|
if int(resource['metadata']
|
||||||
|
['annotations']
|
||||||
|
['deprecated.daemonset.template.generation']) == int(current_revision) - 1:
|
||||||
|
return resource
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
KubernetesRollbackModule().execute_module()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user