Esta pagina se ve mejor con JavaScript habilitado

Restic Backups done right with rest-server

 ·  🎃 kr0m

Restic es un software de backups rápido, eficiente y seguro que soporta multitud de sistemas operativos como Linux, macOS, Windows, FreeBSD y OpenBSD.

Además es capaz de utilizar multitud de sistemas como backend:

  • Locales
  • Sftp
  • REST Server
  • Amazon S3
  • Minio
  • Opentack Swift
  • Backblaze B2
  • Microsoft Azure Blob Storage
  • Google Cloud Storage
  • Rclone
  • Windows

Empezamos por el servidor, primero clonaremos el repositorio del proyecto:

Compilamos el software y lo instalamos:

cd rest-server
make
make install

Creamos el usuario del sistema que correrá el software:

useradd -m restic

En Restic hay dos conceptos que debemos tener claro para evitar confusiones:

  • Rest-server: Es el servidor de restic, este estará protegido mediante password.
  • Repositorio de backups: Es un repositorio privado del cliente dentro del restic server, este es protegido mediante un password único que solo conoce el cliente.

Para generar el fichero de autenticación del restic server necesitamos las apache-tools, así que las instalamos:

emerge -av app-admin/apache-tools

Generamos el fichero de auth:

su restic -l
mkdir backups
cd backups
htpasswd -B -c .htpasswd kr0m

NOTA: Si queremos añadir un segundo usuario al fichero ya existente tan solo debemos suprimir el parámetro -c:

htpasswd -B .htpasswd kr0m2
cat .htpasswd
kr0m:$2y$05$yLbYcT96wddTqvsuwTp0UkSOK.xkvJhs9CwKXKwaAllNkWWT3mIRSO
kr0m2:$2y$05$YQrJ215y3tcIqJbjaEtq..gNBZhqviwERAL1HcGGnfZyCWQJsrwQ4

Generamos un certificado para que los backups se transmitan de forma segura a través de la red, si vamos a utilizar la dirección ip en el certificado SSL tendremos que generar el certificado con un subjectAltName indicando la dirección ip del servidor.

su restic -l
cd
mkdir ssl
cd ssl

Generamos la private key:

openssl genrsa -out private_key 2048

Generamos el certificado con la configuración indicada:

