From 1aba2885dc6aef02661848ba4f75d4ba821550c4 Mon Sep 17 00:00:00 2001 From: Adar Nimrod <nimrod@shore.co.il> Date: Sat, 23 Apr 2022 23:34:16 +0300 Subject: [PATCH] Debian server role: Docker volume backup. Script, Systemd service and timer to backup Docker volumes marked as such. --- Ansible/roles/debian_server/files/dvb | 77 +++++++++++++++++++ Ansible/roles/debian_server/files/dvb.service | 9 +++ Ansible/roles/debian_server/files/dvb.timer | 10 +++ Ansible/roles/debian_server/tasks/docker.yml | 32 ++++++++ 4 files changed, 128 insertions(+) create mode 100755 Ansible/roles/debian_server/files/dvb create mode 100644 Ansible/roles/debian_server/files/dvb.service create mode 100644 Ansible/roles/debian_server/files/dvb.timer diff --git a/Ansible/roles/debian_server/files/dvb b/Ansible/roles/debian_server/files/dvb new file mode 100755 index 0000000..00d0ed5 --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb @@ -0,0 +1,77 @@ +#!/bin/sh +set -eu + +# Cleanup of snapshots, added as trap so it's always executed even in case of a +# failure or ctrl+c. +cleanup () { + btrfs subvolume delete "$snapshot" +} + +usage () { + echo "Usage: $(basename "$0") DESTINATION" >&2 + exit 1 +} + +get_mountpoint () { + df --output=target "$1" | tail +2 +} + +get_snapshot () { + name="$1" + mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name" )" + snapshot="$(dirname "$mountpoint")/snapshot)" + echo "$snapshot" +} + +if [ "$#" -ne 1 ] +then + usage +elif [ ! -d "$1" ] +then + echo "Destinaton $1 not found or is not a directory." >&2 + exit 1 +elif [ "$(id -u)" -ne 0 ] +then + echo "This program must run as root." >&2 + exit 1 +elif ! command -v btrfs >/dev/null +then + echo 'btrfs-progs must be installed.' >&2 + exit 1 +elif ! docker info --format '{{ .ServerVersion }}' >/dev/null +then + echo 'Docker must be installed and running.' >&2 + exit 1 +elif ! df --type=btrfs /var/lib/docker/volumes >/dev/null +then + echo "Docker volumes are not on a btrfs filesystem." >&2 + exit 1 +elif ! df --type=btrfs "$1" >/dev/null +then + echo "Destination $1 is not on a btrfs filesystem." >&2 + exit 1 +elif [ "$(df --output=target /var/lib/docker/volumes)" != "$(df --output=target "$1")" ] +then + echo "The destination $1 is not on the same btrfs volume as the Docker volumes." >&2 + exit 1 +elif [ -z "$(docker volume ls --filter label=snapshot=true --format '{{ .Name }}')" ] +then + echo "No Docker volumes marked for backup, exiting." >&2 + exit 1 +fi + +dest="$1" +root_volume="$(get_mountpoint /var/lib/docker/volumes)" +snapshot="$(mktemp --dry-run "--tmpdir=$root_volume")" +trap 'cleanup' INT QUIT EXIT TERM +btrfs subvolume snapshot "$root_volume" "$snapshot" + +for name in $(docker volume ls --filter label=snapshot=true --format '{{ .Name }}') +do + echo "Backing up $name." + mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name")" + src="$snapshot/${mountpoint#$root_volume/}" + # shellcheck disable=SC2115 + [ ! -e "$dest/$name" ] || rm -rf "$dest/$name" + cp --archive --force --reflink=always "$src" "$dest/$name" +done diff --git a/Ansible/roles/debian_server/files/dvb.service b/Ansible/roles/debian_server/files/dvb.service new file mode 100644 index 0000000..37617a3 --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb.service @@ -0,0 +1,9 @@ +# vim: filetype=systemd +[Unit] +Description=Docker volume backup (dvb) +ConditionACPower=true +After=local-fs.target + +[Service] +Type=exec +ExecStart=dvb /var/backups/docker-volumes diff --git a/Ansible/roles/debian_server/files/dvb.timer b/Ansible/roles/debian_server/files/dvb.timer new file mode 100644 index 0000000..5606bdd --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb.timer @@ -0,0 +1,10 @@ +# vim: filetype=systemd +[Unit] +Description=Docker volume backup (dvb) + +[Timer] +OnCalendar=weekly +RandomizedDelaySec=21600 + +[Install] +WantedBy=multi-user.target diff --git a/Ansible/roles/debian_server/tasks/docker.yml b/Ansible/roles/debian_server/tasks/docker.yml index f5d5e8f..9c15907 100644 --- a/Ansible/roles/debian_server/tasks/docker.yml +++ b/Ansible/roles/debian_server/tasks/docker.yml @@ -55,3 +55,35 @@ direction: in interface: docker0 rule: allow + +- name: Create the Docker volume backup destination directory + ansible.builtin.file: + group: backup + mode: 0o0750 + owner: root + path: /var/backups/docker-volumes + state: directory + +- name: Copy the Docker volume backup script + ansible.builtin.copy: + dest: /usr/local/bin/ + mode: preserve + src: dvb + +- name: Copy the Docker volume backup service and timer + loop: + - service + - timer + ansible.builtin.copy: + dest: /etc/systemd/system/dvb.{{ item }} + mode: 0o0644 + src: dvb.{{ item }} + notify: + - Systemd daemon reload + +- name: Enable the Docker volume backup timer + ansible.builtin.systemd: + enabled: true + name: dvb.timer + notify: + - Systemd daemon reload -- GitLab