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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log

## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Feature) Inventory Collector
- (Feature) (RBAC) Add ArangoPermissionRoleUserBinding handler
- (Feature) (Gateway) Use HTTP/2 for the default upstream to ArangoDB (hidden feature `gateway-default-http2`, enabled by default)
- (Bugfix) (RBAC) Fix scope evaluation, pool propagation, missing indexes, and add APIRefresh endpoint
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Flags:
--kubernetes.qps float32 Number of queries per second for k8s API. If set to 0 or less, API calls won't be throttled (default 32)
--leader.label.skip Skips Leader Label for the Pod
--log.format string Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used (default "pretty")
--log.level stringArray Set log levels in format <level> or <logger>=<level>. Possible loggers: action, agency, arangod-request, assertion, authz-pool-client, backup-operator, backup-policy-operator, chaos-monkey, cli-utils, crd, deployment, deployment-ci, deployment-reconcile, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, generic-parent-operator, grpc-service, helm, http, inspector, integration-authn-v1, integration-authorization-v1, integration-config-v1, integration-envoy-auth-v3, integration-envoy-auth-v3-impl-auth-bearer, integration-envoy-auth-v3-impl-auth-cookie, integration-envoy-auth-v3-impl-custom-openid, integration-envoy-auth-v3-impl-pass-mode, integration-events-v1, integration-meta-v1, integration-pong-v1, integration-scheduler-v2, integration-shutdown-v1, integration-storage-v1-s3, integration-storage-v2, integrations, k8s-client, kubernetes, kubernetes-access, kubernetes-admission, kubernetes-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-v2, operator-v2-event, operator-v2-worker, panics, permission-role-user-binding-operator, permission-token-operator, permission-token-webhook, platform-chart-operator, platform-pod-shutdown, platform-service-operator, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, sidecar, sidecar-authz, sidecar-authz-pool, sidecar-service-client-authentication, webhook (default [info])
--log.level stringArray Set log levels in format <level> or <logger>=<level>. Possible loggers: action, agency, arangod-request, assertion, authz-pool-client, backup-operator, backup-policy-operator, chaos-monkey, cli-utils, collector, crd, deployment, deployment-ci, deployment-reconcile, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, generic-parent-operator, grpc-service, helm, http, inspector, integration-authn-v1, integration-authorization-v1, integration-config-v1, integration-envoy-auth-v3, integration-envoy-auth-v3-impl-auth-bearer, integration-envoy-auth-v3-impl-auth-cookie, integration-envoy-auth-v3-impl-custom-openid, integration-envoy-auth-v3-impl-pass-mode, integration-events-v1, integration-meta-v1, integration-pong-v1, integration-scheduler-v2, integration-shutdown-v1, integration-storage-v1-s3, integration-storage-v2, integrations, k8s-client, kubernetes, kubernetes-access, kubernetes-admission, kubernetes-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-v2, operator-v2-event, operator-v2-worker, panics, permission-role-user-binding-operator, permission-token-operator, permission-token-webhook, platform-chart-operator, platform-pod-shutdown, platform-service-operator, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, sidecar, sidecar-authz, sidecar-authz-pool, sidecar-service-client-authentication, webhook (default [info])
--log.sampling If true, operator will try to minimize duplication of logging events (default true)
--log.stdout If true, operator will log to the stdout (default true)
--memory-limit uint Define memory limit for hard shutdown and the dump of goroutines. Used for testing
Expand Down
62 changes: 62 additions & 0 deletions cmd/lifecycle_post_start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// DISCLAIMER
//
// Copyright 2016-2026 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package cmd

import (
"github.com/spf13/cobra"

"github.com/arangodb/kube-arangodb/pkg/collect"
"github.com/arangodb/kube-arangodb/pkg/util/cli"
)

var (
// cmdLifecyclePostStart is the parent for postStart lifecycle hooks (`lifecycle postStart ...`).
cmdLifecyclePostStart = &cobra.Command{
Use: "postStart",
RunE: cli.Usage,
Hidden: true,
}

// cmdLifecyclePostStartCollector runs the collector as a postStart hook (`lifecycle postStart collector`).
cmdLifecyclePostStartCollector = &cobra.Command{
Use: "collector",
Hidden: true,
}

// lifecyclePostStartCollectorOptions holds the flags bound to the collector command.
lifecyclePostStartCollectorOptions collect.Options
)

func init() {
cmdLifecyclePostStartCollector.RunE = cmdLifecyclePostStartCollectorRunE

f := cmdLifecyclePostStartCollector.Flags()
f.DurationVar(&lifecyclePostStartCollectorOptions.Interval, "interval", collect.DefaultInterval, "Collector retry interval")
f.DurationVar(&lifecyclePostStartCollectorOptions.Timeout, "timeout", collect.DefaultTimeout, "Collector run timeout")

cmdLifecyclePostStart.AddCommand(cmdLifecyclePostStartCollector)
cmdLifecycle.AddCommand(cmdLifecyclePostStart)
}

