mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-26 12:14:17 +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
57 lines
2.6 KiB
Cheetah
57 lines
2.6 KiB
Cheetah
version: "0.1"
|
|
log_level: info
|
|
log_shipper:
|
|
backend: ""
|
|
url: ""
|
|
batch_size: 100
|
|
flush_interval: 5s
|
|
username: ""
|
|
password: ""
|
|
server:
|
|
addr: :9090
|
|
hold:
|
|
# The hold's private-network address. The scanner runs on its own server
|
|
# and reaches the hold over the UpCloud SDN, never through the load
|
|
# balancer (the LB frontend has a 10s client timeout that would cut the
|
|
# scan WebSocket).
|
|
url: "{{.ScannerHoldURL}}"
|
|
secret: "{{.ScannerSecret}}"
|
|
scanner:
|
|
# One worker, deliberately: the hold's proactive dispatch loop gates on
|
|
# waitForCapacity() and hands out one job at a time hold-wide, so a second
|
|
# worker only ever receives work when two pushes coincide. Peak RSS is per
|
|
# concurrent scan (two node:22-class scans measured 1357 MiB with no Go
|
|
# limit), so raise this only together with MemoryMax in the unit file.
|
|
workers: 1
|
|
queue_size: 100
|
|
# Must stay below the hold's 10m scanning timeout, which it measures from
|
|
# the "started" message this scanner sends when a worker picks the job up.
|
|
# Whichever fires first decides: this one stops the scan and tells the hold
|
|
# why, the hold's marks the row failed and re-dispatches work that is still
|
|
# running. Two minutes of margin for the terminal message to land.
|
|
#
|
|
# Note what the deadline does not cover: stereoscope's layer extraction
|
|
# takes no context and cannot be interrupted, and it is ~81% of a scan. The
|
|
# bound on that stage is max_image_size below, not this.
|
|
job_timeout: 8m
|
|
vuln:
|
|
enabled: true
|
|
db_path: "{{.BasePath}}/scanner/vulndb"
|
|
# Must be on disk, not tmpfs. Layers are extracted here and amplify
|
|
# roughly 3.8x over their compressed size (measured: node:22, 389 MiB ->
|
|
# 1493 MiB), so a tmpfs would spend host memory to hold them.
|
|
tmp_dir: "{{.BasePath}}/scanner/tmp"
|
|
# 1 GiB compressed. Peak RSS tracks image size (389 MiB compressed reached
|
|
# 561 MiB RSS with a 512 MiB Go limit) and extraction writes ~3.8x the
|
|
# compressed size to tmp_dir, so this bounds both memory and disk on the
|
|
# 4 GB / 30 GB scanner server. Images above it are rejected before any
|
|
# blob is downloaded. The shipped default is 2 GiB.
|
|
max_image_size: 1073741824
|
|
# Reclaim scan directories left behind by a scanner that was killed
|
|
# mid-scan (a restart or a deploy). Those never run their own cleanup, and
|
|
# on the old shared host they reached 8.8 GB on a 20 GB disk before scans
|
|
# started failing with "no space left on device". Above the 8m job
|
|
# timeout, so a scan in flight elsewhere in the same directory is never
|
|
# touched.
|
|
sweep_max_age: 1h
|