Skip to content

Commit 590f899

Browse files
committed
Configure Patroni Logs to be stored in a file
This commit allows for the configuration of the Postgres instance Patroni logs to go to a file on the 'pgdata' volume instead of to stdout. This file is located at '/pgdata/patroni/log/patroni.log'. Both the log size limit and log level are configurable; only the size limit setting is required. If not set, the default behavior of sending all logs to stdout is maintained. Changes to this configuration require a reload to take effect. - https://patroni.readthedocs.io/en/latest/yaml_configuration.html#log Issue: PGO-1701
1 parent 41fe52e commit 590f899

File tree

12 files changed

+281
-13
lines changed

12 files changed

+281
-13
lines changed

config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11630,6 +11630,28 @@ spec:
1163011630
format: int32
1163111631
minimum: 3
1163211632
type: integer
11633+
log:
11634+
description: Patroni log configuration settings.
11635+
properties:
11636+
fileSize:
11637+
description: Patroni Log file size
11638+
type: string
11639+
logLevel:
11640+
default: INFO
11641+
description: |-
11642+
Patroni log level
11643+
https://docs.python.org/3.6/library/logging.html#levels
11644+
enum:
11645+
- CRITICAL
11646+
- ERROR
11647+
- WARNING
11648+
- INFO
11649+
- DEBUG
11650+
- NOTSET
11651+
type: string
11652+
required:
11653+
- fileSize
11654+
type: object
1163311655
port:
1163411656
default: 8008
1163511657
description: |-

internal/controller/postgrescluster/cluster.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/pkg/errors"
1313
corev1 "k8s.io/api/core/v1"
1414
"k8s.io/apimachinery/pkg/api/meta"
15+
"k8s.io/apimachinery/pkg/api/resource"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1617
"k8s.io/apimachinery/pkg/util/intstr"
1718

