Configuration¶
Every knob Brewlet exposes, in one place: Helm chart values, node-provisioner
environment variables, operator/admission flags, the RuntimeClass, and how the
different layers relate. For how to install, see Installation;
for JDKs and launchers specifically, see JDK management and
Launchers. For how the resulting node labels drive scheduling
and autoscaling, see
Capability labels and autoscaling.
For the operational workflow behind the metrics values, see
Runtime metrics and Grafana dashboards.
For runnable-image cache reclamation and consumer-safety prerequisites, see
Runnable stage cleanup.
Configuration layers (how they connect)¶
There is one source of truth for the JDK/launcher inventory: each
NodeProfile contains complete, immutable source declarations.
Helm values (provisioner.jdks / .launchers + source-mirror allowlist)
│ render NodeProfiles and operator/admission flags
▼
Admission + reconciliation validate each digest, path, name, and mirror
│ render indexed source variables into a DaemonSet
▼
Provisioner env (JDK_SOURCE_* / LAUNCHER_SOURCE_* / mirror policy)
│ derives JDKS and LAUNCHERS, then copies approved image content
▼
Node state: /opt/brewlet/jdks/<dist>-<feature>/ + capability labels/annotations
If you use Helm, set values. If you run the operator directly, set flags. If you hand-wire the DaemonSet, set env vars. Don't mix — the operator overwrites the DaemonSet it manages.
Note on examples. The Helm chart is installable with editable, digest-pinned Temurin 21, Microsoft JDK 25, and
jazentries. They are ordinary values, not a Brewlet-maintained catalog. The manager binary has no runtime inventory defaults; outside Helm you must create aNodeProfile.
Helm chart values¶
From charts/brewlet/values.yaml. Override
with --set key=value or a values file.
| Key | Default | Meaning |
|---|---|---|
namespace |
brewlet |
Component namespace, created by the chart and retained on uninstall to protect unrelated objects. May differ from the Helm release namespace. |
images.registry |
ghcr.io/microsoft |
Registry prefix used to generate component image references. |
images.tag |
chart appVersion |
Shared component tag. A versioned OCI chart therefore selects matching images automatically. |
images.operator |
generated | Explicit operator image override; supports tags or digests. |
images.provisioner |
generated | Explicit provisioner image override; supports tags or digests. |
images.admission |
generated | Explicit admission webhook image override; supports tags or digests. |
images.digests.operator |
recorded at release | Immutable sha256:<64 hex> digest for the operator image. Set by the release workflow in published charts; renders <registry>/brewlet-operator@<digest> and takes precedence over images.tag. |
images.digests.provisioner |
recorded at release | Immutable digest for the node-provisioner image. |
images.digests.admission |
recorded at release | Immutable digest for the admission webhook image. |
images.pullPolicy |
IfNotPresent |
Image pull policy for all components. |
security.allowedSourceMirrorHosts |
[] |
Exact registry destination hosts, including explicit ports, that NodeProfiles may use for JDK/launcher mirrors. Empty disables mirrors. The chart passes the same list to manager and admission. |
provisioner.pools |
[] (required) |
Node pool names the chart-managed default NodeProfile may provision. Use names exactly as your provider reports them (AKS pool names are lowercase alphanumeric, with no dashes). The privileged provisioner is never cluster-wide by default: rendering fails when defaultProfile.enabled is true and this is empty. |
provisioner.poolKey |
"" |
Node label carrying the pool name. Empty auto-detects the well-known provider keys (AKS, EKS, GKE); set it explicitly on bare metal or kubeadm. |
provisioner.includeControlPlane |
false |
Allow the default profile onto control-plane nodes. Off by default, and only needed on single-node clusters such as kind or Docker Desktop, which label their node as the control plane without tainting it. |
provisioner.tolerations |
[] |
Tolerations for the default profile's provisioner pods. Standard Kubernetes toleration entries; each must name a key. Nothing is tolerated implicitly. |
provisioner.jdks |
[] (required) |
JDK entries with distribution, feature, digest-pinned source.image, and absolute source.javaHome (§JDK management). Brewlet ships no built-in runtime catalog, so rendering fails when defaultProfile.enabled is true and this is empty. |
provisioner.launchers |
[] |
Optional launcher entries with name, digest-pinned source.image, and absolute source.path (§Launchers). Empty = vanilla java only. |
provisioner.appCDS.regenerationEnabled |
false |
Authorize node-side AppCDS regeneration for the chart-managed default NodeProfile. |
provisioner.rollout.maxUnavailable |
null |
Bounds the default profile's provisioner DaemonSet rolling update. null keeps the DaemonSet default. |
provisioner.rollout.validate |
true |
Gate node readiness on post-install JDK smoke tests and staged-launcher executable checks. Arbitrary launchers are not executed as a probe. Renders the provisioner BREWLET_VALIDATE env. |
provisioner.rollout.containerdRestart |
validated |
Select containerd activation: transactional config validation, service restart, live health checks, and rollback (validated), or no containerd mutation/signal (none). Invalid values are rejected. Renders BREWLET_CONTAINERD_RESTART (§5.5). |
provisioner.registry.mirrors |
{} |
<upstream-host>: <mirror-host[/path]> map applied to digest-pinned source pulls. Every destination host must appear in security.allowedSourceMirrorHosts. |
defaultProfile.enabled |
true |
Render the chart-managed default NodeProfile from provisioner.*. Requires provisioner.pools. Disable to manage the default profile yourself, e.g. via GitOps (§5.6). |
profiles |
[] |
Additional NodeProfile CRs. Each requires a unique name, a nonempty list of named pools, and explicit JDK sources; the chart never renders an accidental catch-all. Includes AppCDS, rollout, and registry policy (§5.6). |
operator.replicas |
1 |
Operator replica count. |
operator.leaderElect |
true |
Enable leader election for HA. |
operator.nodeSelector |
{} |
Node labels the operator pod must match (for example, to keep it on a system pool). |
operator.resources |
requests 50m/64Mi, limits 200m/128Mi |
Operator pod resources. |
stageGC.enabled |
true |
Run periodic orphaned runnable-image stage cleanup in every managed provisioner, independently of metrics. Fresh nodes activate automatically; existing installations require a matching installation safety record. |
stageGC.interval |
5m |
Delay after each attempt, plus up to 10% jitter. Sweeps do not overlap. |
stageGC.minAge |
24h |
Minimum stage directory age, not time since last use or since becoming unreferenced. |
stageGC.allowNestedPIDNamespace |
false |
Test-only. Let the reaper run on nodes that are themselves containers with a private PID namespace (kind). Never enable on real nodes. |
uninstall.timeoutSeconds |
240 |
Pre-delete cleanup coordinator timeout, 1-86400 whole seconds. Configure before uninstalling; Helm's --timeout must exceed this plus 30 seconds. Failure retains the control plane. See Uninstall. |
uninstall.imagePullSecrets |
[] |
Namespaced registry Secret references for the cleanup Job, which uses the operator image and its own service account. |
uninstall.nodeSelector |
{} |
Extra node labels for the cleanup Job pod; kubernetes.io/os: linux is always set. |
metrics.enabled |
false |
Opt in to Brewlet runtime and control-plane Prometheus endpoints. Enables the operator and admission listeners plus the node exporter sidecar and Services. |
metrics.nodePort |
9090 |
Port exposed by each node-local metrics exporter and the brewlet-node-metrics headless Service. |
metrics.serviceMonitor.enabled |
false |
Create a Prometheus Operator ServiceMonitor. Requires the monitoring.coreos.com/v1 CRDs. |
metrics.serviceMonitor.interval |
30s |
Scrape interval rendered into the optional ServiceMonitor. |
metrics.serviceMonitor.additionalLabels |
{} |
Extra ServiceMonitor labels, commonly used to match a Prometheus serviceMonitorSelector. |
metrics.grafanaDashboard.enabled |
false |
Create the brewlet-grafana-dashboard ConfigMap for dashboard sidecar discovery; does not install Grafana or a data source. |
metrics.grafanaDashboard.labels |
grafana_dashboard: "1" |
Labels applied to the dashboard ConfigMap for sidecar discovery. |
admission.enabled |
true |
Deploy the admission/scheduling webhook. Set false to skip it; the shim still enforces runtime image identity and JDK compatibility. |
admission.replicas |
1 |
Webhook replica count. |
admission.failurePolicy |
Ignore |
Webhook failure policy. Ignore keeps availability high, but runtime identity resolution still fails closed in the shim if containerd metadata cannot be resolved. |
admission.nodeSelector |
{} |
Node labels the admission webhook pods must match. |
admission.port |
9443 |
Webhook server port. |
admission.namespaceSelector |
excludes kube-system |
Namespaces the cluster-wide pod webhook is consulted for. |
admission.platformNamespaceExclusions |
AKS Admissions Enforcer expressions | Appended to the namespaceSelector of every chart webhook so Helm 4 (server-side apply) upgrades on AKS do not conflict with the admissionsenforcer field manager. See the chart README's "AKS Admissions Enforcer" section. |
admission.disableAKSAdmissionsEnforcer |
false |
AKS only, not recommended: opt both webhook configurations out of the Admissions Enforcer with admissions.enforcer/disabled: "true". |
admission.resources |
requests 50m/64Mi, limits 200m/128Mi |
Webhook pod resources. |
Example production values (own runtime source, no optional launcher):
# values-production.yaml
images:
operator: registry.example.com/brewlet/operator@sha256:<digest>
provisioner: registry.example.com/brewlet/node-provisioner@sha256:<digest>
admission: registry.example.com/brewlet/admission@sha256:<digest>
provisioner:
# The privileged provisioner only ever touches the pools you name here.
pools: ["javaworkers"]
poolKey: agentpool
jdks:
- distribution: platform
feature: 21
source:
image: registry.example.com/java/runtime@sha256:<digest>
javaHome: /opt/java/runtime
launchers: []
This selects the latest released chart. To pin a release, add
--version "$BREWLET_VERSION" with its concrete version, and ensure any
component-image overrides match it. For an existing installation, follow
Upgrading to update matching CRDs and retain your values.
JDKs and launchers are always obtained copy-from-image from explicit, tagless SHA-256 digest references. For air-gapped clusters, mirror the exact digest and approve the destination host explicitly.
Runnable-image stage cleanup¶
stageGC.* is operator-wide policy, not a NodeProfile.spec field. It applies
to chart-rendered and externally managed profiles alike. Configure it through
Helm values or operator flags, not by patching a reconciled DaemonSet.
interval and minAge accept Go durations that resolve to positive whole
seconds, at most 2147483647s; the operator rejects invalid values.
Kubelet and containerd still own image retention and content reclamation. Brewlet removes only old, unreferenced, unmounted published stages; it does not copy kubelet's disk thresholds or change kubelet configuration. Containerd references in any namespace can delay reclamation. Unmanaged layouts and abandoned pending trees are not automatically removed.
For existing nodes, follow the GC activation procedure.
Unverified installations stay blocked; there is no acknowledgment override.
Readiness does not imply that GC is active. Setting stageGC.enabled=false
stops automatic sweeps, not separately scheduled or manually invoked cleanup commands.
AppCDS regeneration¶
AppCDS regeneration is default-deny. Enable it for the default profile with
--set provisioner.appCDS.regenerationEnabled=true, or on a named profile with:
profiles:
- name: appcds-builders
pools: ["cdsbuilders"]
jdks:
- distribution: temurin
feature: 21
source:
image: docker.io/library/eclipse-temurin@sha256:85f00967bcc624fc19fa9c2cf124ea426a5363898e267141726f31f358c2e14b
javaHome: /opt/java/openjdk
appCDS:
regenerationEnabled: true
Where the provisioner may run¶
The provisioner is privileged and mutates the host, so its placement is a security boundary rather than a scheduling preference. Three rules decide it, and all three are closed by default.
A profile provisions only the pools it names. The chart will not render its
default NodeProfile until provisioner.pools is set; an install that leaves
it empty fails with an actionable message instead of quietly claiming the
cluster. Set defaultProfile.enabled=false if you would rather author profiles
yourself.
And only the JDKs it names. provisioner.jdks is required for the same
reason a pool is: Brewlet has no built-in runtime catalog, and the platform team
owns every digest it runs. The chart ships no default source, because a
chart-pinned digest could never receive a CVE patch.
Control-plane nodes are excluded. Every profile's DaemonSet requires
node-role.kubernetes.io/control-plane and node-role.kubernetes.io/master to
be absent. This is a node-affinity rule, not a taint rule, because kind and
Docker Desktop label their single node as the control plane without tainting it
— a NoSchedule taint alone would not have kept the provisioner off. The same
rule governs status.assignedNodes, so an excluded node is never counted and
never holds the profile short of Ready.
Nothing is tolerated implicitly. The DaemonSet carries only the tolerations
a profile declares, plus the node-condition tolerations Kubernetes injects into
every DaemonSet. Each declared toleration must name a key, so a blanket
"tolerate everything" entry cannot be written.
To provision a tainted pool, name the taint:
provisioner:
pools: ["javaworkers"]
poolKey: agentpool
tolerations:
- key: workload
operator: Equal
value: java
effect: NoSchedule
On a single-node kind or Docker Desktop cluster, the only node is the control plane, so opt in explicitly:
helm upgrade --install brewlet oci://ghcr.io/microsoft/charts/brewlet \
--set provisioner.pools="{control-plane}" \
--set provisioner.poolKey=kubernetes.io/hostname \
--set provisioner.includeControlPlane=true
Both settings exist per profile as well — pools, poolKey,
includeControlPlane, and tolerations on each profiles[] entry, and
spec.nodePool.includeControlPlane / spec.tolerations on a hand-authored
NodeProfile.
Operator flags¶
The source for these flags lives in
cmd/manager.
When you install via Helm, the chart populates them for you.
| Flag | Default | Meaning |
|---|---|---|
--namespace |
brewlet |
Namespace the provisioner DaemonSet is managed in. |
--provisioner-image |
ghcr.io/microsoft/brewlet-node-provisioner:0.1.0 |
Image the DaemonSet runs. Published Helm charts override this with the release image digest. |
--allowed-source-mirror-hosts |
(empty) | Comma-separated exact destination registry hosts approved for NodeProfile runtime-source rewrites. Configure the same value on manager and admission; empty disables mirrors. |
--leader-elect |
false |
Enable leader election for HA. |
--metrics-bind-address |
0 |
Metrics endpoint; 0 disables it. |
--health-probe-bind-address |
:8081 |
Health/readiness endpoint. |
--node-metrics-enabled |
false |
Run the node-local exporter sidecar in managed provisioner pods. |
--node-metrics-port |
9090 |
Exporter port when node metrics are enabled. |
--stage-gc-enabled |
true |
Enable periodic stage cleanup after successful provisioning and installation safety checks. |
--stage-gc-interval |
5m |
Delay between GC attempts, plus jitter; positive whole seconds in Go duration syntax. |
--stage-gc-min-age |
24h |
Minimum stage directory age; positive whole seconds in Go duration syntax. |
--stage-gc-allow-nested-pid-namespace |
false |
Test-only. Let the reaper run on nodes with a private PID namespace (kind). |
For a local operator built from the same release as the installed CLI, use that CLI's version to select the matching provisioner image. For custom source builds, use your own matching provisioner image instead.
BREWLET_VERSION="$(brewlet version)"
./bin/operator --namespace=brewlet \
--provisioner-image="ghcr.io/microsoft/brewlet-node-provisioner:${BREWLET_VERSION}" \
--allowed-source-mirror-hosts=registry.internal.example.com
kubectl apply -f nodeprofile.yaml
The Helm pre-delete Job selects a separate coordinator-only mode with
--uninstall-release-name, --uninstall-release-namespace, and
--uninstall-timeout. Both release identity flags are required. --namespace
still identifies the worker namespace, which may differ from the release
namespace. This mode does not start controllers or host-mutating workers; see
Uninstall.
Node-provisioner environment variables¶
The provisioner environment contract lives in
provisioner/README.md.
The Kubernetes operator sets these variables on NodeProfile-managed DaemonSets;
configure the profile and operator settings rather than editing worker environments.
| Env var | Default | Meaning |
|---|---|---|
JDK_SOURCE_COUNT |
required | Positive number of indexed JDK source entries. |
JDK_SOURCE_<n>_TOKEN |
required | Safe <distribution>-<feature> inventory token. |
JDK_SOURCE_<n>_IMAGE |
required | Fully qualified, tagless repository@sha256:<64 lowercase hex> source image. |
JDK_SOURCE_<n>_JAVA_HOME |
required | Clean absolute Java-home path inside the image. |
LAUNCHER_SOURCE_COUNT |
0 |
Number of indexed optional launcher sources. |
LAUNCHER_SOURCE_<n>_NAME |
required per entry | Lowercase launcher name; java is reserved. |
LAUNCHER_SOURCE_<n>_IMAGE |
required per entry | Fully qualified, tagless SHA-256 digest source image. |
LAUNCHER_SOURCE_<n>_PATH |
required per entry | Clean absolute launcher path inside the image. |
JDKS / LAUNCHERS |
derived | Internal comma-separated inventories derived from the indexed entries. They are not accepted as independent inputs. |
BREWLET_APP_CDS_REGENERATION_ENABLED |
false |
Internal operator-to-provisioner policy transport. When true, atomically creates the root-owned AppCDS authorization sentinel and publishes brewlet.sh/appcds-regeneration=true; when false or during cleanup/failure, removes both. Configure spec.appCDS.regenerationEnabled, not this variable directly. |
BREWLET_STAGE_GC_ENABLED |
true |
Enable the periodic GC loop after provisioning, subject to installation safety checks. Rendered from operator-wide stageGC.enabled. |
BREWLET_STAGE_GC_INTERVAL_SECONDS |
300 |
Delay between GC attempts, plus up to 10% jitter; integer seconds in 1..2147483647. |
BREWLET_STAGE_GC_MIN_AGE_SECONDS |
86400 |
Minimum stage directory age; integer seconds in 1..2147483647. |
BREWLET_STAGE_GC_ALLOW_NESTED_PID_NAMESPACE |
false |
Test-only. Pass --allow-nested-pid-namespace to the reaper on kind nodes; rendered from operator-wide policy. |
BREWLET_RUNNABLE_STAGE |
/tmp/brewlet-runnable (provisioner) |
Host stage root used by automatic GC. A custom location must also be configured separately for the shim and exporter's read-only mount; this variable does not reconfigure those processes. |
NODE_NAME |
(downward API) | The node to label; injected from spec.nodeName. |
BREWLET_PROFILE_NAME |
default |
Profile name paired with its UID in managed worker authority checks. |
BREWLET_PROFILE_UID |
(empty) | Operator-managed profile UID paired with node ownership and persisted writer authority. Required: provisioning and cleanup always verify node UID/owner labels and durable profile authority. |
BREWLET_TARGET_UIDS |
(empty) | Operator-generated immutable space-separated authorized Node UIDs for this worker. Both modes refuse writes outside this set; missing/empty sets authorize no host. Never supply this manually to adopt a host. |
BREWLET_PROFILE_GENERATION |
0 |
Operator-managed generation paired with BREWLET_PROFILE_UID. |
BREWLET_PREFIX |
/opt/brewlet |
Host install prefix (bin/, jdks/, launchers/). |
CONTAINERD_CONFIG |
/etc/containerd/config.toml |
Primary containerd configuration. Validated mode uses an imported drop-in when supported and otherwise patches this file with a backup. |
CONTAINERD_DROPIN_DIR |
/etc/containerd/config.toml.d |
Drop-in directory used when the primary config imports *.toml from it. |
CONTAINERD_DROPIN_FILE |
/etc/containerd/config.toml.d/99-brewlet.toml |
Brewlet-managed runtime drop-in. |
CONTAINERD_ADDRESS |
/run/containerd/containerd.sock |
Host containerd socket for copy-from-image and stage GC reference inspection. |
CONTAINERD_NAMESPACE |
k8s.io |
containerd namespace for image pulls; does not restrict GC, which checks all namespaces. |
BREWLET_MODE |
provision |
provision installs the runtime; cleanup reverses recorded host state for a retiring target or deleted profile. Managed cleanup uses the frozen per-node containerd policy and runs in brewlet-cleanup-<profile> (§5.6). |
BREWLET_CONTAINERD_RESTART |
validated |
validated validates the effective config with containerd config dump, restarts the host service only when needed, checks containerd and the live brewlet handler, and restores known-good config on activation failure. none neither mutates nor signals containerd during provisioning or cleanup. Rendered from spec.rollout.containerdRestart; other values fail before host operations. |
BREWLET_VALIDATE |
true |
Run java -version for every JDK and verify every staged launcher is executable before publishing runtime or capability labels. Arbitrary launchers are not executed. false skips both sets of checks. Rendered from spec.rollout.validate. |
MIRRORS |
(empty) | Strict comma-separated <upstream-host>=<mirror-host[/path]> pairs rendered from spec.registry.mirrors. Schemes, whitespace, empty entries, duplicates, self-mappings, and unapproved destinations fail closed. |
SOURCE_ALLOWED_MIRROR_HOSTS |
(empty) | Exact destination host allowlist rendered from the operator's --allowed-source-mirror-hosts; empty disables MIRRORS. |
SOURCE_POLICY_BIN |
/opt/brewlet-dist/brewlet-source-policy |
Baked validator for digest references, source paths, registry hosts, and mirror targets. |
Admission webhook¶
The brewlet-admission webhook is
mutating+validating. For every pod on CREATE with runtimeClassName: brewlet it:
- overwrites
brewlet.sh/artifact-container,brewlet.sh/artifact-ref, andbrewlet.sh/artifact-digest(when digest-pinned) as compatibility hints from the selected Pod image; the shim resolves the JAR from containerd-owned metadata and the content store by digest, not from the hints; - matches any requested JDK/launcher/AppCDS-regeneration combination against
the ready fleet plus valid
NodeProfiles, denying withNoCompatibleJDK,NoCompatibleLauncher, orAppCDSRegenerationDisabled(a request only a profile satisfies is admitted with a warning, enabling scale-from-zero); - steers scheduling via
nodeAffinityonto per-capability node labels.
Its NodeProfile endpoint also rejects mutable source references and mirrors
outside --allowed-source-mirror-hosts. The NodeProfileReconciler repeats the
same checks before creating a privileged DaemonSet, so webhook disablement or
bypass does not weaken the source boundary.
Admission matches JDK and launcher capability keys with Operator: Exists.
The Capability labels and autoscaling
guide explains the end-to-end scheduling flow and autoscaler integration. The
canonical capability-label contract
defines the complete key grammar and compatibility guarantees.
Non-brewlet pods pass through untouched. With admission.failurePolicy: Ignore
(default) a webhook outage never blocks workloads. For AppCDS regeneration this
is only an availability choice: the shim independently requires the root-owned
/opt/brewlet/policy/appcds-regeneration-enabled sentinel, so fail-open
admission cannot grant cache-write authority. The shim also fails closed rather
than guessing when it cannot resolve runtime image identity from containerd
metadata.
Serving certificate. By default Helm generates a 90-day self-signed serving
certificate and injects the CA as the caBundle. A helm upgrade regenerates
the Secret and CA bundle together and rolls the webhook pods, but this simple
mode does not renew certificates between Helm operations. Configure its lifetime
with admission.selfSigned.validityDays. Treat it as the simple evaluation path:
long-running installations must either enable cert-manager or schedule a Helm
upgrade before the certificate expires.
For automatic renewal, install cert-manager with its CA injector and enable:
admission:
certManager:
enabled: true
issuerRef:
name: platform-ca
kind: ClusterIssuer
group: cert-manager.io
duration: 2160h
renewBefore: 720h
Set createSelfSignedIssuer: true instead of issuerRef.name when a
chart-managed namespaced self-signed CA chain is appropriate. In either
cert-manager mode, cert-manager creates and rotates the
brewlet-admission-cert Secret and injects the CA into both webhook
configurations. The admission process watches the mounted keypair and reloads
renewed certificates without a pod restart.
admission.nodeProfileFailurePolicy defaults to Ignore so certificate
bootstrap cannot block chart-created profiles. The operator still repeats every
NodeProfile validation before privileged reconciliation. Set it to Fail once
webhook availability is guaranteed if API transport failures must reject profile
writes synchronously.
Network isolation. The chart can add ingress NetworkPolicies for the admission pod and all enabled metrics endpoints:
metrics:
enabled: true
networkPolicy:
enabled: true
healthProbes:
ingressFrom:
- ipBlock:
cidr: 10.1.0.0/16
admission:
apiServerCIDRs: ["10.0.0.0/24"]
metrics:
ingressFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
The chart requires explicit kubelet health-probe peers, API-server sources, and
metrics peers rather than guessing cluster-specific identities. Determine the
source CIDRs used by your nodes and control plane, then verify health probes and
webhook connectivity before rolling this setting into production.
NetworkPolicies require a CNI that enforces the Kubernetes NetworkPolicy API.
When the API server reaches webhooks through in-cluster proxy pods, CIDRs alone
may not match. For example, AKS dials pods through konnectivity-agent pods in
kube-system, and Azure CNI powered by Cilium never applies ipBlock rules to
in-cluster pods. Add those pods with networkPolicy.admission.ingressFrom:
networkPolicy:
admission:
ingressFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app: konnectivity-agent
Pod-side annotations the webhook reads (developer-facing) — see Deploying workloads:
| Annotation | Example | Meaning |
|---|---|---|
brewlet.sh/jdk |
21 or temurin-21 |
Request a specific JDK feature (any distro) or exact <dist>-<feature>. |
brewlet.sh/launcher |
jaz |
Request a launcher. Empty / java = vanilla OpenJDK launcher. |
brewlet.sh/cds-regenerate |
true |
Request node-side AppCDS regeneration. Requires a ready node or valid NodeProfile whose spec.appCDS.regenerationEnabled is true. |
brewlet.sh/artifact-container |
app |
Selects which regular container's image the webhook mirrors into Pod-wide compatibility hints. The webhook normalizes this value to the selected container name; other tasks ignore the shared hints and remain bound to their own CRI images. |
RuntimeClass¶
The operator manages the brewlet RuntimeClass; this is what it generates (mirrors
deploy/runtimeclass.yaml):
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: brewlet
handler: brewlet # matches the containerd runtime name
scheduling:
nodeSelector:
brewlet.sh/runtime: "ready" # only land on provisioned nodes
overhead:
podFixed:
memory: "64Mi" # JVM/runtime baseline overhead accounting
cpu: "50m"
overhead.podFixed is how the scheduler and LimitRange/quotas account for the
JVM/runtime baseline. Adjust it if your JVMs have a materially different fixed
footprint.
Precedence & defaults you should know¶
- JVM launch flags: artifact structured knobs carry app-intrinsic correctness
flags; deployment-descriptor
jvm.argscarries heap/GC/agent tuning and comes after those knobs. The descriptor'sjvm.launcher/brewlet.sh/launcherselectsjavaor a node-installed launcher. Brewlet injects no-XXflags itself. See Resource requests, limits & JVM tuning. - JDK selection: the deployment descriptor is authoritative:
spec.jvm.version(plus optionalspec.jvm.distribution) onJavaApplication, orbrewlet.sh/jdkon raw pods, drives validation, scheduling, and shim launch selection. A bare feature matches any distribution; the shim chooses the lexicographically first active, compatible inventory entry of that feature on each node.<distribution>-<feature>pins one inventory entry, not a patch version. Adding an earlier-sorting name can change future version-only launches, and different node inventories can produce different choices. See JDK selection and defaults. - JDK source selection: every
spec.jdks[]entry carries exactly one digest-pinned source and Java-home path. Distribution names never resolve to Brewlet-owned images. Custom names such astemurin-stableandtemurin-canarylet two builds of the same feature coexist; define them inNodeProfile.spec.jdks[].distribution(orprovisioner.jdks[].distributionfor Helm's default profile). There is no separate patch/build selector inspec.jvm; the selected entry's source digest determines the build. - Mirror selection: a NodeProfile mapping is accepted only when its destination host exactly matches the external operator/admission allowlist.
- cgroup v2 is mandatory on nodes; the provisioner refuses cgroup v1-only nodes.
- Digest-pinned artifact refs are required for Kubernetes execution
(
repo@sha256:…). The shim resolves that exact target from the content store, verifies its platform-manifest config against CRI metadata, and rejects tag-only requests.
Next steps¶
- JDK management — explicit source requirements, copy-from-image mechanics, and patching.
- Launchers — installing and choosing
jaz. - Capability labels and autoscaling —
connect
NodeProfileinventories to workload affinity and node-pool scaling. - Deploying workloads — put these knobs to use.
- Runtime metrics and Grafana dashboards — enable, scrape, query, visualize, and troubleshoot Brewlet telemetry.