mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 03:04:16 +00:00
The scanner shared the hold's 1 GB host and thrashed it twice: 11 hours on 2026-09-12 and again on the 13th (644 MiB resident plus 1.9 GB of swap, 504 on every repo page). It is memory-bound, not CPU-bound, so it now gets a dedicated STARTER-2xCPU-4GB server: own state entry, own plan flag (pinned name, shape match if UpCloud renames the tier again, picker last), own cloud-init, firewall, `update scanner`, `ssh scanner`, status, backup and teardown. Its config reaches the hold over the private network and its unit sets MemorySwapMax=0 so an overshoot is an OOM kill and a restart, not a wedged host. The hold's cloud-init and update paths no longer carry it. Three defects the first provision run exposed, all fixed here: - Frontend HTTP/2 defaulted to on and was reconciled onto the LB every run. Re-enabling it on the 12th stranded the appview<->hold connections for 25 minutes. Default is now off and reconciled off, with a guard test. - The TLS step requested Let's Encrypt bundles for every registry domain, re-adding the .cr ones that were removed when those moved behind Bunny. It now skips any domain whose DNS does not resolve to the LB. - Each prompt built its own bufio.Scanner on stdin, so the first swallowed every piped answer and the second read EOF and took the default, which re-ran cloud-init on the production hold. One shared reader, and no answer now means skip. Also: STARTER- plans take standard storage (maxiops fails with TIER_INVALID), and the cloud-init wait polls for up to 20 minutes instead of one SSH call capped at five, which a first boot with npm exceeds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hho5da4daoCoPBJ9tCrL7s
47 lines
1.6 KiB
Cheetah
47 lines
1.6 KiB
Cheetah
[Unit]
|
|
Description={{.DisplayName}} Scanner (Vulnerability Scanning)
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User={{.User}}
|
|
Group={{.User}}
|
|
ExecStart={{.BinaryPath}} serve --config {{.ConfigPath}}
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
|
|
# The scanner runs on its own server, so it is the only large process on the
|
|
# host and the limits below are sized to the box rather than to a neighbour.
|
|
#
|
|
# GOMEMLIMIT is the Go soft limit: the runtime collects harder as the heap
|
|
# approaches it and never fails an allocation to honour it, so a large enough
|
|
# image walks straight through. It sits below MemoryHigh so the GC gets a
|
|
# chance before the kernel starts reclaiming. Without the variable the scanner
|
|
# defaults itself to 512 MiB (cmd/scanner/main.go), which on a 4 GB host only
|
|
# wastes CPU on collection.
|
|
Environment=GOMEMLIMIT={{.GoMemLimit}}
|
|
|
|
# MemoryHigh throttles and reclaims; MemoryMax kills. MemorySwapMax=0 is the
|
|
# one that matters: with swap available a scanner over its cap is not killed,
|
|
# it is paged out, and the host thrashes instead of failing. That is what took
|
|
# the shared hold host down for eleven hours on 2026-09-12 (644 MiB resident,
|
|
# 1.9 GB swapped). Refusing swap turns that into an OOM kill of one job and a
|
|
# clean restart ten seconds later.
|
|
MemoryHigh={{.MemoryHigh}}
|
|
MemoryMax={{.MemoryMax}}
|
|
MemorySwapMax=0
|
|
|
|
ReadWritePaths={{.DataDir}}
|
|
ProtectSystem=strict
|
|
ProtectHome=yes
|
|
NoNewPrivileges=yes
|
|
PrivateTmp=yes
|
|
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier={{.ServiceName}}
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|