Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/actions/gpu-mig-kind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/installer-check/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/k8s-compat-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ runs:

- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
29 changes: 27 additions & 2 deletions .github/actions/k8s-compat-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ inputs:
k8s-version:
description: "Kubernetes version to spin up the kind cluster on (e.g. v1.35.1)."
required: true
node-image-digest:
description: "Optional \"@sha256:...\" suffix pinning the kindest/node image to the build made for the kind release this action runs."
required: false
default: ""
deployment-method:
description: "helm or manifest — how zxporter is deployed into kind."
required: true
Expand Down Expand Up @@ -52,6 +56,12 @@ runs:

- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand All @@ -67,8 +77,8 @@ runs:
- name: Create k8s Kind Cluster
uses: helm/kind-action@v1
with:
version: v0.32.0
node_image: kindest/node:${{ inputs.k8s-version }}
version: v0.33.0
node_image: kindest/node:${{ inputs.k8s-version }}${{ inputs.node-image-digest }}
cluster_name: kind-${{ inputs.k8s-version }}
wait: 120s

Expand All @@ -92,6 +102,21 @@ runs:
kind load docker-image "$img" --name "kind-${{ inputs.k8s-version }}"
done

# The chart's CRD-adoption hook runs a digest-pinned image from
# public.ecr.aws, which answers anonymous pulls from shared runner IPs
# with "429 Too Many Requests: Data limit exceeded". Pull it once on the
# runner (authenticated if the calling job logged in to public.ecr.aws,
# retried otherwise) and import it into the nodes; the hook pulls
# IfNotPresent. `kind load docker-image` cannot do this for a multi-arch
# image referenced by digest: see the script's header.
- name: Preload the chart's CRD-adoption hook image
if: ${{ inputs.deployment-method == 'helm' }}
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
image="$(yq eval '.crdAdoption.image' helm-chart/zxporter/values.yaml)"
hack/kind-preload-images.sh "kind-${{ inputs.k8s-version }}" "$image"

- name: Deploy testserver to Kubernetes
shell: bash
working-directory: ${{ inputs.working-directory }}
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/lint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
83 changes: 83 additions & 0 deletions .github/actions/node-storage-kind/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: "zxporter: node-storage-kind"
description: |
End-to-end validation of node storage occupancy collection against a real
kubelet on a real kind cluster: the production KubeletSummaryClient reads
node.fs and node.runtime.imageFs through the API-server nodes/proxy
subresource, and the spec asserts the invariants the feature rests on.

Why a live cluster earns its keep here. Every other test of this feature
reads a captured fixture, which cannot catch the kubelet dropping a
section, renaming a field, or the nodes/proxy RBAC verb going missing.
More importantly, imagefs-is-a-subset-of-nodefs -- the invariant that
stops consumers double-counting disk -- has otherwise only ever been
checked against numbers we wrote ourselves, and the shared-device
classifier is a heuristic precisely because the kubelet does not name the
device, so the only honest test of it is a node whose real layout we know.

The cheapest kind spec in this tree: no image to build, no operator to
install, no fixture workload, and no port-forward. The client reads
through the API server, the same path kubectl already proves reachable
from the runner, so a plain cluster with no add-ons is enough. That same
plain cluster also runs the runtimeclass spec (test/e2e/
runtimeclass_test.go): RuntimeClass is a built-in node.k8s.io/v1 kind, so
its collector e2e needs no add-ons either -- one cluster, two labels.

Assertions live in Go/Ginkgo specs (test/e2e/node_storage_test.go,
test/e2e/runtimeclass_test.go) rather than a shell heredoc, matching
oom-flow-kind: the interesting logic is Kubernetes-API-shaped, not
HTTP-response-shaped.

Consumed by two callers:
- operators/zxporter/.github/workflows/node-storage-kind-test.yml
(published to devzero-inc/zxporter; uses this via
./.github/actions/node-storage-kind)
- .github/workflows/zxporter-node-storage-kind.yml at services root
(uses this via ./operators/zxporter/.github/actions/node-storage-kind
with working-directory: operators/zxporter)

Caller checks out the repo; this action assumes cwd contains the
zxporter source (or the working-directory input points at it).

inputs:
working-directory:
description: "Path to the zxporter tree relative to the repo root."
required: false
default: "."

runs:
using: composite
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
cache-dependency-path: ${{ inputs.working-directory }}/go.sum

