Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9f9c439
Adds a New Condition for PVC Resize Errors
andrewlecuyer Feb 7, 2025
a942197
Initial configuration for an OpenTelemetry Collector
cbandy Dec 23, 2024
3ea8f17
Add an OTel Collector with Patroni metrics
dsessler7 Jan 6, 2025
c3a98fb
Add PgBouncer metrics
dsessler7 Jan 14, 2025
9fcef77
Parse Postgres and pgAudit logs using the OTel Collector
benjaminjb Jan 22, 2025
08ab9a4
Parse Patroni logs
dsessler7 Jan 22, 2025
2e59c1b
Parse PgBouncer logs using the OTel Collector
dsessler7 Jan 29, 2025
96e1ffb
Scrape pgAdmin logs using the OTel collector
tony-landreth Jan 29, 2025
ee9bf60
Add pgBackRest repohost log collector
benjaminjb Feb 1, 2025
836572d
Validate and strip/minify Collector SQL files
cbandy Feb 7, 2025
f2a80ac
Change pgbackrest init for running containers
benjaminjb Feb 7, 2025
0dcb1be
Bump controller-gen to v0.17.2
cbandy Feb 10, 2025
fbb4f32
Change PostgresIdentifier to a type alias
cbandy Jan 3, 2025
7089149
Add k8s attributes to patroni logs. Add CompactingProcessor to patron…
dsessler7 Feb 7, 2025
8e37a1f
Create initial API for OTel instrumentation. Allow users to configure…
dsessler7 Feb 9, 2025
38fc33a
Add instrumentation_scope.name and log.record.original attributes to …
dsessler7 Feb 9, 2025
3602c70
Add configurable collector (#4092)
benjaminjb Feb 12, 2025
f7e9625
Add shared functions for quoting shell words
cbandy Nov 4, 2024
d4483cc
Add a function for setting permission on directories
cbandy Feb 10, 2025
e6ea78b
Store pgAdmin log file positions in the logs directory
cbandy Feb 6, 2025
951fa40
Ensure Postgres and Patroni log directories are writable
cbandy Feb 10, 2025
88130ca
Ensure pgBackRest log directories are writable
cbandy Feb 11, 2025
8dbe427
Add a field specifying when to delete log files
cbandy Feb 14, 2025
1797f8f
Rotate PgBouncer logs using specified retention
dsessler7 Feb 11, 2025
8b87822
Document a Kubernetes bug with the duration format
cbandy Feb 18, 2025
85636a8
Add an API struct representing a single Secret value
cbandy Jan 15, 2025
ef1eae0
Allow more control over the arguments to pg_upgrade
cbandy Dec 9, 2024
510ddf4
Validate pg_upgrade versions at the API server
cbandy Feb 19, 2025
e4dfdf2
Add a validated field for Postgres parameters
cbandy Dec 20, 2024
e884806
Otel pgMonitor metrics (#4096)
tony-landreth Feb 21, 2025
00c9068
Add reload logic to collector container start script.
dsessler7 Feb 19, 2025
19a28f7
Add a test helper that unmarshals JSON and YAML
cbandy Feb 26, 2025
9977db2
If the OpenTelemetryLogs feature gate is set, tell patroni to log to …
dsessler7 Feb 26, 2025
bfd4160
Add resources from API to OTEL sidecar (#4104)
benjaminjb Feb 26, 2025
6ba9057
Change PostgresCluster.spec.config to a pointer
cbandy Feb 26, 2025
2a2fe9b
Calculate Postgres parameters in the controller
cbandy Feb 26, 2025
9018342
Rotate postgres logs according to retentionPeriod in spec.
dsessler7 Feb 20, 2025
d04885c
Clone embedded metrics variable to avoid continuous appending.
dsessler7 Feb 28, 2025
00a93f6
Add a script to help with bumping dependencies
cbandy Feb 28, 2025
6dbbf9b
Bump golang.org/x/crypto and golang.org/x/oauth2
cbandy Feb 28, 2025
b50bae9
Rotate pgbackrest (#4108)
benjaminjb Mar 1, 2025
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
Prev Previous commit
Next Next commit
Rotate PgBouncer logs using specified retention
Defaults to 1 day when no retention is set.

Issue: PGO-2169
  • Loading branch information
dsessler7 authored and cbandy committed Feb 18, 2025
commit 1797f8f0eb4b3888ea306049f2e43bd4c36b0736
70 changes: 70 additions & 0 deletions internal/collector/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,24 @@
package collector

import (
"context"
_ "embed"
"fmt"
"math"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/yaml"

"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

// The contents of "logrotate.conf" as a string.
// See: https://pkg.go.dev/embed
//
//go:embed "logrotate.conf"
var logrotateConfigFormatString string

// ComponentID represents a component identifier within an OpenTelemetry
// Collector YAML configuration. Each value is a "type" followed by an optional
// slash-then-name: `type[/name]`
Expand Down Expand Up @@ -102,3 +114,61 @@ func NewConfig(spec *v1beta1.InstrumentationSpec) *Config {

return config
}

// AddLogrotateConfig generates a logrotate configuration and adds it to the
// provided configmap
func AddLogrotateConfig(ctx context.Context, spec *v1beta1.InstrumentationSpec,
outInstanceConfigMap *corev1.ConfigMap, logFilePath, postrotateScript string,
) error {
var err error
var retentionPeriod *v1beta1.Duration

if outInstanceConfigMap.Data == nil {
outInstanceConfigMap.Data = make(map[string]string)
}

// If retentionPeriod is set in the spec, use that value; otherwise, we want
// to use a reasonably short duration. Defaulting to 1 day.
if spec != nil && spec.Logs != nil && spec.Logs.RetentionPeriod != nil {
retentionPeriod = spec.Logs.RetentionPeriod
} else {
retentionPeriod, err = v1beta1.NewDuration("1d")
if err != nil {
return err
}
}

outInstanceConfigMap.Data["logrotate.conf"] = generateLogrotateConfig(logFilePath,
retentionPeriod, postrotateScript)

return err
}

// generateLogrotateConfig generates a configuration string for logrotate based
// on the provided full log file path, retention period, and postrotate script
func generateLogrotateConfig(logFilePath string, retentionPeriod *v1beta1.Duration,
postrotateScript string,
) string {
number, interval := parseDurationForLogrotate(retentionPeriod)

return fmt.Sprintf(
logrotateConfigFormatString,
logFilePath,
number,
interval,
postrotateScript,
)
}

// parseDurationForLogrotate takes a retention period and returns the rotate
// number and interval string that should be used in the logrotate config.
// If the retentionPeriod is less than 24 hours, the function will return the
// number of hours and "hourly"; otherwise, we will round up to the nearest day
// and return the day count and "daily"
func parseDurationForLogrotate(retentionPeriod *v1beta1.Duration) (int, string) {
hours := math.Ceil(retentionPeriod.AsDuration().Hours())
if hours < 24 {
return int(hours), "hourly"
}
return int(math.Ceil(hours / 24)), "daily"
}
137 changes: 137 additions & 0 deletions internal/collector/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"testing"

"gotest.tools/v3/assert"

"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

func TestConfigToYAML(t *testing.T) {
Expand Down Expand Up @@ -61,3 +63,138 @@ service:
`)
})
}

func TestGenerateLogrotateConfig(t *testing.T) {
for _, tt := range []struct {
logFilePath string
retentionPeriod string
postrotateScript string
result string
}{
{
logFilePath: "/this/is/a/file.path",
retentionPeriod: "12h",
postrotateScript: "echo 'Hello, World'",
result: `/this/is/a/file.path {
rotate 12
missingok
sharedscripts
notifempty
nocompress
hourly
postrotate
echo 'Hello, World'
endscript
}
`,
},
{
logFilePath: "/tmp/test.log",
retentionPeriod: "5 days",
postrotateScript: "",
result: `/tmp/test.log {
rotate 5
missingok
sharedscripts
notifempty
nocompress
daily
postrotate

endscript
}
`,
},
{
logFilePath: "/tmp/test.log",
retentionPeriod: "5wk",
postrotateScript: "pkill -HUP --exact pgbouncer",
result: `/tmp/test.log {
rotate 35
missingok
sharedscripts
notifempty
nocompress
daily
postrotate
pkill -HUP --exact pgbouncer
endscript
}
`,
},
} {
t.Run(tt.retentionPeriod, func(t *testing.T) {
duration, err := v1beta1.NewDuration(tt.retentionPeriod)
assert.NilError(t, err)
result := generateLogrotateConfig(tt.logFilePath, duration, tt.postrotateScript)
assert.Equal(t, tt.result, result)
})
}
}

func TestParseDurationForLogrotate(t *testing.T) {
for _, tt := range []struct {
retentionPeriod string
number int
interval string
}{
{
retentionPeriod: "1 h 20 min",
number: 2,
interval: "hourly",
},
{
retentionPeriod: "12h",
number: 12,
interval: "hourly",
},
{
retentionPeriod: "24hr",
number: 1,
interval: "daily",
},
{
retentionPeriod: "35hour",
number: 2,
interval: "daily",
},
{
retentionPeriod: "36 hours",
number: 2,
interval: "daily",
},
{
retentionPeriod: "3d",
number: 3,
interval: "daily",
},
{
retentionPeriod: "365day",
number: 365,
interval: "daily",
},
{
retentionPeriod: "1w",
number: 7,
interval: "daily",
},
{
retentionPeriod: "4wk",
number: 28,
interval: "daily",
},
{
retentionPeriod: "52week",
number: 364,
interval: "daily",
},
} {
t.Run(tt.retentionPeriod, func(t *testing.T) {
duration, err := v1beta1.NewDuration(tt.retentionPeriod)
assert.NilError(t, err)
number, interval := parseDurationForLogrotate(duration)
assert.Equal(t, tt.number, number)
assert.Equal(t, tt.interval, interval)
})
}
}
57 changes: 55 additions & 2 deletions internal/collector/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ func AddToPod(
outPod *corev1.PodSpec,
volumeMounts []corev1.VolumeMount,
sqlQueryPassword string,
includeLogrotate bool,
) {
if !(feature.Enabled(ctx, feature.OpenTelemetryLogs) || feature.Enabled(ctx, feature.OpenTelemetryMetrics)) {
return
}

// Create volume and volume mount for otel collector config
configVolumeMount := corev1.VolumeMount{
Name: "collector-config",
MountPath: "/etc/otel-collector",
Expand All @@ -71,11 +73,15 @@ func AddToPod(
configVolume.Projected.Sources = append(configVolume.Projected.Sources, spec.Config.Files...)
}

// Add configVolume to the pod's volumes
outPod.Volumes = append(outPod.Volumes, configVolume)

// Create collector container
container := corev1.Container{
Name: naming.ContainerCollector,
Image: config.CollectorContainerImage(spec),
ImagePullPolicy: pullPolicy,
Command: []string{"/otelcol-contrib", "--config", "/etc/otel-collector/config.yaml"},
Command: startCommand(includeLogrotate),
Env: []corev1.EnvVar{
{
Name: "K8S_POD_NAMESPACE",
Expand All @@ -99,6 +105,32 @@ func AddToPod(
VolumeMounts: append(volumeMounts, configVolumeMount),
}

// If this is a pod that uses logrotate for log rotation, add config volume
// and mount for logrotate config
if includeLogrotate {
logrotateConfigVolumeMount := corev1.VolumeMount{
Name: "logrotate-config",
MountPath: "/etc/logrotate.d",
ReadOnly: true,
}
logrotateConfigVolume := corev1.Volume{Name: logrotateConfigVolumeMount.Name}
logrotateConfigVolume.Projected = &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: inInstanceConfigMap.Name,
},
Items: []corev1.KeyToPath{{
Key: "logrotate.conf",
Path: "logrotate.conf",
}},
},
}},
}
container.VolumeMounts = append(container.VolumeMounts, logrotateConfigVolumeMount)
outPod.Volumes = append(outPod.Volumes, logrotateConfigVolume)
}

if feature.Enabled(ctx, feature.OpenTelemetryMetrics) {
container.Ports = []corev1.ContainerPort{{
ContainerPort: int32(8889),
Expand All @@ -108,5 +140,26 @@ func AddToPod(
}

outPod.Containers = append(outPod.Containers, container)
outPod.Volumes = append(outPod.Volumes, configVolume)
}

// startCommand generates the command script used by the collector container
func startCommand(includeLogrotate bool) []string {
var startScript = `
/otelcol-contrib --config /etc/otel-collector/config.yaml
`

if includeLogrotate {
startScript = `
/otelcol-contrib --config /etc/otel-collector/config.yaml &

exec {fd}<> <(:||:)
while read -r -t 5 -u "${fd}" ||:; do
logrotate -s /tmp/logrotate.status /etc/logrotate.d/logrotate.conf
done
`
}

wrapper := `monitor() {` + startScript + `}; export -f monitor; exec -a "$0" bash -ceu monitor`

return []string{"bash", "-ceu", "--", wrapper, "collector"}
}
11 changes: 11 additions & 0 deletions internal/collector/logrotate.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%s {
rotate %d
missingok
sharedscripts
notifempty
nocompress
%s
postrotate
%s
endscript
}
10 changes: 9 additions & 1 deletion internal/collector/pgbouncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
//go:embed "generated/pgbouncer_metrics_queries.json"
var pgBouncerMetricsQueries json.RawMessage

// PGBouncerPostRotateScript is the script that is run after pgBouncer's log
// files have been rotated. The pgbouncer process is sent a sighup signal.
const PGBouncerPostRotateScript = "pkill -HUP --exact pgbouncer"

// NewConfigForPgBouncerPod creates a config for the OTel collector container
// that runs as a sidecar in the pgBouncer Pod
func NewConfigForPgBouncerPod(
Expand Down Expand Up @@ -62,7 +66,11 @@ func EnablePgBouncerLogging(ctx context.Context,
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/-/receiver/filelogreceiver#readme
outConfig.Receivers["filelog/pgbouncer_log"] = map[string]any{
// Read the log files and keep track of what has been processed.
"include": []string{directory + "/*.log"},
// We want to watch the ".log.1" file as well as it is possible that
// a log entry or two will end up there after the original ".log"
// file is renamed to ".log.1" during rotation. OTel will not create
// duplicate log entries.
"include": []string{directory + "/*.log", directory + "/*.log.1"},
"storage": "file_storage/pgbouncer_logs",
}

Expand Down
2 changes: 2 additions & 0 deletions internal/collector/pgbouncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ receivers:
filelog/pgbouncer_log:
include:
- /tmp/*.log
- /tmp/*.log.1
storage: file_storage/pgbouncer_logs
service:
extensions:
Expand Down Expand Up @@ -166,6 +167,7 @@ receivers:
filelog/pgbouncer_log:
include:
- /tmp/*.log
- /tmp/*.log.1
storage: file_storage/pgbouncer_logs
service:
extensions:
Expand Down
5 changes: 4 additions & 1 deletion internal/controller/postgrescluster/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -1202,8 +1202,10 @@ func (r *Reconciler) reconcileInstance(

if err == nil &&
(feature.Enabled(ctx, feature.OpenTelemetryLogs) || feature.Enabled(ctx, feature.OpenTelemetryMetrics)) {
// TODO: Setting the includeLogrotate argument to false for now. This
// should be changed when we implement log rotation for postgres
collector.AddToPod(ctx, cluster.Spec.Instrumentation, cluster.Spec.ImagePullPolicy, instanceConfigMap, &instance.Spec.Template.Spec,
[]corev1.VolumeMount{postgres.DataVolumeMount()}, "")
[]corev1.VolumeMount{postgres.DataVolumeMount()}, "", false)
}

// Add pgMonitor resources to the instance Pod spec
Expand Down Expand Up @@ -1407,6 +1409,7 @@ func (r *Reconciler) reconcileInstanceConfigMap(
naming.LabelInstance: instance.Name,
})

// If OTel logging or metrics is enabled, add collector config
if err == nil && (feature.Enabled(ctx, feature.OpenTelemetryLogs) || feature.Enabled(ctx, feature.OpenTelemetryMetrics)) {
err = collector.AddToConfigMap(ctx, otelConfig, instanceConfigMap)
}
Expand Down
Loading