@@ -44,7 +45,7 @@ func (r *Reconciler) reconcileClusterConfigMap(
4445

4546
if err == nil {
4647
err = patroni.ClusterConfigMap(ctx, cluster, pgHBAs, pgParameters,
47-
clusterConfigMap)
48+
clusterConfigMap, r.patroniLogSize(ctx, cluster))
4849
}
4950
if err == nil {
5051
err = errors.WithStack(r.apply(ctx, clusterConfigMap))
@@ -53,6 +54,32 @@ func (r *Reconciler) reconcileClusterConfigMap(
5354
return clusterConfigMap, err
5455
}
5556

57+
// patroniLogSize attemps to parse the defined log file size, if configured.
58+
// If a value is set, this enables volume based log storage and triggers the
59+
// relevant Patroni configuration. If the value given cannot be parsed, the log
60+
// file size defaults to 25M and a warning event is triggered.
61+
func (r *Reconciler) patroniLogSize(ctx context.Context, cluster *v1beta1.PostgresCluster) int64 {
62+
63+
if cluster.Spec.Patroni != nil {
64+
if cluster.Spec.Patroni.Log != nil {
65+
if cluster.Spec.Patroni.Log.FileSize != nil {
66+
67+
fileSize, err := resource.ParseQuantity(*cluster.Spec.Patroni.Log.FileSize)
68+
sizeInBytes := fileSize.Value()
69+
70+
if err != nil {
71+
r.Recorder.Eventf(cluster, corev1.EventTypeWarning, "InvalidPatroniLogFileSize",
72+
"Error when parsing spec.patroni.log.fileSize. File size will default to 25M.")
73+
74+
sizeInBytes = 25000000
75+
}
76+
return sizeInBytes
77+
}
78+
}
79+
}
80+
return 0
81+
}
82+
5683
// +kubebuilder:rbac:groups="",resources="services",verbs={create,patch}
5784

5885
// reconcileClusterPodService writes the Service that can provide stable DNS

internal/controller/postgrescluster/cluster_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/crunchydata/postgres-operator/internal/initialize"
2424
"github.com/crunchydata/postgres-operator/internal/naming"
2525
"github.com/crunchydata/postgres-operator/internal/testing/cmp"
26+
"github.com/crunchydata/postgres-operator/internal/testing/events"
2627
"github.com/crunchydata/postgres-operator/internal/testing/require"
2728
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
2829
)
@@ -783,3 +784,73 @@ postgres-operator.crunchydata.com/role: replica
783784
`))
784785
})
785786
}
787+
788+
func TestPatroniLogSize(t *testing.T) {
789+
ctx := context.Background()
790+
791+
oneMeg := "1M"
792+
badSize := "notASize"
793+
794+
cluster := v1beta1.PostgresCluster{
795+
ObjectMeta: metav1.ObjectMeta{
796+
Name: "sometest",
797+
Namespace: "test-namespace",
798+
},
799+
Spec: v1beta1.PostgresClusterSpec{}}
800+
801+
t.Run("Default", func(t *testing.T) {
802+
recorder := events.NewRecorder(t, runtime.Scheme)
803+
reconciler := &Reconciler{Recorder: recorder}
804+
805+
size := reconciler.patroniLogSize(ctx, &cluster)
806+
807+
assert.Equal(t, size, int64(0))
808+
assert.Equal(t, len(recorder.Events), 0)
809+
})
810+
811+
t.Run("NoSize", func(t *testing.T) {
812+
recorder := events.NewRecorder(t, runtime.Scheme)
813+
reconciler := &Reconciler{Recorder: recorder}
814+
815+
cluster.Spec.Patroni = &v1beta1.PatroniSpec{
816+
Log: &v1beta1.PatroniLogConfig{}}
817+
818+
size := reconciler.patroniLogSize(ctx, &cluster)
819+
820+
assert.Equal(t, size, int64(0))
821+
assert.Equal(t, len(recorder.Events), 0)
822+
})
823+
824+
t.Run("ValidSize", func(t *testing.T) {
825+
recorder := events.NewRecorder(t, runtime.Scheme)
826+
reconciler := &Reconciler{Recorder: recorder}
827+
828+
cluster.Spec.Patroni = &v1beta1.PatroniSpec{
829+
Log: &v1beta1.PatroniLogConfig{
830+
FileSize: &oneMeg,
831+
}}
832+
833+
size := reconciler.patroniLogSize(ctx, &cluster)
834+
835+
assert.Equal(t, size, int64(1000000))
836+
assert.Equal(t, len(recorder.Events), 0)
837+
})
838+
839+
t.Run("BadSize", func(t *testing.T) {
840+
recorder := events.NewRecorder(t, runtime.Scheme)
841+
reconciler := &Reconciler{Recorder: recorder}
842+
843+
cluster.Spec.Patroni = &v1beta1.PatroniSpec{
844+
Log: &v1beta1.PatroniLogConfig{
845+
FileSize: &badSize,
846+
}}
847+
848+
size := reconciler.patroniLogSize(ctx, &cluster)
849+
850+
assert.Equal(t, size, int64(25000000))
851+
assert.Equal(t, len(recorder.Events), 1)
852+
assert.Equal(t, recorder.Events[0].Regarding.Name, cluster.Name)
853+
assert.Equal(t, recorder.Events[0].Reason, "InvalidPatroniLogFileSize")
854+
assert.Equal(t, recorder.Events[0].Note, "Error when parsing spec.patroni.log.fileSize. File size will default to 25M.")
855+
})
856+
}

internal/naming/names.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ const (
131131
)
132132

133133
const (
134+
// PatroniPGDataLogPath is the Patroni default log path configuration used by the
135+
// PostgreSQL instance.
136+
PatroniPGDataLogPath = "/pgdata/patroni/log"
137+
134138
// PGBackRestRepoContainerName is the name assigned to the container used to run pgBackRest
135139
PGBackRestRepoContainerName = "pgbackrest"
136140

internal/patroni/config.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func quoteShellWord(s string) string {
4343
// clusterYAML returns Patroni settings that apply to the entire cluster.
4444
func clusterYAML(
4545
cluster *v1beta1.PostgresCluster,
46-
pgHBAs postgres.HBAs, pgParameters postgres.Parameters,
46+
pgHBAs postgres.HBAs, pgParameters postgres.Parameters, patroniLogFileSize int64,
4747
) (string, error) {
4848
root := map[string]any{
4949
// The cluster identifier. This value cannot change during the cluster's
@@ -152,6 +152,20 @@ func clusterYAML(
152152
},
153153
}
154154

155+
// if a Patroni log file size is configured, configure volume file storage
156+
if patroniLogFileSize != 0 {
157+
158+
root["log"] = map[string]any{
159+
// Configure the Patroni log settings
160+
// - https://patroni.readthedocs.io/en/latest/yaml_configuration.html#log
161+
"dir": naming.PatroniPGDataLogPath,
162+
"file_num": 1,
163+
"file_size": patroniLogFileSize,
164+
"type": "json",
165+
"level": cluster.Spec.Patroni.Log.LogLevel, // defaults to "INFO"
166+
}
167+
}
168+
155169
if !ClusterBootstrapped(cluster) {
156170
// Patroni has not yet bootstrapped. Populate the "bootstrap.dcs" field to
157171
// facilitate it. When Patroni is already bootstrapped, this field is ignored.

internal/patroni/config_test.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestClusterYAML(t *testing.T) {
3232
cluster.Namespace = "some-namespace"
3333
cluster.Name = "cluster-name"
3434

35-
data, err := clusterYAML(cluster, postgres.HBAs{}, postgres.Parameters{})
35+
data, err := clusterYAML(cluster, postgres.HBAs{}, postgres.Parameters{}, 0)
3636
assert.NilError(t, err)
3737
assert.Equal(t, data, strings.TrimSpace(`
3838
# Generated by postgres-operator. DO NOT EDIT.
@@ -90,7 +90,7 @@ watchdog:
9090
cluster.Name = "cluster-name"
9191
cluster.Spec.PostgresVersion = 14
9292