# Two nodes on purpose: the spec walks every node in the cluster, so a
# multi-node cluster exercises the per-node loop rather than a
# single-node special case. kind's control-plane and worker both run
# containerd with its state on the same filesystem as the kubelet's,
# which is what makes the shared-device assertion meaningful.
- name: Create kind cluster
uses: helm/kind-action@v1
with:
version: v0.27.0
node_image: kindest/node:v1.32.3
cluster_name: kind-node-storage
config: ${{ inputs.working-directory }}/.github/actions/node-storage-kind/kind-config.yaml
wait: 120s

- name: Run node-storage and runtimeclass e2e specs
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
# Ginkgo's label-filter grammar uses || for OR (a single | is a syntax
# error that aborts the suite before any spec runs).
go test ./test/e2e/... -v -ginkgo.v -ginkgo.label-filter='node-storage || runtimeclass' -timeout 10m
9 changes: 9 additions & 0 deletions .github/actions/node-storage-kind/kind-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Two nodes on purpose. The node-storage spec walks every node in the cluster,
# so a multi-node cluster exercises the per-node loop rather than a single-node
# special case -- and it means the shared-device assertion has to hold on more
# than one kubelet before the suite goes green.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
6 changes: 6 additions & 0 deletions .github/actions/oom-flow-kind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/policy-kind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down
63 changes: 61 additions & 2 deletions .github/actions/runtime-metrics-kind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand Down Expand Up @@ -58,6 +64,7 @@ runs:
# The deprecated key must render the exact same manifests as the
# canonical one (privileges, env, RBAC), and disabling via the
# deprecated key alone must still work.
helm dependency build ./helm-chart/zxporter-nodemon
helm template zxporter-nodemon ./helm-chart/zxporter-nodemon \
--set global.k8sProvider=other --set dcgmExporter.enabled=false \
--set jvmMetrics.enabled=true > /tmp/render-deprecated.yaml
Expand Down Expand Up @@ -149,6 +156,8 @@ runs:
kubectl -n jvm-spike rollout status deploy/java21-precedence-env --timeout=300s
kubectl -n jvm-spike rollout status deploy/java21-precedence-cmdline --timeout=300s
kubectl -n jvm-spike rollout status deploy/java21-per-flag-sources --timeout=300s
kubectl -n jvm-spike rollout status deploy/zipkin-writable-rootfs --timeout=300s
kubectl -n jvm-spike rollout status deploy/zipkin-readonly-rootfs --timeout=300s
kubectl -n jvm-spike rollout status deploy/not-java --timeout=300s

