Backups are important. Very important. Fortunately, the combined power of zfs
, rsync
, and a few shell scripts makes things a breeze.
I generally try my best to follow a 3 2 1 rule for backups. As it currently stands, I have a FreeBSD server running the latest -RELEASE channel (12.2-RELEASE as of this writing) with zfs on all filesystems. Zfs pool is divided into volumes for photos, documents, videos, etc.
TODO Offsite backups with ZFS
Jails
Backing up jails with zfs snapshots takes a toll on storage as, for example, my unifi controller jail takes up 30GB after only a few months due to the logs it collects. Log data is not important enough for me to keep as archive, so I'll have to be selective about what I'm backing up.
For jails, my strategy involves listing all configuration files I care about in a file in the root of the jail and having rsync
collect all jail-related config files in a central location (a zfs volume somewhere) neatly beside all the other files I want to backup.
There are generally 3 pieces of information I need to have to be able to fully restore a jail:
- service-specific configuration files
- rc.conf
- pkgs
to get a list of manually installed (non-automatic), use pkg --chroot /path/to/jail/root query -e '%a = 0' %o
(see pkg(8)
and pkg-query(8)
). In my case, I have rsyslog
and ssmtp
installed in all my jails with uniform configs, so I'll ignore those. I also don't use bitlbee
anymore.
irc/bitlbee
ports-mgmt/dialog4ports
devel/libuv
ports-mgmt/pkg
sysutils/rsyslog8
mail/ssmtp
security/sudo
editors/vim-lite
irc/znc
shells/zsh
Now that we have all the data we need, it's a good idea to start thinking about how this data will be restored in case of catastrophic failure. I've opted to use ansible
to automate restoration of not only the jail environment (including packages) but also configurations which can be shared or unique to each jail.
Restoring jails with ansible
I'll be using /mpool/scripts/backups/jail-deployment
as the root of my ansible configuration files for jail deployment. I'm using ansible 2.9.7 on FreeBSD 12.2-RELEASE.
Setting up ansible
Ansible has support to run tasks in jails which we will use to setup the jails. You can also use ssh if your controller is remote, but I'll be using ansible on the host itself to keep things tidy. As jail connections use ansible connection plugins, a bit of setup is required.
configurations for jails goes into ansible.cfg
. I had to specify python3
as ansible was only looking for specific python3 versions.
ansible.cfg
[defaults]
inventory = hosts
interpreter_python = python3
My hosts file looks as follows.
hosts
znc ansible_connection=jail ansible_jail_host=/jails/znc ansible_jail_user=root
db ansible_connection=jail ansible_jail_host=/jails/db ansible_jail_user=root
web ansible_connection=jail ansible_jail_host=/jails/web ansible_jail_user=root
...
site.yml
- import_playbook: znc/site.yml
znc/site.yml
- name: setup ZNC jail
hosts: znc
tasks:
- name: test connection
ping:
Make sure the jail is running. And test it.
ansible-playbook site.yml
And it fails! FreeBSD doesn't include python
in its base install. To fix it, install python
using pkg
in the jail's root.
pkg --rootdir /jails/znc install python3
Why --rootdir
as opposed to --chroot
? --rootdir
uses the host's package repository and simply installs the package when specified. I have setup a poudriere
repository on my host system with custom port configurations which I want my jails to use, hence my use of --rootdir
.
This is when I found pkg fails because bsdinstall
had installed 10.2-RELEASE instead of 12.2-RELEASE in my basejail. So back to the beginning! After fixing that, all was well.
From here on, ansible can be used as usual to configure any of the jails. How to use ansible is left as an exercise to the reader.
Databases
postgresql
postgresql
provides pg_basebackup
which is very handy. Postgres also has pg_dumpall
. See their respective documentation for further info. I ended up using pg_basebackup
since it backs up the entire $PGDATA
directory including configuration files and WAL files.
mkdir postgres_backup
pg_basebackup -D postgres_backup --format=tar --gzip --progress
To see what has been backed up, use tar
.
tar -t -f base.tar.gz
To restore the database, untar base.tar.gz
into $PGDATA
(on FreeBSD it's /var/db/postgres/dataXX/
and untar pg_wal.tar.gz
into $PGDATA/pg_wal
.
mariadb
mariadb
provides mysqldump
which is equally as handy. my.cnf
has to be backed up separately. I couldn't find an equivalent to pg_basebackup
for mysql.
mysqldump -u root -p -x -A | gzip > mariadb_backup.gz
Be careful, if you want to backup from pre-10.4 server and restore in 10.4 or above, make sure to add --add-drop-database
so the mysql
database including users
is removed before potentially being created.
The error in question is ERROR 1050 (42S01) at line 2027: Table 'user' already exists
.
The issue is described here.
To restore, start mysql-server
, and restore.
gunzip mariadb_backup.gz
mysql -u root < mariadb_backup
To user mysqldump
without entering a password (i.e. for scripted backups), add the following to my.cnf
. On FreeBSD, it's /usr/local/etc/mysql/my.cnf
.
[mysqldump]
user=root
password=<password>
Making things regular
To perform regular backup, I have written a script for each component that I want backed up. These "components" include either full jail configurations, or single databases.
I then symlink the script to /usr/local/etc/periodic/weekly/
which then runs the backups every week. A sample script is as follows.
#!/bin/sh
set -o pipefail
set -o nounset
NAME="db"
COMPONENT="postgresql"
BASEDIR="/mpool/jail_backup/$NAME"
JAILDIR="/zroot/iocage/jails/$NAME/root"
BACKUPDIR="$BASEDIR/$(date +'%Y%m%d')/$COMPONENT"
mkdir -p "$BACKUPDIR"
# backup postgresql
iocage exec db -- pg_basebackup -D "/pgbackup" --format=tar --gzip
rsync -q --remove-source-files --recursive "$JAILDIR/pgbackup/" "$BACKUPDIR/"