What needs attention in an evaluation?
Brewlet is a pre-1.0 preview. Use a disposable evaluation environment.
- Live admission enforcement and CPU-driven autoscaling validation remain pending;
+ Live admission enforcement remains pending; CPU scaling passed with a fixed-shim candidate, not unmodified 0.5.0;
see validation coverage and remaining gaps.
Privileged node provisioning adds platform responsibilities.
Current capability admission does not support waking a completely zero-sized pool;
diff --git a/site/index.html b/site/index.html
index dc13e999..acb4400e 100644
--- a/site/index.html
+++ b/site/index.html
@@ -245,7 +245,7 @@
Run
Pods use CNI, kubectl logs/exec, probes, and Services.
CPU HPA configuration is available;
- live metrics-driven validation is pending.
+ live validation uses a fixed-shim candidate.
@@ -331,8 +331,9 @@ See Brewlet run locally and on Kubernetes
The JVM sees 1 CPU and a heap bounded under 256Mi because the
- containerd shim wires the pod's cgroup limits into the process. Existing E2E
- coverage exercises manual scaling, not the live CPU autoscaling loop.
+ containerd shim wires the pod's cgroup limits into the process.
+ Live CPU scaling passed with a fixed-shim candidate;
+ unmodified 0.5.0 exposed a packed-layer GC scale-out failure.
With Brewlet's default
runnable-image format, image: <ref> is all the pod needs; kubelet
pulls it like any container image.
@@ -361,7 +362,7 @@
Brewlet vs. containers vs. SpinKube
| Runtime location | inside the image | shared node JDK | node-installed Wasm runtime |
| Patch the runtime | update images and roll workloads | update node JDKs, then roll workloads | update node runtime |
| Isolation | namespaces + cgroups (runc) | namespaces + cgroups (runc) | Wasm sandbox |
- | K8s integration | full | Services, probes, logs; CPU HPA validation pending | full |
+ | K8s integration | full | Services, probes, logs; CPU HPA candidate validated | full |
| Best fit | general-purpose workloads | JVM compatibility without full images | small, fast-starting Wasm workloads |
diff --git a/site/mkdocs.yml b/site/mkdocs.yml
index ae536a66..1eb00771 100644
--- a/site/mkdocs.yml
+++ b/site/mkdocs.yml
@@ -98,6 +98,7 @@ nav:
- Admission enforcement: admission-enforcement.md
- Observability: observability.md
- Runtime metrics and Grafana dashboards: runtime-metrics.md
+ - Disposable live validation: live-validation.md
- Multi-architecture fleets: multi-arch.md
- Troubleshooting: troubleshooting.md
- Reference:
diff --git a/site/scripts/test_site_contracts.py b/site/scripts/test_site_contracts.py
index d10dcad7..6ca3ca45 100644
--- a/site/scripts/test_site_contracts.py
+++ b/site/scripts/test_site_contracts.py
@@ -213,13 +213,15 @@ def test_validation_status_distinguishes_smoke_component_and_live_coverage(self)
for boundary in ("does not install the chart or provision nodes",
"substituted registry access and plugin transport",
"simulated HPA ownership",
+ "fixed-shim candidate",
+ "packed-layer GC scale-out failure",
"two consecutive fresh disposable clusters",
"existing E2E harness permits skips"):
self.assertIn(boundary, text)
for issue in (13, 93, 94, 95):
self.assertIn(f"https://github.com/microsoft/brewlet/issues/{issue}", document)
- def test_operational_guides_link_pending_live_validation(self):
+ def test_operational_guides_distinguish_pending_and_candidate_validation(self):
guides = {
"docs/admission-enforcement.md": 95,
"admission/README.md": 95,
@@ -231,7 +233,12 @@ def test_operational_guides_link_pending_live_validation(self):
with self.subTest(document=filename):
text = " ".join((ROOT / filename).read_text(encoding="utf-8").split())
self.assertIn(f"https://github.com/microsoft/brewlet/issues/{issue}", text)
- self.assertIn("pending", text)
+ if issue == 95:
+ self.assertIn("pending", text)
+ else:
+ self.assertIn("fixed-shim candidate", text)
+ self.assertIn("0.5.0", text)
+ self.assertIn("cold startup", text)
self.assertIn("disposable", text)
self.assertNotIn("production admission integration", text)
self.assertNotIn("Production admission policy that", text)
diff --git a/specs/SPECIFICATION.md b/specs/SPECIFICATION.md
index 61dc9e8b..abd939ba 100644
--- a/specs/SPECIFICATION.md
+++ b/specs/SPECIFICATION.md
@@ -66,7 +66,9 @@ capability model.
node, is shared across workloads, and is upgraded independently of app artifacts.
- **G6 — First-class Kubernetes citizen.** Use standard Kubernetes Services,
Ingress, probes, autoscaling, logs, and metrics interfaces. Live CPU HPA
- validation remains pending in [#94](https://github.com/microsoft/brewlet/issues/94).
+ validation passed twice on fresh local arm64 clusters with a fixed-shim
+ candidate over 0.5.0, not the unmodified release; see
+ [#94](https://github.com/microsoft/brewlet/issues/94).
### 2.2 Non-Goals (for v1)
- Replacing OCI *images* for apps that legitimately need OS packages/native deps.
@@ -330,7 +332,11 @@ publishes the *same* JAR as a **standard, kubelet-pullable OCI image**:
portable bytecode JAR, so any provisioned node matches; narrowed to `--arch` for a
JAR carrying native libraries).
-containerd/kubelet pull and unpack this image with **no special configuration**.
+containerd/kubelet pull and unpack this image using the standard OCI image path.
+Packed layers must remain available until the shim publishes a verified stage;
+for reliable cold starts, retain them in the effective containerd configuration
+(`discard_unpacked_layers=false`). An unpacked snapshot alone is not verified
+input to the Brewlet blob resolver.
Kubernetes execution requires a digest-pinned request. The shim takes that exact
manifest/index target from protected CRI requested-image metadata, requires the
containerd-owned `io.kubernetes.cri.image-name` OCI annotation to name the same
@@ -1300,6 +1306,14 @@ and builds/runs on Linux:
Repeated and concurrent resolutions, including separate shim processes, reuse
the published stage without truncating or replacing files held by existing
workloads. Failed extraction never publishes an incomplete stage.
+ The `immutable-v2` stage also retains the exact descriptor-verified packed
+ layers. Cache reuse verifies those retained bytes and tolerates source-layer
+ removal by containerd GC, but not other source I/O errors or present corrupt
+ source bytes. Missing/corrupt retained evidence fails closed. Older stage
+ directories remain untouched during upgrades; a new stage requires available
+ source bytes. This warm-reuse protection does not guarantee cold startup
+ after source-layer GC. Re-pull affected digest-pinned images with packed-layer
+ retention enabled, and allow extra disk capacity for retained layer bytes.
The workload image reference and manifest digest hints are managed **cluster-side,
not in the shim**: the `brewlet-admission` webhook (§8.3) overwrites the
@@ -1496,7 +1510,10 @@ as per deployment descriptor.”* The descriptor is the `JavaApplication`.
> reference and garbage-collected with the `JavaApplication`.
>
> Existing tests cover HPA resource creation, simulated HPA ownership, and manual
-> scaling. Real metrics-server-driven CPU scale-up and scale-down remain pending
+> scaling. Real metrics-server-driven 1-to-3-to-1 scaling passed twice on fresh
+> local arm64 clusters with the fixed-shim candidate over 0.5.0. Unmodified
+> 0.5.0 failed warm scale-out after packed-layer GC; cold missing-source startup
+> remains outside the correction. Evidence and remaining scope are tracked
> validation in [#94](https://github.com/microsoft/brewlet/issues/94).
### 8.3 Pod admission/scheduling webhook