From 790bec29abeffd38b58c6deba34b6c2ecbd0b558 Mon Sep 17 00:00:00 2001 From: Adrien Date: Mon, 8 Oct 2018 19:35:10 +0200 Subject: [PATCH] Externalize role --- defaults/main.yml | 5 + files/etc/yum.repos.d/docker.repo | 7 + handlers/main.yml | 4 + library/docker_stack.py | 254 +++++++++++++++ library/docker_swarm.py | 520 ++++++++++++++++++++++++++++++ meta/main.yml | 4 + tasks/Debian.yml | 37 +++ tasks/RedHat.yml | 17 + tasks/main.yml | 69 ++++ tasks/swarm.yml | 129 ++++++++ tasks/tools.yml | 75 +++++ vars/Debian_docker-ce.yml | 12 + vars/Debian_docker-io.yml | 6 + vars/RedHat_docker-ce.yml | 15 + 14 files changed, 1154 insertions(+) create mode 100644 defaults/main.yml create mode 100644 files/etc/yum.repos.d/docker.repo create mode 100644 handlers/main.yml create mode 100644 library/docker_stack.py create mode 100644 library/docker_swarm.py create mode 100644 meta/main.yml create mode 100644 tasks/Debian.yml create mode 100644 tasks/RedHat.yml create mode 100644 tasks/main.yml create mode 100644 tasks/swarm.yml create mode 100644 tasks/tools.yml create mode 100644 vars/Debian_docker-ce.yml create mode 100644 vars/Debian_docker-io.yml create mode 100644 vars/RedHat_docker-ce.yml diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..ec5a90a --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,5 @@ +--- +# Possible value: docker-ce , docker-io +docker_ver: docker-ce +docker_swarmmode: false +docker_swarm_port: 2377 diff --git a/files/etc/yum.repos.d/docker.repo b/files/etc/yum.repos.d/docker.repo new file mode 100644 index 0000000..3ffc784 --- /dev/null +++ b/files/etc/yum.repos.d/docker.repo @@ -0,0 +1,7 @@ +[dockerrepo] +name=Docker Repository +baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/ +enabled=1 +gpgcheck=1 +gpgkey=https://yum.dockerproject.org/gpg + diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..64641b3 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: Restart docker + service: name=docker state=restarted + diff --git a/library/docker_stack.py b/library/docker_stack.py new file mode 100644 index 0000000..8c9f507 --- /dev/null +++ b/library/docker_stack.py @@ -0,0 +1,254 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 Dario Zanzico (git@dariozanzico.com) +# 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 + + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.1'} + +DOCUMENTATION = ''' +--- +module: docker_stack +author: "Dario Zanzico (@dariko)" +short_description: docker stack module +description: | + Manage docker stacks using the 'docker stack' command + on the target node + (see examples) +version_added: "2.5" +options: + name: + required: true + description: + - Stack name + state: + description: + - Service state. + default: "present" + choices: + - present + - absent + compose_yaml: + required: false + default: "" + description: + - String containing the yaml definition of the stack. + - Must be in compose format. + compose_file: + required: false + default: "" + description: + - Path of the stack file on the remote/target machine. + prune: + required: false + default: false + description: + - > + If true will add the `--prune` option to the `docker stack deploy` command. + This will have docker remove the services not present in the + current stack definition. + with_registry_auth: + required: false + default: false + description: + - > + If true will add the `--with-registry-auth` option to the `docker stack deploy` command. + This will have docker send registry authentication details to Swarm agents. + resolve_image: + required: false + choices: ["always", "changed", "never"] + description: + - > + If set will add the `--resolve-image` option to the `docker stack deploy` command. + This will have docker query the registry to resolve image digest and + supported platforms. If not set, docker use "always" by default. + +requirements: +- "jsondiff" +''' + +RETURN = ''' +docker_stack_spec_diff: + description: | + dictionary containing the differences between the 'Spec' field + of the stack services before and after applying the new stack + definition. + sample: > + "docker_stack_specs_diff": + {'test_stack_test_service': {u'TaskTemplate': {u'ContainerSpec': {delete: [u'Env']}}}} + returned: on change + type: dict +''' + +EXAMPLES = ''' +- name: deploy 'stack1' stack from file + docker_stack: + state: present + name: stack1 + compose_file: /opt/stack.compose + +- name: deploy 'stack2' from yaml + docker_stack: + state: present + name: stack2 + compose_yaml: | + version: '3' + services: + web: + image: nginx + ports: + - "80:80" + +- name: deprovision 'stack1' + docker_stack: + state: absent +''' + + +import json +import tempfile + +try: + from jsondiff import diff as json_diff + HAS_JSONDIFF = True +except ImportError: + HAS_JSONDIFF = False + +from ansible.module_utils.basic import AnsibleModule, os + + +def docker_stack_services(module, stack_name): + docker_bin = module.get_bin_path('docker', required=True) + rc, out, err = module.run_command([docker_bin, + "stack", + "services", + stack_name, + "--format", + "{{.Name}}"]) + if out != ("Nothing found in stack %s\n" % stack_name): + return out.strip().split('\n') + return [] + + +def docker_service_inspect(module, service_name): + docker_bin = module.get_bin_path('docker', required=True) + rc, out, err = module.run_command([docker_bin, + "service", + "inspect", + service_name]) + if rc != 0: + return None + else: + ret = json.loads(out)[0]['Spec'] + return ret + + +def docker_stack_deploy(module, stack_name, compose_file): + docker_bin = module.get_bin_path('docker', required=True) + command = [docker_bin, "stack", "deploy"] + if module.params["prune"]: + command += ["--prune"] + if module.params["with_registry_auth"]: + command += ["--with-registry-auth"] + if module.params["resolve_image"]: + command += ["--resolve-image", + module.params["resolve_image"]] + command += ["--compose-file", + compose_file, + stack_name] + return module.run_command(command) + + +def docker_stack_inspect(module, stack_name): + ret = {} + for service_name in docker_stack_services(module, stack_name): + ret[service_name] = docker_service_inspect(module, service_name) + return ret + + +def main(): + module = AnsibleModule( + argument_spec={ + 'name': dict(required=True, type='str'), + 'compose_yaml': dict(), + 'compose_file': dict(), + 'prune': dict(default=False, type='bool'), + 'with_registry_auth': dict(default=False, type='bool'), + 'resolve_image': dict(type='str', choices=['always', 'changed', 'never']), + 'state': dict(default='present', choices=['present', 'absent']) + }, + supports_check_mode=False, + mutually_exclusive=[['compose_yaml', 'compose_file']] + ) + + if not HAS_JSONDIFF: + return module.fail_json(msg="jsondiff is not installed, try `pip install jsondiff`") + + state = module.params['state'] + compose_yaml = module.params['compose_yaml'] + compose_file = module.params['compose_file'] + name = module.params['name'] + + if state == 'present': + try: + if compose_yaml: + compose_file_fd, compose_file = tempfile.mkstemp() + with os.fdopen(compose_file_fd, 'w') as stack_file: + stack_file.write(compose_yaml) + elif not compose_file: + module.fail_json(msg="compose_yaml or compose_file " + + "parameters required if state=='present'") + + before_stack_services = docker_stack_inspect(module, name) + + rc, out, err = docker_stack_deploy(module, name, compose_file) + + after_stack_services = docker_stack_inspect(module, name) + + finally: + if compose_yaml and compose_file: + os.remove(compose_file) + if rc != 0: + module.fail_json(msg="docker stack up deploy command failed", + out=out, + rc=rc, err=err) + + before_after_differences = json_diff(before_stack_services, + after_stack_services) + for k in before_after_differences.keys(): + if isinstance(before_after_differences[k], dict): + before_after_differences[k].pop('UpdatedAt', None) + before_after_differences[k].pop('Version', None) + if not list(before_after_differences[k].keys()): + before_after_differences.pop(k) + + if not before_after_differences: + module.exit_json(changed=False) + else: + module.exit_json( + changed=True, + docker_stack_spec_diff=str(before_after_differences)) + + else: + docker_bin = module.get_bin_path('docker', required=True) + rc, out, err = module.run_command([docker_bin, + "stack", + "down", + name]) + + if rc != 0: + module.fail_json(msg="'docker stack down' command failed", + out=out, + rc=rc, + err=err) + + module.exit_json(changed=True, msg=out, err=err) + +if __name__ == "__main__": + main() diff --git a/library/docker_swarm.py b/library/docker_swarm.py new file mode 100644 index 0000000..204c605 --- /dev/null +++ b/library/docker_swarm.py @@ -0,0 +1,520 @@ +#!/usr/bin/python + +# Copyright 2016 Red Hat | Ansible +# 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 + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: docker_swarm +short_description: Manage Swarm cluster +version_added: "2.7" +description: + - Create a new Swarm cluster. + - Add/Remove nodes or managers to an existing cluster. +options: + advertise_addr: + description: + - Externally reachable address advertised to other nodes. + - This can either be an address/port combination + in the form C(192.168.1.1:4567), or an interface followed by a + port number, like C(eth0:4567). + - If the port number is omitted, + the port number from the listen address is used. + - If C(advertise_addr) is not specified, it will be automatically + detected when possible. + listen_addr: + description: + - Listen address used for inter-manager communication. + - This can either be an address/port combination in the form + C(192.168.1.1:4567), or an interface followed by a port number, + like C(eth0:4567). + - If the port number is omitted, the default swarm listening port + is used. + default: 0.0.0.0:2377 + force: + description: + - Use with state C(present) to force creating a new Swarm, even if already part of one. + - Use with state C(absent) to Leave the swarm even if this node is a manager. + type: bool + default: 'no' + state: + description: + - Set to C(present), to create/update a new cluster. + - Set to C(join), to join an existing cluster. + - Set to C(absent), to leave an existing cluster. + - Set to C(remove), to remove an absent node from the cluster. + - Set to C(inspect) to display swarm informations. + required: true + default: present + choices: + - present + - join + - absent + - remove + - inspect + node_id: + description: + - Swarm id of the node to remove. + - Used with I(state=remove). + join_token: + description: + - Swarm token used to join a swarm cluster. + - Used with I(state=join). + remote_addrs: + description: + - Remote address of a manager to connect to. + - Used with I(state=join). + task_history_retention_limit: + description: + - Maximum number of tasks history stored. + - Docker default value is C(5). + snapshot_interval: + description: + - Number of logs entries between snapshot. + - Docker default value is C(10000). + keep_old_snapshots: + description: + - Number of snapshots to keep beyond the current snapshot. + - Docker default value is C(0). + log_entries_for_slow_followers: + description: + - Number of log entries to keep around to sync up slow followers after a snapshot is created. + heartbeat_tick: + description: + - Amount of ticks (in seconds) between each heartbeat. + - Docker default value is C(1s). + election_tick: + description: + - Amount of ticks (in seconds) needed without a leader to trigger a new election. + - Docker default value is C(10s). + dispatcher_heartbeat_period: + description: + - The delay for an agent to send a heartbeat to the dispatcher. + - Docker default value is C(5s). + node_cert_expiry: + description: + - Automatic expiry for nodes certificates. + - Docker default value is C(3months). + name: + description: + - The name of the swarm. + labels: + description: + - User-defined key/value metadata. + signing_ca_cert: + description: + - The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format. + signing_ca_key: + description: + - The desired signing CA key for all swarm node TLS leaf certificates, in PEM format. + ca_force_rotate: + description: + - An integer whose purpose is to force swarm to generate a new signing CA certificate and key, + if none have been specified. + - Docker default value is C(0). + autolock_managers: + description: + - If set, generate a key and use it to lock data stored on the managers. + - Docker default value is C(no). + type: bool + rotate_worker_token: + description: Rotate the worker join token. + type: bool + default: 'no' + rotate_manager_token: + description: Rotate the manager join token. + type: bool + default: 'no' +extends_documentation_fragment: + - docker +requirements: + - python >= 2.7 + - Docker API >= 1.35 +author: + - Thierry Bouvet (@tbouvet) +''' + +EXAMPLES = ''' + +- name: Init a new swarm with default parameters + docker_swarm: + state: present + advertise_addr: 192.168.1.1 + +- name: Update swarm configuration + docker_swarm: + state: present + election_tick: 5 + +- name: Add nodes + docker_swarm: + state: join + advertise_addr: 192.168.1.2 + join_token: SWMTKN-1--xxxxx + remote_addrs: [ '192.168.1.1:2377' ] + +- name: Leave swarm for a node + docker_swarm: + state: absent + +- name: Remove a swarm manager + docker_swarm: + state: absent + force: true + +- name: Remove node from swarm + docker_swarm: + state: remove + node_id: mynode + +- name: Inspect swarm + docker_swarm: + state: inspect + register: swarm_info +''' + +RETURN = ''' +swarm_facts: + description: Informations about swarm. + returned: success + type: complex + contains: + JoinTokens: + description: Tokens to connect to the Swarm. + returned: success + type: complex + contains: + Worker: + description: Token to create a new I(worker) node + returned: success + type: str + example: SWMTKN-1--xxxxx + Manager: + description: Token to create a new I(manager) node + returned: success + type: str + example: SWMTKN-1--xxxxx +actions: + description: Provides the actions done on the swarm. + returned: when action failed. + type: list + example: "['This cluster is already a swarm cluster']" + +''' + +import json +from time import sleep +try: + from docker.errors import APIError +except ImportError: + # missing docker-py handled in ansible.module_utils.docker + pass + +from ansible.module_utils.docker_common import AnsibleDockerClient, DockerBaseClass +from ansible.module_utils._text import to_native + + +class TaskParameters(DockerBaseClass): + def __init__(self, client): + super(TaskParameters, self).__init__() + + self.state = None + self.advertise_addr = None + self.listen_addr = None + self.force_new_cluster = None + self.remote_addrs = None + self.join_token = None + + # Spec + self.snapshot_interval = None + self.task_history_retention_limit = None + self.keep_old_snapshots = None + self.log_entries_for_slow_followers = None + self.heartbeat_tick = None + self.election_tick = None + self.dispatcher_heartbeat_period = None + self.node_cert_expiry = None + self.external_cas = None + self.name = None + self.labels = None + self.log_driver = None + self.signing_ca_cert = None + self.signing_ca_key = None + self.ca_force_rotate = None + self.autolock_managers = None + self.rotate_worker_token = None + self.rotate_manager_token = None + + for key, value in client.module.params.items(): + setattr(self, key, value) + + self.update_parameters(client) + + def update_parameters(self, client): + self.spec = client.create_swarm_spec( + snapshot_interval=self.snapshot_interval, + task_history_retention_limit=self.task_history_retention_limit, + keep_old_snapshots=self.keep_old_snapshots, + log_entries_for_slow_followers=self.log_entries_for_slow_followers, + heartbeat_tick=self.heartbeat_tick, + election_tick=self.election_tick, + dispatcher_heartbeat_period=self.dispatcher_heartbeat_period, + node_cert_expiry=self.node_cert_expiry, + name=self.name, + labels=self.labels, + signing_ca_cert=self.signing_ca_cert, + signing_ca_key=self.signing_ca_key, + ca_force_rotate=self.ca_force_rotate, + autolock_managers=self.autolock_managers, + log_driver=self.log_driver + ) + + +class SwarmManager(DockerBaseClass): + + def __init__(self, client, results): + + super(SwarmManager, self).__init__() + + self.client = client + self.results = results + self.check_mode = self.client.check_mode + + self.parameters = TaskParameters(client) + + def __call__(self): + choice_map = { + "present": self.init_swarm, + "join": self.join, + "absent": self.leave, + "remove": self.remove, + "inspect": self.inspect_swarm + } + + choice_map.get(self.parameters.state)() + + def __isSwarmManager(self): + try: + data = self.client.inspect_swarm() + json_str = json.dumps(data, ensure_ascii=False) + self.swarm_info = json.loads(json_str) + return True + except APIError: + return False + + def inspect_swarm(self): + try: + data = self.client.inspect_swarm() + json_str = json.dumps(data, ensure_ascii=False) + self.swarm_info = json.loads(json_str) + self.results['changed'] = False + self.results['swarm_facts'] = self.swarm_info + except APIError: + return + + def init_swarm(self): + if self.__isSwarmManager(): + self.__update_swarm() + return + + try: + if self.parameters.advertise_addr is None: + self.client.fail(msg="advertise_addr is required to initialize a swarm cluster.") + + self.client.init_swarm( + advertise_addr=self.parameters.advertise_addr, listen_addr=self.parameters.listen_addr, + force_new_cluster=self.parameters.force_new_cluster, swarm_spec=self.parameters.spec) + except APIError as exc: + self.client.fail(msg="Can not create a new Swarm Cluster: %s" % to_native(exc)) + + self.__isSwarmManager() + self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info['ID'])) + self.results['changed'] = True + self.results['swarm_facts'] = {u'JoinTokens': self.swarm_info['JoinTokens']} + + def __update_spec(self, spec): + if (self.parameters.node_cert_expiry is None): + self.parameters.node_cert_expiry = spec['CAConfig']['NodeCertExpiry'] + + if (self.parameters.dispatcher_heartbeat_period is None): + self.parameters.dispatcher_heartbeat_period = spec['Dispatcher']['HeartbeatPeriod'] + + if (self.parameters.snapshot_interval is None): + self.parameters.snapshot_interval = spec['Raft']['SnapshotInterval'] + if (self.parameters.keep_old_snapshots is None): + self.parameters.keep_old_snapshots = spec['Raft']['KeepOldSnapshots'] + if (self.parameters.heartbeat_tick is None): + self.parameters.heartbeat_tick = spec['Raft']['HeartbeatTick'] + if (self.parameters.log_entries_for_slow_followers is None): + self.parameters.log_entries_for_slow_followers = spec['Raft']['LogEntriesForSlowFollowers'] + if (self.parameters.election_tick is None): + self.parameters.election_tick = spec['Raft']['ElectionTick'] + + if (self.parameters.task_history_retention_limit is None): + self.parameters.task_history_retention_limit = spec['Orchestration']['TaskHistoryRetentionLimit'] + + if (self.parameters.autolock_managers is None): + self.parameters.autolock_managers = spec['EncryptionConfig']['AutoLockManagers'] + + if (self.parameters.name is None): + self.parameters.name = spec['Name'] + + if (self.parameters.labels is None): + self.parameters.labels = spec['Labels'] + + if 'LogDriver' in spec['TaskDefaults']: + self.parameters.log_driver = spec['TaskDefaults']['LogDriver'] + + self.parameters.update_parameters(self.client) + + return self.parameters.spec + + def __update_swarm(self): + try: + self.inspect_swarm() + version = self.swarm_info['Version']['Index'] + spec = self.swarm_info['Spec'] + new_spec = self.__update_spec(spec) + del spec['TaskDefaults'] + if spec == new_spec: + self.results['actions'].append("No modification") + self.results['changed'] = False + return + self.client.update_swarm( + version=version, swarm_spec=new_spec, rotate_worker_token=self.parameters.rotate_worker_token, + rotate_manager_token=self.parameters.rotate_manager_token) + except APIError as exc: + self.client.fail(msg="Can not update a Swarm Cluster: %s" % to_native(exc)) + return + + self.inspect_swarm() + self.results['actions'].append("Swarm cluster updated") + self.results['changed'] = True + + def __isSwarmNode(self): + info = self.client.info() + if info: + json_str = json.dumps(info, ensure_ascii=False) + self.swarm_info = json.loads(json_str) + if self.swarm_info['Swarm']['NodeID']: + return True + return False + + def join(self): + if self.__isSwarmNode(): + self.results['actions'].append("This node is already part of a swarm.") + return + try: + self.client.join_swarm( + remote_addrs=self.parameters.remote_addrs, join_token=self.parameters.join_token, listen_addr=self.parameters.listen_addr, + advertise_addr=self.parameters.advertise_addr) + except APIError as exc: + self.client.fail(msg="Can not join the Swarm Cluster: %s" % to_native(exc)) + self.results['actions'].append("New node is added to swarm cluster") + self.results['changed'] = True + + def leave(self): + if not(self.__isSwarmNode()): + self.results['actions'].append("This node is not part of a swarm.") + return + try: + self.client.leave_swarm(force=self.parameters.force) + except APIError as exc: + self.client.fail(msg="This node can not leave the Swarm Cluster: %s" % to_native(exc)) + self.results['actions'].append("Node has leaved the swarm cluster") + self.results['changed'] = True + + def __get_node_info(self): + try: + node_info = self.client.inspect_node(node_id=self.parameters.node_id) + except APIError as exc: + raise exc + json_str = json.dumps(node_info, ensure_ascii=False) + node_info = json.loads(json_str) + return node_info + + def __check_node_is_down(self): + for _x in range(0, 5): + node_info = self.__get_node_info() + if node_info['Status']['State'] == 'down': + return True + sleep(5) + return False + + def remove(self): + if not(self.__isSwarmManager()): + self.client.fail(msg="This node is not a manager.") + + try: + status_down = self.__check_node_is_down() + except APIError: + return + + if not(status_down): + self.client.fail(msg="Can not remove the node. The status node is ready and not down.") + + try: + self.client.remove_node(node_id=self.parameters.node_id, force=self.parameters.force) + except APIError as exc: + self.client.fail(msg="Can not remove the node from the Swarm Cluster: %s" % to_native(exc)) + self.results['actions'].append("Node is removed from swarm cluster.") + self.results['changed'] = True + + +def main(): + argument_spec = dict( + advertise_addr=dict(type='str'), + state=dict(type='str', choices=['present', 'join', 'absent', 'remove', 'inspect'], default='present'), + force=dict(type='bool', default=False), + listen_addr=dict(type='str', default='0.0.0.0:2377'), + remote_addrs=dict(type='list'), + join_token=dict(type='str'), + snapshot_interval=dict(type='int'), + task_history_retention_limit=dict(type='int'), + keep_old_snapshots=dict(type='int'), + log_entries_for_slow_followers=dict(type='int'), + heartbeat_tick=dict(type='int'), + election_tick=dict(type='int'), + dispatcher_heartbeat_period=dict(type='int'), + node_cert_expiry=dict(type='int'), + name=dict(type='str'), + labels=dict(type='dict'), + signing_ca_cert=dict(type='str'), + signing_ca_key=dict(type='str'), + ca_force_rotate=dict(type='int'), + autolock_managers=dict(type='bool'), + node_id=dict(type='str'), + rotate_worker_token=dict(type='bool', default=False), + rotate_manager_token=dict(type='bool', default=False) + ) + + required_if = [ + ('state', 'join', ['advertise_addr', 'remote_addrs', 'join_token']), + ('state', 'remove', ['node_id']) + ] + + client = AnsibleDockerClient( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=required_if + ) + + results = dict( + changed=False, + result='', + actions=[] + ) + + SwarmManager(client, results)() + client.module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..68cb2ed --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,4 @@ +#--- +#dependencies: +# - { role: yumrepo } +# - { role: yum } diff --git a/tasks/Debian.yml b/tasks/Debian.yml new file mode 100644 index 0000000..0edd5df --- /dev/null +++ b/tasks/Debian.yml @@ -0,0 +1,37 @@ +--- +- name: add docker apt key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + when: + - docker_ver == "docker-ce" + +- name: add docker repository + apt_repository: repo='deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ansible_distribution_release}} stable' state=present update_cache=yes + when: + - docker_ver == "docker-ce" + +- name: "Ensure GRUB_CMDLINE_LINUX is updated" + lineinfile: dest=/etc/default/grub regexp='^(GRUB_CMDLINE_LINUX=".*)"$' line='\1 cgroup_enable=memory swapaccount=1"' backrefs=yes + when: + - not docker_installed.stat.exists + +- name: "Update grub.conf" + command: update-grub + when: + - not docker_installed.stat.exists + +- name: "Ensure DEFAULT_FORWARD_POLICY in /etc/default/ufw is updated" + lineinfile: dest=/etc/default/ufw regexp='^(DEFAULT_FORWARD_POLICY=").*"$' line='\1ACCEPT"' backrefs=yes + notify: reload ufw + tags: [docker,firewall] + +# Need Certificat ? Only in local +#- name: "Add docker port 2376/TCP " +# ufw: rule=allow port=2376 proto=tcp +# notify: reload ufw +# tags: [docker,firewall] + +#- name: "Start UFW rules" +# service: name=ufw state=started +# tags: [docker,firewall] diff --git a/tasks/RedHat.yml b/tasks/RedHat.yml new file mode 100644 index 0000000..134740d --- /dev/null +++ b/tasks/RedHat.yml @@ -0,0 +1,17 @@ +--- +#- name: Add docker repository +# yumrepo: +# name: docker +# description: "Docker Repository" +# baseurl: https://yum.dockerproject.org/repo/main/centos/$releasever/ +# gpgcheck: yes +# enabled: yes +# gpgkey: https://yum.dockerproject.org/gpg +# state: present + +- name: Add Official docker's repo + get_url: + url: https://download.docker.com/linux/centos/docker-ce.repo + dest: /etc/yum.repos.d/docker-ce.repo + mode: 0644 + diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..2285dd1 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: Include vars for {{ ansible_os_family }} + include_vars: "{{ ansible_os_family }}_{{ docker_ver }}.yml" + +# Is it needed any more ? +#- name: "Ensure system is x86_64" +# fail: msg="Docker requires a 64bit system architecture" +# when: "ansible_architecture != 'x86_64'" +# +#- name: See if docker is installed +# stat: path=/usr/bin/docker +# register: docker_installed + +- name: Install docker rules for {{ ansible_os_family }} OS family + include_tasks: "{{ ansible_os_family }}.yml" + +- name: Remove all other's docker version packages + package: name="{{ docker_remove_packages_name }}" state=absent update_cache=yes + +- name: Install docker + package: name="{{ docker_package_name }}" state=latest update_cache=yes + # when: + # - not docker_installed.stat.exists + notify: Restart docker + +- name: Enable docker on boot + service: name=docker state=started enabled=yes + +# Create docker group if needed + +# Add normal user to docker group + +- include_tasks: tools.yml + +# Configuration dans /etc/docker/daemon.json +- name: config_docker | Ensuring /etc/docker Folder Exists + file: + path: "/etc/docker" + state: "directory" + group: root + owner: root + mode: 0700 + +#- name: config_docker | Configuring Docker +# template: +# src: "etc/docker/daemon.json.j2" +# dest: "/etc/docker/daemon.json" +# group: root +# owner: root +# mode: 0644 +# notify: Restart docker + +#- port TCP 2376: permet au client local de communiquer de façon sécurisée avec le daemon tournant sur une machine du swarm +# +#- port TCP 2377: permet la communication entre les managers du swarm (port seulement ouvert sur les managers) +# +#- port UDP 4789: permet la communication entre les containers sur un réseau overlay +# +#- port TCP et UDP 7946: permet la communication entre les machines du swarm +# +#- interfaces docker0 and docker_gwbridge ? firewall-cmd --change-zone=docker0 --zone=trusted --permanent; firewall-cmd --change-zone=docker_gwbridge --zone=trusted --permanent + +- name: Install python library for docker + package: name="{{ docker_python_lib }}" state=latest update_cache=yes + +- name: Enable swarm mode + include_tasks: swarm.yml + when: + - docker_swarmmode diff --git a/tasks/swarm.yml b/tasks/swarm.yml new file mode 100644 index 0000000..0b7cb85 --- /dev/null +++ b/tasks/swarm.yml @@ -0,0 +1,129 @@ +--- +- name: Create ClusterSwarm group + group_by: key=ClusterSwarm + when: + - docker_swarmmode + +- name: Checking Swarm Mode Status + command: "docker info" + register: "docker_info" + changed_when: false + check_mode: no + +- name: Create Master Swarm group + group_by: key=MasterSwarm + when: + - '"Swarm: active" in docker_info.stdout' + - '" Is Manager: true" in docker_info.stdout' + +- name: Init Docker Swarm Mode On First Manager + command: > + docker swarm init + --listen-addr {{ internal_interface }}:{{ docker_swarm_port }} + --advertise-addr {{ internal_interface }} + when: + - not MasterSwarm is defined + - '"Swarm: inactive" in docker_info.stdout' + - inventory_hostname == groups['ClusterSwarm'][0] + +- name: Add the new master to MasterSwarm group + add_host: + name: '{{ inventory_hostname }}' + groups: MasterSwarm + when: + - not MasterSwarm is defined + - inventory_hostname == groups['ClusterSwarm'][0] + +#- name: cluster | Capturing Docker Swarm Worker join-token +# command: "docker swarm join-token -q worker" +# changed_when: false +# register: "docker_swarm_worker_token" +# delegate_to: groups['MasterSwarm'][0] +# when: +# - inventory_hostname != groups['MasterSwarm'][0] +# - '"Swarm: inactive" in docker_info.stdout' + +#- name: cluster | Capturing Docker Swarm Manager join-token +# command: "docker swarm join-token -q manager" +# changed_when: false +# register: "docker_swarm_manager_token" +# when: > +# inventory_hostname == groups['MasterSwarm'][0] +#- name: cluster | Defining Docker Swarm Manager Address +# set_fact: +# docker_swarm_manager_address: "{{ docker_swarm_addr }}:{{ docker_swarm_port }}" +# changed_when: false +# when: > +# inventory_hostname == groups['MasterSwarm'][0] +#- name: cluster | Defining Docker Swarm Manager Address +# set_fact: +# docker_swarm_manager_address: "{{ hostvars[docker_swarm_primary_manager]['docker_swarm_manager_address'] }}" +# changed_when: false +# when: > +# inventory_hostname != docker_swarm_primary_manager +#- name: cluster | Defining Docker Swarm Manager join-token +# set_fact: +# docker_swarm_manager_token: "{{ hostvars[docker_swarm_primary_manager]['docker_swarm_manager_token'] }}" +# changed_when: false +# when: > +# inventory_hostname != docker_swarm_primary_manager +#- name: cluster | Defining Docker Swarm Worker join-token +# set_fact: +# docker_swarm_worker_token: "{{ hostvars[docker_swarm_primary_manager]['docker_swarm_worker_token'] }}" +# changed_when: false +# when: > +# inventory_hostname != docker_swarm_primary_manager +#- name: cluster | Joining Additional Docker Swarm Managers To Cluster +# command: > +# docker swarm join +# --listen-addr {{ docker_swarm_addr }}:{{ docker_swarm_port }} +# --advertise-addr {{ docker_swarm_addr }} +# --token {{ docker_swarm_manager_token.stdout }} +# {{ docker_swarm_manager_address }} +# when: > +# inventory_hostname != groups['MasterSwarm'][0] and +# inventory_hostname not in groups[docker_swarm_workers_ansible_group] and +# 'Swarm: active' not in docker_info.stdout and +# 'Swarm: pending' not in docker_info.stdout +#- name: cluster | Joining Docker Swarm Workers To Cluster +# command: > +# docker swarm join +# --listen-addr {{ docker_swarm_addr }}:{{ docker_swarm_port }} +# --advertise-addr {{ docker_swarm_addr }} +# --token {{ docker_swarm_worker_token.stdout }} +# {{ docker_swarm_manager_address }} +# when: > +# inventory_hostname in groups[docker_swarm_workers_ansible_group] and +# 'Swarm: active' not in docker_info.stdout and +# 'Swarm: pending' not in docker_info.stdout +# +############### +#- name: Initialize Swarm Master +# hosts: swarm-master +# gather_facts: yes +# tasks: +# - command: "docker swarm init --advertise-addr {{inventory_hostname}}" +# - command: "docker swarm join-token -q worker" +# register: swarm_token +# - set_fact: swarmtoken="{{swarm_token.stdout}}" +# +#- name: Join Swarm Nodes +# hosts: swarm-nodes +# gather_facts: yes +# tasks: +# - command: "docker swarm join --advertise-addr {{inventory_hostname}} --token {{hostvars[groups['swarm-master'][0]].swarmtoken}} {{hostvars[groups['swarm-master'][0]].inventory_hostname}}:2377" +# +##- name: Leave Swarm +## hosts: swarm-master:swarm-nodes +## gather_facts: yes +## tasks: +## - command: "docker swarm leave --force" +# +## - name: docker_swarm | Managing Docker Swarm Networks +## docker_network: +## name: "{{ item.name }}" +## driver: "{{ item.driver }}" +## state: "{{ item.state }}" +## with_items: '{{ docker_swarm_networks }}' +## when: > +## inventory_hostname == docker_swarm_primary_manager diff --git a/tasks/tools.yml b/tasks/tools.yml new file mode 100644 index 0000000..6fe5864 --- /dev/null +++ b/tasks/tools.yml @@ -0,0 +1,75 @@ +--- +# Docker machine +- name: Check if docker-machine is already installed + stat: path=/usr/local/bin/docker-machine + register: dockermachine + +- name: Check local version installed + shell: docker-compose --version | sed 's|docker-compose version \([^ ,]*\).*|\1|' + register: dockermachine_locver + changed_when: false + when: + - dockermachine.stat.exists + +- name: Check online version + shell: curl -s https://github.com/docker/machine/releases/latest | sed 's|.*tag/\(.*\)".*|\1|' + register: dockermachine_ver + changed_when: false +- name: "Retreive docker-machine version {{ dockermachine_ver.stdout }}" + get_url: + url: https://github.com/docker/machine/releases/download/{{ dockermachine_ver.stdout }}/docker-machine-{{ ansible_system }}-{{ ansible_machine }} + dest: /usr/local/bin/docker-machine + owner: root + group: root + mode: 0755 + +- name: Check docker-machine-driver-kvm online version + shell: curl -s https://github.com/dhiltgen/docker-machine-kvm/releases/latest | sed 's|.*tag/\(.*\)".*|\1|' + register: dockermachinekvm_ver + changed_when: false +- name: Retreive docker-machine-driver-kvm + get_url: + url: https://github.com/dhiltgen/docker-machine-kvm/releases/download/{{ dockermachinekvm_ver.stdout }}/docker-machine-driver-kvm-{{ ansible_distribution | lower }}{{ ansible_distribution_major_version }} + dest: /usr/local/bin/docker-machine-driver-kvm + owner: root + group: root + mode: 0755 + +- name: Retreive docker-machine-driver-kvm2 + get_url: + url: https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-kvm2 + dest: /usr/local/bin/docker-machine-driver-kvm2 + owner: root + group: root + mode: 0755 + +- name: Check docker-machine-driver-scaleway online version + shell: curl -s https://github.com/scaleway/docker-machine-driver-scaleway/releases/latest | sed 's|.*tag/\(.*\)".*|\1|' + register: dockermachinescw_ver + changed_when: false +- name: Retreive docker-machine-driver-scaleway + get_url: + url: https://github.com/scaleway/docker-machine-driver-scaleway/releases/download/{{ dockermachinescw_ver.stdout }}/docker-machine-driver-scaleway-{{ ansible_system | lower }}-{{ ansible_machine | regex_replace('x86_64', 'amd64')}} + dest: /usr/local/bin/docker-machine-driver-scaleway + owner: root + group: root + mode: 0755 + +# docker-compose +- name: Check if docker-compose is already installed + stat: path=/usr/local/bin/docker-compose + register: dockercompose + +- name: Check online version + shell: curl -s https://github.com/docker/compose/releases/latest | sed 's|.*tag/\(.*\)".*|\1|' + register: dockercompose_ver + changed_when: false + +- name: Retreive docker-compose + get_url: + url: https://github.com/docker/compose/releases/download/{{ dockercompose_ver.stdout }}/docker-compose-{{ ansible_system }}-{{ ansible_machine }} + dest: /usr/local/bin/docker-compose + owner: root + group: root + mode: 0755 + diff --git a/vars/Debian_docker-ce.yml b/vars/Debian_docker-ce.yml new file mode 100644 index 0000000..f728472 --- /dev/null +++ b/vars/Debian_docker-ce.yml @@ -0,0 +1,12 @@ +--- +docker_package_name: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + - docker-ce +docker_remove_packages_name: + - docker-engine + - docker.io +docker_python_lib: + - python3-docker \ No newline at end of file diff --git a/vars/Debian_docker-io.yml b/vars/Debian_docker-io.yml new file mode 100644 index 0000000..3c0be4f --- /dev/null +++ b/vars/Debian_docker-io.yml @@ -0,0 +1,6 @@ +--- +docker_packages_name: + - docker.io +docker_remove_packages_name: + - docker-engine + - docker-ce \ No newline at end of file diff --git a/vars/RedHat_docker-ce.yml b/vars/RedHat_docker-ce.yml new file mode 100644 index 0000000..3ac3494 --- /dev/null +++ b/vars/RedHat_docker-ce.yml @@ -0,0 +1,15 @@ +--- +docker_package_name: + - docker-ce +# - docker-ce-selinux # obsolète +docker_remove_packages_name: + - docker + - docker-client + - docker-common +# - container-selinux # Remove docker-ce + - container-storage-setup + - oci-register-machine + - oci-systemd-hook + - oci-umount +docker_python_lib: + - python-docker-py