setup_lakekeeper
The setup_lakekeeper role configures Lakekeeper's catalog database, starts
the service, and bootstraps the Iceberg warehouse and namespace ColdFront
commits through.
The role performs the following tasks on inventory hosts:
- Create and start the catalog's own Postgres instance:
initdbintolakekeeper_pg_data, run by alakekeeper-postgresservice unit the role writes, listening onpg_portat the loopback address only. - Check that the S3-compatible object store is reachable from this host, since creating the warehouse makes Lakekeeper write to the bucket to prove the credentials work.
- Create the Lakekeeper database role and catalog database, and the five
extensions its migration needs (
uuid-ossp,pgcrypto,pg_trgm,btree_gin,btree_gist) as the Postgres superuser, keeping the Lakekeeper role itself unprivileged. - Generate
/etc/lakekeeper/encryption.keyonce and write/etc/lakekeeper/lakekeeper.env. - Run
lakekeeper migrateto create the catalog schema. This is idempotent, so it runs on every pass rather than being guarded. - Enable and start the
lakekeeperservice, and restart it when its environment file is newer than the running process. - Wait for
/healthto reportok, rather than for the port alone to accept connections. - Bootstrap the catalog, create the warehouse over the object store or bring an existing warehouse's storage profile up to date, and pre-create the Iceberg namespace.
Role Dependencies
This role requires the following roles for normal operation:
role_configprovides shared configuration variables to the role.install_lakekeeperinstalls the Lakekeeper and Postgres packages.
When to Use
Execute this role immediately after install_lakekeeper, on the same
dedicated lakekeeper host, which init_server has prepared and checked
like every other host.
- hosts: lakekeeper
collections:
- pgedge.platform
roles:
- install_repos
- install_lakekeeper
- setup_lakekeeper
Configuration
This role uses the following parameters from the inventory file:
| Parameter | Use Case |
|---|---|
lakekeeper_host |
Address Lakekeeper is reached at. Shared with setup_coldfront, which needs to know where the catalog is. |
lakekeeper_bind_ip |
Address the Lakekeeper service binds. Defaults to lakekeeper_host, and differs only where the bind address is not the reachable one. |
lakekeeper_port |
Port Lakekeeper's REST catalog and management API listen on. Shared with setup_coldfront. |
lakekeeper_metrics_port |
Port for Lakekeeper's Prometheus metrics endpoint. Written into the service environment here. |
lakekeeper_warehouse |
Name of the Iceberg warehouse this role creates over the object store. Shared with setup_coldfront. |
pg_port |
Port the catalog's Postgres instance listens on, at the loopback address only. |
lakekeeper_pg_data |
Data directory of the catalog's Postgres instance. Defaults to lakekeeper beside the platform's usual data directories. |
lakekeeper_db |
Name of the private catalog database. |
lakekeeper_db_user |
Database role that owns the catalog database. |
lakekeeper_db_password |
Password for lakekeeper_db_user. Declared in role_config, and init_server refuses the default. |
lakekeeper_namespace |
Iceberg namespace pre-created in the warehouse. |
coldfront_s3_* |
The S3-compatible object store the warehouse is created over. See the ColdFront Configuration reference. |
See the Configuration Reference for descriptions and defaults.
How It Works
The catalog's Postgres instance
The catalog's Postgres instance belongs to this role alone. It is not
created by setup_postgres and shares nothing with a pgEdge Distributed
Postgres node's: the role initializes it with initdb, with peer
authentication over the Unix socket and SCRAM over TCP, and runs it with a
service unit of its own, the same way on Debian and RHEL. A changed unit,
such as a new pg_port, restarts the instance.
On Debian, installing the server package creates and starts a main
cluster on 5432. Where pg_port is 5432, the role stops that cluster and
marks it manual, so it does not take the port back at boot.
Reachability
Both the catalog's own Postgres instance and the object store have to answer before this role does anything else with them: creating a warehouse makes Lakekeeper write to the bucket, so a check here turns "the warehouse create failed" into a named failure with an address attached, rather than a generic timeout later.
Health, not just an open port
/health is checked rather than the port alone, because a port that merely
accepts connections proves nothing about what is behind it. On a host
where SeaweedFS's own built-in Iceberg REST catalog collides with
Lakekeeper's port, both answer on the same port, and only /health's
response body tells them apart — pgedge-lakekeeper 0.13.1 answers unknown
paths with an empty body, while SeaweedFS's Iceberg catalog answers with
a JSON 404.
{"error":{"message":"Path not found","type":"NotFound","code":404}}
Getting that body back from /health on the expected Lakekeeper host means
the responder is a different service.
Lakekeeper's unit is Type=exec
systemd reports a successful start before the process actually binds its port. A Lakekeeper that then fails to bind restarts until it trips the start limit, and systemd refuses to start it again until that is cleared — which is why this role clears any failed unit state before starting it.
Bootstrap and warehouse creation
A first bootstrap answers 204. A repeat answers 400 with type
CatalogAlreadyBootstrapped, which is the only 400 this role accepts;
any other 400 fails the play. The warehouse's storage credential field
names come from the management API of the packaged Lakekeeper 0.13.1
(S3AccessKeyCredential) — an older aws-access-key-id /
aws-secret-access-key spelling exists in some ColdFront documentation,
but this Lakekeeper version does not accept it.
The warehouse request carries the object-store secret, so its output is hidden. When Lakekeeper refuses the warehouse, the task after it fails instead, with the HTTP status and Lakekeeper's explanation from the response body.
When coldfront_s3_endpoint is empty, the storage profile leaves out
endpoint altogether and uses the aws flavor, so Lakekeeper derives
the endpoint from coldfront_s3_region. Lakekeeper reads endpoint as a
URL, and rejects an empty string with a 422 for the whole request. Any
other endpoint is sent as given, with the s3-compat flavor.
Keeping the warehouse up to date
On a later run the warehouse already exists, and the role compares the storage profile Lakekeeper reports for it with the one the inventory describes: the bucket, region, path-style addressing, flavor and endpoint. Where any differ, it sends the whole profile again, with the credential, since Lakekeeper removes a warehouse's credential when an update leaves it out. Lakekeeper checks the new profile by writing to the bucket, as it does on create, and refuses a changed bucket, or a changed region without an endpoint, rather than lose the data the warehouse holds. The task after the update reports a refusal with Lakekeeper's explanation.
Lakekeeper never returns a stored credential, so a change to
coldfront_s3_access_key or coldfront_s3_secret_key alone is not
detected and does not reach the warehouse. The pgEdge nodes take the new
credential on their next run of setup_coldfront.
Restarts
Whether Lakekeeper is restarted is decided by when it last started, as
setup_etcd decides for etcd, not by whether this run changed its
environment file. An environment file newer than the running process is
one Lakekeeper has not read, so a run that wrote the file and failed before
the restart is caught by the next run.
Artifacts
| File | New / Modified | Explanation |
|---|---|---|
/etc/systemd/system/lakekeeper-postgres.service |
New | Runs the catalog's Postgres instance. |
lakekeeper_pg_data |
New | The catalog's Postgres data directory. |
/etc/lakekeeper/encryption.key |
New | Encrypts the S3 credential Lakekeeper stores for the warehouse. Generated once; a new key makes existing warehouse secrets unrecoverable. |
/etc/lakekeeper/lakekeeper.env |
New | Database DSN, encryption key, bind address, and ports the systemd unit reads. |
Idempotency
This role is idempotent and safe to re-run on inventory hosts. The warehouse and namespace creation calls check for an existing entry with the same name first and skip creation when found. An existing warehouse's storage profile is updated only where it differs from the inventory. The environment file is only rewritten when its content changes, and the service only restarted when that file is newer than the running process.
No authorization backend
Lakekeeper runs with no authorization backend, so anything that can
reach lakekeeper_port has full use of the catalog API, including
reading warehouse definitions. Firewall it to the hosts that need it.