Bucket replication¶
Copy every new version written to a bucket into a bucket on another IOLake cluster, asynchronously, so losing a site loses nothing that had time to replicate.
Set it up¶
1. On the target cluster: a versioned bucket, and credentials for the
source to write with. The admin's work; for least privilege, a user with
s3:PutObject, s3:ReplicateObject (and s3:DeleteObject,
s3:ReplicateDelete if delete markers replicate) on that bucket.
aws s3api create-bucket --bucket dr-copy --endpoint-url https://site-b:9000
aws s3api put-bucket-versioning --bucket dr-copy \
--versioning-configuration Status=Enabled --endpoint-url https://site-b:9000
2. On the source cluster: the target. Targets belong to a tenant: a
bucket's rules can name only its own tenant's targets, so one tenant can
never send data into another's remote bucket with the other's
credentials. A tenant's admin manages its tenant's targets; the system
admin manages the system's (no ?tenant=) and any tenant's (?tenant=).
curl -X POST https://site-a:9000/_admin/replication/targets \
--aws-sigv4 aws:amz:us-east-1:s3 --user "$AK:$SK" \
-H 'content-type: application/json' \
-d '{"name": "site-b", "endpoint": "https://site-b:9000", "bucket": "dr-copy",
"access_key": "...", "secret_key": "...", "bandwidth": 104857600}'
Or with iolakectl (the remote secret from IOLAKECTL_REMOTE_SECRET_KEY if
not given, which keeps it out of the process list):
iolakectl replication target set site-b --remote-endpoint https://site-b:9000 \
--remote-bucket dr-copy --remote-access-key ... --bandwidth 100M
iolakectl replication target list
bandwidth is optional: the bytes a second the whole cluster may send to
the target (see Bandwidth); without it, as fast as the
link goes.
GET /_admin/replication/targets lists a tenant's targets (the caller's
own, for a tenant admin; secrets never shown);
DELETE /_admin/replication/targets/{name} removes one. Both take
?tenant= for the system admin. The secret is kept in meta, as users'
secret keys are.
A target a tenant's admin sets must be https://, on a host the operator
allows tenants (as for tenant audit webhooks); otherwise a tenant could
make the gateway send requests to addresses inside the cluster. Until the
operator allows some, tenant admins can't set targets, and the system
admin sets them with ?tenant=, unrestricted:
curl -X PUT https://site-a:9000/_admin/replication/settings \
--aws-sigv4 aws:amz:us-east-1:s3 --user "$AK:$SK" \
-H 'content-type: application/json' \
-d '{"allowed_tenant_hosts": ["dr.example.com", "*.dr.example.com"]}'
Or with iolakectl (settings set replaces the list; with no host, tenant
admins can't set targets):
iolakectl replication settings set \
--allowed-tenant-host dr.example.com --allowed-tenant-host '*.dr.example.com'
iolakectl replication settings get
The list is checked again at every send, so removing a host stops tenants' targets on it.
3. On the source bucket: versioning, then the rules (S3's
PutBucketReplication), by the bucket's owner or anyone its policy grants
s3:PutReplicationConfiguration. The destination names one of the
bucket's tenant's targets: arn:obio:replication:::<target>.
<ReplicationConfiguration>
<Rule>
<ID>everything</ID>
<Status>Enabled</Status>
<Filter><Prefix></Prefix></Filter>
<DeleteMarkerReplication><Status>Disabled</Status></DeleteMarkerReplication>
<Destination><Bucket>arn:obio:replication:::site-b</Bucket></Destination>
</Rule>
</ReplicationConfiguration>
aws s3api put-bucket-replication --bucket photos \
--replication-configuration file://rules.json --endpoint-url https://site-a:9000
A rule filters by prefix and tags (<And> for both). Both buckets must be
versioned; the rules are refused otherwise.
What replicates¶
| Change on the source | On the target |
|---|---|
| A new version (PUT, multipart upload, copy) | The same version: same version id, bytes, ETag, content headers, x-amz-meta-*, tags, Object Lock retention and legal hold, SSE-S3 / SSE-KMS encryption (re-encrypted with the target's keys; an SSE-KMS key id must exist there) |
| A delete marker | Only if the rule has DeleteMarkerReplication Enabled |
| A DELETE of a specific version | Never: a mistake or an attacker on one site can't destroy the other's copy |
| An SSE-C object | Not replicated (its key isn't ours to send), as in S3 |
| Objects written before the rule | Only through a resync |
The target never replicates a replica onward, so two clusters with rules pointing at each other don't loop.
How it works¶
- A version a rule covers is committed with each target marked
PENDING, in the same metadata write: no version is ever committed and forgotten. - The gateway that took the write sends it at once. One gateway at a time
(a lease in meta) also scans every bucket with rules, every
--replication-scan-secs(30 s by default), for versions stillPENDINGorFAILED, and sends them oldest first. That is what makes it reliable: after a crash, or while a target was down, every marked version is found. - A failed send is retried with backoff (2 s, doubling, up to 5 min),
indefinitely.
FAILEDis a status to alert on, not a give-up. - Objects up to 64 MiB go in one PUT; larger ones as a multipart upload of 16 MiB parts.
- The target stores a version it already holds (same version id and ETag) once: a send repeated after a crash changes nothing. An older version arriving after a newer one is kept as a version, never made current.
Existing objects: resync¶
A rule sends what is written after it. To send what a bucket already holds (turning replication on for a bucket with data, or bringing a new or rebuilt target up to date), start a resync of the bucket to one of its targets. The bucket's tenant's admin, or the system admin, may:
iolakectl replication resync start photos site-b
# or: POST /_admin/replication/resync {"bucket": "photos", "target": "site-b"}
The bucket must have an enabled rule sending to the target. The scanner
(one gateway at a time) walks the bucket in key order, a page of 1000
keys at a time: every version a rule sends to the target and that the
target hasn't got is marked PENDING and sent. Up to 8 sends run at
once, within the target's bandwidth limit. Each pass gives resyncs a
minute, then scans for versions not yet sent as usual, so a resync of a
big bucket doesn't hold back other buckets' retries.
A version already COMPLETED for the target is left alone, and so is one
PENDING or FAILED (the fast path and the scanner send it). To send
everything again, for a target that was replaced or lost data, add
--again ("again": true): versions already sent are marked and sent
too; the target keeps one copy of each.
Watching and stopping it:
iolakectl replication resync status photos
# TARGET STATE AFTER SCANNED MARKED SENT FAILED BYTES ERROR
# site-b running img/0412.jpg 412000 409870 409870 0 88123456 -
iolakectl replication resync cancel photos site-b
GET /_admin/replication/resync?bucket= gives each target's resync
(running, or the last one, done or cancelled): the last key done
(after), the versions looked at (scanned), those a rule sends to the
target (covered), marked, sent, failed, the bytes, and the last error.
Starting a resync that is running returns it as it is.
A resync survives restarts. Its record in meta moves past a page only
once every version in it is marked and sent. If the gateway stops
mid-page, the scanner, wherever it runs next, takes the page again (after
the scanner's lease runs out, up to a minute or three scan intervals);
what that page had marked but not sent is PENDING by then and goes out
with the scanner's next pass. Cancelling keeps what was sent and marked;
the marked versions still go.
Bandwidth¶
A target's bandwidth is the cluster's limit for it: the fast path, the
scanner and resyncs on every gateway together send it no more than that
many bytes a second. Each gateway takes an equal share (the limit divided
by the gateways up, as meta last heard from them, refreshed every 30 s)
and paces its sends to it, 64 KiB at a time; a quiet spell saves nothing
up, so no burst follows it. A gateway can't use another's idle share, so
with traffic on one gateway only, the cluster sends below the limit, not
above it; and for the seconds before a new gateway first reports, the
others still use the old shares.
The limit counts request bodies (the objects' bytes), not headers. A paced send may take longer than the 5-minute send timeout; its timeout grows with what its bytes take at the gateway's share.
Watching it¶
x-amz-replication-statuson HEAD and GET:PENDING,COMPLETEDorFAILEDon the source,REPLICAon the target.- Metrics (gateway
/metrics): iolake_replication_backlog{target}: versions not yet replicated, at the last scan;iolake_replication_lag_seconds{target}: the oldest one's age, in effect the RPO;iolake_replication_sent_total{target,result};iolake_replication_bytes_total{target}.
Alert on lag that keeps growing, and on result="error".
Not yet¶
Two-way replication's conflict tests, and targets that aren't IOLake (AWS S3, MinIO), are the next phases.