Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66946df756 | |||
| 556fac3002 | |||
| 1ad7df2df3 | |||
| a3d010c5f0 |
@@ -1,11 +1,12 @@
|
|||||||
[](https://github.com/zrepl/zrepl/blob/master/LICENSE)
|
[](https://github.com/zrepl/zrepl/blob/master/LICENSE)
|
||||||
[](https://golang.org/)
|
[](https://golang.org/)
|
||||||
[](https://zrepl.github.io)
|
[](https://zrepl.github.io)
|
||||||
[](https://patreon.com/zrepl)
|
[](https://patreon.com/zrepl)
|
||||||
[](https://github.com/sponsors/problame)
|
[](https://github.com/sponsors/problame)
|
||||||
[](https://liberapay.com/zrepl/donate)
|
[](https://liberapay.com/zrepl/donate)
|
||||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R5QSXJVYHGX96)
|
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R5QSXJVYHGX96)
|
||||||
[](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fzrepl%2Fzrepl)
|
[](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fzrepl%2Fzrepl)
|
||||||
|
[](https://matrix.to/#/#zrepl:matrix.org)
|
||||||
|
|
||||||
# zrepl
|
# zrepl
|
||||||
zrepl is a one-stop ZFS backup & replication solution.
|
zrepl is a one-stop ZFS backup & replication solution.
|
||||||
|
|||||||
@@ -9,7 +9,4 @@ jobs:
|
|||||||
key: "/etc/zrepl/backups.key"
|
key: "/etc/zrepl/backups.key"
|
||||||
client_cns:
|
client_cns:
|
||||||
- "prod"
|
- "prod"
|
||||||
recv:
|
|
||||||
placeholder:
|
|
||||||
encryption: inherit # use 'off' if sender uses send.encrypted
|
|
||||||
root_fs: "storage/zrepl/sink"
|
root_fs: "storage/zrepl/sink"
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ jobs:
|
|||||||
key: /etc/zrepl/prod.key
|
key: /etc/zrepl/prod.key
|
||||||
server_cn: "backups"
|
server_cn: "backups"
|
||||||
filesystems: {
|
filesystems: {
|
||||||
"zroot/var/db": true,
|
"zroot<": true,
|
||||||
"zroot/usr/home<": true,
|
"zroot/var/tmp<": false,
|
||||||
"zroot/usr/home/paranoid": false
|
"zroot/usr/home/paranoid": false
|
||||||
}
|
}
|
||||||
snapshotting:
|
snapshotting:
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
jobs:
|
||||||
|
# Separate job for snapshots and pruning
|
||||||
|
- name: snapshots
|
||||||
|
type: snap
|
||||||
|
filesystems:
|
||||||
|
'tank<': true # all filesystems
|
||||||
|
snapshotting:
|
||||||
|
type: periodic
|
||||||
|
prefix: zrepl_
|
||||||
|
interval: 10m
|
||||||
|
pruning:
|
||||||
|
keep:
|
||||||
|
# Keep non-zrepl snapshots
|
||||||
|
- type: regex
|
||||||
|
negate: true
|
||||||
|
regex: '^zrepl_'
|
||||||
|
# Time-based snapshot retention
|
||||||
|
- type: grid
|
||||||
|
grid: 1x1h(keep=all) | 24x1h | 30x1d | 12x30d
|
||||||
|
regex: '^zrepl_'
|
||||||
|
|
||||||
|
# Source job for target B
|
||||||
|
- name: target_b
|
||||||
|
type: source
|
||||||
|
serve:
|
||||||
|
type: tls
|
||||||
|
listen: :8888
|
||||||
|
ca: /etc/zrepl/b.example.com.crt
|
||||||
|
cert: /etc/zrepl/a.example.com.crt
|
||||||
|
key: /etc/zrepl/a.example.com.key
|
||||||
|
client_cns:
|
||||||
|
- b.example.com
|
||||||
|
filesystems:
|
||||||
|
'tank<': true # all filesystems
|
||||||
|
# Snapshots are handled by the separate snap job
|
||||||
|
snapshotting:
|
||||||
|
type: manual
|
||||||
|
|
||||||
|
# Source job for target C
|
||||||
|
- name: target_c
|
||||||
|
type: source
|
||||||
|
serve:
|
||||||
|
type: tls
|
||||||
|
listen: :8889
|
||||||
|
ca: /etc/zrepl/c.example.com.crt
|
||||||
|
cert: /etc/zrepl/a.example.com.crt
|
||||||
|
key: /etc/zrepl/a.example.com.key
|
||||||
|
client_cns:
|
||||||
|
- c.example.com
|
||||||
|
filesystems:
|
||||||
|
'tank<': true # all filesystems
|
||||||
|
# Snapshots are handled by the separate snap job
|
||||||
|
snapshotting:
|
||||||
|
type: manual
|
||||||
|
|
||||||
|
# Source jobs for remaining targets. Each one should listen on a different port
|
||||||
|
# and reference the correct certificate and client CN.
|
||||||
|
# - name: target_c
|
||||||
|
# ...
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
jobs:
|
||||||
|
# Pull from source server A
|
||||||
|
- name: source_a
|
||||||
|
type: pull
|
||||||
|
connect:
|
||||||
|
type: tls
|
||||||
|
# Use the correct port for this specific client (eg. B is 8888, C is 8889, etc.)
|
||||||
|
address: a.example.com:8888
|
||||||
|
ca: /etc/zrepl/a.example.com.crt
|
||||||
|
# Use the correct key pair for this specific client
|
||||||
|
cert: /etc/zrepl/b.example.com.crt
|
||||||
|
key: /etc/zrepl/b.example.com.key
|
||||||
|
server_cn: a.example.com
|
||||||
|
root_fs: pool0/backup
|
||||||
|
interval: 10m
|
||||||
|
pruning:
|
||||||
|
keep_sender:
|
||||||
|
# Source does the pruning in its snap job
|
||||||
|
- type: regex
|
||||||
|
regex: '.*'
|
||||||
|
# Receiver-side pruning can be configured as desired on each target server
|
||||||
|
keep_receiver:
|
||||||
|
# Keep non-zrepl snapshots
|
||||||
|
- type: regex
|
||||||
|
negate: true
|
||||||
|
regex: '^zrepl_'
|
||||||
|
# Time-based snapshot retention
|
||||||
|
- type: grid
|
||||||
|
grid: 1x1h(keep=all) | 24x1h | 30x1d | 12x30d
|
||||||
|
regex: '^zrepl_'
|
||||||
@@ -248,7 +248,7 @@ Limitations
|
|||||||
Multiple Jobs & More than 2 Machines
|
Multiple Jobs & More than 2 Machines
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
The quick-start guides focus on simple setups with a single sender and a single receiver.
|
Most users are served well with a single sender and a single receiver job.
|
||||||
This section documents considerations for more complex setups.
|
This section documents considerations for more complex setups.
|
||||||
|
|
||||||
.. ATTENTION::
|
.. ATTENTION::
|
||||||
@@ -288,11 +288,46 @@ This section might be relevant to users who wish to *fan-in* (N machines replica
|
|||||||
|
|
||||||
**Working setups**:
|
**Working setups**:
|
||||||
|
|
||||||
* N ``push`` identities, 1 ``sink`` (as long as the different push jobs have a different :ref:`client identity <overview-passive-side--client-identity>`)
|
* **Fan-in: N servers replicated to one receiver, disjoint dataset trees.**
|
||||||
|
|
||||||
* ``sink`` constrains each client to a disjoint sub-tree of the sink-side dataset hierarchy ``${root_fs}/${client_identity}``.
|
* This is the common use case of a centralized backup server.
|
||||||
|
|
||||||
|
* Implementation:
|
||||||
|
|
||||||
|
* N ``push`` jobs (one per sender server), 1 ``sink`` (as long as the different push jobs have a different :ref:`client identity <overview-passive-side--client-identity>`)
|
||||||
|
* N ``source`` jobs (one per sender server), N ``pull`` on the receiver server (unique names, disjoing ``root_fs``)
|
||||||
|
|
||||||
|
* The ``sink`` job automatically constrains each client to a disjoint sub-tree of the sink-side dataset hierarchy ``${root_fs}/${client_identity}``.
|
||||||
Therefore, the different clients cannot interfere.
|
Therefore, the different clients cannot interfere.
|
||||||
|
|
||||||
|
* The ``pull`` job only pulls from one host, so it's up to the zrepl user to ensure that the different ``pull`` jobs don't interfere.
|
||||||
|
|
||||||
|
.. _fan-out-replication:
|
||||||
|
|
||||||
|
* **Fan-out: 1 server replicated to N receivers**
|
||||||
|
|
||||||
|
* Can be implemented either in a pull or push fashion.
|
||||||
|
|
||||||
|
* **pull setup**: 1 ``pull`` job on each receiver server, each with a corresponding **unique** ``source`` job on the sender server.
|
||||||
|
* **push setup**: 1 ``sink`` job on each receiver server, each with a corresponding **unique** ``push`` job on the sender server.
|
||||||
|
|
||||||
|
* It is critical that we have one sending-side job (``source``, ``push``) per receiver.
|
||||||
|
The reason is that :ref:`zrepl's ZFS abstractions <zrepl-zfs-abstractions>` (``zrepl zfs-abstraction list``) include the name of the ``source``/``push`` job, but not the receive-side job name or client identity (see :issue:`380`).
|
||||||
|
As a counter-example, suppose we used multiple ``pull`` jobs with only one ``source`` job.
|
||||||
|
All ``pull`` jobs would share the same :ref:`replication cursor bookmark <replication-cursor-and-last-received-hold>` and trip over each other, breaking incremental replication guarantees quickly.
|
||||||
|
The anlogous problem exists for 1 ``push`` to N ``sink`` jobs.
|
||||||
|
|
||||||
|
* The ``filesystems`` matched by the sending side jobs (``source``, ``push``) need not necessarily be disjoint.
|
||||||
|
For this to work, we need to avoid interference between snapshotting and pruning of the different sending jobs.
|
||||||
|
The solution is to centralize sender-side snapshot management in a separate ``snap`` job.
|
||||||
|
Snapshotting in the ``source``/``push`` job should then be disabled (``type: manual``).
|
||||||
|
And sender-side pruning (``keep_sender``) needs to be disabled in the active side (``pull`` / ``push``), since that'll be done by the ``snap job``.
|
||||||
|
|
||||||
|
* **Restore limitations**: when restoring from one of the ``pull`` targets (e.g., using ``zfs send -R``), the replication cursor bookmarks don't exist on the restored system.
|
||||||
|
This can break incremental replication to all other receive-sides after restore.
|
||||||
|
|
||||||
|
* See :ref:`the fan-out replication quick-start guide <quickstart-fan-out-replication>` for an example of this setup.
|
||||||
|
|
||||||
|
|
||||||
**Setups that do not work**:
|
**Setups that do not work**:
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
.. |GitHub license| image:: https://img.shields.io/github/license/zrepl/zrepl.svg
|
.. |GitHub license| image:: https://img.shields.io/github/license/zrepl/zrepl.svg
|
||||||
:target: https://github.com/zrepl/zrepl/blob/master/LICENSE
|
:target: https://github.com/zrepl/zrepl/blob/master/LICENSE
|
||||||
.. |Language: Go| image:: https://img.shields.io/badge/language-Go-6ad7e5.svg
|
.. |Language: Go| image:: https://img.shields.io/badge/lang-Go-6ad7e5.svg
|
||||||
:target: https://golang.org/
|
:target: https://golang.org/
|
||||||
.. |User Docs| image:: https://img.shields.io/badge/docs-web-blue.svg
|
.. |User Docs| image:: https://img.shields.io/badge/docs-web-blue.svg
|
||||||
:target: https://zrepl.github.io
|
:target: https://zrepl.github.io
|
||||||
@@ -13,13 +13,14 @@
|
|||||||
:target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R5QSXJVYHGX96
|
:target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R5QSXJVYHGX96
|
||||||
.. |Donate via Liberapay| image:: https://img.shields.io/liberapay/patrons/zrepl.svg?logo=liberapay
|
.. |Donate via Liberapay| image:: https://img.shields.io/liberapay/patrons/zrepl.svg?logo=liberapay
|
||||||
:target: https://liberapay.com/zrepl/donate
|
:target: https://liberapay.com/zrepl/donate
|
||||||
.. |Donate via Patreon| image:: https://img.shields.io/badge/dynamic/json?color=yellow&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F3095079
|
.. |Donate via Patreon| image:: https://img.shields.io/badge/dynamic/json?color=yellow&label=Patreon&query=data.attributes.patron_count&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F3095079
|
||||||
:target: https://www.patreon.com/zrepl
|
:target: https://www.patreon.com/zrepl
|
||||||
.. |Donate via GitHub Sponsors| image:: https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=yellow
|
.. |Donate via GitHub Sponsors| image:: https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=yellow
|
||||||
:target: https://github.com/sponsors/problame
|
:target: https://github.com/sponsors/problame
|
||||||
.. |Twitter| image:: https://img.shields.io/twitter/url/https/github.com/zrepl/zrepl.svg?style=social
|
.. |Twitter| image:: https://img.shields.io/twitter/url/https/github.com/zrepl/zrepl.svg?style=social
|
||||||
:target: https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fzrepl%2Fzrepl
|
:target: https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fzrepl%2Fzrepl
|
||||||
|
.. |Matrix| image:: https://img.shields.io/badge/chat-matrix-blue.svg
|
||||||
|
:target: https://matrix.to/#/#zrepl:matrix.org
|
||||||
|
|
||||||
.. |serve-transport| replace:: :ref:`serve specification<transport>`
|
.. |serve-transport| replace:: :ref:`serve specification<transport>`
|
||||||
.. |connect-transport| replace:: :ref:`connect specification<transport>`
|
.. |connect-transport| replace:: :ref:`connect specification<transport>`
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
.. include:: global.rst.inc
|
.. include:: global.rst.inc
|
||||||
|
|
||||||
|GitHub license| |Language: Go| |Twitter| |Donate via Patreon| |Donate via GitHub Sponsors| |Donate via Liberapay| |Donate via PayPal|
|
|GitHub license| |Language: Go| |Twitter| |Donate via Patreon| |Donate via GitHub Sponsors| |Donate via Liberapay| |Donate via PayPal| |Matrix|
|
||||||
|
|
||||||
|
|
||||||
zrepl - ZFS replication
|
zrepl - ZFS replication
|
||||||
@@ -137,5 +137,6 @@ Table of Contents
|
|||||||
pr
|
pr
|
||||||
changelog
|
changelog
|
||||||
GitHub Repository & Issue Tracker <https://github.com/zrepl/zrepl>
|
GitHub Repository & Issue Tracker <https://github.com/zrepl/zrepl>
|
||||||
|
Chat: Matrix <https://matrix.to/#/#zrepl:matrix.org>
|
||||||
supporters
|
supporters
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Keep the :ref:`full config documentation <configuration_toc>` handy if a config
|
|||||||
|
|
||||||
quickstart/continuous_server_backup
|
quickstart/continuous_server_backup
|
||||||
quickstart/backup_to_external_disk
|
quickstart/backup_to_external_disk
|
||||||
|
quickstart/fan_out_replication
|
||||||
|
|
||||||
Use ``zrepl configcheck`` to validate your configuration.
|
Use ``zrepl configcheck`` to validate your configuration.
|
||||||
No output indicates that everything is fine.
|
No output indicates that everything is fine.
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ This config example shows how we can backup our ZFS-based server to another mach
|
|||||||
|
|
||||||
* Production server ``prod`` with filesystems to back up:
|
* Production server ``prod`` with filesystems to back up:
|
||||||
|
|
||||||
* ``zroot/var/db``
|
* The entire pool ``zroot``
|
||||||
* ``zroot/usr/home`` and all its child filesystems
|
* except ``zroot/var/tmp`` and all child datasets of it
|
||||||
* **except** ``zroot/usr/home/paranoid`` belonging to a user doing backups themselves
|
* and except ``zroot/usr/home/paranoid`` which belongs to a user doing backups themselves.
|
||||||
|
|
||||||
* Backup server ``backups`` with
|
* Backup server ``backups`` with a dataset sub-tree for use by zrepl:
|
||||||
|
|
||||||
* Filesystem ``storage/zrepl/sink/prod`` + children dedicated to backups of ``prod``
|
* In our example, that will be ``storage/zrepl/sink/prod``.
|
||||||
|
|
||||||
Our backup solution should fulfill the following requirements:
|
Our backup solution should fulfill the following requirements:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
.. include:: ../global.rst.inc
|
||||||
|
|
||||||
|
.. _quickstart-fan-out-replication:
|
||||||
|
|
||||||
|
Fan-out replication
|
||||||
|
===================
|
||||||
|
|
||||||
|
This quick-start example demonstrates how to implement a fan-out replication setup where datasets on a server (A) are replicated to multiple targets (B, C, etc.).
|
||||||
|
|
||||||
|
This example uses multiple ``source`` jobs on server A and ``pull`` jobs on the target servers.
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
|
||||||
|
Before implementing this setup, please see the caveats listed in the :ref:`fan-out replication configuration overview <fan-out-replication>`.
|
||||||
|
|
||||||
|
Overview
|
||||||
|
--------
|
||||||
|
|
||||||
|
On the source server (A), there should be:
|
||||||
|
|
||||||
|
* A ``snap`` job
|
||||||
|
|
||||||
|
* Creates the snapshots
|
||||||
|
* Handles the pruning of snapshots
|
||||||
|
|
||||||
|
* A ``source`` job for target B
|
||||||
|
|
||||||
|
* Accepts connections from server B and B only
|
||||||
|
|
||||||
|
* Further ``source`` jobs for each additional target (C, D, etc.)
|
||||||
|
|
||||||
|
* Listens on a unique port
|
||||||
|
* Only accepts connections from the specific target
|
||||||
|
|
||||||
|
On each target server, there should be:
|
||||||
|
|
||||||
|
* A ``pull`` job that connects to the corresponding ``source`` job on A
|
||||||
|
|
||||||
|
* ``prune_sender`` should keep all snapshots since A's ``snap`` job handles the pruning
|
||||||
|
* ``prune_receiver`` can be configured as appropriate on each target server
|
||||||
|
|
||||||
|
Generate TLS Certificates
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Mutual TLS via the :ref:`TLS client authentication transport <transport-tcp+tlsclientauth>` can be used to secure the connections between the servers. In this example, a self-signed certificate is created for each server without setting up a CA.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
source=a.example.com
|
||||||
|
targets=(
|
||||||
|
b.example.com
|
||||||
|
c.example.com
|
||||||
|
# ...
|
||||||
|
)
|
||||||
|
|
||||||
|
for server in "${source}" "${targets[@]}"; do
|
||||||
|
openssl req -x509 -sha256 -nodes \
|
||||||
|
-newkey rsa:4096 \
|
||||||
|
-days 365 \
|
||||||
|
-keyout "${server}.key" \
|
||||||
|
-out "${server}.crt" \
|
||||||
|
-addext "subjectAltName = DNS:${server}" \
|
||||||
|
-subj "/CN=${server}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Distribute each host's keypair
|
||||||
|
for server in "${source}" "${targets[@]}"; do
|
||||||
|
ssh root@"${server}" mkdir /etc/zrepl
|
||||||
|
scp "${server}".{crt,key} root@"${server}":/etc/zrepl/
|
||||||
|
done
|
||||||
|
|
||||||
|
# Distribute target certificates to the source
|
||||||
|
scp "${targets[@]/%/.crt}" root@"${source}":/etc/zrepl/
|
||||||
|
|
||||||
|
# Distribute source certificate to the targets
|
||||||
|
for server in "${targets[@]}"; do
|
||||||
|
scp "${source}.crt" root@"${server}":/etc/zrepl/
|
||||||
|
done
|
||||||
|
|
||||||
|
Configure source server A
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. literalinclude:: ../../config/samples/quickstart_fan_out_replication_source.yml
|
||||||
|
|
||||||
|
Configure each target server
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. literalinclude:: ../../config/samples/quickstart_fan_out_replication_target.yml
|
||||||
|
|
||||||
|
Go Back To Quickstart Guide
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
:ref:`Click here <quickstart-apply-config>` to go back to the quickstart guide.
|
||||||
@@ -9,9 +9,11 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/zrepl/zrepl/util/chainlock"
|
"github.com/zrepl/zrepl/util/chainlock"
|
||||||
|
"github.com/zrepl/zrepl/util/zreplcircleci"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContextWithOptionalDeadline(t *testing.T) {
|
func TestContextWithOptionalDeadline(t *testing.T) {
|
||||||
|
zreplcircleci.SkipOnCircleCI(t, "test relies on predictably low scheduler latency")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
cctx, enforceDeadline := ContextWithOptionalDeadline(ctx)
|
cctx, enforceDeadline := ContextWithOptionalDeadline(ctx)
|
||||||
@@ -72,6 +74,7 @@ func TestContextWithOptionalDeadlineNegativeDeadline(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContextWithOptionalDeadlineParentCancellation(t *testing.T) {
|
func TestContextWithOptionalDeadlineParentCancellation(t *testing.T) {
|
||||||
|
zreplcircleci.SkipOnCircleCI(t, "test relies on predictably low scheduler latency")
|
||||||
|
|
||||||
pctx, cancel := context.WithCancel(context.Background())
|
pctx, cancel := context.WithCancel(context.Background())
|
||||||
cctx, enforceDeadline := ContextWithOptionalDeadline(pctx)
|
cctx, enforceDeadline := ContextWithOptionalDeadline(pctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user