- name: Deploy Node.js test workloads
Expand Down Expand Up @@ -213,10 +222,17 @@ runs:
# until everything expected has appeared, then run the strict
# assertions (which give precise failure messages if it never does).
need_versions = ['node-version-env', 'node-version-binaryscan', 'python-app',
'ruby-app', 'go-app', 'dotnet-app', 'deno-app', 'bun-app', 'rust-app']
'ruby-app', 'go-app', 'dotnet-app', 'deno-app', 'bun-app', 'rust-app',
# hsperfdata-less JVM: version comes from the image's
# JAVA_VERSION env, so it resolves like any other.
'zipkin-readonly-rootfs']
need_jvm = ['java8-xmx-only', 'zipkin-writable-rootfs']
def all_present():
c = fetch('/container/runtime-metrics')
if not any((m.get('pod') or '').startswith('java8-xmx-only') for m in c.get('jvm', [])):
jvm = c.get('jvm', [])
if not all(
any((m.get('pod') or '').startswith(p) for m in jvm)
for p in need_jvm):
return False
rt = c.get('runtimes', [])
return all(
Expand Down Expand Up @@ -381,6 +397,49 @@ runs:
if any((p or '').startswith('not-node') for p in all_runtime_pods):
raise SystemExit(f'runtime-metrics unexpectedly includes not-node in runtimes bucket; got {all_runtime_pods}')

# ============ hsperfdata-less JVM fallback ============
# zipkin-{writable,readonly}-rootfs are the same image and uid, differing
# only in readOnlyRootFilesystem. With a read-only rootfs and no writable
# /tmp, HotSpot cannot create /tmp/hsperfdata_<user>/<pid>, so the
# hsperfdata pipeline has nothing to read — the process must still be
# reported, as runtime "java" in the generic bucket.
jvm_entries = runtime.get('jvm', [])

# Writable rootfs: full JVM pipeline, real heap metrics.
m_rw = [m for m in jvm_entries if (m.get('pod') or '').startswith('zipkin-writable-rootfs')]
if not m_rw:
raise SystemExit(f'zipkin-writable-rootfs missing from jvm bucket; got {jvm_pods}')
if (m_rw[0].get('heap_max_size_bytes') or 0) <= 0:
raise SystemExit(f'zipkin-writable-rootfs expected heap.max_bytes > 0; got {m_rw[0]!r}')
# The image's entrypoint sets -Xms160m -Xmx160m; proves the flags are
# read from the live cmdline, not from the pod spec (which has neither).
if m_rw[0].get('flags_extracted', {}).get('xmx_bytes') != 167772160:
raise SystemExit(
f'zipkin-writable-rootfs expected xmx_bytes=167772160 from the image entrypoint; '
f'got {m_rw[0].get("flags_extracted")!r}')
if any((p or '').startswith('zipkin-writable-rootfs') for p in all_runtime_pods):
raise SystemExit('zipkin-writable-rootfs must not be double-reported in the runtimes bucket')

# Read-only rootfs: no heap metrics are possible, so it must NOT be in
# the jvm bucket — and must NOT vanish entirely either.
if any((p or '').startswith('zipkin-readonly-rootfs') for p in jvm_pods):
raise SystemExit('zipkin-readonly-rootfs has no hsperfdata and must not appear in the jvm bucket')
m_ro = [m for m in runtime.get('runtimes', [])
if (m.get('pod') or '').startswith('zipkin-readonly-rootfs')]
if not m_ro:
raise SystemExit(
'zipkin-readonly-rootfs missing from BOTH buckets — a JVM without hsperfdata '
f'must fall back to the runtimes bucket; got runtimes={all_runtime_pods} jvm={jvm_pods}')
if m_ro[0].get('runtime') != 'java':
raise SystemExit(f'zipkin-readonly-rootfs expected runtime="java"; got {m_ro[0]!r}')
# Version still resolves passively (JAVA_VERSION env in the image).
if not (m_ro[0].get('version') or '').startswith('21.'):
raise SystemExit(f'zipkin-readonly-rootfs expected a 21.x version from env; got {m_ro[0]!r}')
if m_ro[0].get('version_source') != 'env':
raise SystemExit(f'zipkin-readonly-rootfs expected version_source=env; got {m_ro[0]!r}')
if m_ro[0].get('namespace') != 'jvm-spike' or not m_ro[0].get('container_id'):
raise SystemExit(f'zipkin-readonly-rootfs has bad pod metadata: {m_ro[0]!r}')

# ============ Generic runtimes bucket ============
# dotnet-app deliberately exercises the apphost probe path (comm is the
# app name, not "dotnet"); go-app exercises the buildinfo probe.
Expand Down
15 changes: 15 additions & 0 deletions .github/actions/tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ runs:
steps:
- name: Set up Go
uses: actions/setup-go@v5
# setup-go runs `go version` / `go env` from the workspace root. In the
# services monorepo that is the root module, whose newer go line would make
# GOTOOLCHAIN=auto download that toolchain (unused, and it collides with the
# cache restore). local keeps the Go this module's go.mod asks for.
env:
GOTOOLCHAIN: local
with:
go-version-file: ${{ inputs.working-directory }}/go.mod
cache: true
Expand All @@ -34,6 +40,15 @@ runs:
working-directory: ${{ inputs.working-directory }}
run: go mod download

# helm-chart/*/chart_resources_test.go shells out to `helm template`, and
# skips itself when helm is absent. Install it explicitly so the chart
# guards actually run instead of reporting a silent green -- the runner
# image is not guaranteed to carry helm. Pinned to the same version the
# tunr chart tests use (.github/workflows/tunr-test.yml).
- uses: azure/setup-helm@v5
with:
version: "3.12.3"

- name: Run make test
shell: bash
working-directory: ${{ inputs.working-directory }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/chart-zxporter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ jobs:
- name: Package and push chart
run: |
rm -f helm-chart/zxporter/charts/zxporter-nodemon-*.tgz
# Vendor nodemon's own devzero-common first, so the nodemon copy
# bundled into this chart is complete on its own.
helm dependency build helm-chart/zxporter-nodemon
helm dependency update helm-chart/${{ env.CHART_NAME }}
helm lint helm-chart/${{ env.CHART_NAME }}
mkdir -p helm-chart/packages
Expand Down
Loading
Loading