Facts¶
facts tells a recipe what the machine it runs on has: CPUs, memory, disk,
addresses, whether it is a virtual machine or a container. Use facts to size
configuration to the machine (worker counts, caches, buffers), to bind to
the right address, and to leave out what makes no sense in a container.
sadame facts shows what a recipe will see on each host.
New in 0.3.0
Minimal example¶
file "/etc/demo-app/app.conf", content: "workers = #{facts[:cpus] * 2}\n"
Templates see the same facts:
listen <%= facts[:ipv4] %>:443;
The facts¶
| Fact | Type | Example | Read from (Linux) |
|---|---|---|---|
hostname |
String | "web1" |
uname -n |
fqdn |
String | "web1.example.com" |
hostname -f; hostname when that fails |
arch |
String | "x86_64", "aarch64" |
uname -m |
kernel |
String | "6.1.0-26-amd64" |
uname -r |
cpus |
Integer | 4 |
nproc: the CPUs sadame may run on |
memory_mb |
Integer | 7941 |
MemTotal in /proc/meminfo |
swap_mb |
Integer | 2047 |
SwapTotal in /proc/meminfo |
root_disk_gb |
Integer | 39 |
size of the file system holding /, from df -Pk / |
ipv4 |
String or nil | "203.0.113.10" |
source address of the route to the internet (ip -4 route get); nothing is sent |
default_interface |
String or nil | "eth0" |
interface of that route |
ipv4_addresses |
Array of Strings | ["203.0.113.10", "10.8.0.1"] |
every IPv4 address except 127.0.0.0/8 (ip -4 -o addr show) |
ipv6_addresses |
Array of Strings | ["2001:db8::10"] |
global IPv6 addresses, from /proc/net/if_inet6 |
virtualization |
String or nil | "kvm", "docker" |
systemd-detect-virt; nil on bare metal |
container |
true or false |
false |
systemd-detect-virt --container |
service_manager |
String or nil | "systemd", "openrc" |
what the service resource would use |
machine_id |
String or nil | "4c2a0f5e9b1d..." |
/etc/machine-id: unique per installation, never changes |
Where ip is not installed, as in many container images, the addresses
come from /proc/net/fib_trie and /proc/net/route; where
systemd-detect-virt is missing, as on Alpine, /.dockerenv,
/run/.containerenv and the DMI vendor in /sys/class/dmi/id stand in.
For local runs on macOS, sadame reads sysctl, route and ifconfig;
virtualization and machine_id are nil there.
How facts are read¶
- When asked for. Each fact is read the first time the recipe, or a template, asks for it, then kept for the run. A recipe that uses no facts runs no extra commands.
- On the machine itself. With
-hor-g, the recipe runs on the target, so the facts are the target's.hosts.rbis evaluated on the machine running sadame and has nofacts. - Never failing. A fact that cannot be read is nil (an empty Array for
the address lists). Write
facts[:ipv4] || "127.0.0.1"when a recipe must work without it. - Spelled right.
facts[:cpu]is an error that lists every fact, so a typo stopsplaninstead of becoming nil.
Facts are read on every run, so a configuration made from them follows the
machine: after a memory upgrade, plan shows the new value and drift
reports it until it is applied.
Seeing the facts¶
sadame facts prints what recipes see on this machine; with -h, -g or
--limit it connects to those hosts, as plan does, and prints theirs.
--json prints them as JSON (see JSON output).
$ sadame facts
sadame 0.5.1 facts on local (Debian GNU/Linux 12 (bookworm))
hostname 3207ad2a2e73
fqdn 3207ad2a2e73
arch x86_64
kernel 7.2.12-orbstack-00394-gbecdd6130068
cpus 10
memory_mb 8003
swap_mb 9027
root_disk_gb 140
ipv4 192.168.215.2
default_interface eth0
ipv4_addresses 192.168.215.2
ipv6_addresses (none)
virtualization docker
container true
service_manager (none)
machine_id (none)
$ sadame facts -h web1.example.com
[web1.example.com] sadame 0.5.1 facts on web1.example.com (Debian GNU/Linux 12 (bookworm))
[web1.example.com]
[web1.example.com] hostname web1
[web1.example.com] fqdn web1
[web1.example.com] arch aarch64
[web1.example.com] kernel 7.2.12-orbstack-00394-gbecdd6130068
[web1.example.com] cpus 10
[web1.example.com] memory_mb 8003
[web1.example.com] swap_mb 9027
[web1.example.com] root_disk_gb 140
[web1.example.com] ipv4 192.168.107.3
[web1.example.com] default_interface eth0
[web1.example.com] ipv4_addresses 192.168.107.3
[web1.example.com] ipv6_addresses (none)
[web1.example.com] virtualization docker
[web1.example.com] container true
[web1.example.com] service_manager systemd
[web1.example.com] machine_id d9eb31414e07441d850ab7344644b841
Hosts
web1.example.com ok
facts on 1 host: 1 ok. Finished in 0.22s.
Scenarios¶
# What a recipe can know about the machine it runs on. Each fact is read on
# that machine the first time the recipe asks for it.
# A login banner that says what this machine is; the template reads facts too.
file "/etc/motd", template: "motd.erb", mode: "0644"
# An application sized to the machine: two workers per CPU, an eighth of the
# memory for its cache, listening on the address of the default route.
directory "/etc/demo-app", mode: "0755"
file "/etc/demo-app/app.conf",
content: "workers = #{facts[:cpus] * 2}\n" \
"cache_mb = #{facts[:memory_mb] / 8}\n" \
"listen = #{facts[:ipv4] || "127.0.0.1"}:8080\n",
mode: "0644"
# A nightly job at a minute of its own, so that a fleet does not start it at
# the same moment. machine_id does not change, so neither does the minute.
seed = facts[:machine_id] || facts[:hostname]
minute = seed.bytes.inject(0) { |sum, byte| sum + byte } % 60
file "/etc/demo-app/nightly.schedule", content: "#{minute} 4 * * *\n", mode: "0644"
# Kernel settings belong to the host, not to a container that shares its kernel.
unless facts[:container]
directory "/etc/sysctl.d", mode: "0755"
file "/etc/sysctl.d/90-demo-app.conf", content: "net.core.somaxconn = 4096\n", mode: "0644"
end
<%= facts[:fqdn] %>, <%= facts[:virtualization] || "no virtualization detected" %>
<%= facts[:cpus] %> CPUs, <%= facts[:memory_mb] %> MB of memory, <%= facts[:root_disk_gb] %> GB on /
<%= facts[:ipv4] || "no IPv4 address" %> on <%= facts[:default_interface] || "no default route" %>
Managed by sadame.
Planned in a container¶
The container has no /etc/machine-id, so the minute comes from the
hostname, and container is true, so the kernel setting is left out:
$ sudo sadame plan examples/facts
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
~ file[/etc/motd]
--- /etc/motd
+++ /etc/motd (desired)
@@ -1,7 +1,4 @@
-
-The programs included with the Debian GNU/Linux system are free software;
-the exact distribution terms for each program are described in the
-individual files in /usr/share/doc/*/copyright.
-
-Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
-permitted by applicable law.
+3207ad2a2e73, docker
+10 CPUs, 8003 MB of memory, 140 GB on /
+192.168.215.2 on eth0
+Managed by sadame.
+ directory[/etc/demo-app]
state: absent -> present
mode: 0755
+ file[/etc/demo-app/app.conf]
state: absent -> present
mode: 0644
--- /dev/null
+++ /etc/demo-app/app.conf (desired)
@@ -0,0 +1,3 @@
+workers = 20
+cache_mb = 1000
+listen = 192.168.215.2:8080
+ file[/etc/demo-app/nightly.schedule]
state: absent -> present
mode: 0644
--- /dev/null
+++ /etc/demo-app/nightly.schedule (desired)
@@ -0,0 +1 @@
+25 4 * * *
Plan: 3 to create, 1 to update, 0 to remove, 0 to run, 0 unchanged.
Sizing PostgreSQL to the machine¶
examples/05-postgres sets shared_buffers to a quarter of the memory
unless vars set it:
shared_buffers = vars[:shared_buffers] || "#{facts[:memory_mb] / 4}MB"
Common mistakes¶
- Facts in
hosts.rb. The inventory is evaluated before sadame connects to anything; decide per host in the recipe instead. - Memory and CPUs in a container.
/proc/meminfoin a container shows the host's memory, not the container's limit, andcpuscounts the CPUs it may be scheduled on, which ignores CPU quotas such as Docker's--cpus. Size containers with their limits in mind. - Assuming an address. A machine without a default route has no
ipv4; one with several networks has severalipv4_addresses. Pick the one you mean, or set it invars. - Using
hostnameas an ID. Hostnames get reused and changed;machine_idstays the same for the life of the installation.
See also¶
- Recipe basics for
vars,platformand templates. - Inventory for per-host vars, set in
hosts.rbrather than read from the machine.