feat: apply memory shrink + daemon.json — ground truth update [#731]

11 apps reslidered via API (53.4->29.0GB explicit limits); daemon.json
live-restore + nofile defaults applied (log keys banned: unit flag
conflict — see #731 incident note). perf/apply-memory-shrink.sh codifies
the change. All 159 apps verified running.
Meat: https://projects.knownelement.com/issues/731#note-3929
This commit is contained in:
2026-09-02 19:48:45 -05:00
parent d9d512c9f1
commit 1267e60e8e
5 changed files with 76 additions and 20 deletions
+17 -11
View File
@@ -11,7 +11,7 @@ Postgres/MySQL/mail containers, monitored by Uptime Kuma (Cloudron group).
| Path | What | | Path | What |
|------|------| |------|------|
| `perf/` | [#685](https://projects.knownelement.com/issues/685) perf work: memory census, pg role-cap SQL, applied sysctl, planned docker daemon.json | | `perf/` | [#685](https://projects.knownelement.com/issues/685) perf work: memory census, pg role-cap SQL, applied sysctl, applied `daemon.json`, `apply-memory-shrink.sh` |
| `monitoring/` | Uptime Kuma Cloudron app coverage — 55-app inventory + sync checker (moved from PFVCluster, #435/#685/#727) | | `monitoring/` | Uptime Kuma Cloudron app coverage — 55-app inventory + sync checker (moved from PFVCluster, #435/#685/#727) |
| `ansible/` | Reserved — AWX/Ansible IaC convergence ([#454](https://projects.knownelement.com/issues/454)); playbooks land here as manual changes are codified | | `ansible/` | Reserved — AWX/Ansible IaC convergence ([#454](https://projects.knownelement.com/issues/454)); playbooks land here as manual changes are codified |
| `scripts/` | Rule engine + git hooks (enforcement layer, ADOPTING pattern) | | `scripts/` | Rule engine + git hooks (enforcement layer, ADOPTING pattern) |
@@ -25,16 +25,22 @@ Postgres/MySQL/mail containers, monitored by Uptime Kuma (Cloudron group).
idle-in-transaction timeout 15min. Victim `too many clients` FATALs → zero. idle-in-transaction timeout 15min. Victim `too many clients` FATALs → zero.
- Host sysctl: `vm.swappiness=10` (`perf/sysctl/`). THP already `never`, virtio - Host sysctl: `vm.swappiness=10` (`perf/sysctl/`). THP already `never`, virtio
scheduler `none` — optimal, left alone. scheduler `none` — optimal, left alone.
- **`/etc/docker/daemon.json` is still ABSENT on the host** (`live-restore=false`, - **`/etc/docker/daemon.json` APPLIED 2026-09-02** (#731): `live-restore: true`
containers default to nofile=1024 unless bridged). The postgres fd bridge (future dockerd restarts are zero-downtime) + `default-ulimits nofile
(prlimit 262144) is EPHEMERAL — lost on any postgres container recreate. 262144` (every container start gets real fd limits — the postgres prlimit
Applying `perf/docker-daemon.json.planned` needs ONE planned dockerd bounce bridge is no longer needed; `perf/docker-daemon.json` is the SoR copy).
(restarts all ~300 containers) in a human-chosen window. NOTE: this host's dockerd runs `--log-driver=journald` as a unit flag —
- Memory sliders: shrink table on t/316 (~28GB headroom). Do NOT shrink daemon.json must NOT set log-driver/log-opts or dockerd refuses to start
`bi.knownelement.com` or platform services (Cloudron auto-sizes those). (bit us once, see #731). Container log rotation is journald's job here.
- **Memory shrink table APPLIED 2026-09-02** (#731) via the panel API
(`perf/apply-memory-shrink.sh`): 11 apps resliders, explicit limits
53.4 GB → 29.0 GB (~24 GB of the ~28 GB headroom reclaimed). Skips:
photos (manifest floor 3.5 GB), `bi.knownelement.com` + platform services
(do NOT shrink). All 159 apps verified running post-change.
## Access ## Access
SSH `root@my.knownelement.com` is the only access path — routed through the SSH `root@my.knownelement.com` — routed through the KNEL/PFVCluster
KNEL/PFVCluster `tests/remote.sh` chokepoint (see AGENTS.md). Cloudron API `tests/remote.sh` chokepoint (see AGENTS.md). Panel API token issued
token pending; once issued, refs live in `~/.creds/cloudron.env`, never here. 2026-09-02 (`~/.creds/cloudron.env`, refs only in this repo) — prefer the
API over SSH for reads/limits; base image rebuilds (#710) still need SSH.
+7 -5
View File
@@ -7,8 +7,9 @@ Last updated: 2026-09-02
| ticket | state | item | | ticket | state | item |
|--------|-------|------| |--------|-------|------|
| #727 | 🔄 | repo bootstrap + perf script capture | | #727 | | repo bootstrap + perf script capture + API token received (suggest close) |
| #685 | | founder soak — evening storm window is the real test; caps stay until tymeslot ships a POOL_SIZE env | | #731 | | memory shrink applied (53.4→29.0GB) + daemon.json live-restore bounce (suggest close after soak) |
| #685 | ⏳ | tonight 20:00-04:00 CDT storm window is the soak test; caps stay till tymeslot POOL_SIZE |
| #626 | ⏳ | Cloudron proxying (Grocy multi-user + HA general access) | | #626 | ⏳ | Cloudron proxying (Grocy multi-user + HA general access) |
| #710 | ⏳ | custom Ubuntu 24.04 base image for Cloudron hosts | | #710 | ⏳ | custom Ubuntu 24.04 base image for Cloudron hosts |
| #723 | ⏳ | linkwarden save-to function | | #723 | ⏳ | linkwarden save-to function |
@@ -16,6 +17,7 @@ Last updated: 2026-09-02
## Inbox (founder action items) ## Inbox (founder action items)
- Cloudron API token (asks in #685/#727): create in the panel → material value - axiosheartstudios.com returns 500 — pre-existing app breakage (founder
to `~/.creds/cloudron.env` — unblocks API-first access + future AWX modules. confirmed 2026-09-02), unrelated to its memory reslider. Needs an app-level
- docker daemon.json bounce window (see README) — ticket it when scheduled. look when scheduled.
- Tymeslot upstream: ask for a POOL_SIZE env so the role caps can be removed.
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# perf/apply-memory-shrink.sh — apply the t/316 memory shrink table via the
# Cloudron API. Applied live 2026-09-02 (Redmine #731). Idempotent: re-running
# sets the same values; 202 = accepted, app restarts.
#
# Requires: curl, jq. Creds: ~/.creds/cloudron.env (CLOUDRON_URL, CLOUDRON_API_TOKEN).
#
# Gotchas learned on this host:
# - Route is POST /api/v1/apps/<id>/configure/memory_limit (NOT /memory_limit).
# - Values <= manifest.memoryLimit (package floor) are rejected 400 "too small"
# (e.g. photos/immich floor 3.5GB — left at 4096).
# - This host's dockerd runs --log-driver=journald as a systemd-unit flag;
# daemon.json must NOT set log-driver/log-opts (dockerd refuses to start).
set -euo pipefail
# shellcheck disable=SC1090 # creds path is env-overridable by design
source "${CLOUDRON_CREDS:-$HOME/.creds/cloudron.env}"
# fqdn|target MB (t/316 table; photos skipped — manifest floor 3.5GB)
TABLE='
axiosheartstudios.com|512
community.turnsys.com|4096
nextcloud.knownelement.com|2048
bookmarks.knownelement.com|2048
share.knownelement.com|512
hfnfc.net|512
notes.knownelement.com|1024
digital-assets-manager.knownelement.com|512
finance.turnsys.com|512
finance.reachableceo.com|512
'
APPS=$(curl -s --max-time 20 -H "Authorization: Bearer $CLOUDRON_API_TOKEN" "$CLOUDRON_URL/api/v1/apps")
echo "$TABLE" | while IFS='|' read -r fqdn mb; do
[ -z "$fqdn" ] && continue
app_id=$(jq -r --arg f "$fqdn" '.apps[] | select(.fqdn==$f) | .id' <<<"$APPS")
[ -z "$app_id" ] && { echo "SKIP (not found): $fqdn"; continue; }
code=$(curl -s --max-time 20 -X POST \
-H "Authorization: Bearer $CLOUDRON_API_TOKEN" -H "Content-Type: application/json" \
-d "{\"memoryLimit\": $((mb * 1048576))}" \
"$CLOUDRON_URL/api/v1/apps/$app_id/configure/memory_limit" -o /dev/null -w '%{http_code}')
echo "$code $fqdn -> ${mb}MB"
sleep 10
done
@@ -1,6 +1,4 @@
{ {
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"default-ulimits": { "default-ulimits": {
"nofile": { "Name": "nofile", "Soft": 262144, "Hard": 262144 } "nofile": { "Name": "nofile", "Soft": 262144, "Hard": 262144 }
}, },
+6 -2
View File
@@ -26,5 +26,9 @@ bounces (~300). live-restore is currently false so there is no zero-downtime
path. Until then the postgres nofile bridge is ephemeral (lost on postgres path. Until then the postgres nofile bridge is ephemeral (lost on postgres
container recreate). container recreate).
- [ ] Pick a window (suggest: pre-storm weekday morning) — agent will ticket ## 2. docker daemon.json bounce window (see README "Ground truth")
the runbook + execute on your go
- [x] Pick a window — founder chose 2026-09-02 ~19:30 CDT (pre-storm).
APPLIED: live-restore + default-ulimits nofile 262144 now active; first
attempt failed (log-driver flag conflict — unit passes journald), fixed
by dropping log keys; see #731 for the incident record.