Configuration¶
Each node runs two processes: etcfuse-meta (Go, owns etcd and the block
device) and etcfuse (C, mounts the filesystem, forwards VFS ops to
etcfuse-meta over a Unix socket). etcfuse-meta must be up first —
etcfuse.service depends on it.
Where settings come from¶
Each setting can be given in three places. In decreasing precedence:
- A command-line flag.
- An environment variable,
ETCFS_plus the flag's name uppercased with dashes as underscores —--metrics-addrisETCFS_METRICS_ADDR. - A YAML file, keyed by the flag's own name without the dashes that
introduce it —
metrics-addr: :9090.
Anything none of them sets keeps the default in the table below. There is one vocabulary throughout: the flag's name is also the environment variable's name and the file's key, so nothing needs a translation table.
The .deb/.rpm install a commented reference copy of every key at
/etc/etcfs/etcfuse-meta.yaml.example and create /etc/etcfs/ — copy the
example to etcfuse-meta.yaml and delete what you do not need. The package
deliberately does not own the live path: a package that did would overwrite
a real configuration on upgrade.
The file is /etc/etcfs/etcfuse-meta.yaml unless --config (or
ETCFS_CONFIG) names another. That default path is optional — a cluster
that passes everything on the command line, as this repo's own scripts do,
never creates one — but a file named explicitly and then missing is an
error. So is a key that matches no flag, which would otherwise read as a
setting that silently does nothing.
--config, --version, --fsck and --info select what the process does
rather than how it is configured, and are accepted only on the command
line. A file that could turn on --fsck would leave a node that never
starts its daemon, with nothing on the command line to explain why.
# /etc/etcfs/etcfuse-meta.yaml
etcd-endpoints: # a list, or the comma-separated string the flag takes
- http://10.0.1.10:2379
- http://10.0.1.11:2379
etcd-local-endpoint: http://10.0.1.10:2379
cluster-name: prod
lease-ttl: 10s
volume-id: vol-0abcdef1234567890
ebs-volume-id: vol-0abcdef1234567890
metrics-addr: :9090
metadata-flush-interval: 100ms
Leaving node-id and ec2-instance-id out of the file is deliberate in
that example: they differ per node, so they belong on the command line or
in the environment, which lets one identical file be shipped to every node.
etcfuse-meta flags¶
| Flag | Default | Notes |
|---|---|---|
--config |
/etc/etcfs/etcfuse-meta.yaml |
YAML file holding any of the flags below. Command line only. Missing is an error only when named explicitly. |
--listen |
/run/etcfuse/etcfuse.sock |
IPC socket the C daemon connects to. |
--notify-socket |
/run/etcfuse/etcfuse-notify.sock |
Cache-invalidation notifications to the C daemon. |
--etcd-endpoints |
http://localhost:2379 |
Comma-separated. |
--etcd-local-endpoint |
unset | Endpoint of the etcd member colocated with this node. Reads are served through it — otherwise the client round-robins and a serializable data-path read still crosses the network. Falls back to --etcd-endpoints if the local member cannot answer. Leave unset when no member runs on this node. |
--etcd-cert, --etcd-key, --etcd-ca |
unset | etcd client TLS. Every cluster this repo's own scripts provision runs plaintext etcd (see bootstrap-cluster.sh's header) — set these if yours does not. |
--node-id |
hostname | |
--cluster-name |
etcfuse |
Namespaces this node's membership/fencing keys in etcd. |
--lease-ttl |
10s |
Membership lease TTL. Must clear 2× lease-ttl > 10s (the request timeout) — Parse refuses a TTL that would let the self-fencing watchdog kill the daemon before a stalled request's own deadline could fail it cleanly. |
--log-level |
1 |
0=error, 1=info, 2=debug. |
--block-device |
unset | Raw device path, e.g. /dev/nvme1n1. Prefer --volume-id — a literal device path doesn't survive a detach/reattach cycle. |
--volume-id |
unset | Cloud volume ID (e.g. vol-0abc...); resolved to a device path on every start. Takes precedence over --block-device. |
--metrics-addr |
unset | HTTP listen address for /metrics, /healthz and /readyz, e.g. :9090. Unset disables all three. |
--pprof |
false |
Serve Go's profiling endpoints under /debug/pprof on the metrics listener. Off by default because that listener is reachable by anything that can route to the node, and a profile is both expensive to produce and a description of what the process is doing. Turn it on to answer "where is the time going", and turn it off again. |
--fsck |
false |
Run the offline checker and exit — prefer etcfsctl fsck, which needs no daemon. |
--info |
false |
Print filesystem stats and exit — prefer etcfsctl status. |
--ebs-volume-id |
unset | Enables dual-confirmed external fencing (detach + poll) via the AWS API. Needs the etcfs-nodes IAM instance profile — see Terraform: Prerequisite. |
--ec2-instance-id |
unset | This node's instance ID, recorded in its membership key so peers can detach the volume on expiry. |
--nvme-reservations |
false |
Device-enforced fencing: peers preempt an expired node's NVMe reservation key. Requires an NVMe reservation-capable device (an EBS io2 Multi-Attach volume qualifies) and either --volume-id or --block-device. Takes precedence over --ebs-volume-id when both are set — it is the stronger guarantee. |
--allow-buffered-io |
false |
Opens the data device without O_DIRECT. A correctness change, not a fallback, on a device shared by more than one node — a write served back out of this node's page cache never proves it reached the other attachers. For single-node mounts and file-backed test devices only. |
--write-barriers |
false |
Flush + range-sync + readback after every write, flush before every read. Off by default: against a volume that only acknowledges durable, visible O_DIRECT writes, these publish nothing the write hasn't already published. A device with a volatile write cache needs them; buffered mode (--allow-buffered-io) forces them on regardless. |
--metadata-flush-interval |
100ms |
How long a write's extent may stay buffered in memory before it is published to etcd. While this node holds an inode's exclusive lock no peer can read it, so deferring the commit costs no cross-node correctness — it costs a peer's stat freshness, and it costs any unflushed write in a crash. fsync, close, O_SYNC/O_DSYNC and a peer's recall all force a flush. 0 commits every write before acknowledging it: nothing is ever lost, and every write carries a Raft commit. See Consistency and Durability. |
--write-data-cache |
true |
Buffers a deferred write's data in memory alongside its extent, and puts it on the device at flush time. Only applies where it pays: a write continuing a contiguous device run (so the flush merges it into fewer device operations) or one of at least 64 KiB (where the device is latency-bound rather than rate-limited). Every other write puts its bytes down as it is served and defers only its extent. Ignored when --metadata-flush-interval is 0. false restores a device write per write. See Consistency and Durability. |
--entry-timeout |
1m |
How long the kernel may answer a name's existence or absence from its own cache before asking the daemon again. Every node watches the whole dirent: prefix and pushes an INVAL_ENTRY for each change, so this is the backstop for a watch that could not be resumed, not the mechanism coherence rests on. It was one second, which is shorter than a walk of any real tree — a sweep of 80k files takes about eleven — so nothing was ever still cached when the walk came back to it. 0 disables the cache and sends every path traversal to the daemon. See FUSE Cache Management. |
--attr-timeout |
1m |
How long the kernel may answer an inode's attributes from its own cache. Backed by a matching watch on the inode: prefix, which pushes an INVAL_ATTR for every inode a peer writes; an inode this node holds the lock key for is skipped, since that change can only be its own. Lowering it to 1s restores the previous behaviour at the cost of every stat in a tree walk. 0 disables the cache. See FUSE Cache Management. |
--page-cache |
true |
Lets the kernel keep data pages for a file this node holds a lock on, so a re-read costs no FUSE upcall. Sound because the lock is the coherence protocol: the daemon drops the pages, and waits for the drop, before it yields the lock. Has no effect on a reader using O_DIRECT, which bypasses the page cache by definition. false returns to unconditional direct_io = 1, keep_cache = 0. See FUSE Cache Management. |
--history-log |
unset | Appends every served operation to this file, as input for the offline consistency checkers. One line per operation — a measurable cost on a busy mount, meant for test runs. See Porcupine. |
--read-only |
false |
Rejects every mutating FUSE operation with EROFS. For a backup/inspection mount alongside a writer, or to run --fsck against a live volume. |
--version |
Print version and exit. |
See Fencing before
choosing between --ebs-volume-id and --nvme-reservations — they are not
interchangeable in what they guarantee.
etcfuse flags¶
| Flag | Default | Notes |
|---|---|---|
--socket |
/run/etcfuse/etcfuse.sock |
Must match etcfuse-meta's --listen. |
--notify-socket |
/run/etcfuse/etcfuse-notify.sock |
Must match etcfuse-meta's --notify-socket. |
--volume-id |
unset | Informational; the C daemon does not open the device itself. |
--node-id |
hostname | Should match the paired etcfuse-meta's --node-id. |
--log-level |
2 (info) |
0=error, 1=warn, 2=info, 3=debug. |
| (positional) | required | Mountpoint. |
Quotas¶
Advisory per-subtree quotas — a directory becomes a quota root with a byte
and/or inode ceiling, and pkg/quota computes usage against it when the report
is run. No write or create is rejected for exceeding a limit; charging a write
to its enclosing root inline would cost a parent pointer on every inode or
another Raft round trip on the write path, and neither is worth paying for a
policy limit. See etcfsctl. Configured live
via etcfsctl, not a daemon flag:
etcfsctl quota set <inode> --bytes=10737418240 --inodes=100000 # 10 GiB, 100k inodes
etcfsctl quota # report usage against every root
etcfsctl quota clear <inode>
Systemd units¶
deploy/systemd/etcfuse-meta.service and deploy/systemd/etcfuse.service
— installed by the .deb/.rpm packages to /usr/lib/systemd/system/
(see Binaries), not enabled automatically.
etcfuse-meta.service runs the binary with no flags at all, so
everything it uses comes from /etc/etcfs/etcfuse-meta.yaml (or the
environment). Put your cluster's settings there rather than editing the
unit: a flag beats the file, so any flag added back to ExecStart silently
overrides the file's value for that setting, and an edit to a packaged unit
is lost on the next upgrade. For what differs per node — node-id,
ec2-instance-id — use a drop-in, which survives upgrades:
# /etc/systemd/system/etcfuse-meta.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/local/bin/etcfuse-meta --node-id=node-3 --ec2-instance-id=i-0abc123
etcfuse (the C daemon) has no config file — it takes its socket paths and
its mountpoint on the command line, so edit its unit or drop in an override
as usual.
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now etcfuse-meta
sudo systemctl enable --now etcfuse
etcfuse.service declares After=/Wants=etcfuse-meta.service, so
starting it alone brings up both, in the right order.