Skip to content

Commit

Permalink
feat: add ephemeralVolumeSource option to cluster spec. (#3678)
Browse files Browse the repository at this point in the history
Allow the user to optionally set the `ephemeralStorage` option in the
cluster spec.

Closes #3677

Refs:
 - #3677
 - #317

Signed-off-by: Josh Fyne <josh.fyne@gmail.com>
Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
Co-authored-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
  • Loading branch information
jfyne and armru committed Jan 23, 2024
1 parent ca6be9d commit 0e7ac95
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .wordlist-en-custom.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ EnterpriseDB's
EnvFrom
EnvFromSource
EnvVar
EphemeralVolumeSource
EphemeralVolumesSizeLimit
EphemeralVolumesSizeLimitConfiguration
ExternalCluster
Expand Down Expand Up @@ -649,6 +650,7 @@ endpointURL
enterprisedb
env
envFrom
ephemeralVolumeSource
ephemeralVolumesSizeLimit
executables
expirations
Expand Down
4 changes: 4 additions & 0 deletions api/v1/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ type ClusterSpec struct {
// +optional
WalStorage *StorageConfiguration `json:"walStorage,omitempty"`

// EphemeralVolumeSource allows the user to configure the source of ephemeral volumes.
// +optional
EphemeralVolumeSource *corev1.EphemeralVolumeSource `json:"ephemeralVolumeSource,omitempty"`

// The time in seconds that is allowed for a PostgreSQL instance to
// successfully start up (default 3600).
// The startup probe failure threshold is derived from this value using the formula:
Expand Down
16 changes: 16 additions & 0 deletions api/v1/cluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ func (r *Cluster) Validate() (allErrs field.ErrorList) {
r.validateMaxSyncReplicas,
r.validateStorageSize,
r.validateWalStorageSize,
r.validateEphemeralVolumeSource,
r.validateTablespaceStorageSize,
r.validateName,
r.validateTablespaceNames,
Expand Down Expand Up @@ -1475,6 +1476,21 @@ func (r *Cluster) validateWalStorageSize() field.ErrorList {
return result
}

func (r *Cluster) validateEphemeralVolumeSource() field.ErrorList {
var result field.ErrorList

if r.Spec.EphemeralVolumeSource != nil && (r.Spec.EphemeralVolumesSizeLimit != nil &&
r.Spec.EphemeralVolumesSizeLimit.TemporaryData != nil) {
result = append(result, field.Duplicate(
field.NewPath("spec", "ephemeralVolumeSource"),
"Conflicting settings: provide either ephemeralVolumeSource "+
"or ephemeralVolumesSizeLimit.TemporaryData, not both.",
))
}

return result
}

func (r *Cluster) validateTablespaceStorageSize() field.ErrorList {
if r.Spec.Tablespaces == nil {
return nil
Expand Down
56 changes: 56 additions & 0 deletions api/v1/cluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2842,6 +2842,62 @@ var _ = Describe("Storage configuration validation", func() {
})
})

var _ = Describe("Ephemeral volume configuration validation", func() {
It("succeeds if no ephemeral configuration is present", func() {
cluster := Cluster{
Spec: ClusterSpec{},
}
Expect(cluster.validateEphemeralVolumeSource()).To(BeEmpty())
})

It("succeeds if ephemeralVolumeSource is set", func() {
cluster := Cluster{
Spec: ClusterSpec{
EphemeralVolumeSource: &corev1.EphemeralVolumeSource{},
},
}
Expect(cluster.validateEphemeralVolumeSource()).To(BeEmpty())
})

It("succeeds if ephemeralVolumesSizeLimit.temporaryData is set", func() {
onegi := resource.MustParse("1Gi")
cluster := Cluster{
Spec: ClusterSpec{
EphemeralVolumesSizeLimit: &EphemeralVolumesSizeLimitConfiguration{
TemporaryData: &onegi,
},
},
}
Expect(cluster.validateEphemeralVolumeSource()).To(BeEmpty())
})

It("succeeds if ephemeralVolumeSource and ephemeralVolumesSizeLimit.shm are set", func() {
onegi := resource.MustParse("1Gi")
cluster := Cluster{
Spec: ClusterSpec{
EphemeralVolumeSource: &corev1.EphemeralVolumeSource{},
EphemeralVolumesSizeLimit: &EphemeralVolumesSizeLimitConfiguration{
Shm: &onegi,
},
},
}
Expect(cluster.validateEphemeralVolumeSource()).To(BeEmpty())
})

It("produces one error if conflicting ephemeral storage options are set", func() {
onegi := resource.MustParse("1Gi")
cluster := Cluster{
Spec: ClusterSpec{
EphemeralVolumeSource: &corev1.EphemeralVolumeSource{},
EphemeralVolumesSizeLimit: &EphemeralVolumesSizeLimitConfiguration{
TemporaryData: &onegi,
},
},
}
Expect(cluster.validateEphemeralVolumeSource()).To(HaveLen(1))
})
})

var _ = Describe("Role management validation", func() {
It("should succeed if there is no management stanza", func() {
cluster := Cluster{
Expand Down
5 changes: 5 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

248 changes: 248 additions & 0 deletions config/crd/bases/postgresql.cnpg.io_clusters.yaml

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions docs/src/cloudnative-pg.v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,13 @@ user by setting it to <code>NULL</code>. Disabled by default.</p>
<p>Configuration of the storage for PostgreSQL WAL (Write-Ahead Log)</p>
</td>
</tr>
<tr><td><code>ephemeralVolumeSource</code><br/>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#ephemeralvolumesource-v1-core"><i>core/v1.EphemeralVolumeSource</i></a>
</td>
<td>
<p>EphemeralVolumeSource allows the user to configure the source of ephemeral volumes.</p>
</td>
</tr>
<tr><td><code>startDelay</code><br/>
<i>int32</i>
</td>
Expand Down
29 changes: 25 additions & 4 deletions docs/src/cluster_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,32 @@ CloudNativePG relies on [ephemeral volumes](https://kubernetes.io/docs/concepts/
for part of the internal activities. Ephemeral volumes exist for the sole
duration of a pod's life, without persisting across pod restarts.

### Volume for temporary storage
# Volume Claim Template for Temporary Storage

An ephemeral volume used for temporary storage. You can configure an upper
bound on the size using the `.spec.ephemeralVolumesSizeLimit.temporaryData`
field in the cluster spec.
The operator uses by default an `emptyDir` volume, which can be customized by using the `.spec.ephemeralVolumesSizeLimit field`.
This can be overridden by specifying a volume claim template in the `.spec.ephemeralVolumeSource` field.

In the following example, a `1Gi` ephemeral volume is set.

```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example-ephemeral-volume-source
spec:
instances: 3
ephemeralVolumeSource:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
# example storageClassName, replace with one existing in your Kubernetes cluster
storageClassName: "scratch-storage-class"
resources:
requests:
storage: 1Gi
```

Both `.spec.emphemeralVolumeSource` and `.spec.ephemeralVolumesSizeLimit.temporaryData` cannot be specified simultaneously.

### Volume for shared memory

Expand Down
24 changes: 16 additions & 8 deletions pkg/specs/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,7 @@ func createPostgresVolumes(cluster apiv1.Cluster, podName string) []corev1.Volum
},
},
},
{
Name: "scratch-data",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
SizeLimit: cluster.Spec.EphemeralVolumesSizeLimit.GetTemporaryDataLimit(),
},
},
},
createEphemeralVolume(cluster),
{
Name: "shm",
VolumeSource: corev1.VolumeSource{
Expand Down Expand Up @@ -325,6 +318,21 @@ func getSortedTablespaceList(cluster apiv1.Cluster) []string {
return tbsNames
}

func createEphemeralVolume(cluster apiv1.Cluster) corev1.Volume {
scratchVolumeSource := corev1.VolumeSource{}
if cluster.Spec.EphemeralVolumeSource != nil {
scratchVolumeSource.Ephemeral = cluster.Spec.EphemeralVolumeSource
} else {
scratchVolumeSource.EmptyDir = &corev1.EmptyDirVolumeSource{
SizeLimit: cluster.Spec.EphemeralVolumesSizeLimit.GetTemporaryDataLimit(),
}
}
return corev1.Volume{
Name: "scratch-data",
VolumeSource: scratchVolumeSource,
}
}

func createProjectedVolume(cluster apiv1.Cluster) corev1.Volume {
return corev1.Volume{
Name: "projected",
Expand Down
46 changes: 46 additions & 0 deletions pkg/specs/volumes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package specs

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/ptr"

apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"

Expand Down Expand Up @@ -472,3 +474,47 @@ var _ = DescribeTable("test creation of volumes",
},
}),
)

var _ = Describe("createEphemeralVolume", func() {
var cluster apiv1.Cluster

BeforeEach(func() {
cluster = apiv1.Cluster{}
})

It("should create an emptyDir volume by default", func() {
ephemeralVolume := createEphemeralVolume(cluster)
Expect(ephemeralVolume.Name).To(Equal("scratch-data"))
Expect(ephemeralVolume.VolumeSource.EmptyDir).NotTo(BeNil())
})

It("should create an ephemeral volume when specified in the cluster", func() {
const storageClass = "test-storageclass"
cluster.Spec.EphemeralVolumeSource = &corev1.EphemeralVolumeSource{
VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{
Spec: corev1.PersistentVolumeClaimSpec{
StorageClassName: ptr.To(storageClass),
},
},
}

ephemeralVolume := createEphemeralVolume(cluster)

Expect(ephemeralVolume.Name).To(Equal("scratch-data"))
Expect(ephemeralVolume.EmptyDir).To(BeNil())
Expect(ephemeralVolume.VolumeSource.Ephemeral).NotTo(BeNil())
Expect(*ephemeralVolume.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName).To(Equal(storageClass))
})

It("should set size limit when specified in the cluster", func() {
quantity := resource.MustParse("1Gi")
cluster.Spec.EphemeralVolumesSizeLimit = &apiv1.EphemeralVolumesSizeLimitConfiguration{
TemporaryData: &quantity,
}

ephemeralVolume := createEphemeralVolume(cluster)

Expect(ephemeralVolume.Name).To(Equal("scratch-data"))
Expect(*ephemeralVolume.VolumeSource.EmptyDir.SizeLimit).To(Equal(quantity))
})
})

0 comments on commit 0e7ac95

Please sign in to comment.