// cmdLifecyclePostStartCollectorRunE delegates to the collector implementation in pkg/collect.
func cmdLifecyclePostStartCollectorRunE(cmd *cobra.Command, _ []string) error {
return collect.PostStart(cmd.Context(), lifecyclePostStartCollectorOptions)
}
2 changes: 1 addition & 1 deletion docs/cli/arangodb_operator.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Flags:
--kubernetes.qps float32 Number of queries per second for k8s API. If set to 0 or less, API calls won't be throttled (default 32)
--leader.label.skip Skips Leader Label for the Pod
--log.format string Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used (default "pretty")
--log.level stringArray Set log levels in format <level> or <logger>=<level>. Possible loggers: action, agency, arangod-request, assertion, authz-pool-client, backup-operator, backup-policy-operator, chaos-monkey, cli-utils, crd, deployment, deployment-ci, deployment-reconcile, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, generic-parent-operator, grpc-service, helm, http, inspector, integration-authn-v1, integration-authorization-v1, integration-config-v1, integration-envoy-auth-v3, integration-envoy-auth-v3-impl-auth-bearer, integration-envoy-auth-v3-impl-auth-cookie, integration-envoy-auth-v3-impl-custom-openid, integration-envoy-auth-v3-impl-pass-mode, integration-events-v1, integration-meta-v1, integration-pong-v1, integration-scheduler-v2, integration-shutdown-v1, integration-storage-v1-s3, integration-storage-v2, integrations, k8s-client, kubernetes, kubernetes-access, kubernetes-admission, kubernetes-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-v2, operator-v2-event, operator-v2-worker, panics, permission-role-user-binding-operator, permission-token-operator, permission-token-webhook, platform-chart-operator, platform-pod-shutdown, platform-service-operator, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, sidecar, sidecar-authz, sidecar-authz-pool, sidecar-service-client-authentication, webhook (default [info])
--log.level stringArray Set log levels in format <level> or <logger>=<level>. Possible loggers: action, agency, arangod-request, assertion, authz-pool-client, backup-operator, backup-policy-operator, chaos-monkey, cli-utils, collector, crd, deployment, deployment-ci, deployment-reconcile, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, generic-parent-operator, grpc-service, helm, http, inspector, integration-authn-v1, integration-authorization-v1, integration-config-v1, integration-envoy-auth-v3, integration-envoy-auth-v3-impl-auth-bearer, integration-envoy-auth-v3-impl-auth-cookie, integration-envoy-auth-v3-impl-custom-openid, integration-envoy-auth-v3-impl-pass-mode, integration-events-v1, integration-meta-v1, integration-pong-v1, integration-scheduler-v2, integration-shutdown-v1, integration-storage-v1-s3, integration-storage-v2, integrations, k8s-client, kubernetes, kubernetes-access, kubernetes-admission, kubernetes-client, kubernetes-informer, monitor, networking-route-operator, operator, operator-v2, operator-v2-event, operator-v2-worker, panics, permission-role-user-binding-operator, permission-token-operator, permission-token-webhook, platform-chart-operator, platform-pod-shutdown, platform-service-operator, platform-storage-operator, pod_compare, root, root-event-recorder, scheduler-batchjob-operator, scheduler-cronjob-operator, scheduler-deployment-operator, scheduler-pod-operator, scheduler-profile-operator, sidecar, sidecar-authz, sidecar-authz-pool, sidecar-service-client-authentication, webhook (default [info])
--log.sampling If true, operator will try to minimize duplication of logging events (default true)
--log.stdout If true, operator will log to the stdout (default true)
--memory-limit uint Define memory limit for hard shutdown and the dump of goroutines. Used for testing
Expand Down
191 changes: 191 additions & 0 deletions pkg/collect/collect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//
// DISCLAIMER
//
// Copyright 2024-2026 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

// Package collect implements the collector run by the arangod/gateway postStart lifecycle hook.
//
// The collector is a simple application started by the postStart hook. It runs in the foreground,
// collects the events for the current boot on a fixed interval and stops as soon as a cycle
// succeeds. It is not a daemon - there is no background process to keep alive - it simply runs until
// it is done. A timeout bounds the whole run so it cannot retry indefinitely if it never succeeds.
//
// Collected events are printed to stdout.
//
// Each boot is identified by a unique boot id (see shutdown.BootID), which is attached to every
// event the collector emits so that all activity from a single pod boot can be correlated.
package collect

