Files
cloud-apache-container/scripts/detect-memory.sh
Claude Code 1756d496e5
All checks were successful
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m20s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m15s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m25s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m14s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m21s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m15s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m23s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m15s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 27s
detect-memory: raise PHP_WORKER_ESTIMATE_MB default 60→128
The 60 MB worker estimate was optimistic for plugin-heavy WordPress
and WooCommerce stacks. Concrete measurement on alphaone 2026-06-01:

  Container memory : 1024 MiB (later 2048 MiB)
  Pool sized by formula : pm.max_children = (1024-100)/60 = 15
  Actual per-worker RSS : ~193 MB (anon+file+shmem from kernel OOM dumps)
  Worst-case peak       : 15 × 193 MB ≈ 2.9 GB

That math put traffic-burst peak demand well over the container cap,
producing 1,586 cumulative oom_kills across alphaone's two containers
over 18 days and intermittent fork-starvation for unrelated tenants
on the host.

128 MB is a more realistic baseline: closer to actual WP+Woo+page-
builder worker footprint, still conservative enough that lighter
sites continue to get reasonable concurrency. The matrix at common
container tiers:

  Tier (MiB)  | old children | new children | new peak demand
  256         | 2 (floored)  | 2 (floored)  | ~256 MB
  512         | 6            | 3            | ~384 MB
  768         | 11           | 5            | ~640 MB
  1024        | 15           | 7            | ~896 MB
  2048        | 15 (capped*) | 15           | ~1.9 GB
  (* old formula returned 32 at 2 GiB but production containers were
    booted at lower tiers and never recalculated; see whp01 audit.)

Existing containers keep their boot-time pm.max_children until they
are recreated — this change only affects new containers. Customers
or operators can override per-container via FPM_MAX_CHILDREN env.
2026-06-01 08:23:09 -07:00

