Skip to content

JEP-0013 Phase 3 - operator image - #1061

Open
RoddieKieley wants to merge 3 commits into
jumpstarter-dev:mainfrom
RoddieKieley:jep-0013-phase3-operator-image
Open

JEP-0013 Phase 3 - operator image#1061
RoddieKieley wants to merge 3 commits into
jumpstarter-dev:mainfrom
RoddieKieley:jep-0013-phase3-operator-image

Conversation

@RoddieKieley

@RoddieKieley RoddieKieley commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

JEP-0013 Phase 3 PR B**: make the MetricsStream hub from PR A runnable in-cluster.

Depends on PR A: #1060

Do not merge until A is on main, then rebase this branch onto main. Related PRs C–E will be linked here as they are opened.

  • Build and copy /telemetry in controller/Containerfile and make build / docker-build-ci (operator already Command: ["/telemetry"]; main still only ships manager + router).
  • Expose telemetry HTTP metrics on Service/container port 8080; /healthz and /readyz probes on that port (replacing TCP probes on gRPC :9093).
  • CR: spec.telemetry.metrics.scrapeTimeout, driverTypeEnum, exemplarKeys. Defaults match JEP-0013 (7s, the driver-type enum, client,lease_id). No ServiceMonitor (Phase 5).
  • Set GRPC_TELEMETRY_ENDPOINT so the telemetry process advertises the in-cluster Service DNS.

TLS: #1023 already landed telemetry TLS on main. This PR keeps that path (cert-manager or manual spec.telemetry.grpc.tls.certSecret, rolling-restart hash, non-fatal missing CA). It does not reimplement TLS.

Lab: in-cluster reverse-scrape of GET /metrics on Service port 8080 worked (port-forward). After driver ops, Python OpenMetrics exemplars still fail Go parseMetricFamilies; the hub from A omits that snapshot and increments jumpstarter_metrics_parse_errors_total. spec.telemetry.metrics.exemplarKeys only allowlists keys on the merge path after a successful parse — it does not fix exemplar decode. That remains A / JEP DD-3, not this PR.

DEMO

The lab walkthrough for the stacked Phase 3 work (A+B+C+D+E plus lab-only Route) is on the fork demo branch, not this PR:

See MetricsStream during the lease for the DD-3 exemplar limitation observed on :8080.

How this PR fits the series

flowchart TB
  A["PR A #1060: proto + hub"]
  B["PR B this PR: image + metrics port + scrape CR"]
  C["PR C: exporter MetricsStream client"]
  D["PR D: Loki HTTP push"]
  E["PR E: jmp PushLogs"]
  A --> B
  A --> C
  B --> D
  D --> E
Loading
PR Branch Status
A jep-0013-phase3-metricsstream #1060
B jep-0013-phase3-operator-image This PR
C jep-0013-phase3-exporter-metricsstream #1062
D jep-0013-phase3-loki-push #1063
E jep-0013-phase3-client-pushlogs #1064

Unique work vs PR A: the operator/image commit on this branch (435eebee). If GitHub shows A's commits, that is stacking against main; review that unique commit only.

Out of scope

  • MetricsStream protocol and parse-error omit (A / JEP-0013 Phase 3 - MetricsStream #1060)
  • Exporter MetricsStream client and Register identity (C) — after #1058, identity must be exporter_name, not Metadata.name
  • Loki / PushLogs (D, E), including exporter TFTP errors that only show up on the log path
  • Advertising a Route/hostname for out-of-cluster exporters (lab-only on the demo branch)
  • ServiceMonitor (Phase 5)

NOTE

Open #1027 also touches telemetry operator files (log-ingest e2e).

RoddieKieley and others added 3 commits September 2, 2026 10:39
Add the MetricsStream protocol and Go hub so Prometheus can scrape
merged exporter OpenMetrics from telemetry without an exporter client
yet. Generated Python stubs are included for proto consistency.

Co-authored-by: Cursor <cursoragent@cursor.com>
Stop silently dropping unparseable exporter snapshots. Log the exporter and
error, and increment jumpstarter_metrics_parse_errors_total so reverse-scrape
omissions are visible on the same /metrics response.

Co-authored-by: Cursor <cursoragent@cursor.com>
…e 3)

Build /telemetry into the controller image and have the operator mount
cert-manager TLS, advertise the CA, and expose scrape flags plus the
HTTP metrics port so reverse-scrape can run in-cluster.
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 54 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: e8a9202e-3561-4b2c-9e70-c2cd15edac03

📥 Commits

Reviewing files that changed from the base of the PR and between f1b7f24 and 435eebe.

