Skip to content

Commit 56a8263

Browse files
committed
Set correct permissions on the specified log directory
Controllers ignored the specified "log_directory" when preparing directories for Postgres. This parameter can be specified when the OpenTelemetryLogs gate is disabled. This does not change what happens when the OpenTelemetryLogs gate is enabled. In that case, controllers override the spec with their own value and prepare that directory. Issue: PGO-2558
1 parent 43539ca commit 56a8263

File tree

11 files changed

+125
-69
lines changed

11 files changed

+125
-69
lines changed

internal/collector/postgres.go

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,60 @@ import (
1919
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
2020
)
2121

22+
func PostgreSQLParameters(ctx context.Context,
23+
inCluster *v1beta1.PostgresCluster,
24+
outParameters *postgres.Parameters,
25+
) {
26+
version := inCluster.Spec.PostgresVersion
27+
28+
if OpenTelemetryLogsEnabled(ctx, inCluster) {
29+
var spec *v1beta1.InstrumentationLogsSpec
30+
if inCluster != nil && inCluster.Spec.Instrumentation != nil {
31+
spec = inCluster.Spec.Instrumentation.Logs
32+
}
33+
34+
// Retain logs for a short time unless specified.
35+
retention := metav1.Duration{Duration: 24 * time.Hour}
36+
if spec != nil && spec.RetentionPeriod != nil {
37+
retention = spec.RetentionPeriod.AsDuration()
38+
}
39+
40+
// Rotate log files according to retention and name them for the OpenTelemetry Collector.
41+
//
42+
// The ".log" suffix is replaced by ".csv" for CSV log files, and
43+
// the ".log" suffix is replaced by ".json" for JSON log files.
44+
//
45+
// https://www.postgresql.org/docs/current/runtime-config-logging.html
46+
for k, v := range postgres.LogRotation(retention, "postgresql-", ".log") {
47+
outParameters.Mandatory.Add(k, v)
48+
}
49+
50+
// Enable logging to file. Postgres uses a "logging collector" to safely write concurrent messages.
51+
// NOTE: That collector is designed to not lose messages. When it is overloaded, other Postgres processes block.
52+
//
53+
// https://www.postgresql.org/docs/current/runtime-config-logging.html
54+
outParameters.Mandatory.Add("logging_collector", "on")
55+
56+
// PostgreSQL v8.3 adds support for CSV logging, and
57+
// PostgreSQL v15 adds support for JSON logging.
58+
// The latter is preferred because newlines are escaped as "\n", U+005C + U+006E.
59+
if version >= 15 {
60+
outParameters.Mandatory.Add("log_destination", "jsonlog")
61+
} else {
62+
outParameters.Mandatory.Add("log_destination", "csvlog")
63+
}
64+
65+
// Log in a timezone the OpenTelemetry Collector understands.
66+
outParameters.Mandatory.Add("log_timezone", "UTC")
67+
68+
// TODO(logs): Remove this call and do it in [postgres.NewParameters] regardless of the gate.
69+
outParameters.Mandatory.Add("log_directory", fmt.Sprintf("%s/logs/postgres", postgres.DataStorage(inCluster)))
70+
}
71+
}
72+
2273
func NewConfigForPostgresPod(ctx context.Context,
2374
inCluster *v1beta1.PostgresCluster,
24-
outParameters *postgres.ParameterSet,
75+
inParameters *postgres.ParameterSet,
2576
) *Config {
2677
config := NewConfig(inCluster.Spec.Instrumentation)
2778

@@ -30,7 +81,7 @@ func NewConfigForPostgresPod(ctx context.Context,
3081
EnablePatroniMetrics(ctx, inCluster, config)
3182

3283
// Logging
33-
EnablePostgresLogging(ctx, inCluster, config, outParameters)
84+
EnablePostgresLogging(ctx, inCluster, inParameters, config)
3485
EnablePatroniLogging(ctx, inCluster, config)
3586

3687
return config
@@ -76,51 +127,18 @@ func postgresCSVNames(version int) string {
76127
func EnablePostgresLogging(
77128
ctx context.Context,
78129
inCluster *v1beta1.PostgresCluster,
130+
inParameters *postgres.ParameterSet,
79131
outConfig *Config,
80-
outParameters *postgres.ParameterSet,
81132
) {
82133
var spec *v1beta1.InstrumentationLogsSpec
83134
if inCluster != nil && inCluster.Spec.Instrumentation != nil {
84135
spec = inCluster.Spec.Instrumentation.Logs
85136
}
86137

87138
if OpenTelemetryLogsEnabled(ctx, inCluster) {
88-
directory := postgres.LogDirectory()
139+
directory := inParameters.Value("log_directory")
89140
version := inCluster.Spec.PostgresVersion
90141

91-
// https://www.postgresql.org/docs/current/runtime-config-logging.html
92-
outParameters.Add("logging_collector", "on")
93-
outParameters.Add("log_directory", directory)
94-
95-
// PostgreSQL v8.3 adds support for CSV logging, and
96-
// PostgreSQL v15 adds support for JSON logging. The latter is preferred
97-
// because newlines are escaped as "\n", U+005C + U+006E.
98-
if version < 15 {
99-
outParameters.Add("log_destination", "csvlog")
100-
} else {
101-
outParameters.Add("log_destination", "jsonlog")
102-
}
103-
104-
// If retentionPeriod is set in the spec, use that value; otherwise, we want
105-
// to use a reasonably short duration. Defaulting to 1 day.
106-
retentionPeriod := metav1.Duration{Duration: 24 * time.Hour}
107-
if spec != nil && spec.RetentionPeriod != nil {
108-
retentionPeriod = spec.RetentionPeriod.AsDuration()
109-
}
110-
111-
// Rotate log files according to retention.
112-
//
113-
// The ".log" suffix is replaced by ".csv" for CSV log files, and
114-
// the ".log" suffix is replaced by ".json" for JSON log files.
115-
//
116-
// https://www.postgresql.org/docs/current/runtime-config-logging.html
117-
for k, v := range postgres.LogRotation(retentionPeriod, "postgresql-", ".log") {
118-
outParameters.Add(k, v)
119-
}
120-
121-
// Log in a timezone that the OpenTelemetry Collector will understand.
122-
outParameters.Add("log_timezone", "UTC")
123-
124142
// Keep track of what log records and files have been processed.
125143
// Use a subdirectory of the logs directory to stay within the same failure domain.
126144
// TODO(log-rotation): Create this directory during Collector startup.
@@ -145,8 +163,8 @@ func EnablePostgresLogging(
145163
// The 2nd through 5th fields are optional, so match through to the 7th field.
146164
// This should do a decent job of not matching the middle of some SQL statement.
147165
//
148-
// The number of fields has changed over the years, but the first few
149-
// are always formatted the same way.
166+
// The number of fields has changed over the years, but the first few are always formatted the same way.
167+
// [PostgreSQLParameters] ensures the timezone is UTC.
150168
//
151169
// NOTE: This regexp is invoked in multi-line mode. https://go.dev/s/re2syntax
152170
"multiline": map[string]string{

internal/collector/postgres_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ func TestEnablePostgresLogging(t *testing.T) {
3131
}`)
3232

3333
config := NewConfig(nil)
34-
params := postgres.NewParameterSet()
34+
params := postgres.NewParameters()
3535

36-
EnablePostgresLogging(ctx, cluster, config, params)
36+
// NOTE: This call is necessary only because the default "log_directory" is not absolute.
37+
PostgreSQLParameters(ctx, cluster, &params)
38+
EnablePostgresLogging(ctx, cluster, params.Mandatory, config)
3739

3840
result, err := config.ToYAML()
3941
assert.NilError(t, err)
@@ -293,9 +295,11 @@ service:
293295
cluster.Spec.Instrumentation = testInstrumentationSpec()
294296

295297
config := NewConfig(cluster.Spec.Instrumentation)
296-
params := postgres.NewParameterSet()
298+
params := postgres.NewParameters()
297299

298-
EnablePostgresLogging(ctx, cluster, config, params)
300+
// NOTE: This call is necessary only because the default "log_directory" is not absolute.
301+
PostgreSQLParameters(ctx, cluster, &params)
302+
EnablePostgresLogging(ctx, cluster, params.Mandatory, config)
299303

300304
result, err := config.ToYAML()
301305
assert.NilError(t, err)

internal/controller/postgrescluster/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func (r *Reconciler) Reconcile(
339339
ctx, cluster, clusterConfigMap, clusterReplicationSecret, rootCA,
340340
clusterPodService, instanceServiceAccount, instances, patroniLeaderService,
341341
primaryCertificate, clusterVolumes, exporterQueriesConfig, exporterWebConfig,
342-
backupsSpecFound, otelConfig,
342+
backupsSpecFound, otelConfig, pgParameters,
343343
)
344344
}
345345

internal/controller/postgrescluster/instance.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ func (r *Reconciler) reconcileInstanceSets(
593593
exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap,
594594
backupsSpecFound bool,
595595
otelConfig *collector.Config,
596+
pgParameters *postgres.ParameterSet,
596597
) error {
597598

598599
// Go through the observed instances and check if a primary has been determined.
@@ -630,7 +631,7 @@ func (r *Reconciler) reconcileInstanceSets(
630631
patroniLeaderService, primaryCertificate,
631632
findAvailableInstanceNames(*set, instances, clusterVolumes),
632633
numInstancePods, clusterVolumes, exporterQueriesConfig, exporterWebConfig,
633-
backupsSpecFound, otelConfig,
634+
backupsSpecFound, otelConfig, pgParameters,
634635
)
635636

636637
if err == nil {
@@ -1066,6 +1067,7 @@ func (r *Reconciler) scaleUpInstances(
10661067
exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap,
10671068
backupsSpecFound bool,
10681069
otelConfig *collector.Config,
1070+
pgParameters *postgres.ParameterSet,
10691071
) ([]*appsv1.StatefulSet, error) {
10701072
log := logging.FromContext(ctx)
10711073

@@ -1112,7 +1114,7 @@ func (r *Reconciler) scaleUpInstances(
11121114
rootCA, clusterPodService, instanceServiceAccount,
11131115
patroniLeaderService, primaryCertificate, instances[i],
11141116
numInstancePods, clusterVolumes, exporterQueriesConfig, exporterWebConfig,
1115-
backupsSpecFound, otelConfig,
1117+
backupsSpecFound, otelConfig, pgParameters,
11161118
)
11171119
}
11181120
if err == nil {
@@ -1144,6 +1146,7 @@ func (r *Reconciler) reconcileInstance(
11441146
exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap,
11451147
backupsSpecFound bool,
11461148
otelConfig *collector.Config,
1149+
pgParameters *postgres.ParameterSet,
11471150
) error {
11481151
log := logging.FromContext(ctx).WithValues("instance", instance.Name)
11491152
ctx = logging.NewContext(ctx, log)
@@ -1187,7 +1190,7 @@ func (r *Reconciler) reconcileInstance(
11871190
postgres.InstancePod(
11881191
ctx, cluster, spec,
11891192
primaryCertificate, replicationCertSecretProjection(clusterReplicationSecret),
1190-
postgresDataVolume, postgresWALVolume, tablespaceVolumes,
1193+
postgresDataVolume, postgresWALVolume, tablespaceVolumes, pgParameters,
11911194
&instance.Spec.Template)
11921195

11931196
if backupsSpecFound {

internal/controller/postgrescluster/postgres.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/util/validation/field"
2727
"sigs.k8s.io/controller-runtime/pkg/client"
2828

29+
"github.com/crunchydata/postgres-operator/internal/collector"
2930
"github.com/crunchydata/postgres-operator/internal/feature"
3031
"github.com/crunchydata/postgres-operator/internal/initialize"
3132
"github.com/crunchydata/postgres-operator/internal/logging"
@@ -130,6 +131,7 @@ func (*Reconciler) generatePostgresParameters(
130131
ctx context.Context, cluster *v1beta1.PostgresCluster, backupsSpecFound bool,
131132
) *postgres.ParameterSet {
132133
builtin := postgres.NewParameters()
134+
collector.PostgreSQLParameters(ctx, cluster, &builtin)
133135
pgaudit.PostgreSQLParameters(&builtin)
134136
pgbackrest.PostgreSQLParameters(cluster, &builtin, backupsSpecFound)
135137
pgmonitor.PostgreSQLParameters(ctx, cluster, &builtin)

internal/postgres/config.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ func ConfigDirectory(cluster *v1beta1.PostgresCluster) string {
8989
// DataDirectory returns the absolute path to the "data_directory" of cluster.
9090
// - https://www.postgresql.org/docs/current/runtime-config-file-locations.html
9191
func DataDirectory(cluster *v1beta1.PostgresCluster) string {
92-
return fmt.Sprintf("%s/pg%d", dataMountPath, cluster.Spec.PostgresVersion)
92+
return fmt.Sprintf("%s/pg%d", DataStorage(cluster), cluster.Spec.PostgresVersion)
9393
}
9494

95-
// LogDirectory returns the absolute path to the "log_directory" of cluster.
96-
// - https://www.postgresql.org/docs/current/runtime-config-logging.html
97-
func LogDirectory() string {
98-
return fmt.Sprintf("%s/logs/postgres", dataMountPath)
95+
// DataStorage returns the absolute path to the disk where cluster stores its data.
96+
// Use [DataDirectory] for the exact directory that Postgres uses.
97+
func DataStorage(cluster *v1beta1.PostgresCluster) string {
98+
return dataMountPath
9999
}
100100

101101
// LogRotation returns parameters that rotate log files while keeping a minimum amount.
@@ -310,9 +310,12 @@ done
310310
// PostgreSQL.
311311
func startupCommand(
312312
ctx context.Context,
313-
cluster *v1beta1.PostgresCluster, instance *v1beta1.PostgresInstanceSetSpec,
313+
cluster *v1beta1.PostgresCluster,
314+
instance *v1beta1.PostgresInstanceSetSpec,
315+
parameters *ParameterSet,
314316
) []string {
315317
version := fmt.Sprint(cluster.Spec.PostgresVersion)
318+
logDir := parameters.Value("log_directory")
316319
walDir := WALDirectory(cluster, instance)
317320

318321
// If the user requests tablespaces, we want to make sure the directories exist with the
@@ -447,8 +450,9 @@ chmod +x /tmp/pg_rewind_tde.sh
447450
`halt "$(permissions ` + naming.PGBackRestPGDataLogPath + ` ||:)"`,
448451
`(` + shell.MakeDirectories(dataMountPath, naming.PatroniPGDataLogPath) + `) ||`,
449452
`halt "$(permissions ` + naming.PatroniPGDataLogPath + ` ||:)"`,
450-
`(` + shell.MakeDirectories(dataMountPath, LogDirectory()) + `) ||`,
451-
`halt "$(permissions ` + LogDirectory() + ` ||:)"`,
453+
`(` + shell.MakeDirectories(DataDirectory(cluster), logDir) + `) ||`,
454+
// FIXME: This error prints the wrong directory when logDir is relative (the default).
455+
`halt "$(permissions ` + logDir + ` ||:)"`,
452456

453457
// Copy replication client certificate files
454458
// from the /pgconf/tls/replication directory to the /tmp/replication directory in order

internal/postgres/config_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ func TestDataDirectory(t *testing.T) {
4040
assert.Equal(t, DataDirectory(cluster), "/pgdata/pg12")
4141
}
4242

43+
func TestDataStorage(t *testing.T) {
44+
cluster := new(v1beta1.PostgresCluster)
45+
cluster.Spec.PostgresVersion = rand.IntN(20)
46+
47+
assert.Equal(t, DataStorage(cluster), "/pgdata")
48+
}
49+
4350
func TestLogRotation(t *testing.T) {
4451
t.Parallel()
4552

@@ -543,8 +550,10 @@ func TestStartupCommand(t *testing.T) {
543550
cluster.Spec.PostgresVersion = 13
544551
instance := new(v1beta1.PostgresInstanceSetSpec)
545552

553+
parameters := NewParameters().Default
554+
546555
ctx := context.Background()
547-
command := startupCommand(ctx, cluster, instance)
556+
command := startupCommand(ctx, cluster, instance, parameters)
548557

549558
// Expect a bash command with an inline script.
550559
assert.DeepEqual(t, command[:3], []string{"bash", "-ceu", "--"})
@@ -579,7 +588,7 @@ func TestStartupCommand(t *testing.T) {
579588
},
580589
},
581590
}
582-
command := startupCommand(ctx, cluster, instance)
591+
command := startupCommand(ctx, cluster, instance, parameters)
583592
assert.Assert(t, len(command) > 3)
584593
assert.Assert(t, strings.Contains(command[3], `cat << "EOF" > /tmp/pg_rewind_tde.sh
585594
#!/bin/sh

internal/postgres/parameters.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ func NewParameters() Parameters {
4848
// - https://www.postgresql.org/docs/current/auth-password.html
4949
parameters.Default.Add("password_encryption", "scram-sha-256")
5050

51+
// Explicitly use Postgres' default log directory. This value is relative to the "data_directory" parameter.
52+
//
53+
// Controllers look at this parameter to grant group-write S_IWGRP on the directory.
54+
// Postgres does not grant this permission on the directories it creates.
55+
//
56+
// TODO(logs): A better default would be *outside* the data directory.
57+
// This parameter needs to be configurable and documented before the default can change.
58+
//
59+
// PostgreSQL must be reloaded when changing this parameter.
60+
parameters.Default.Add("log_directory", "log")
61+
5162
// Pod "securityContext.fsGroup" ensures processes and filesystems agree on a GID;
5263
// use the same permissions for group and owner.
5364
// This allows every process in the pod to read Postgres log files.

internal/postgres/parameters_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func TestNewParameters(t *testing.T) {
2828
assert.DeepEqual(t, parameters.Default.AsMap(), map[string]string{
2929
"jit": "off",
3030

31+
"log_directory": "log",
32+
3133
"password_encryption": "scram-sha-256",
3234
})
3335
}

internal/postgres/reconcile.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func InstancePod(ctx context.Context,
6868
inClusterCertificates, inClientCertificates *corev1.SecretProjection,
6969
inDataVolume, inWALVolume *corev1.PersistentVolumeClaim,
7070
inTablespaceVolumes []*corev1.PersistentVolumeClaim,
71+
inParameters *ParameterSet,
7172
outInstancePod *corev1.PodTemplateSpec,
7273
) {
7374
certVolumeMount := corev1.VolumeMount{
@@ -201,7 +202,7 @@ func InstancePod(ctx context.Context,
201202
startup := corev1.Container{
202203
Name: naming.ContainerPostgresStartup,
203204

204-
Command: startupCommand(ctx, inCluster, inInstanceSpec),
205+
Command: startupCommand(ctx, inCluster, inInstanceSpec, inParameters),
205206
Env: Environment(inCluster),
206207

207208
Image: container.Image,

0 commit comments

Comments
 (0)