The realistic case: several components, a dependency graph, mixed process and
container workloads, and one dependency that must not cascade.
The shape
flowchart TB
INF["influxdb"]
API["api"]
TEL["telemetry"]
WEB["web"]
INF -- "hard" --> API
INF -- "ordering" --> TEL
API -- "hard" --> WEB
api is hard on influxdb: it holds a connection pool, so it must restart
when the database restarts.
telemetry is ordering on influxdb: it writes metrics but reconnects on its
own, so restarting the database should not disturb it.
web is hard on api: it is a container serving a front end configured with
the API’s address.
Picking ordering for the telemetry agent is the decision that stops one database
restart from rippling through the whole device.
The recipes
recipes/com.acme.influxdb.toml — a database with a slow start:
[metadata]name="com.acme.influxdb"version="2.7.5"[[artifacts]]uri="https://artifacts.acme.com/influxdb-2.7.5-linux-arm64.tar.gz"sha256="4d1b…"unpack=true[lifecycle.install]script="mkdir -p data && chmod +x ./influxd"[lifecycle.run]restart_policy="always"[lifecycle.run.exec]command="./influxd"args=["--bolt-path","data/influxd.bolt","--engine-path","data/engine"][lifecycle.run.security]user="influx:influx"no_new_privileges=truecapabilities=[][lifecycle.run.health]check="http://127.0.0.1:8086/health"interval="10s"timeout="3s"failure_threshold=6
failure_threshold = 6 with a 10 s interval gives it a minute to come up before the
apply gives up — a database that replays a write-ahead log needs that room.
Container components are confined through [lifecycle.run.container] — user,
privileged, resource limits. Putting [lifecycle.run.security] on a container
component is rejected when the plan is applied, and the reverse is too. See
Process privileges.
keystonectl apply-dry plan.toml # validates and prints the plan, changes nothingkeystonectl graph # the resolved orderkeystonectl apply plan.toml
The dry run loads every recipe, verifies every signature and computes the reconcile —
without installing anything. It is the cheapest way to catch a bad dependency or an
unsigned artifact.
api and telemetry start in parallel — they are both in layer 1 — which on a
four-core box is the difference between a 40-second and a 90-second deployment.
Prove the cascade behaves
# what a database restart would touch, without doing itcurl -s -X POST "localhost:8080/v1/components/influxdb:restart?dry=true"| jq
Bump com.acme.api to 1.5.0 in its recipe, then re-apply:
keystonectl apply plan.toml
[agent] reconcile plan stop_order=[web api] start_order=[api web] no_touch=[influxdb telemetry]
[agent] component=influxdb msg=reusing existing running instance (no restart)
[agent] component=telemetry msg=reusing existing running instance (no restart)
The database and the telemetry agent are never touched; web restarts because it is
hard on api. That is the whole point of reconciling rather than restarting.