Skip to content

Commit a47ab81

Browse files
RoyLinRoyLin
authored andcommitted
release: v0.5.1 — production hardening (liveness, DaemonSet, supply chain)
Collector refreshes a liveness heartbeat (/run/a3s-observer.alive) at startup + every report tick; DaemonSet gains system-node-critical priority, RollingUpdate, 30s grace, and a heartbeat livenessProbe; release workflow adds Trivy CVE scan (report-only), keyless cosign signing, and SLSA provenance + SBOM. 0.5.0 -> 0.5.1.
1 parent 756045e commit a47ab81

9 files changed

Lines changed: 68 additions & 15 deletions

File tree

.github/workflows/image.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: image
22

33
# Canonical release: on a version tag, build the collector clean-room (the eBPF crate +
4-
# userspace binary) and push the runtime image to GHCR. The internal A3S registry (what the
5-
# egress-constrained cluster pulls) is fed by promoting/mirroring this image.
4+
# userspace binary), push the runtime image to GHCR, scan it for CVEs, and keyless-sign it.
5+
# The internal A3S registry (what the egress-constrained cluster pulls) is fed by mirroring.
66
on:
77
push:
88
tags: ['v*']
@@ -11,6 +11,7 @@ on:
1111
permissions:
1212
contents: read
1313
packages: write
14+
id-token: write # keyless cosign signing via GitHub OIDC
1415

1516
jobs:
1617
image:
@@ -30,17 +31,33 @@ jobs:
3031
registry: ghcr.io
3132
username: ${{ github.actor }}
3233
password: ${{ secrets.GITHUB_TOKEN }}
34+
- id: img
35+
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" # ghcr needs lowercase
3336
- id: meta
3437
uses: docker/metadata-action@v5
3538
with:
36-
images: ghcr.io/${{ github.repository }} # -> ghcr.io/a3s-lab/observer (lowercased)
39+
images: ${{ steps.img.outputs.name }}
3740
tags: |
3841
type=semver,pattern={{version}}
3942
type=ref,event=tag
40-
- uses: docker/build-push-action@v6
43+
- id: build
44+
uses: docker/build-push-action@v6
4145
with:
4246
context: dist
4347
file: deploy/Dockerfile
4448
push: true
4549
tags: ${{ steps.meta.outputs.tags }}
4650
labels: ${{ steps.meta.outputs.labels }}
51+
provenance: true # SLSA build provenance attestation
52+
sbom: true # SBOM attestation
53+
# Supply chain: scan for fixable CRITICAL CVEs, then keyless-sign the pushed digest.
54+
- name: Trivy image scan (report fixable CRITICAL/HIGH)
55+
uses: aquasecurity/trivy-action@0.24.0
56+
with:
57+
image-ref: ${{ steps.img.outputs.name }}@${{ steps.build.outputs.digest }}
58+
severity: CRITICAL,HIGH
59+
ignore-unfixed: true
60+
exit-code: '0' # report-only; set to '1' to block releases once the base is confirmed clean
61+
- uses: sigstore/cosign-installer@v3
62+
- name: Sign the image (keyless, GitHub OIDC)
63+
run: cosign sign --yes ${{ steps.img.outputs.name }}@${{ steps.build.outputs.digest }}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
All notable changes to a3s-observer will be documented in this file.
44

5+
## [0.5.1] — production hardening
6+
7+
### Changed
8+
9+
- **Liveness heartbeat**: the collector refreshes `/run/a3s-observer.alive`
10+
(`A3S_OBSERVER_HEARTBEAT`) at startup and every 60s report tick, so a k8s `livenessProbe`
11+
restarts a collector that has wedged (stopped pumping events).
12+
- **DaemonSet** is production-grade: `system-node-critical` priority, `RollingUpdate`
13+
(maxUnavailable 1), a 30s graceful-shutdown window for the SIGTERM flush, and the heartbeat
14+
liveness probe.
15+
- **Release supply chain**: the image workflow scans the pushed image for CVEs (Trivy,
16+
report-only for now), keyless-signs it (cosign / GitHub OIDC), and attaches SLSA build
17+
provenance + an SBOM.
18+
519
## [0.5.0] — SSL/TLS content capture (opt-in)
620

721
### Added

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-observer"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2021"
55
license = "MIT"
66
description = "General-purpose, language-agnostic eBPF observability for AI agents (LLM calls, tools, files, network egress)."

a3s-observer-collector/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-observer-collector"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2021"
55
license = "MIT"
66
description = "a3s-observer collector: loads the eBPF probes and exports enriched events."