openssl req -x509 -sha256 -days 3650 -key private_key -out public_key -config <(
cat «-EOF
default_bits = 2048
distinguished_name = dn
x509_extensions = san
req_extensions = san
extensions = san
prompt = no
[ dn ]
countryName = ES
stateOrProvinceName = XXXX
localityName = XXXX
organizationName = Alfaexploit
[ san ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = RESTICSERVERIP

EOF
)

exit

Creamos un script de arranque del servidor:

vi /etc/local.d/rest-server.start

su restic -s /bin/bash -c "nohup /usr/local/bin/rest-server --path /home/restic/backups --private-repos --prometheus --tls --tls-cert /home/restic/ssl/public_key --tls-key /home/restic/ssl/private_key &"

Asignamos los permisos necesarios:

chmod 700 /etc/local.d/rest-server.start

Lo arrancamos manualmente:

/etc/local.d/rest-server.start

Comprobamos que está arrancado:

ps aux|grep rest-server

restic   18354  0.0  0.0 112272 10668 ?        Sl   08:03   0:00 /usr/local/bin/rest-server --path /home/restic/backups --private-repos --prometheus --tls --tls-cert /home/restic/ssl/public_key --tls-key /home/restic/ssl/private_key

NOTA: El parámetro --private-repos es interesante para que los clientes no puedan ver los backups de los otros clientes, otro parámetro destacable es --append-only, con este parámetro habilitado los clientes solo pueden realizar backups pero no borrarlos, si el servidor cliente es hackeado no podrá borrar backups, otra opción muy interesante es --prometheus que nos presentrará métricas que podremos leer mediante Prometheus.

Podemos debugear arrancándolo con el parámetro debug:

su restic -l
/usr/local/bin/rest-server --path /home/restic/backups --private-repos --prometheus --tls --tls-cert /home/restic/ssl/public_key --tls-key /home/restic/ssl/private_key --debug


Para el cliente tendremos que bajamos el binario:
https://github.com/restic/restic/releases

Lo compilamos:

make

Lo instalamos:

su
cp restic /usr/local/bin/
useradd -m restic
chown restic:restic /usr/local/bin/restic
chmod 500 /usr/local/bin/restic

Si vamos a backupear ficheros fuera del home de nuestro usuario como backups de bases de datos por ejemplo, necesitaremos acceso a estos datos, para ello modificamos las capabilities del binario de restic:

setcap cap_dac_read_search=+ep /usr/local/bin/restic

El binario restic ya puede leer cualquier fichero y directorio independientemente de quién lo ejecute, pero si queremos hacer backups mediante streaming como por ejemplo con xtrabackup, este también necesitará las mismas capabilities ya que es este el que leerá los ficheros.

setcap cap_dac_read_search=+ep /usr/bin/xtrabackup

Copiamos el certificado del rest-server al cliente:

mkdir /usr/local/share/ca-certificates
scp root@RESTICSERVERIP:/home/restic/ssl/public_key /usr/local/share/ca-certificates/resticKey.crt

Updateamos la base de datos de CAs:

update-ca-certificates

Consultamos los datos de la CA:

openssl x509 -text -noout -in /usr/local/share/ca-certificates/resticKey.crt

        Issuer: C = ES, ST = XXXX, L = XXXX, O = Alfaexploit
        Validity
            Not Before: Feb 25 08:03:25 2020 GMT
            Not After : Feb 22 08:03:25 2030 GMT
        Subject: C = ES, ST = XXXX, L = XXXX, O = Alfaexploit

        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                IP Address:RESTICSERVERIP

Y del servidor:

openssl s_client -connect RESTICSERVERIP:8000

depth=0 C = ES, ST = XXXX, L = XXXX, O = Alfaexploit

Definimos los datos de acceso al servidor de restic y el password de nuestro repo:

su restic -l

vi .bashrc
export RESTIC_REPOSITORY=rest:https://kr0m:PASSWORD@RESTICSERVERIP:8000/kr0m
export RESTIC_PASSWORD=REPOSITORY-PASSWORD
chmod 600 .bashrc
source .bashrc

Inicializamos el repo:

restic -v init

El password del repo es importante, sin él no podremos acceder a los backups realizados.

Podemos ver las keys de un repo con:

restic -v key list

repository 17def041 opened successfully, password is correct
 ID        User    Host  Created
--------------------------------------------
*65c102fa  restic  RX4   2020-02-25 09:07:21
--------------------------------------------

NOTA: Se pueden añadir mas keys, por si queremos utilizar distintas contraseñas para el mismo repo.

Backupeamos el directorio work:

mkdir work
touch work/AA
restic -v backup ~/work

Comprobamos que toda la info del repo se ha almacenado sin errores, esto es importante para asegurarse de que los backups son consistentes incluso si se compromete el rest-server:

restic -v check

no errors were found

Restic permite comparar snapshots:

touch work/BB
restic -v backup ~/work
restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
------------------------------------------------------------------------
restic -v diff 0dd2c06a 5cf720c5
comparing snapshot 0dd2c06a to 5cf720c5:
+    /home/restic/work/BB

Files:           1 new,     0 removed,     0 changed
Dirs:            0 new,     0 removed
Others:          0 new,     0 removed
Data Blobs:      0 new,     0 removed
Tree Blobs:      3 new,     3 removed
  Added:   1.312 KiB
  Removed: 1.031 KiB

Es posible leer los datos de stdin, puede ser útil para backupear mysqls por ejemplo, los siguientes comandos leerán la salida del dump y lo guardará con el nombre indicado en el parámetro --stdin-filename:

set -o pipefail
mysqldump --all-databases -uUSER -p’PASSWORD’ | restic backup -v --stdin --stdin-filename dump.sql
xtrabackup --user=USER --password=PASSWORD --backup --stream=tar | restic -v backup --stdin --stdin-filename xtrabackup.tar

Se pueden asignar tags a los snapshots:

restic -v --tag TAG00 backup ~/work
restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
------------------------------------------------------------------------

Podemos filtrar por path:

mysqldump --all-databases -uUSER -p’PASSWORD’ | restic backup -v --tag MYSQLDUMP --stdin --stdin-filename dump.sql
restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------
restic --path="/dump.sql" -v snapshots
ID        Time                 Host        Tags        Paths
----------------------------------------------------------------
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
----------------------------------------------------------------

También se puede filtrar por el host que realizó el snapshot:

restic --host RX4 -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------

Para restaurar un snapshot primero consultamos los snapshots:

restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------

Restauramos el que nos interese:

restic -v restore 41f32b15 --target /tmp/restore

restoring <Snapshot 41f32b15 of [/home/restic/work] at 2020-02-25 09:41:47.447912749 +0100 CET by restic@RX4> to /tmp/restore

Comprobamos que se haya restaurado con el contenido correcto:

ls -la /tmp/restore/home/restic/work/

total 8
drwxr-xr-x 2 restic restic 4096 feb 25 09:25 .
drwxr-xr-x 3 restic restic 4096 feb 25 09:28 ..
-rw-r--r-- 1 restic restic    0 feb 25 09:08 AA
-rw-r--r-- 1 restic restic    0 feb 25 09:25 BB

Si solo queremos un fichero en concreto:

rm -rf /tmp/restore/
restic -v restore 41f32b15 --target /tmp/restore --include /home/restic/work/BB
ls -la /tmp/restore/home/restic/work/

total 8
drwx------ 2 restic restic 4096 feb 25 09:51 .
drwx------ 3 restic restic 4096 feb 25 09:51 ..
-rw-r--r-- 1 restic restic    0 feb 25 09:25 BB

Restic nos brinda la posibilidad de montar los repositorios mediante FUSE(RO):

mkdir mntRestic
restic -v mount mntRestic

En otro terminal podemos navegar por el repo:

ls -la mntRestic/

total 4
dr-xr-xr-x 1 restic restic    0 feb 25 09:54 .
drwxr-xr-x 6 restic restic 4096 feb 25 09:54 ..
dr-xr-xr-x 1 restic restic    0 feb 25 09:54 hosts
dr-xr-xr-x 1 restic restic    0 feb 25 09:54 ids
dr-xr-xr-x 1 restic restic    0 feb 25 09:54 snapshots
dr-xr-xr-x 1 restic restic    0 feb 25 09:54 tags

Se puede sacar por la stdout un fichero de un snapshot, por ejemplo podríamos empipar un dump de mysql directamente a un mysql, es parecido a un cat:

restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
194533ab  2020-02-25 10:00:53  RX4         MYSQLDUMP2  /dump.sql
eb0045b8  2020-02-25 10:05:45  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------

En este caso restauramos la base de datos directamente desde el backup:

restic -v dump eb0045b8 /dump.sql | mysql -uUSER -p’PASSWORD'

Debemos tener en cuenta ciertos aspectos de Restic cuando eliminemos snapshots:

  • El proceso de borrado puede llevar mucho tiempo, mientras se borra el índice se bloquea y los backups se paralizan hasta que termine.
  • Si tenemos un repo por cada cliente solo se bloquean los backups de ese cliente, pero hay que tener en cuenta que la deduplicación funciona a nivel de repo, por lo tanto si los otros clientes copian la misma info NO se deduplicará.
  • Es aconsejable realizar un check después de cada prune.
  • También se podría dejar un día de descanso de backups para realizar mantenimiento.

Los pasos para eliminar un snapshot son forget y prune:

restic -v snapshots

ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
41f32b15  2020-02-25 09:41:47  RX4         TAG00       /home/restic/work
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
194533ab  2020-02-25 10:00:53  RX4         MYSQLDUMP2  /dump.sql
eb0045b8  2020-02-25 10:05:45  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------

Si hemos arrancado el server con --append-only al eliminar snapshots saldrá el siguiente error:

restic -v forget 41f32b15

repository 17def041 opened successfully, password is correct
blob not removed, server response: 403 Forbidden (403)

Sin --append-only funcionará correctamente:

restic -v forget 41f32b15

repository 17def041 opened successfully, password is correct
removed snapshot 41f32b15

Realizamos el prune para que libere el espacio de los snapshots borrados:

restic -v prune

repository 17def041 opened successfully, password is correct
counting files in repo
building new index for repo
[0:00] 100.00% 9 / 9 packs
repository contains 9 packs (17 blobs) with 3.153 MiB
processed 17 blobs: 0 duplicate blobs, 0 B duplicate
load all snapshots
find data that is still in use for 7 snapshots
[0:00] 100.00% 7 / 7 snapshots
found 15 of 17 data blobs still in use, removing 2 blobs
will remove 0 invalid files
will delete 1 packs and rewrite 0 packs, this frees 810 B
counting files in repo
[0:00] 100.00% 8 / 8 packs
finding old index files
saved new indexes as [4cbba758]
remove 7 old index files
[0:00] 100.00% 1 / 1 packs deleted
done

NOTA: Si algún snapshot contiene ficheros del snapshot eliminado no se recupera el espacio ya que todavía es necesario mantener dichos ficheros.

El comando forget permite varias opciones muy interesantes:

 --keep-last n, mantiene n snapshots.
 --keep-hourly n, mantiene n snapshots guardando el último de cada hora.
 --keep-daily n, mantiene n snapshots guardando el último de cada día.
 --keep-weekly n, mantiene n snapshots guardando el último de cada semana.
 --keep-monthly n, mantiene n snapshots guardando el último de cada mes.
 --keep-yearly n, mantiene n snapshots guardando el último de cada año.
 --keep-tag tag, mantiene todos los snapshots con la etiqueta indicada.
 --keep-within duración, mantiene todos los snapshots hacia atrás el tiempo indicado
 ex: 2y5m7d mantendrá los snapshots realizados antes de 2y5m7d desde el último snapshot.

NOTA: Antes de ejecutar comandos que eliminen snapshots es recomendable hacerlo con el parámetro --dry-run para que nos muestre lo que va a hacer.

restic -v snapshots
ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
5cf720c5  2020-02-25 09:25:08  RX4                     /home/restic/work
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
194533ab  2020-02-25 10:00:53  RX4         MYSQLDUMP2  /dump.sql
eb0045b8  2020-02-25 10:05:45  RX4         MYSQLDUMP   /dump.sql
------------------------------------------------------------------------
restic -v forget --keep-last 1 --dry-run
repository 17def041 opened successfully, password is correct
Applying Policy: keep the last 1 snapshots snapshots
snapshots for (host [RX4], paths [/dump.sql]):
keep 1 snapshots:
ID        Time                 Host        Tags        Reasons        Paths
-------------------------------------------------------------------------------
eb0045b8  2020-02-25 10:05:45  RX4         MYSQLDUMP   last snapshot  /dump.sql
-------------------------------------------------------------------------------
1 snapshots

remove 3 snapshots:
ID        Time                 Host        Tags        Paths
----------------------------------------------------------------
0424e81b  2020-02-25 09:39:47  RX4                     /dump.sql
59e93273  2020-02-25 09:45:58  RX4         MYSQLDUMP   /dump.sql
194533ab  2020-02-25 10:00:53  RX4         MYSQLDUMP2  /dump.sql
----------------------------------------------------------------
3 snapshots

snapshots for (host [RX4], paths [/home/restic/work]):
keep 1 snapshots:
ID        Time                 Host        Tags        Reasons        Paths
---------------------------------------------------------------------------------------
5cf720c5  2020-02-25 09:25:08  RX4                     last snapshot  /home/restic/work
---------------------------------------------------------------------------------------
1 snapshots

remove 2 snapshots:
ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
df975885  2020-02-25 09:08:33  RX4                     /home/restic/work
0dd2c06a  2020-02-25 09:24:31  RX4                     /home/restic/work
------------------------------------------------------------------------
2 snapshots

Para actualizar el binario basta con:

restic self-update

writing restic to /usr/local/bin/restic
find latest release of restic at GitHub
restic is up to date

La monitorización de los backups se puede realizar en el propio script de backup, hay algunos comandos que permiten la salida en formato json, la lista de snapshots por ejemplo, si después del backup sacamos los snapshots y parseamos las fechas podemos saber si todo está en orden o si ha fallado.

Instalamos la herramienta jq para poder parsear los jsons:

emerge -av app-misc/jq

Para obtener la salida en json debemos especificar la opción --json:

restic --json -v snapshots|jq

[
  {
    "time": "2020-02-25T09:08:33.133725964+01:00",
    "tree": "bb4f59228062d10744ed264faef9885665054cc2b7a9a565e2e192ec72cb0775",
    "paths": [
      "/home/restic/work"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "id": "df9758853f76a6ab34c30f2235f0437ff583e299965d490253d75d1d02f312dd",
    "short_id": "df975885"
  },
  {
    "time": "2020-02-25T09:24:31.60798947+01:00",
    "parent": "df9758853f76a6ab34c30f2235f0437ff583e299965d490253d75d1d02f312dd",
    "tree": "bb4f59228062d10744ed264faef9885665054cc2b7a9a565e2e192ec72cb0775",
    "paths": [
      "/home/restic/work"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "id": "0dd2c06afd43c4887cff65e4272685bea0f336088ac39384b69983fdf8b79c02",
    "short_id": "0dd2c06a"
  },
  {
    "time": "2020-02-25T09:25:08.029555517+01:00",
    "parent": "0dd2c06afd43c4887cff65e4272685bea0f336088ac39384b69983fdf8b79c02",
    "tree": "e2c27a6608162f37683543d2f387f0989f30cf5cfbb092b942f794d3bbafab94",
    "paths": [
      "/home/restic/work"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "id": "5cf720c54226ef0777248c43819c31bbaf88359ae472101c434907648de72516",
    "short_id": "5cf720c5"
  },
  {
    "time": "2020-02-25T09:39:47.189660055+01:00",
    "parent": "5cf720c54226ef0777248c43819c31bbaf88359ae472101c434907648de72516",
    "tree": "7c47d858d305bd52e87fc2a60621e966f2e091faea90a974a356ed154476d773",
    "paths": [
      "/dump.sql"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "id": "0424e81b2e8c93a8a1a7d607a5d432b9429502f8232ac5db381430c46a104d7a",
    "short_id": "0424e81b"
  },
  {
    "time": "2020-02-25T09:45:58.501015101+01:00",
    "parent": "41f32b15861e7fc5b831f7b6c5bee556891f4cd92d760e75a04fc3e218fdca1f",
    "tree": "2815575e89be143230ac007e0e6c8d66b0fbff87785a31461fa08f23980f3ca4",
    "paths": [
      "/dump.sql"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "tags": [
      "MYSQLDUMP"
    ],
    "id": "59e932730b2152f16040bde3628220b9bb700fcc3a38bd32f2b5877cdb09e49d",
    "short_id": "59e93273"
  },
  {
    "time": "2020-02-25T10:00:53.224338735+01:00",
    "parent": "59e932730b2152f16040bde3628220b9bb700fcc3a38bd32f2b5877cdb09e49d",
    "tree": "71188ad64f4ec0fa21a9a358e5d4d37f806b1dda3fe920d2afe44fdf74b44ea9",
    "paths": [
      "/dump.sql"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "tags": [
      "MYSQLDUMP2"
    ],
    "id": "194533ab50641d55aa91a9738bf23b0964f2f98b7d5a7a346f48db35e2971e5d",
    "short_id": "194533ab"
  },
  {
    "time": "2020-02-25T10:05:45.896647702+01:00",
    "parent": "194533ab50641d55aa91a9738bf23b0964f2f98b7d5a7a346f48db35e2971e5d",
    "tree": "5b6420bf946faed29e452d36a67b0f6e0a75eb55c909c479238c6c9180d66f70",
    "paths": [
      "/dump.sql"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "tags": [
      "MYSQLDUMP"
    ],
    "id": "eb0045b835292f7528cb192698e09e32ed756de4ef8035266e8d003d1a91e8be",
    "short_id": "eb0045b8"
  },
  {
    "time": "2020-02-25T12:39:07.288277607+01:00",
    "parent": "5cf720c54226ef0777248c43819c31bbaf88359ae472101c434907648de72516",
    "tree": "826f0ce90202612a7d961cd5a26b132dbea9efb7f6cc7de4d83d944449e6959c",
    "paths": [
      "/home/restic/work"
    ],
    "hostname": "RX4",
    "username": "restic",
    "uid": 1004,
    "gid": 1004,
    "id": "c905cdff6e951cbdf02f93ea1e970face6430b371631dc7f10d3fdcb3aba2816",
    "short_id": "c905cdff"
  }
]

Mediante jq parseamos el campo time del json:

restic --json -v snapshots|jq ‘.[].time’

"2020-02-25T09:08:33.133725964+01:00"
"2020-02-25T09:24:31.60798947+01:00"
"2020-02-25T09:25:08.029555517+01:00"
"2020-02-25T09:39:47.189660055+01:00"
"2020-02-25T09:45:58.501015101+01:00"
"2020-02-25T10:00:53.224338735+01:00"
"2020-02-25T10:05:45.896647702+01:00"

Creamos un bot con botfather para recibir alertas cuando los backups fallen:

/newbot
Use this token to access the HTTP API:
XXXXXX:YYYYYYYYYYYY

Programamos un script de envío de mensajes:

mkdir .scripts

vi ~/.scripts/tg.py

import requests
import sys

apiKey = "XXXXXX:YYYYYYYYYYYY"
url = "https://api.telegram.org/bot{}/sendMessage".format(apiKey)
msg = sys.argv[1]
print msg

userId = "ZZZZZZ"
data = {"chat_id":userId,"text":msg}
r = requests.post(url,json=data)

Le asignamos los permisos necesarios:

chmod 700 ~/.scripts/tg.py

Ahora procedemos con el script de backup:

vi ~/.scripts/resticBackup.sh

#! /bin/bash
HOSTNAME=$(hostname)

# Make backup
echo -e ">> Making Backup"
restic -v backup ~/work2

# Make backups manteinance
echo -e ">> Keeping last 7 backups and prunning old backups"
restic -v forget -\-keep-last 7
restic prune

# Check last backup time
echo -e ">> Checking last backup time"
TIME=$(restic --json -v snapshots|jq '.[-1].time')
TIME=$(echo $TIME | tr -d '"')
#echo Processing: $TIME
YEAR=$(echo $TIME | awk -F "-" '{print$1}')
#echo YEAR: $YEAR
MONTH=$(echo $TIME | awk -F "-" '{print$2}')
#echo MONTH: $MONTH
DAY=$(echo $TIME | awk -F "-" '{print$3}' | awk -F "T" '{print$1}')
#echo DAY: $DAY
HOUR=$(echo $TIME | awk -F "T" '{print$2}' | awk -F ":" '{print$1}')
#echo HOUR: $HOUR
MINUTE=$(echo $TIME | awk -F "T" '{print$2}' | awk -F ":" '{print$2}')
#echo MINUTE: $MINUTE
SECOND=$(echo $TIME | awk -F "T" '{print$2}' | awk -F ":" '{print$3}' | awk -F "." '{print$1}')
#echo SECOND: $SECOND
DATESTRING="$MONTH/$DAY/$YEAR $HOUR:$MINUTE:$SECOND"
#echo dateString: $DATESTRING
EPOCH=$(date -d "$DATESTRING" +%s)
#echo EPOCH: $EPOCH

EPOCHNOW=$(date +%s)
# One day in s, we add some time because of job is cronttabed, we prefer not to count 24h exactly:
# 24*60*60=86400+3600
TRESHOLD=$(echo "scale=3;$EPOCHNOW-86400+3600" | bc)
#echo TRESHOLD: $TRESHOLD

if [ $TRESHOLD -gt $EPOCH ]; then
        #echo -e "ERROR: Last day backup NOT detected!!"
        #echo -e "Last correct backup: $TIME"
        python ~/.scripts/tg.py "ERROR $HOSTNAME: Last day backup NOT detected -> last correct backup: $TIME"
fi

Le asignamos los permisos necesarios:

chmod 700 ~/.scripts/resticBackup.sh

NOTA: Solo comprobamos el último backup, si falla nos avisará diariamente del fallo.


Restic permite la exportación de métricas a través de Prometheus, para ello arrancamos el rest-server con el parámetro --prometheus:

su restic -s /bin/bash -c “nohup /usr/local/bin/rest-server --path /home/restic/backups --private-repos --prometheus --tls --tls-cert /home/restic/ssl/public_key --tls-key /home/restic/ssl/private_key &”

Podemos acceder a las métricas manualmente mediante curl:

curl --silent -k --user kr0m:PASSWORD https://RESTICSERVERIP:8000/metrics 2>&1 | grep -v ‘#’

rest_server_blob_delete_bytes_total{repo="kr0m",type="locks",user="kr0m"} 334
rest_server_blob_delete_total{repo="kr0m",type="locks",user="kr0m"} 2
rest_server_blob_read_bytes_total{repo="kr0m",type="keys",user="kr0m"} 894
rest_server_blob_read_total{repo="kr0m",type="keys",user="kr0m"} 2
rest_server_blob_write_bytes_total{repo="kr0m",type="data",user="kr0m"} 920
rest_server_blob_write_bytes_total{repo="kr0m",type="index",user="kr0m"} 354
rest_server_blob_write_bytes_total{repo="kr0m",type="locks",user="kr0m"} 334
rest_server_blob_write_bytes_total{repo="kr0m",type="snapshots",user="kr0m"} 318
rest_server_blob_write_total{repo="kr0m",type="data",user="kr0m"} 1
rest_server_blob_write_total{repo="kr0m",type="index",user="kr0m"} 1
rest_server_blob_write_total{repo="kr0m",type="locks",user="kr0m"} 2
rest_server_blob_write_total{repo="kr0m",type="snapshots",user="kr0m"} 1

La configuración de scraping de Prometheus quedaría del siguiente modo:

vi /etc/prometheus.yml

scrape_configs:
- job_name: rest_server
  scrape_interval: 5s
  scrape_timeout: 5s
  metrics_path: /metrics
  scheme: https
  static_configs:
  - targets:
    - RESTICSERVERIP:8000
  basic_auth:
    username: kr0m
    password: PASSWORD
  tls_config:
    ca_file: /usr/local/share/ca-certificates/resticKey.crt
    insecure_skip_verify: false

NOTA: Debemos instalar en Prometheus nuestra CA de confianza:

mkdir /usr/local/share/ca-certificates
scp root@RESTICSERVERIP:/home/restic/ssl/public_key /usr/local/share/ca-certificates/resticKey.crt
update-ca-certificates

Hay dashboards ya creadas listas para importarlas en Grafana, en mi caso he tenido que editar cada una de las gráficas porque el source era prometheus y en mi instalación se llama Prometheus:
https://github.com/restic/rest-server/tree/master/examples/compose-with-grafana/dashboards

El resultado final es este:


Troubleshooting

Bloqueos
Hay ocasiones en las que el repositorio queda bloqueado, si al realizar backups vemos:

Fatal: unable to create lock in backend: repository is already locked by PID 28679 on RX4 by restic (UID 1005, GID 1006)

Debemos hacer limpieza mediante:

restic -v unlock
restic -v rebuild-index
restic -v check
restic -v prune

Política de backups ignorada
Cuando ejecutamos el comando forget Restic por defecto hace un group by path, esto implica que si path backupeado cambia, por ejemplo un fichero que tiene como nombre un hash distinto en cada backup, el comando forget identificará cada backup como único.

Esto provocará que el keep-last X no se respete ya que cada backup es independiente y no se seguirá la política de backups indicada, si esto no es detectado a tiempo corremos el peligro de llenar el disco duro.

Para solventar el problema en el comando forget debemos agrupar por host y no por path:

--group-by host
Si te ha gustado el artículo puedes invitarme a un RedBull aquí