跳到正文
DEV Community· Jordan Huang·· 2 小時前AI 評分47

免費方案主機不是你的手提電腦:環境可攜性的五個迷思及可供比對的探測腳本

The Free Box Is Not Your Laptop: Five Portability Myths and a Probe You Can Diff

AI 導讀

環境可攜性 FAQ 拆解五個常見迷思,指出容器映像相同不代表主機資源限制相同。只讀探測腳本輸出資源及執行環境快照的 JSON,供兩部機器比對。測試通過只證明程式行為符合預期,不能證明主機容量足夠。

正文

Your tests passed on a free-tier box. Then they failed somewhere else. Which machine lied?

Neither one lied. You never measured either box. An image is a filesystem. A host is a capacity contract. Those are two different things, and only the second one breaks builds.

This is a myth-busting FAQ about environment portability. Every myth below is something developers repeat in code review. Each one gets evidence, a command, and a corrected mental model. Then I'll give you a read-only probe script you can run on two machines and diff in one line.

Myth 1: "Same image, same machine."

The claim: if the container image is identical, the runtime environment is identical.

The evidence: container limits live outside the image. They live in cgroups, on the host, and the host changes between providers.

# memory ceiling, cgroup v2 then v1
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory/memory.limit_in_bytes

# CPU quota, cgroup v2 then v1
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us

If memory.max says max, no limit is set. If it says a number, that number is your real ceiling. /proc/meminfo will still happily report the host's total RAM. That number is a fiction from inside your container.

Corrected model: the image declares what software exists. The host declares how much of it can run at once.

Myth 2: "If it builds, it fits."

The claim: a successful build proves the workload fits in available memory.

The evidence: builds are the spikiest thing you do. Steady-state memory is usually far lower. A build that peaks at 1.2 GB on a host with no limit will get OOM-killed on a host with a 1 GB ceiling — and it will fail in a confusing place, like inside a linker or a test runner.

Sample the peak instead of guessing:

#!/usr/bin/env bash
# peak.sh — sample cgroup memory while a command runs
set -uo pipefail
memf=/sys/fs/cgroup/memory.current
[ -r "$memf" ] || memf=/sys/fs/cgroup/memory/memory.usage_in_bytes
"$@" & pid=$!
peak=0
while kill -0 "$pid" 2>/dev/null; do
  cur=$(cat "$memf" 2>/dev/null || echo 0)
  [ "$cur" -gt "$peak" ] 2>/dev/null && peak=$cur
  sleep 0.2
done
wait "$pid"; code=$?
echo "peak_bytes=$peak exit=$code"

Run ./peak.sh npm run build and compare peak_bytes to memory.max. Leave 30% headroom. Compilers are not polite.

Corrected model: measure two numbers, not one. Peak is what kills you. Steady state is what you budget for.

Myth 3: "Slow means bad code."

The claim: a slow run on a small box is an algorithm problem.

The evidence: CPU-throttled tasks show up as wall-clock delay with no extra work. The kernel simply stopped scheduling you mid-run.

cat /sys/fs/cgroup/cpu.stat
# nr_periods, nr_throttled, throttled_usec

If nr_throttled climbs during your run, your process was paused by the quota. Profiling your code at that moment is a waste of an afternoon.

Corrected model: separate quota time from work time. Profile only when throttling is flat.

Myth 4: "free -h tells you your memory."

The claim: the free column is your working budget.

The evidence: free on Linux reports host-level counters and page cache accounting. Inside a container it can be misleading in both directions. The cgroup files are authoritative for your process tree.

Also check the OOM counters, because a kill is silent from the app's point of view:

cat /sys/fs/cgroup/memory.events
# low, high, max, oom, oom_kill

If oom_kill is above zero, something already died. No exception was raised. That is how you get a mystery exit code 137.

Corrected model: read cgroup counters for limits. Read /proc/meminfo only when you actually want host context.

Myth 5: "A green test run proves the environment."

The claim: tests passing means the box is fine to ship on.

The evidence: tests assert behavior. They do not assert capacity. A test suite that passes in 400 MB passes on almost anything. It tells you nothing about your peak, your disk, or your outbound network.

Two receipts, two purposes:

  • Tests answer: does the code do the right thing?
  • Probes answer: does this box let the code do it here?

You need both. You probably only have the first.

The artifact: a read-only probe you can diff

Here is the script. It only reads. It writes one JSON file and nothing else. Run it on a laptop, in CI, or on a free server.

You can run this anywhere, including MonkeyCode's free server option and its free model access for the summarization step later. Treat both as operator-supplied availability, not as a capacity promise. If you build on either, measure the box yourself rather than trusting the label.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

#!/usr/bin/env bash
# probe.sh — read-only capacity snapshot
set -uo pipefail
out="${1:-probe-$(date -u +%Y%m%dT%H%M%SZ).json}"