import (
"context"
"fmt"
"os"
"time"

"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/timestamppb"

pbEventsV1 "github.com/arangodb/kube-arangodb/integrations/events/v1/definition"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
"github.com/arangodb/kube-arangodb/pkg/version"
)

const (
// serviceID identifies the collector as the source of the emitted event.
serviceID = "collector"

// eventTypeStartup is the type of the event emitted on every pod startup.
eventTypeStartup = "startup"

// dimensionBootID is the event dimension carrying the unique boot identifier.
dimensionBootID = "bootID"
)

const (
// DefaultInterval is the default retry interval between collector cycles.
DefaultInterval = 30 * time.Second

// DefaultTimeout is the default upper bound for the collector, after which it gives up.
DefaultTimeout = 30 * time.Minute
)

// Options configures a collector run.
type Options struct {
// Interval is the retry interval between collector cycles. Defaults to DefaultInterval.
Interval time.Duration

// Timeout bounds the whole run. When the timeout elapses before a cycle succeeds the collector
// gives up and returns. A non-positive value falls back to DefaultTimeout.
Timeout time.Duration
}

// interval returns the configured retry interval, falling back to DefaultInterval.
func (o Options) interval() time.Duration {
if o.Interval <= 0 {
return DefaultInterval
}
return o.Interval
}

// timeout returns the configured run timeout, falling back to DefaultTimeout.
func (o Options) timeout() time.Duration {
if o.Timeout <= 0 {
return DefaultTimeout
}
return o.Timeout
}

// PostStart is the entrypoint for the postStart collector lifecycle hook.
//
// It runs the collector loop in the foreground and returns once a cycle succeeds, the context is
// cancelled, or the timeout elapses. It never spawns a background process.
func PostStart(ctx context.Context, opts Options) error {
ctx, cancel := context.WithTimeout(ctx, opts.timeout())
defer cancel()

return run(ctx, opts)
}

// run executes the collector loop. A single failing cycle never aborts the run, it just waits for
// the next interval and retries, until a cycle succeeds or the context is closed (cancelled or timed
// out).
func run(ctx context.Context, opts Options) error {
// bootID is stable for the lifetime of this process and identifies the current boot.
bootID := shutdown.BootID()

// created is the process boot time, injected into every collected event so all events share a
// single, stable timestamp regardless of which cycle produced them. It is trimmed to seconds to
// keep cross-platform compatibility with the events integration.
created := shutdown.BootTime().Truncate(time.Second)

logger.Str("bootID", bootID).Info("Starting arangodb-operator collector (%s), version %s build %s",
version.GetVersionV1().Edition.Title(), version.GetVersionV1().Version, version.GetVersionV1().Build)

t := time.NewTicker(opts.interval())
defer t.Stop()

for {
if err := collect(bootID, created); err != nil {
logger.Err(err).Str("bootID", bootID).Warn("Collector cycle failed, will retry")
} else {
logger.Str("bootID", bootID).Info("Collector finished")
return nil
}

select {
case <-ctx.Done():
logger.Err(ctx.Err()).Str("bootID", bootID).Warn("Collector stopped before completion")
return ctx.Err()
case <-t.C:
}
}
}

// collect performs a single collection cycle for the given boot: it runs every registered collector,
// each pushing its metrics into a shared collector, waits until all of them have completed, builds a
// single startup event whose body is the collected metrics and prints it to stdout. The event is
// tagged with the boot id and the start timestamp so it can be correlated to a single pod boot.
func collect(bootID string, created time.Time) error {
metrics, err := GetCollector().Collect()
if err != nil {
return err
}

event := buildEvent(metrics, bootID, created)

if err := print(event); err != nil {
return err
}

logger.Str("bootID", bootID).Int("metrics", len(metrics)).Debug("Collected metrics")
return nil
}

// buildEvent assembles the startup event from the collected metrics, tagging it with the boot id and
// the start timestamp.
func buildEvent(metrics []Metric, bootID string, created time.Time) *pbEventsV1.Event {
body := make(map[string]float32, len(metrics))
for _, m := range metrics {
body[m.K] = m.V
}

return &pbEventsV1.Event{
Type: eventTypeStartup,
ServiceId: serviceID,
Created: timestamppb.New(created),
Dimensions: map[string]string{
dimensionBootID: bootID,
},
Body: body,
}
}

// print writes the event to stdout as a single JSON line.
func print(event *pbEventsV1.Event) error {
data, err := protojson.Marshal(event)
if err != nil {
return errors.Wrapf(err, "unable to marshal event")
}

if _, err := fmt.Fprintln(os.Stdout, string(data)); err != nil {
return errors.Wrapf(err, "unable to write event")
}

return nil
}
Loading