143 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# detect-memory.sh — Detect container memory and calculate tuning parameters.
# Must be sourced (not executed) so variables are available to the caller.
# --- Memory detection (cgroups v2 → v1 → /proc/meminfo → fallback) ---
CONTAINER_MEMORY_BYTES=""
# cgroups v2
if [ -f /sys/fs/cgroup/memory.max ]; then
val=$(cat /sys/fs/cgroup/memory.max 2>/dev/null)
if [ "$val" != "max" ] && [ -n "$val" ]; then
CONTAINER_MEMORY_BYTES=$val
fi
fi
# cgroups v1
if [ -z "$CONTAINER_MEMORY_BYTES" ] && [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then
val=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null)
# Values near page-aligned max (like 9223372036854771712) mean "no limit"
if [ -n "$val" ] && [ "$val" -lt 8589934592000 ] 2>/dev/null; then
CONTAINER_MEMORY_BYTES=$val
fi
fi
# /proc/meminfo (host memory — used when no cgroup limit is set)
if [ -z "$CONTAINER_MEMORY_BYTES" ] && [ -f /proc/meminfo ]; then
mem_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
if [ -n "$mem_kb" ]; then
CONTAINER_MEMORY_BYTES=$((mem_kb * 1024))
fi
fi
# Fallback
if [ -z "$CONTAINER_MEMORY_BYTES" ]; then
CONTAINER_MEMORY_BYTES=$((512 * 1024 * 1024))
fi
CONTAINER_MEMORY_MB=$((CONTAINER_MEMORY_BYTES / 1024 / 1024))
# --- Budget calculation ---
CONTAINER_ROLE=${CONTAINER_ROLE:-combined} # combined | fpm_only | httpd_only
OS_RESERVE_MB=50
FIXED_PROCESS_MB=50
DEV_OVERHEAD_MB=0
if [ "$environment" = "DEV" ]; then
DEV_OVERHEAD_MB=125
fi
AVAILABLE_MB=$((CONTAINER_MEMORY_MB - OS_RESERVE_MB - FIXED_PROCESS_MB - DEV_OVERHEAD_MB))
if [ "$AVAILABLE_MB" -lt 60 ]; then
AVAILABLE_MB=60
fi
case "$CONTAINER_ROLE" in
fpm_only)
PHP_BUDGET_MB=$AVAILABLE_MB
APACHE_BUDGET_MB=0
;;
httpd_only)
PHP_BUDGET_MB=0
APACHE_BUDGET_MB=$AVAILABLE_MB
;;
*)
PHP_BUDGET_MB=$((AVAILABLE_MB * 80 / 100))
APACHE_BUDGET_MB=$((AVAILABLE_MB * 20 / 100))
;;
esac
# --- PHP-FPM parameters (skipped for httpd_only) ---
if [ "$CONTAINER_ROLE" != "httpd_only" ]; then
# PHP_WORKER_ESTIMATE_MB sizes the divisor for pm.max_children. The
# previous default of 60 was optimistic for modern Woo/Elementor stacks:
# the alphaone 2026-06-01 incident measured ~193 MB resident per worker
# against the 60 MB assumption, and 15 calculated children put peak
# demand (15 * 193 = 2.9 GB) over the 1-2 GiB container cap. 128 lands
# closer to plugin-heavy WP reality while remaining conservative for
# leaner sites. Customers can still override via the FPM_MAX_CHILDREN
# env var on the container if a different shape is justified.
PHP_WORKER_ESTIMATE_MB=${PHP_WORKER_ESTIMATE_MB:-128}
calc_max_children=$((PHP_BUDGET_MB / PHP_WORKER_ESTIMATE_MB))
# Floor at 2, cap at 50
if [ "$calc_max_children" -lt 2 ]; then
calc_max_children=2
fi
if [ "$calc_max_children" -gt 50 ]; then
calc_max_children=50
fi
PHP_FPM_PM=${FPM_PM:-ondemand}
PHP_FPM_MAX_CHILDREN=${FPM_MAX_CHILDREN:-$calc_max_children}
PHP_FPM_PROCESS_IDLE_TIMEOUT=${FPM_PROCESS_IDLE_TIMEOUT:-5s}
PHP_FPM_MAX_REQUESTS=${FPM_MAX_REQUESTS:-200}
# Dynamic mode fallbacks (used if user overrides FPM_PM=dynamic)
PHP_FPM_START_SERVERS=${FPM_START_SERVERS:-2}
PHP_FPM_MIN_SPARE=${FPM_MIN_SPARE_SERVERS:-1}
PHP_FPM_MAX_SPARE=${FPM_MAX_SPARE_SERVERS:-3}
fi
# --- Apache MPM parameters (skipped for fpm_only) ---
if [ "$CONTAINER_ROLE" != "fpm_only" ]; then
# ServerLimit: roughly 1 process per ~25 workers, floor 2, cap 16
calc_server_limit=$((APACHE_BUDGET_MB / 30))
if [ "$calc_server_limit" -lt 2 ]; then
calc_server_limit=2
fi
if [ "$calc_server_limit" -gt 16 ]; then
calc_server_limit=16
fi
# MaxRequestWorkers: ServerLimit * ThreadsPerChild (25)
calc_max_request_workers=$((calc_server_limit * 25))
if [ "$calc_max_request_workers" -gt 400 ]; then
calc_max_request_workers=400
fi
# StartServers: 1 for ≤1GB, 2 for larger
calc_start_servers=1
if [ "$CONTAINER_MEMORY_MB" -gt 1024 ]; then
calc_start_servers=2
fi
APACHE_START_SERVERS=${APACHE_START_SERVERS:-$calc_start_servers}
APACHE_SERVER_LIMIT=${APACHE_SERVER_LIMIT:-$calc_server_limit}
APACHE_MAX_REQUEST_WORKERS=${APACHE_MAX_REQUEST_WORKERS:-$calc_max_request_workers}
APACHE_MIN_SPARE_THREADS=${APACHE_MIN_SPARE_THREADS:-5}
APACHE_MAX_SPARE_THREADS=${APACHE_MAX_SPARE_THREADS:-15}
APACHE_MAX_CONNECTIONS_PER_CHILD=${APACHE_MAX_CONNECTIONS_PER_CHILD:-3000}
fi
# --- Export all variables ---
export CONTAINER_ROLE CONTAINER_MEMORY_MB
if [ "$CONTAINER_ROLE" != "httpd_only" ]; then
export PHP_FPM_PM PHP_FPM_MAX_CHILDREN PHP_FPM_PROCESS_IDLE_TIMEOUT PHP_FPM_MAX_REQUESTS
export PHP_FPM_START_SERVERS PHP_FPM_MIN_SPARE PHP_FPM_MAX_SPARE
fi
if [ "$CONTAINER_ROLE" != "fpm_only" ]; then
export APACHE_START_SERVERS APACHE_SERVER_LIMIT APACHE_MAX_REQUEST_WORKERS
export APACHE_MIN_SPARE_THREADS APACHE_MAX_SPARE_THREADS APACHE_MAX_CONNECTIONS_PER_CHILD
fi