a3s-observer-collector/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ async fn main() -> anyhow::Result<()> {
166166
streaming (Ctrl-C to stop)"
167167
);
168168

169+
// Liveness heartbeat: refresh a file at startup and on every report tick, so a k8s
170+
// livenessProbe can detect a wedged collector (file goes stale → restart the pod).
171+
let heartbeat = std::env::var("A3S_OBSERVER_HEARTBEAT")
172+
.unwrap_or_else(|_| "/run/a3s-observer.alive".into());
173+
let _ = std::fs::write(&heartbeat, b"ok");
174+
169175
let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())?;
170176
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
171177
let mut stats = Stats::default();
@@ -176,6 +182,7 @@ async fn main() -> anyhow::Result<()> {
176182
_ = sigint.recv() => break,
177183
_ = sigterm.recv() => break, // k8s sends SIGTERM on pod termination
178184
_ = report.tick() => {
185+
let _ = std::fs::write(&heartbeat, b"ok"); // refresh liveness heartbeat
179186
let dropped: u64 = drops
180187
.get(&0, 0)
181188
.map(|v| v.iter().copied().sum())

a3s-observer-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-observer-common"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2021"
55
license = "MIT"
66
description = "Shared no_std types crossing the eBPF <-> userspace boundary for a3s-observer."

a3s-observer-ebpf/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-observer-ebpf"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
edition = "2021"
55
license = "MIT"
66
publish = false

deploy/daemonset.yaml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Run a3s-observer on every node. It writes NDJSON to stdout; a node-level OpenTelemetry
22
# Collector then tails the container log and ships OTLP (see deploy/otel-collector.yaml).
33
#
4-
# docker build -f deploy/Dockerfile -t <registry>/a3s-observer:0.2.0 . && docker push ...
54
# kubectl apply -f deploy/daemonset.yaml
65
#
76
# No k8s API access / RBAC is needed: pod-UID + container-id come from /proc/<pid>/cgroup.
@@ -14,14 +13,19 @@ metadata:
1413
spec:
1514
selector:
1615
matchLabels: { app: a3s-observer }
16+
updateStrategy:
17+
type: RollingUpdate
18+
rollingUpdate: { maxUnavailable: 1 } # controlled node-by-node rollout
1719
template:
1820
metadata:
1921
labels: { app: a3s-observer }
2022
spec:
2123
hostPID: true # resolve /proc/<pid> for host processes (identity)
24+
priorityClassName: system-node-critical # observability shouldn't be evicted under pressure
25+
terminationGracePeriodSeconds: 30 # the collector flushes a final report on SIGTERM
2226
containers:
2327
- name: a3s-observer
24-
image: 10.12.111.133:49164/a3s/observer:0.2.4 # A3S OCI Registry (built from deploy/Dockerfile)
28+
image: 10.12.111.133:49164/a3s/observer:0.5.1 # mirror of ghcr.io/a3s-lab/observer:0.5.1
2529
securityContext:
2630
# eBPF load + tracepoint attach requires privileged. (Verified: a non-root
2731
# process with only CAP_BPF+CAP_PERFMON fails to attach — the tracefs tracepoint
@@ -30,12 +34,23 @@ spec:
3034
env:
3135
- { name: A3S_OBSERVER_JSON, value: "1" }
3236
# - { name: A3S_OBSERVER_FILES, value: "1" } # opt in to file-write capture
37+
# - { name: A3S_OBSERVER_SSL, value: "1" } # opt in to OpenSSL content (uprobe)
3338
volumeMounts:
3439
- { name: sys, mountPath: /sys, readOnly: true } # tracepoint ids under /sys
3540
resources:
41+
# No CPU limit on purpose: a tracing agent should not be CPU-throttled into
42+
# dropping events. Memory is bounded; the rings are fixed-size.
3643
requests: { cpu: 50m, memory: 64Mi }
3744
limits: { memory: 256Mi }
45+
livenessProbe:
46+
# Restart a wedged collector: it refreshes /run/a3s-observer.alive at startup and
47+
# every 60s report tick, so a stale (>180s) heartbeat means it stopped pumping.
48+
exec:
49+
command:
50+
["sh", "-c", "test $(( $(date +%s) - $(stat -c %Y /run/a3s-observer.alive) )) -lt 180"]
51+
initialDelaySeconds: 20
52+
periodSeconds: 60
3853
volumes:
3954
- { name: sys, hostPath: { path: /sys } }
4055
tolerations:
41-
- operator: Exists # run on every node, including control-plane
56+
- operator: Exists # run on every node, including control-plane / tainted nodes

0 commit comments

Comments
 (0)