read_first() { for f in "$@"; do [ -r "$f" ] && { cat "$f"; return; }; done; echo unknown; }

mem_max=$(read_first /sys/fs/cgroup/memory.max /sys/fs/cgroup/memory/memory.limit_in_bytes)
mem_now=$(read_first /sys/fs/cgroup/memory.current /sys/fs/cgroup/memory/memory.usage_in_bytes)
cpu_max=$(read_first /sys/fs/cgroup/cpu.max /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
throttled=$(awk '/nr_throttled/ {print $2}' /sys/fs/cgroup/cpu.stat 2>/dev/null || echo 0)
cpus=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo unknown)
uptime=$(cut -d. -f1 /proc/uptime 2>/dev/null || echo unknown)
disk_kb=$(df -Pk . | awk 'NR==2 {print $4}')
py=$(python3 -V 2>&1 | awk '{print $2}' || echo none)
node=$(command -v node >/dev/null 2>&1 && node -v || echo none)

if curl -fsS -m 5 -o /dev/null https://example.com 2>/dev/null; then
  net=ok
else
  net=blocked
fi

cat > "$out" <<EOF
{
  "cpus": "$cpus",
  "cpu_max": "$cpu_max",
  "nr_throttled": "$throttled",
  "mem_max_bytes": "$mem_max",
  "mem_used_bytes": "$mem_now",
  "disk_avail_kb": "$disk_kb",
  "uptime_s": "$uptime",
  "python": "$py",
  "node": "$node",
  "outbound_https": "$net"
}
EOF
echo "wrote $out"

Run it twice, on two boxes, into two files:

./probe.sh before.json
./probe.sh after.json   # different machine, same script

Diff the two probes

No jq needed. Ten lines of Python do it:

# diffprobe.py — show only the fields that changed
import json, sys
a, b = (json.load(open(p)) for p in sys.argv[1:3])
for k in sorted(set(a) | set(b)):
    if a.get(k) != b.get(k):
        print(f"{k:16} {a.get(k)!s:>18} -> {b.get(k)!s}")
python3 diffprobe.py before.json after.json

Expected output shape, from an example run you should reproduce yourself:

cpus                      4 -> 2
mem_max_bytes     4294967296 -> max
outbound_https             ok -> blocked

Read that straight: half the CPU, no memory ceiling, no network. That is three separate incidents waiting to happen.

Turn the diff into a review comment (optional)

A raw diff is fine for you. Reviewers want a sentence. This is a proposal, not a tested integration — the endpoint and model name must come from your own account settings, so I am not inventing them here:

prompt = """
These are two environment probes for the same project.
Name the three differences most likely to break a build.
One sentence each. No speculation about causes you cannot see.
"""

Pipe the diff into whatever OpenAI-compatible endpoint you already use. A free-tier model is enough for this. The output is short and the input is small. Keep the raw JSON attached to the PR anyway — the model's summary is not a receipt.

The workflow, in five steps

  1. Run probe.sh on the box where the code currently works. Commit the JSON.
  2. Run it on the box where you plan to run next. Commit that JSON too.
  3. Diff them. Read only the changed lines.
  4. Run peak.sh around your heaviest command on both boxes.
  5. Record peak-vs-ceiling headroom in the PR description.

That is the whole method. It takes about ten minutes per environment.

Decision table: what the numbers should make you do

Probe signal Likely symptom Action
mem_max_bytes = max on one box only OOM kill, exit 137 Pin a limit locally and re-run the build
Peak within 20% of mem_max_bytes Intermittent build failure Cut parallelism or move the job
nr_throttled rising during the run Random 3x wall-clock swings Reduce worker count before profiling
outbound_https = blocked Dependency install hangs Vendor deps or add a proxy
Low disk_avail_kb Half-written artifacts Clean caches, then re-measure
uptime_s in the hundreds Work killed mid-run Assume the process is not long-lived

Limitations, and who should skip this

This probe measures the box you are on, at that moment. It does not model load from other tenants, and it does not predict tomorrow.

Skip this approach if any of these are true:

  • You need a written latency SLO. A probe is not a benchmark.
  • Your workload needs a persistent disk or a long-lived process. Probe the actual runtime instead.
  • You need GPUs. cpu.stat says nothing useful about accelerators.
  • Your platform already exports these metrics. Use the platform's numbers first.

And do not turn the JSON into a leaderboard. "This box is faster" is not a finding. "This box throttled me 40 times during the build" is a finding.

One closing question

If a build failed on a new machine tomorrow, would you know which number changed?

If not, run the probe before you blame your code. Two JSON files and a diff will save you a day. Keep the files — they are the only receipt the box will ever give you.

來源:DEV Community · dev.to