Files
at-container-registry/deploy/upcloud/systemd/scanner.service.tmpl
T
Evan JarrettandClaude Fable 5.1 83092d9aee deploy: give the scanner its own server, and stop the tool from undoing production fixes on provision
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
2026-09-12 21:49:26 -05:00

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