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/"