From a64636bdedf842402016b29932862896dd6a6265 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sat, 1 May 2021 20:15:49 +0200 Subject: add experimental syncoid zfs backup role --- roles/storage/zfs/syncoid/defaults/main.yml | 19 +++++++++ .../zfs/syncoid/filter_plugins/zfs_syncoid.py | 29 +++++++++++++ roles/storage/zfs/syncoid/tasks/datasets.yml | 16 ++++++++ roles/storage/zfs/syncoid/tasks/main.yml | 47 ++++++++++++++++++++++ .../zfs/syncoid/templates/systemd.service.j2 | 16 ++++++++ 5 files changed, 127 insertions(+) create mode 100644 roles/storage/zfs/syncoid/defaults/main.yml create mode 100644 roles/storage/zfs/syncoid/filter_plugins/zfs_syncoid.py create mode 100644 roles/storage/zfs/syncoid/tasks/datasets.yml create mode 100644 roles/storage/zfs/syncoid/tasks/main.yml create mode 100644 roles/storage/zfs/syncoid/templates/systemd.service.j2 (limited to 'roles/storage') diff --git a/roles/storage/zfs/syncoid/defaults/main.yml b/roles/storage/zfs/syncoid/defaults/main.yml new file mode 100644 index 00000000..cdc58750 --- /dev/null +++ b/roles/storage/zfs/syncoid/defaults/main.yml @@ -0,0 +1,19 @@ +--- +# zfs_syncoid_target_pool: target + +# zfs_syncoid_sources: +# somehost: +# ssh_hostname: 192.0.2.0 +# ssh_port: 222 +# paths: +# nvme/vm: +# recursive: yes +# skip_parent: yes +# storage/vm: +# recursive: yes +# skip_parent: yes +# storage: +# recursive: yes +# skip_parent: yes +# exclude: +# - storage/vm diff --git a/roles/storage/zfs/syncoid/filter_plugins/zfs_syncoid.py b/roles/storage/zfs/syncoid/filter_plugins/zfs_syncoid.py new file mode 100644 index 00000000..085d7c74 --- /dev/null +++ b/roles/storage/zfs/syncoid/filter_plugins/zfs_syncoid.py @@ -0,0 +1,29 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from functools import partial + +from ansible import errors + + +def zfs_syncoid_explode_paths(data): + try: + result = [] + for path in data: + parts = path.split('/') + for i in range(len(parts)): + result.append('/'.join(parts[:(i+1)])) + return result + except Exception as e: + raise errors.AnsibleFilterError("zfs_syncoid_explode_paths(): %s" % str(e)) + + +class FilterModule(object): + + ''' zfs syncoid filters ''' + filter_map = { + 'zfs_syncoid_explode_paths': zfs_syncoid_explode_paths, + } + + def filters(self): + return self.filter_map diff --git a/roles/storage/zfs/syncoid/tasks/datasets.yml b/roles/storage/zfs/syncoid/tasks/datasets.yml new file mode 100644 index 00000000..91db0f67 --- /dev/null +++ b/roles/storage/zfs/syncoid/tasks/datasets.yml @@ -0,0 +1,16 @@ +--- +- name: create base dataset for host + zfs: + name: "{{ zfs_syncoid_target_pool }}/{{ source.key }}" + state: present + extra_zfs_properties: + compression: lz4 + xattr: sa + +- name: create datasets for path + loop: "{{ source.value.paths | zfs_syncoid_explode_paths | unique }}" + loop_control: + label: "{{ source.key }}/{{ item }}" + zfs: + name: "{{ zfs_syncoid_target_pool }}/{{ source.key }}/{{ item }}" + state: present diff --git a/roles/storage/zfs/syncoid/tasks/main.yml b/roles/storage/zfs/syncoid/tasks/main.yml new file mode 100644 index 00000000..53aa434a --- /dev/null +++ b/roles/storage/zfs/syncoid/tasks/main.yml @@ -0,0 +1,47 @@ +--- +- name: install sanoid package + apt: + name: sanoid + state: present + +- name: create syncoid directory + file: + path: /var/lib/syncoid + state: directory + mode: 0700 + +- name: genarate ssh keypair for syncoid + openssh_keypair: + path: /var/lib/syncoid/id_ssh_ed25519 + type: ed25519 + comment: ZFS Backup syncoid@{{ host_name }} + +- name: generate syncoid ssh config wrapper + copy: + content: | + #!/bin/bash + exec /usr/sbin/syncoid --sshoption "UserKnownHostsFile=/var/lib/syncoid/ssh.knownhosts" --sshoption "HashKnownHosts=no" --sshkey "/var/lib/syncoid/id_ssh_ed25519" --no-sync-snap --compress zstd-fast "$@" + dest: /var/lib/syncoid/syncoid_wrapper + mode: 0755 + +- name: configure lvm to ignore zfs volumes + lineinfile: + path: /etc/lvm/lvm.conf + backrefs: yes + regexp: '^\s*#?\s*global_filter\s*=' + line: ' global_filter = [ "r|/dev/zd[0-9]+|" ]' + +- name: create target datasets + loop: "{{ zfs_syncoid_sources | dict2items }}" + loop_control: + loop_var: source + label: "{{ source.key }}" + include_tasks: datasets.yml + +- name: create systemd units + loop: "{{ zfs_syncoid_sources | dict2items }}" + loop_control: + label: "{{ item.key }}" + template: + src: systemd.service.j2 + dest: "/etc/systemd/system/syncoid-{{ item.key }}.service" diff --git a/roles/storage/zfs/syncoid/templates/systemd.service.j2 b/roles/storage/zfs/syncoid/templates/systemd.service.j2 new file mode 100644 index 00000000..5a6eba97 --- /dev/null +++ b/roles/storage/zfs/syncoid/templates/systemd.service.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=Schlagergarten Stream Stats Collector + +[Service] +Type=oneshot +{% for path,config in item.value.paths.items() %} +ExecStart=/var/lib/syncoid/syncoid_wrapper --dumpsnaps --quiet {{ config.recursive | default(false) | ternary('-r ', '') }}{{ config.skip_parent | default(false) | ternary('--skip-parent ', '') }}{% for re in config.exclude | default([]) %}--exclude='{{ re }}' {% endfor %}{% if 'ssh_port' in item.value %}--sshport {{ item.value.ssh_port }} {% endif %}root@{{ item.value.ssh_hostname }}:{{ path }} {{ zfs_syncoid_target_pool }}/{{ item.key }}/{{ path }} +{% endfor %} +PrivateTmp=yes +ProtectHome=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +RestrictRealtime=yes + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3