Linux¶
Linux is the primary production target. Both amd64 and arm64 are
first-class, and both erasure-code with ISA-L (AVX2, AVX-512 and GFNI
on amd64; NEON and SVE on arm64). They write the same stripes, so
amd64 and arm64 nodes mix in one cluster.
This page covers, in increasing order of commitment:
- Release binary —
iolake-aio, one process - A cluster under systemd — meta, OSDs and gateways as separate services
- From source — Rust toolchain; also how you get the service binaries outside the container image
If you just want a quick local endpoint, use
iolake-aio — one
binary, no daemons. The Helm chart (Kubernetes) is the
tested way to run separate services; this page covers doing it by hand.
System requirements¶
| Component | Minimum | Recommended |
|---|---|---|
| Kernel | 4.19+ | 5.15+ (for io_uring) |
| CPU | 2 cores | 8+ cores |
| RAM | 4 GB | 16+ GB (OSD), 8 GB (meta), 4 GB (gateway) |
| Disks (OSD) | 1 × any block device | 4+ dedicated raw disks per OSD node |
| Network | 1 Gbps | 10+ Gbps (especially between OSDs) |
Filesystem: OSDs prefer raw block devices (/dev/sdb,
/dev/nvme0n1). File-backed disks (/mnt/.../disk.raw) work but skip
the O_DIRECT fast path and lose some durability guarantees. Disks
are formatted with IOLake's own on-disk layout (superblock + WAL +
blocks), not ext4/xfs — mkfs-ing first is not required and is
not recommended for production OSDs.
Kernel features used:
O_DIRECTon raw block devices (required for OSD disks)ioctl BLKGETSIZE64for disk sizingio_uringwhen available (5.15+); falls back to threaded I/O, with a warning in the OSD's log, where the kernel or a seccomp profile denies it
1. Release binary¶
A release publishes iolake-aio only, for linux-amd64 and
linux-arm64 (and darwin-arm64), named with the tag; there is no
archive of the service binaries. Those are in the container image
(ghcr.io/io-lake/iolake, see Docker) or
built from source.
TAG=v0.1.0-rc.4
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
BASE=https://github.com/io-lake/iolake/releases/download/$TAG
curl -LO $BASE/iolake-aio-$TAG-linux-$ARCH
curl -LO $BASE/iolake-aio-$TAG-linux-$ARCH.sha256
sha256sum -c iolake-aio-$TAG-linux-$ARCH.sha256
sudo install -m 0755 iolake-aio-$TAG-linux-$ARCH /usr/local/bin/iolake-aio
iolake-aio --version
releases/latest/download/… doesn't work for a pre-release such as
v0.1.0-rc.4: name the tag.
The release binary needs glibc 2.28 or later and nothing else. It
is built on AlmaLinux 8 (glibc 2.28); PCRE2 is linked in, and its TLS
is rustls, so it loads no OpenSSL. The only shared libraries it loads
are glibc's own, libgcc_s and the dynamic loader. By glibc, that
covers (the kernel must meet the system requirements
too):
| Distribution | Release | glibc |
|---|---|---|
| RHEL, Rocky Linux, AlmaLinux | 8 and later | 2.28+ |
| Debian | 10 and later | 2.28+ |
| Ubuntu | 20.04 and later | 2.31+ |
| Amazon Linux | 2023 | 2.34 |
| Fedora | 29 and later | 2.28+ |
Before a release is published, each Linux binary (amd64 and arm64)
is checked to need no glibc symbol newer than 2.28 and no other library,
and is run on Ubuntu 22.04, Debian 12, Rocky Linux 9 and Amazon Linux
2023. Amazon Linux 2 (glibc 2.26) and CentOS 7 (2.17) are too old: use
the container image there.
Outbound HTTPS (to an OIDC provider, for one) trusts Mozilla's root
certificates, which are built in, and the system's certificate store,
so a private CA added there (update-ca-certificates,
update-ca-trust) is trusted too.
v0.1.0-rc.1's binaries are the exception: built on Ubuntu 24.04, they
need glibc 2.39, libssl3 and libpcre2-8-0.
2. A cluster under systemd¶
Every service is configured with flags; only iolake-osd also reads a
TOML file (--config), which is where an OSD's failure domain (region,
datacenter, rack, host) is set. The ports are the cluster's
(ports), every binary's defaults.
iolake-install writes each host's units and OSD files and forms
meta's Raft group: below. The same by hand
follows it. Either way the binaries come from the image or a
build, in /usr/local/bin.
With iolake-install¶
On each host, as root, with its own --node-id (1, 2, 3), the address the
other hosts reach it at, every meta node's address, and its disks:
sudo iolake-install init --role all \
--node-id 1 \
--advertise node1.internal \
--meta-endpoint http://node1.internal:9100,http://node2.internal:9100,http://node3.internal:9100 \
--disks /dev/disk/by-id/nvme-A,/dev/disk/by-id/nvme-B \
--ec-profile 4+2 \
--systemd
It writes an OSD per disk (/etc/objectio/osd-0.toml, osd-1.toml, …,
the units iolake-osd-0, iolake-osd-1, …), the units
iolake-meta and iolake-gateway with their flags, and the data
directories under /var/lib/objectio, and creates the system user
objectio that meta and the gateway run as. It formats no disk: each must
be blank, which its OSD formats when it first starts, or an IOLake disk.
--ec-profile (or --replication <n>) sets the default pool
(profiles allowed):
it needs k+m OSDs, on as many hosts where there are that many. A host
needn't run everything: --role meta,osd on the others, say, and the
gateway on some (behind a load balancer). The flags
include each port.
Start meta on every meta host, form the Raft group once, from any host, then start the rest:
sudo systemctl enable --now iolake-meta # every meta host
iolake-install cluster init node1.internal node2.internal node3.internal
sudo systemctl enable --now iolake-osd-0 iolake-osd-1 iolake-gateway
cluster init calls each meta node's admin port (9102), as the
by-hand steps do, and is safe to run again. The
units bind it on every interface (--admin-host 0.0.0.0), so it can run
from any host. Without mTLS the port is plain HTTP with no
authentication: keep it to the operators' network. With
mTLS it is HTTPS, and changes membership
only for a certificate of role admin; give cluster init one:
iolake-install cluster init --tls-cert admin.pem --tls-key admin.key --tls-ca ca.pem \
node1.internal node2.internal node3.internal
The units carry no TLS flags of their own: under mTLS, give each service
its certificate with a drop-in (systemctl edit iolake-meta, and so on)
setting Environment=IOLAKE_TLS_CERT=… IOLAKE_TLS_KEY=…
IOLAKE_TLS_CA=…, each its own role's (mTLS).
The admin's keys are then in /var/lib/objectio/meta/admin-creds.env on
every meta host.
One host is the same with one meta node: --role all, no
--meta-endpoint (this host's meta), and an erasure code its disks can
hold, one OSD each: three disks, --ec-profile 2+1; one disk,
--replication 1. Then iolake-install cluster init 127.0.0.1.
For server-side encryption, put IOLAKE_MASTER_KEY=<base64 of 32 random
bytes> in /etc/objectio/gateway.env (mode 0600), the same on every
gateway, before starting it. Each OSD's failure domain goes in its file,
[osd.failure_domain] (commented out as written); restart the OSD after
changing it.
This path is tested end to end: one host, then three, set up by
iolake-install, their units' commands run, the group formed by
cluster init, then objects written and read through the gateway.
By hand¶
Service user and directories¶
sudo groupadd -r objectio
sudo useradd -r -g objectio -d /var/lib/objectio -s /usr/sbin/nologin objectio
sudo mkdir -p /etc/objectio /var/lib/objectio/meta /var/lib/objectio/osd
sudo chown -R objectio:objectio /var/lib/objectio
Meta (three nodes, Raft)¶
On each meta node, with its own --node-id (1, 2, 3) and the address
the others reach it at:
iolake-meta \
--node-id 1 \
--listen 0.0.0.0:9100 \
--raft-advertise meta1.internal:9100 \
--metrics-port 9101 \
--admin-port 9102 \
--data-dir /var/lib/objectio/meta \
--ec-k 4 --ec-m 2
--ec-k/--ec-m set the default pool's erasure code
(profiles allowed);
it needs at least k+m OSDs, on as many hosts. Once all three run, form
the Raft group from node 1, over its admin port (plain HTTP, no
authentication: keep 9102 to the operators' network; under
mTLS HTTPS, with
--cacert ca.pem --cert admin.pem --key admin.key). The admin port binds
the host --listen names (--admin-host to change it): with
--listen 0.0.0.0:9100, every interface.
curl -X POST http://meta1.internal:9102/init
curl -X POST http://meta1.internal:9102/add-learner \
-H 'content-type: application/json' -d '{"node_id": 2, "addr": "meta2.internal:9100"}'
curl -X POST http://meta1.internal:9102/add-learner \
-H 'content-type: application/json' -d '{"node_id": 3, "addr": "meta3.internal:9100"}'
curl -X POST http://meta1.internal:9102/change-membership \
-H 'content-type: application/json' -d '{"voters": [1, 2, 3]}'
curl -s http://meta1.internal:9102/status # leader, voters, learners
The first leader creates the admin user once, and every meta node writes
its credentials to <data-dir>/admin-creds.env (AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY), readable by the service user only.
OSDs¶
One OSD per drive. /etc/objectio/osd.toml:
[osd]
listen = "0.0.0.0:9200"
advertise_addr = "http://osd1.internal:9200"
meta_endpoint = "http://meta1.internal:9100,http://meta2.internal:9100,http://meta3.internal:9100"
[osd.failure_domain]
datacenter = "dc1"
rack = "rack-a"
# host: the machine's hostname when left out
[storage]
disks = ["/dev/disk/by-id/nvme-…"]
data_dir = "/var/lib/objectio/osd"
iolake-osd --config /etc/objectio/osd.toml --metrics-port 9201
Flags given as well win over the file. A blank disk named in disks is
formatted on first start; one that is neither blank nor this OSD's stops
it (configuration).
Gateways¶
iolake-gateway \
--listen 0.0.0.0:9000 \
--meta-endpoint http://meta1.internal:9100,http://meta2.internal:9100,http://meta3.internal:9100 \
--external-endpoint https://s3.example.com \
--region us-east-1
Set IOLAKE_MASTER_KEY (base64 of 32 random bytes, the same on every
gateway) for server-side encryption, and run several gateways behind a
load balancer that checks /_ready. With more than one gateway, add
--no-reexport-metrics and scrape meta and the OSDs directly
(metrics).
3. Build from source¶
Needed if you want to track main, run a custom branch, or run the
services outside the image. A binary needs the glibc it was built
against or newer: to build one that runs where the release's does,
build in an almalinux:8 container, with PCRE2_SYS_STATIC=1 to link
PCRE2 in (the release workflow, .github/workflows/release.yml, is the
recipe).
Toolchain¶
# Rust (1.93+)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustup toolchain install 1.93
# Build deps
# Debian / Ubuntu
sudo apt-get install -y build-essential nasm autoconf automake libtool \
libclang-dev pkg-config protobuf-compiler libprotobuf-dev cmake
# RHEL / Rocky / Alma 9+
sudo dnf install -y gcc-c++ make nasm autoconf automake libtool \
clang-devel pkgconf protobuf-compiler protobuf-devel cmake
Every build erasure-codes with ISA-L, which is built from source with
autotools (autoconf, automake, libtool) and bound with libclang.
NASM is only required on x86_64, for ISA-L's x86 assembly; on ARM64
leave it out.
Console (SPA) prerequisites¶
iolake-aio and the gateway's /_console/ surface embed a
pre-built React bundle. If you're only building server binaries and
don't need the console, nothing to do. To build the console you need
Node 22+:
# Ubuntu — NodeSource 22.x
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
cd console && npm ci && npm run build # → console/dist
Build¶
git clone https://github.com/io-lake/iolake
cd iolake
cargo build --workspace --release
Binaries land in target/release/. For a system install:
sudo install -m 0755 target/release/{iolake-aio,iolake-gateway,iolake-meta,iolake-osd,iolakectl,iolake-install,iolake-block-gateway} \
/usr/local/bin/
Then proceed as in section 2.
Run the test suite¶
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
# Or the one-shot:
make ci
Disk preparation — production checklist¶
- [ ] Dedicate whole disks to OSDs (no partitioning, no filesystem).
- [ ] Disable write-cache on SAS/SATA drives with non-battery-backed
controllers:
hdparm -W 0 /dev/sdX. - [ ] For NVMe, confirm the drive supports FUA (Force Unit Access) —
check
nvme id-ctrl -H <dev> | grep -i fua. - [ ] Raise
/proc/sys/fs/file-maxandulimit -n(OSD opens one fd per concurrent operation × disk). - [ ] Pin CPUs / set
tuned-adm profile throughput-performance. - [ ] If using
io_uring, make sure the kernel is 5.15+ and/proc/sys/kernel/io_uring_disabledis 0, and that the OSD's log has nofalling back to preadwarning.
See topologies.md for how many disks and nodes to size for a given durability target.
systemd unit reference¶
The units iolake-install writes; to write them by hand, keep their
shape. An OSD's, one per disk:
# /etc/systemd/system/iolake-osd-0.service
[Unit]
Description=IOLake OSD 0 (/dev/disk/by-id/nvme-A)
Documentation=https://iolake.dev/
After=network-online.target
Wants=network-online.target
After=iolake-meta.service
[Service]
Type=simple
# Opens raw block devices.
User=root
Group=root
ExecStart=/usr/local/bin/iolake-osd --config /etc/objectio/osd-0.toml --metrics-port 9201
Restart=on-failure
RestartSec=5s
# Every acknowledged write is already on disk; this is time to finish
# the calls in flight.
TimeoutStopSec=60
LimitNOFILE=1048576
LimitMEMLOCK=infinity
NoNewPrivileges=yes
ProtectHome=yes
PrivateTmp=yes
SyslogIdentifier=iolake-osd-0
[Install]
WantedBy=multi-user.target
Meta's and the gateway's run as objectio, with their flags on
ExecStart and LimitNOFILE=65536, ProtectSystem=strict, meta's data
directory its one writable path (ReadWritePaths=/var/lib/objectio/meta),
and the gateway reading EnvironmentFile=-/etc/objectio/gateway.env. The
After=iolake-meta.service line is there on a host that runs meta.
Every service stops cleanly on SIGTERM
(how a node stops); none
reloads on SIGHUP, so there is no ExecReload.
Uninstall¶
sudo systemctl disable --now iolake-gateway iolake-meta iolake-osd-0 iolake-osd-1 # every OSD
sudo rm /etc/systemd/system/iolake-*.service
sudo systemctl daemon-reload
sudo rm /usr/local/bin/iolake-* /usr/local/bin/iolakectl
# Data + config (destructive):
sudo rm -rf /etc/objectio /var/lib/objectio /var/log/objectio
sudo userdel objectio && sudo groupdel objectio