⛔ Files ignored due to path filters (2)
  • controller/internal/protocol/jumpstarter/v1/telemetry.pb.go is excluded by !**/*.pb.go
  • controller/internal/protocol/jumpstarter/v1/telemetry_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • controller/Containerfile
  • controller/Makefile
  • controller/cmd/telemetry/main.go
  • controller/deploy/operator/api/v1alpha1/jumpstarter_types.go
  • controller/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go
  • controller/deploy/operator/config/crd/bases/operator.jumpstarter.dev_jumpstarters.yaml
  • controller/deploy/operator/internal/controller/jumpstarter/jumpstarter_controller.go
  • controller/deploy/operator/internal/controller/jumpstarter/telemetry.go
  • controller/deploy/operator/internal/controller/jumpstarter/telemetry_phase3_test.go
  • controller/deploy/operator/internal/controller/jumpstarter/telemetry_test.go
  • controller/image_layout_test.go
  • controller/internal/service/metrics_merge.go
  • controller/internal/service/metrics_merge_test.go
  • controller/internal/service/metrics_stream.go
  • controller/internal/service/metrics_stream_test.go
  • controller/internal/service/telemetry_http.go
  • controller/internal/service/telemetry_identity.go
  • controller/internal/service/telemetry_service.go
  • protocol/proto/jumpstarter/v1/telemetry.proto
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2.pyi
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.py
  • python/packages/jumpstarter-protocol/jumpstarter_protocol/jumpstarter/v1/telemetry_pb2_grpc.pyi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@raballew raballew left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments for now. Waiting for merge of the first PR.

Comment on lines +326 to +332
return []string{
fmt.Sprintf("--grpc-bind=:%d", telemetryPort),
fmt.Sprintf("-metrics-bind-address=:%d", telemetryMetricsPort),
fmt.Sprintf("-scrape-timeout=%s", timeout),
fmt.Sprintf("-driver-type-enum=%s", strings.Join(enum, ",")),
fmt.Sprintf("-exemplar-keys=%s", strings.Join(keys, ",")),
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed use of dash and double-dash

Comment on lines +342 to +345
// Max wait for parallel exporter MetricsStream responses during a /metrics fan-out.
// Should be lower than the Prometheus scrape_timeout.
// +kubebuilder:default="7s"
ScrapeTimeout *metav1.Duration `json:"scrapeTimeout,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also set an upper limit to avoid keeping connections open for very long times.

Comment on lines 280 to 284
srv := grpc.NewServer(
grpc.Creds(creds),
grpc.ChainUnaryInterceptor(recovery.UnaryServerInterceptor()),
grpc.ChainStreamInterceptor(recovery.StreamServerInterceptor()),
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding grpc.MaxRecvMsgSize to the server options would provide an explicit limit especially if you are running 1000s of exporters that are scraped concurrently (each with up to 4 MB per message). Otherwise this might cause some OOM killed processes.

Comment on lines 107 to 116
func (s *TelemetryService) PushLogs(ctx context.Context, req *pb.PushLogsRequest) (*pb.PushLogsResponse, error) {
token, err := authentication.BearerTokenFromContext(ctx)
id, err := s.authenticateExporter(ctx)
if err != nil {
return nil, err
}

// Validate token and extract the subject (format: exporter:namespace:name:uid).
subject, err := s.Signer.ParseSubject(token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
}

// Only exporter tokens are allowed to push logs. Any other validly-signed
// token (e.g. a client token) is rejected immediately so that the identity
// checks below always have a non-empty claimedName/claimedNamespace.
parts := strings.SplitN(subject, ":", 4)
if len(parts) != 4 || parts[0] != "exporter" {
return nil, status.Errorf(codes.PermissionDenied, "token is not an exporter token")
}
claimedNamespace := parts[1]
claimedName := parts[2]
if claimedNamespace == "" || claimedName == "" {
return nil, status.Errorf(codes.PermissionDenied, "token has incomplete exporter identity")
}
claimedNamespace := id.namespace
claimedName := id.name

// Use context-based logger so tests can inject their own via logf.IntoContext.
logger := log.FromContext(ctx).WithName("telemetry")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry.Message, entry.Component, entry.Severity, entry.Lease, entry.Client, entry.Operation, entry.Result, entry.DriverType have no size bound. Should we truncate them too?

Comment on lines +334 to +341
// Allowlist of keys to include in Prometheus exemplars. Unlisted keys are omitted.
// +kubebuilder:default={"client","lease_id"}
ExemplarKeys []string `json:"exemplarKeys,omitempty"`

// Allowed driver_type label values. Unlisted types are remapped to "other".
// +kubebuilder:default={"power","storage","network","serial","console","video","composite"}
DriverTypeEnum []string `json:"driverTypeEnum,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user with CR write access can set either list to an arbitrarily large array for ExemplarKeys []string and DriverTypeEnum []string. Adding // +kubebuilder:validation:MaxItems=<insert a good limit here> and // +kubebuilder:validation:MaxLength=<insert a good limit here> per item would enforce a bound.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants