Skip to content

Commit b2fc0de

Browse files
committed
bootstrap: add Debian low-ram tuning
1 parent a983877 commit b2fc0de

File tree

1 file changed

+185
-1
lines changed

1 file changed

+185
-1
lines changed

terraform/bootstrap/bootstrap.py

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@
2121
SWAPFILE = "/swapfile"
2222
SWAP_SIZE_GB = 4
2323
SWAP_SWAPPINESS = 10
24+
LOWRAM_JOURNALD_DROPIN = "/etc/systemd/journald.conf.d/99-lowram.conf"
25+
LOWRAM_SYSCTL = "/etc/sysctl.d/99-lowram.conf"
26+
LOWRAM_ZRAM_DEFAULTS = {"algo": "zstd", "percent": 25}
27+
LOWRAM_DISABLE_SERVICES = [
28+
"avahi-daemon",
29+
"ModemManager",
30+
"bluetooth",
31+
"cups",
32+
"packagekit",
33+
"rsyslog",
34+
]
35+
LOWRAM_MASK_TARGETS = [
36+
"sleep.target",
37+
"suspend.target",
38+
"hibernate.target",
39+
"hybrid-sleep.target",
40+
]
2441

2542

2643
def encryption_config_has_keys(path: str) -> bool:
@@ -117,6 +134,168 @@ def run(
117134
return result
118135

119136

137+
def _write_file(path: str, content: str, mode: int = 0o644) -> None:
138+
os.makedirs(os.path.dirname(path), exist_ok=True)
139+
with open(path, "w", encoding="utf-8") as fh:
140+
fh.write(content)
141+
os.chmod(path, mode)
142+
143+
144+
def read_os_release(path: str = "/etc/os-release") -> dict[str, str]:
145+
data: dict[str, str] = {}
146+
try:
147+
with open(path, "r", encoding="utf-8") as fh:
148+
for line in fh:
149+
line = line.strip()
150+
if not line or line.startswith("#") or "=" not in line:
151+
continue
152+
key, value = line.split("=", 1)
153+
data[key] = value.strip().strip('"')
154+
except OSError:
155+
return data
156+
return data
157+
158+
159+
def is_debian_like(os_info: dict[str, str]) -> bool:
160+
if os_info.get("ID") == "debian":
161+
return True
162+
like = os_info.get("ID_LIKE", "")
163+
return "debian" in like.split() or "debian" in like
164+
165+
166+
def service_exists(service: str) -> bool:
167+
unit = f"{service}.service"
168+
for base in ("/etc/systemd/system", "/lib/systemd/system", "/usr/lib/systemd/system"):
169+
if os.path.exists(os.path.join(base, unit)):
170+
return True
171+
return False
172+
173+
174+
def command_exists(command: str) -> bool:
175+
return shutil.which(command) is not None
176+
177+
178+
def disable_services(services: Sequence[str]) -> None:
179+
for service in services:
180+
run(f"systemctl disable --now {service}", check=False, quiet=True, log_ok=False)
181+
182+
183+
def mask_targets(targets: Sequence[str]) -> None:
184+
if targets:
185+
joined = " ".join(shlex.quote(target) for target in targets)
186+
run(f"systemctl mask {joined}", check=False, quiet=True, log_ok=False)
187+
188+
189+
def disable_swapfile(swapfile: str) -> None:
190+
run(f"swapoff {swapfile}", check=False, quiet=True, log_ok=False)
191+
try:
192+
with open("/etc/fstab", "r", encoding="utf-8") as fh:
193+
lines = fh.readlines()
194+
except OSError:
195+
lines = []
196+
kept = [line for line in lines if swapfile not in line]
197+
if kept != lines:
198+
with open("/etc/fstab", "w", encoding="utf-8") as fh:
199+
fh.writelines(kept)
200+
if os.path.exists(swapfile):
201+
try:
202+
os.remove(swapfile)
203+
except OSError:
204+
pass
205+
if os.path.exists("/etc/sysctl.d/99-swap.conf"):
206+
try:
207+
os.remove("/etc/sysctl.d/99-swap.conf")
208+
except OSError:
209+
pass
210+
211+
212+
def ensure_lowram_tuning(os_info: dict[str, str], swap_swappiness: int) -> bool:
213+
setting = os.environ.get("LOWRAM_TUNE", "auto").lower()
214+
if setting in ("0", "false", "no", "off"):
215+
log("[lowram] disabled by env", level="INFO")
216+
return False
217+
if setting in ("1", "true", "yes", "on"):
218+
enabled = True
219+
else:
220+
enabled = os_info.get("ID") == "debian"
221+
if not enabled:
222+
return False
223+
224+
if not is_debian_like(os_info):
225+
log("[lowram] requested but OS is not Debian-like; skipping", level="WARN")
226+
return False
227+
228+
zram_setting = os.environ.get("LOWRAM_ZRAM_ENABLED", "1").lower()
229+
zram_enabled = zram_setting not in ("0", "false", "no", "off")
230+
zram_algo = os.environ.get("LOWRAM_ZRAM_ALGO", LOWRAM_ZRAM_DEFAULTS["algo"])
231+
zram_percent_raw = os.environ.get("LOWRAM_ZRAM_PERCENT", str(LOWRAM_ZRAM_DEFAULTS["percent"]))
232+
try:
233+
zram_percent = int(zram_percent_raw)
234+
except ValueError:
235+
log(f"[lowram] invalid LOWRAM_ZRAM_PERCENT={zram_percent_raw!r}; using default", level="WARN")
236+
zram_percent = LOWRAM_ZRAM_DEFAULTS["percent"]
237+
238+
swappiness_value = 180 if zram_enabled else swap_swappiness
239+
240+
_write_file(
241+
LOWRAM_JOURNALD_DROPIN,
242+
"[Journal]\nStorage=volatile\nRuntimeMaxUse=16M\nSystemMaxUse=0\n",
243+
)
244+
_write_file(
245+
LOWRAM_SYSCTL,
246+
textwrap.dedent(
247+
f"""\
248+
vm.swappiness={swappiness_value}
249+
vm.vfs_cache_pressure=200
250+
vm.dirty_ratio=5
251+
vm.dirty_background_ratio=3
252+
vm.min_free_kbytes=65536
253+
"""
254+
),
255+
)
256+
run("sysctl --system", check=False, quiet=True, log_ok=False)
257+
258+
disable_services(LOWRAM_DISABLE_SERVICES)
259+
mask_targets(LOWRAM_MASK_TARGETS)
260+
261+
if zram_enabled:
262+
run("apt-get install -y zram-tools", check=False, quiet=True, log_ok=False)
263+
_write_file(
264+
"/etc/default/zramswap",
265+
f"ALGO={zram_algo}\nPERCENT={zram_percent}\n",
266+
)
267+
if service_exists("zramswap"):
268+
run("systemctl restart zramswap", check=False, quiet=True, log_ok=False)
269+
270+
if service_exists("systemd-journald"):
271+
run("systemctl restart systemd-journald", check=False, quiet=True, log_ok=False)
272+
273+
if service_exists("systemd-oomd"):
274+
run("systemctl enable --now systemd-oomd", check=False, quiet=True, log_ok=False)
275+
276+
_write_file(
277+
"/etc/docker/daemon.json",
278+
textwrap.dedent(
279+
"""\
280+
{
281+
"log-driver": "local",
282+
"log-opts": {
283+
"max-size": "10m",
284+
"max-file": "3"
285+
},
286+
"live-restore": true,
287+
"userland-proxy": false
288+
}
289+
"""
290+
),
291+
)
292+
if service_exists("docker") or command_exists("dockerd") or command_exists("docker"):
293+
run("systemctl restart docker", check=False, quiet=True, log_ok=False)
294+
295+
run("apt-get purge -y snapd", check=False, quiet=True, log_ok=False)
296+
return zram_enabled
297+
298+
120299
def wait_for_k8s():
121300
for _ in range(60):
122301
if subprocess.run("kubectl get nodes", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
@@ -465,7 +644,12 @@ def main():
465644
os.environ["KUBECONFIG"] = "/etc/rancher/k3s/k3s.yaml"
466645
allow_fresh_env = os.environ.get("ALLOW_FRESH_BOOTSTRAP", "").lower() in ("1", "true", "yes")
467646
ensure_data_mount(env_name, luks_key_url, luks_key_access, luks_key_secret)
468-
if swap_size_gb <= 0:
647+
os_info = read_os_release()
648+
zram_enabled = ensure_lowram_tuning(os_info, swap_swappiness)
649+
if zram_enabled:
650+
log("[swap] zram enabled; skipping swapfile setup", level="INFO")
651+
disable_swapfile(swap_file)
652+
elif swap_size_gb <= 0:
469653
log("SWAP_SIZE_GB <= 0; skipping swap setup", level="WARN")
470654
else:
471655
ensure_swap(swap_file, swap_size_gb, swap_swappiness)

0 commit comments

Comments
 (0)