93-
data, err := clusterYAML(cluster, postgres.HBAs{}, postgres.Parameters{})
93+
data, err := clusterYAML(cluster, postgres.HBAs{}, postgres.Parameters{}, 0)
9494
assert.NilError(t, err)
9595
assert.Equal(t, data, strings.TrimSpace(`
9696
# Generated by postgres-operator. DO NOT EDIT.
@@ -136,6 +136,77 @@ restapi:
136136
keyfile: null
137137
verify_client: optional
138138
scope: cluster-name-ha
139+
watchdog:
140+
mode: "off"
141+
`)+"\n")
142+
})
143+
144+
t.Run("PatroniLogSizeConfigured", func(t *testing.T) {
145+
cluster := new(v1beta1.PostgresCluster)
146+
cluster.Default()
147+
cluster.Namespace = "some-namespace"
148+
cluster.Name = "cluster-name"
149+
cluster.Spec.PostgresVersion = 14
150+
151+
fileSize := "1k"
152+
logLevel := "DEBUG"
153+
cluster.Spec.Patroni.Log = &v1beta1.PatroniLogConfig{
154+
FileSize: &fileSize,
155+
LogLevel: &logLevel,
156+
}
157+
158+
data, err := clusterYAML(cluster, postgres.HBAs{}, postgres.Parameters{}, 1000)
159+
assert.NilError(t, err)
160+
assert.Equal(t, data, strings.TrimSpace(`
161+
# Generated by postgres-operator. DO NOT EDIT.
162+
# Your changes will not be saved.
163+
bootstrap:
164+
dcs:
165+
loop_wait: 10
166+
postgresql:
167+
parameters: {}
168+
pg_hba: []
169+
use_pg_rewind: true
170+
use_slots: false
171+
ttl: 30
172+
ctl:
173+
cacert: /etc/patroni/~postgres-operator/patroni.ca-roots
174+
certfile: /etc/patroni/~postgres-operator/patroni.crt+key
175+
insecure: false
176+
keyfile: null
177+
kubernetes:
178+
labels:
179+
postgres-operator.crunchydata.com/cluster: cluster-name
180+
namespace: some-namespace
181+
role_label: postgres-operator.crunchydata.com/role
182+
scope_label: postgres-operator.crunchydata.com/patroni
183+
use_endpoints: true
184+
log:
185+
dir: /pgdata/patroni/log
186+
file_num: 1
187+
file_size: 1000
188+
level: DEBUG
189+
type: json
190+
postgresql:
191+
authentication:
192+
replication:
193+
sslcert: /tmp/replication/tls.crt
194+
sslkey: /tmp/replication/tls.key
195+
sslmode: verify-ca
196+
sslrootcert: /tmp/replication/ca.crt
197+
username: _crunchyrepl
198+
rewind:
199+
sslcert: /tmp/replication/tls.crt
200+
sslkey: /tmp/replication/tls.key
201+
sslmode: verify-ca
202+
sslrootcert: /tmp/replication/ca.crt
203+
username: _crunchyrepl
204+
restapi:
205+
cafile: /etc/patroni/~postgres-operator/patroni.ca-roots
206+
certfile: /etc/patroni/~postgres-operator/patroni.crt+key
207+
keyfile: null
208+
verify_client: optional
209+
scope: cluster-name-ha
139210
watchdog:
140211
mode: "off"
141212
`)+"\n")

internal/patroni/reconcile.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ func ClusterConfigMap(ctx context.Context,
3232
inHBAs postgres.HBAs,
3333
inParameters postgres.Parameters,
3434
outClusterConfigMap *corev1.ConfigMap,
35+
patroniLogFileSize int64,
3536
) error {
3637
var err error
3738

3839
initialize.Map(&outClusterConfigMap.Data)
3940

4041
outClusterConfigMap.Data[configMapFileKey], err = clusterYAML(inCluster, inHBAs,
41-
inParameters)
42+
inParameters, patroniLogFileSize)
4243

4344
return err
4445
}

internal/patroni/reconcile_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ func TestClusterConfigMap(t *testing.T) {
2929

3030
cluster.Default()
3131
config := new(corev1.ConfigMap)
32-
assert.NilError(t, ClusterConfigMap(ctx, cluster, pgHBAs, pgParameters, config))
32+
assert.NilError(t, ClusterConfigMap(ctx, cluster, pgHBAs, pgParameters, config, 0))
3333

3434
// The output of clusterYAML should go into config.
35-
data, _ := clusterYAML(cluster, pgHBAs, pgParameters)
35+
data, _ := clusterYAML(cluster, pgHBAs, pgParameters, 0)
3636
assert.DeepEqual(t, config.Data["patroni.yaml"], data)
3737

3838
// No change when called again.
3939
before := config.DeepCopy()
40-
assert.NilError(t, ClusterConfigMap(ctx, cluster, pgHBAs, pgParameters, config))
40+
assert.NilError(t, ClusterConfigMap(ctx, cluster, pgHBAs, pgParameters, config, 0))
4141
assert.DeepEqual(t, config, before)
4242
}
4343

internal/postgres/config.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,9 @@ chmod +x /tmp/pg_rewind_tde.sh
291291
`
292292
}
293293

294-
args := []string{version, walDir, naming.PGBackRestPGDataLogPath}
294+
args := []string{version, walDir, naming.PGBackRestPGDataLogPath, naming.PatroniPGDataLogPath}
295295
script := strings.Join([]string{
296-
`declare -r expected_major_version="$1" pgwal_directory="$2" pgbrLog_directory="$3"`,
296+
`declare -r expected_major_version="$1" pgwal_directory="$2" pgbrLog_directory="$3" patroniLog_directory="$4"`,
297297

298298
// Function to print the permissions of a file or directory and its parents.
299299
bashPermissions,
@@ -369,6 +369,11 @@ chmod +x /tmp/pg_rewind_tde.sh
369369
`install --directory --mode=0775 "${pgbrLog_directory}" ||`,
370370
`halt "$(permissions "${pgbrLog_directory}" ||:)"`,
371371

372+
// Create the Patroni log directory.
373+
`results 'Patroni log directory' "${patroniLog_directory}"`,
374+
`install --directory --mode=0775 "${patroniLog_directory}" ||`,
375+
`halt "$(permissions "${patroniLog_directory}" ||:)"`,
376+
372377
// Copy replication client certificate files
373378
// from the /pgconf/tls/replication directory to the /tmp/replication directory in order
374379
// to set proper file permissions. This is required because the group permission settings

0 commit comments

Comments
 (0)