feat: highmem
runner ubuntu smoke / ubuntu-latest label smoke (push) Successful in 0s
runner nix smoke / nix label and flake smoke (push) Failing after 9s

This commit is contained in:
2026-09-10 17:44:56 +00:00
parent f286ab632c
commit 0a2b96abc2
6 changed files with 103 additions and 4 deletions
+10 -1
View File
@@ -10,6 +10,15 @@ on: # yamllint disable-line rule:truthy
required: false required: false
type: boolean type: boolean
default: true default: true
runner_label:
description: Runner label for deploy job
required: false
type: choice
default: nix
options:
- nix
- gross-nix-x86-perf
- gross-nix-x86-highmem
concurrency: concurrency:
group: deploy-neuro group: deploy-neuro
@@ -19,7 +28,7 @@ jobs:
deploy: deploy:
name: deploy neuro from performance node name: deploy neuro from performance node
if: ${{ gitea.ref == 'refs/heads/master' }} if: ${{ gitea.ref == 'refs/heads/master' }}
runs-on: gross-nix-x86-perf runs-on: ${{ gitea.event.inputs.runner_label || 'nix' }}
timeout-minutes: 435 timeout-minutes: 435
env: env:
NIX_CONFIG: | NIX_CONFIG: |
+7
View File
@@ -567,6 +567,7 @@ The `deploy-neuro` workflow uses these nested limits:
| Final cache drain | 1 hour (`WITH_ATTIC_DRAIN_TIMEOUT=3600`) | | Final cache drain | 1 hour (`WITH_ATTIC_DRAIN_TIMEOUT=3600`) |
| Workflow job | 435 minutes, including 15 minutes of setup/cleanup margin | | Workflow job | 435 minutes, including 15 minutes of setup/cleanup margin |
| `gross-nix-x86-perf` runner | 480 minutes | | `gross-nix-x86-perf` runner | 480 minutes |
| `gross-nix-x86-highmem` runner | 480 minutes |
| Gitea `actions.ENDLESS_TASK_TIMEOUT` | 8 hours | | Gitea `actions.ENDLESS_TASK_TIMEOUT` | 8 hours |
| VM hard lifetime from allocation | 480 minutes plus 10-minute controller grace | | VM hard lifetime from allocation | 480 minutes plus 10-minute controller grace |
@@ -575,6 +576,12 @@ and Gitea watchdog settings before dispatching the longer workflow. Already
allocated VMs retain the TTL and runner configuration assigned when they were allocated VMs retain the TTL and runner configuration assigned when they were
created; updating the controller does not extend a running job. created; updating the controller does not extend a running job.
`gross-nix-x86-highmem` is an explicit costly high-memory escape hatch backed
only by Hetzner CCX53 in `nbg1`, `fsn1`, or `hel1`; it may fall back by region
only, never to a lower-RAM server type. Current Hetzner public pricing for
Germany/Finland CCX53 is 0.8550 EUR/hour excluding IPv4, so one 480-minute
allocation reserves 6.84 EUR against the controller budget before VM creation.
These are maximum lifetimes: terminal jobs still trigger immediate VM teardown. These are maximum lifetimes: terminal jobs still trigger immediate VM teardown.
The controller's budget reservation uses the full label TTL, so a long-running The controller's budget reservation uses the full label TTL, so a long-running
label reserves more of the existing monthly budget. Do not raise that budget or label reserves more of the existing monthly budget. Do not raise that budget or
@@ -14,6 +14,7 @@ gcr_server_hourly_rate() {
cax21) printf '0.003' ;; cax21) printf '0.003' ;;
cax31) printf '0.006' ;; cax31) printf '0.006' ;;
cax41) printf '0.012' ;; cax41) printf '0.012' ;;
ccx53) printf '0.8550' ;;
cpx52) printf '0.036' ;; cpx52) printf '0.036' ;;
cpx62) printf '0.072' ;; cpx62) printf '0.072' ;;
*) return 1 ;; *) return 1 ;;
@@ -29,6 +30,7 @@ gcr_label_ttl() {
gross-nix-x86) printf '180' ;; gross-nix-x86) printf '180' ;;
gross-nix-arm) printf '180' ;; gross-nix-arm) printf '180' ;;
gross-nix-x86-perf) printf '480' ;; gross-nix-x86-perf) printf '480' ;;
gross-nix-x86-highmem) printf '480' ;;
gross-nix-mixed-econ) printf '180' ;; gross-nix-mixed-econ) printf '180' ;;
*) return 1 ;; *) return 1 ;;
esac esac
@@ -80,6 +82,10 @@ gcr_label_candidates() {
'cpx62 nbg1 amd64' 'cpx62 fsn1 amd64' 'cpx62 hel1 amd64' \ 'cpx62 nbg1 amd64' 'cpx62 fsn1 amd64' 'cpx62 hel1 amd64' \
'cpx52 nbg1 amd64' 'cpx52 fsn1 amd64' 'cpx52 hel1 amd64' 'cpx52 nbg1 amd64' 'cpx52 fsn1 amd64' 'cpx52 hel1 amd64'
;; ;;
gross-nix-x86-highmem)
printf '%s\n' \
'ccx53 nbg1 amd64' 'ccx53 fsn1 amd64' 'ccx53 hel1 amd64'
;;
gross-nix-mixed-econ) gross-nix-mixed-econ)
printf '%s\n' \ printf '%s\n' \
'cx53 nbg1 amd64' 'cx53 fsn1 amd64' 'cx53 hel1 amd64' \ 'cx53 nbg1 amd64' 'cx53 fsn1 amd64' 'cx53 hel1 amd64' \
+1 -1
View File
@@ -341,7 +341,7 @@ set -eu
nix_arch="" nix_arch=""
nix_sha="" nix_sha=""
case "$label" in case "$label" in
nix|gross-nix-x86|gross-nix-arm|gross-nix-x86-perf|gross-nix-mixed-econ) nix|gross-nix-*)
case "\$(uname -m)" in case "\$(uname -m)" in
x86_64) x86_64)
nix_arch=x86_64-linux nix_arch=x86_64-linux
@@ -0,0 +1,67 @@
#!/bin/dash
set -eu
. "$LOG_SH"
. "$STATE_SH"
. "$DECIDE_SH"
. "$HCLOUD_SH"
gcr_state_init
export GCR_ALLOWED_REPOS='hectic-lab/util.nix'
export GCR_NIX_IMAGE_ID='424242'
profile="$(gcr_decide gross-nix-x86-highmem hectic-lab/util.nix)"
test "$profile" = 'ccx53 480 0.8550'
test "$(gcr_label_ttl gross-nix-x86-highmem)" = '480'
candidates="$(gcr_label_candidates gross-nix-x86-highmem)"
expected='ccx53 nbg1 amd64
ccx53 fsn1 amd64
ccx53 hel1 amd64'
test "$candidates" = "$expected"
if printf '%s\n' "$candidates" | grep -Eq '(^| )c[axp]x| cx[0-9]'; then
printf 'highmem candidates must not downgrade from ccx53\n' >&2
exit 1
fi
if printf '%s\n' "$candidates" | grep -Evq '^ccx53 (nbg1|fsn1|hel1) amd64$'; then
printf 'highmem candidates must be ccx53 amd64 in allowed regions only\n' >&2
exit 1
fi
export GCR_BUDGET_EUR_MONTHLY='6.83'
if gcr_budget_add 0.8550 480; then
printf 'highmem full-TTL reservation must obey budget cap\n' >&2
exit 1
fi
export GCR_BUDGET_EUR_MONTHLY='6.84'
gcr_budget_add 0.8550 480
test "$(cat "$GCR_STATE_DIR/budget/$(date -u '+%Y-%m')")" = '6.8400'
calls="$GCR_STATE_DIR/hcloud-calls"
sleep() { :; }
gcr_hcloud_token() { printf token; }
gcr_hcloud_req() {
method="$1"; path="$2"; body="${3:-}"
test "$method" = POST
test "$path" = /servers
printf '%s\n' "$body" | jq -c . >> "$calls"
count="$(wc -l < "$calls" | tr -d ' ')"
GCR_LAST_BODY="$GCR_STATE_DIR/last-body.json"
case "$count" in
1|2) return 1 ;;
3) printf '{"server":{"id":9001}}\n' > "$GCR_LAST_BODY"; return 0 ;;
*) return 1 ;;
esac
}
vm_id="$(gcr_vm_create gcr-9001-1 gross-nix-x86-highmem ccx53 480 reg-token 9001 1 hectic-lab/util.nix)"
test "$vm_id" = '9001'
test "$(wc -l < "$calls" | tr -d ' ')" = '3'
jq -e 'select(.server_type == "ccx53" and .location == "nbg1" and .labels["gcr.arch"] == "amd64" and .labels["gcr.label"] == "gross-nix-x86-highmem" and .labels["gcr.ttl-min"] == "480")' "$calls" >/dev/null
jq -e 'select(.server_type == "ccx53" and .location == "fsn1" and .labels["gcr.arch"] == "amd64")' "$calls" >/dev/null
jq -e 'select(.server_type == "ccx53" and .location == "hel1" and .labels["gcr.arch"] == "amd64")' "$calls" >/dev/null
if jq -e 'select(.server_type != "ccx53" or .labels["gcr.arch"] != "amd64")' "$calls" >/dev/null; then
printf 'highmem VM creation attempted non-ccx53 or non-amd64 candidate\n' >&2
exit 1
fi
+12 -2
View File
@@ -32,10 +32,13 @@ def workflow_budget(path):
with open(path, encoding="utf-8") as handle: with open(path, encoding="utf-8") as handle:
data = yaml.safe_load(handle) data = yaml.safe_load(handle)
job = data["jobs"]["deploy"] job = data["jobs"]["deploy"]
workflow_on = data.get("on", data.get(True))
inputs = workflow_on["workflow_dispatch"]["inputs"]
step = next(s for s in job["steps"] if s.get("name") == "Deploy neuro") step = next(s for s in job["steps"] if s.get("name") == "Deploy neuro")
env = step["env"] env = step["env"]
return { return {
"label": job["runs-on"], "label": job["runs-on"],
"runner_input": inputs["runner_label"],
"workflow": int(job["timeout-minutes"]), "workflow": int(job["timeout-minutes"]),
"build": int(env["WITH_ATTIC_BUILD_TIMEOUT"]) // 60, "build": int(env["WITH_ATTIC_BUILD_TIMEOUT"]) // 60,
"drain": int(env["WITH_ATTIC_DRAIN_TIMEOUT"]) // 60, "drain": int(env["WITH_ATTIC_DRAIN_TIMEOUT"]) // 60,
@@ -63,11 +66,13 @@ def shell_budgets(label):
budget = workflow_budget(os.environ["WORKFLOW_FILE"]) budget = workflow_budget(os.environ["WORKFLOW_FILE"])
need(budget["label"] == "gross-nix-x86-perf", "unexpected deployment runner label") need(budget["label"] == "${{ gitea.event.inputs.runner_label || 'nix' }}", "unexpected deployment runner expression")
need(budget["runner_input"]["default"] == "nix", "runner label default must stay nix")
need(budget["runner_input"]["options"] == ["nix", "gross-nix-x86-perf", "gross-nix-x86-highmem"], "runner label choices drifted")
need(budget["build"] > 0 and budget["drain"] > 0 and budget["upload"] > 0, "non-positive timeout") need(budget["build"] > 0 and budget["drain"] > 0 and budget["upload"] > 0, "non-positive timeout")
need(budget["build"] + budget["drain"] + 15 <= budget["workflow"], "workflow too short for build+drain") need(budget["build"] + budget["drain"] + 15 <= budget["workflow"], "workflow too short for build+drain")
ttl, profile, grace, runner_timeout, other_ttls = shell_budgets(budget["label"]) ttl, profile, grace, runner_timeout, other_ttls = shell_budgets("gross-nix-x86-perf")
need(ttl == 480, f"expected 480m ttl, got {ttl}") need(ttl == 480, f"expected 480m ttl, got {ttl}")
need(profile.split()[1] == "480", f"profile ttl drifted: {profile}") need(profile.split()[1] == "480", f"profile ttl drifted: {profile}")
need(all(line.endswith("=180") for line in other_ttls), f"default ttl drift: {other_ttls}") need(all(line.endswith("=180") for line in other_ttls), f"default ttl drift: {other_ttls}")
@@ -75,4 +80,9 @@ need(budget["workflow"] < runner_timeout, "workflow must be below runner timeout
need(minutes(os.environ["GITEA_WATCHDOG"]) >= budget["workflow"], "Gitea watchdog too short") need(minutes(os.environ["GITEA_WATCHDOG"]) >= budget["workflow"], "Gitea watchdog too short")
need(ttl + grace >= budget["workflow"] + 15, "VM ttl lacks bootstrap allowance") need(ttl + grace >= budget["workflow"] + 15, "VM ttl lacks bootstrap allowance")
highmem_ttl, highmem_profile, _, highmem_runner_timeout, _ = shell_budgets("gross-nix-x86-highmem")
need(highmem_ttl == 480, f"expected highmem 480m ttl, got {highmem_ttl}")
need(highmem_profile == "ccx53 480 0.8550", f"highmem profile drifted: {highmem_profile}")
need(highmem_runner_timeout == 480, f"highmem runner timeout drifted: {highmem_runner_timeout}")
print(f"PASS build={budget['build']}m drain={budget['drain']}m upload={budget['upload']}m workflow={budget['workflow']}m runner={runner_timeout}m ttl={ttl}m grace={grace}m watchdog={os.environ['GITEA_WATCHDOG']}") print(f"PASS build={budget['build']}m drain={budget['drain']}m upload={budget['upload']}m workflow={budget['workflow']}m runner={runner_timeout}m ttl={ttl}m grace={grace}m watchdog={os.environ['GITEA_WATCHDOG']}")