keelctl CLI reference
keelctl talks to keel-agentd's local HTTP API over its Unix socket, or routes through keel-controlplane to a specific node.
Global flags
| Flag | Default | Meaning |
|---|---|---|
--socket PATH | /var/run/keel-agentd.sock | Path to the keel-agentd Unix socket to talk to. Ignored if --control-plane-addr is given. |
--control-plane-addr ADDR | none | Address of a keel-controlplane to route through instead of a local socket. Alone, the control plane schedules the request onto whichever alive node has the most CPU/memory headroom; combined with --node, it routes to that exact node instead. |
--node ID | none | Pins the request to a specific node's keel-agentd instead of letting the control plane schedule it. Requires --control-plane-addr. |
--tls-ca-file PATH | none | The cluster's private CA certificate. Required, together with the other three --tls-*-file flags below, whenever --control-plane-addr is given. |
--tls-cert-file PATH | none | This caller's own certificate, signed by the cluster CA (one per human operator, issued by scripts/gen-certs.sh client <name>; see Certificates below). |
--tls-key-file PATH | none | The private key matching --tls-cert-file. |
--tls-crl-file PATH | none | The cluster's certificate revocation list. Checked on every connection in both directions; a revoked certificate, either the caller's own or the control plane's, fails the TLS handshake. |
Giving --node without --control-plane-addr is a usage error. Giving --control-plane-addr alone is valid: it schedules automatically. Giving neither preserves keelctl's original behavior exactly: talk directly to --socket (or its default).
Giving --control-plane-addr without all four --tls-*-file flags is also a usage error, and fails locally before keelctl ever reaches the network. Every control-plane-routed connection is mutual TLS: keelctl proves its own identity with --tls-cert-file/--tls-key-file, and verifies the control plane's certificate against --tls-ca-file and --tls-crl-file, the same way the control plane verifies keelctl's.
Certificates
Control-plane-routed mode never involves keelctl generating or handling key material on its own: an operator generates a private CA once, then issues one client certificate per human operator and one dual-use certificate per node or control-plane instance, using scripts/gen-certs.sh.
./scripts/gen-certs.sh init # generate the CA once ./scripts/gen-certs.sh client alice # issue alice's own operator certificate ./scripts/gen-certs.sh revoke alice # eject a compromised identity, refreshes crl.pem ./scripts/gen-certs.sh crl # refresh crl.pem with no status change
Reissuing an existing name (client alice again) rotates it: the new certificate is issued first, and only once that succeeds is the previous certificate revoked and crl.pem regenerated, so a failed reissue never strands an identity with zero valid certificates. keel-controlplane and keel-agentd both reload their certificate, key, CA, and CRL files from disk on a background timer (every 30 seconds), so a rotated certificate or a refreshed CRL takes effect without restarting either daemon; a keelctl invocation, being a single short-lived process, always reads the current files fresh on every run.
keelctl apply -f FILE
Reads a spec YAML file, validates it locally, and sniffs kind to decide where it goes: a kind: Jail sends PUT /jails/<name>; a kind: Service sends PUT /services/<name>; a kind: Ingress sends PUT /ingress/<name> (the name comes from the spec's own metadata.name in every case). Routed through a control plane with no --node, a kind: Jail's PUT /jails/<name> is scheduled automatically onto the least-loaded alive node; with --node, it's PUT /nodes/<node>/jails/<name> instead, landing on exactly that node. A kind: Service always goes through the control plane's own scheduler and same-service spreading; --node does not apply to it. A kind: Ingress is never routed through a control plane at all: it always applies directly to --socket's node, regardless of --control-plane-addr/--node, since an Ingress and its backend Service must already be co-located on one node (see the HTTP API reference).
keelctl apply -f jail.yaml keelctl --control-plane-addr 10.0.0.2:7620 apply -f jail.yaml keelctl --control-plane-addr 10.0.0.2:7620 --node node-4 apply -f jail.yaml keelctl --control-plane-addr 10.0.0.2:7620 apply -f service.yaml keelctl apply -f ingress.yaml
keelctl get [name]
With no argument, lists every jail keel-agentd (or the routed-to node) is tracking (GET /jails, or GET /nodes/<node>/jails when routed by name); this never lists services or ingresses, see the notes below. With a name, tries GET /jails/<name> first, then falls back to GET /services/<name> on a 404 (a service's currently-healthy replicas), then to GET /ingress/<name> on a further 404, since all three share one flat namespace and a 404 on one path is a cheap, unambiguous signal to try the next. Through a control plane with no --node, the jail-path lookup resolves against whichever node the jail was placed on; with --node, GET /nodes/<node>/jails/<name>. The Ingress fallback leg always targets the local --socket, the same reasoning apply above uses.
keelctl get keelctl get web-1 keelctl --control-plane-addr 10.0.0.2:7620 get web-1 keelctl --control-plane-addr 10.0.0.2:7620 --node node-4 get web-1 keelctl --control-plane-addr 10.0.0.2:7620 get web # falls back to the service, if web-1 above doesn't exist keelctl get blog # falls back further to the Ingress, if neither above exists
No bare-collection verb for services. keel-controlplane's GET /services (every known service, including each one's virtual IP and port, see the HTTP API reference) has no corresponding keelctl command: get either lists jails (no argument) or resolves a single name (jail, falling back to service, falling back to ingress). Reaching a service's VIP today means a direct HTTP call against the control plane.
keelctl delete NAME
Tears down the named jail, its ZFS dataset, its network attachment, and its resource limits (DELETE /jails/<name>, or DELETE /nodes/<node>/jails/<name> when routed by name), falling back to DELETE /services/<name> then DELETE /ingress/<name> on successive 404s the same way get does, tearing down every one of a service's replicas (and, once its next heartbeat lands on every node, its virtual IP alias), or removing an Ingress's server block from nginx on the node's next reconcile pass. Through a control plane with no --node, deleting a jail also clears its recorded placement.
keelctl delete web-1 keelctl --control-plane-addr 10.0.0.2:7620 delete web-1 keelctl --control-plane-addr 10.0.0.2:7620 --node node-4 delete web-1 keelctl --control-plane-addr 10.0.0.2:7620 delete web # falls back to the service, if web-1 above doesn't exist keelctl delete blog # falls back further to the Ingress, if neither above exists
keelctl delete-volume NAME
Destroys a named volume's ZFS dataset for good (DELETE /volumes/<name>, or DELETE /nodes/<node>/volumes/<name> when routed with --node). A volume is never scheduled on its own, so there's no equivalent of automatic node selection here the way apply has: routed mode always needs an explicit --node naming the node the volume's dataset actually lives on. Fails with a clean error (rather than force-unmounting anything) if a jail still has the volume mounted; delete that jail first.
keelctl delete-volume web-data keelctl --control-plane-addr 10.0.0.2:7620 --node node-4 delete-volume web-data
There is no keelctl get-volume or bare-collection verb for volumes; see the HTTP API reference for the full GET/DELETE /volumes/<name> surface, reachable directly over HTTP.
Errors
On any non-2xx response, keelctl prints the server's error message to stderr and exits non-zero. See the HTTP API reference for the specific error cases each endpoint can return.
Keel