Skip to content

Commit 73b6c0e

Browse files
author
Lars Maier
authored
Merge pull request #379 from arangodb/feature/volume-claim-templates
Volume Claim Templates
2 parents 1c75b2a + 34c3c9e commit 73b6c0e

File tree

14 files changed

+295
-65
lines changed

14 files changed

+295
-65
lines changed

docs/Manual/Deployment/Kubernetes/DeploymentResource.md

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -377,25 +377,22 @@ Specifies a maximum for the count of servers. If set, a specification is invalid
377377
This setting specifies additional commandline arguments passed to all servers of this group.
378378
The default value is an empty array.
379379

380-
### `spec.<group>.resources.requests.cpu: cpuUnit`
380+
### `spec.<group>.resources: ResourceRequirements`
381381

382-
This setting specifies the amount of CPU requested by server of this group.
382+
This setting specifies the resources required by pods of this group. This includes requests and limits.
383383

384384
See https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ for details.
385385

386-
### `spec.<group>.resources.requests.memory: memoryUnit`
386+
### `spec.<group>.volumeClaimTemplate.Spec: PersistentVolumeClaimSpec`
387387

388-
This setting specifies the amount of memory requested by server of this group.
388+
Specifies a volumeClaimTemplate used by operator to create to volume claims for pods of this group.
389+
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`.
389390

390-
See https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ for details.
391-
392-
### `spec.<group>.resources.requests.storage: storageUnit`
391+
The default value describes a volume with `8Gi` storage, `ReadWriteOnce` access mode and volume mode set to `PersistentVolumeFilesystem`.
393392

394-
This setting specifies the amount of storage required for each server of this group.
395-
The default value is `8Gi`.
396-
397-
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`
398-
because servers in these groups do not need persistent storage.
393+
If this field is not set and `spec.<group>.resources.requests.storage` is set, then a default volume claim
394+
with size as specified by `spec.<group>.resources.requests.storage` will be created. In that case `storage`
395+
and `iops` is not forwarded to the pods resource requirements.
399396

400397
### `spec.<group>.serviceAccountName: string`
401398

@@ -405,14 +402,6 @@ for each server of this group.
405402
Using an alternative `ServiceAccount` is typically used to separate access rights.
406403
The ArangoDB deployments do not require any special rights.
407404

408-
### `spec.<group>.storageClassName: string`
409-
410-
This setting specifies the `storageClass` for the `PersistentVolume`s created
411-
for each server of this group.
412-
413-
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`
414-
because servers in these groups do not need persistent storage.
415-
416405
### `spec.<group>.priorityClassName: string`
417406

418407
Priority class name for pods of this group. Will be forwarded to the pod spec. [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/)
@@ -443,3 +432,29 @@ For more information on tolerations, consult the [Kubernetes documentation](http
443432
This setting specifies a set of labels to be used as `nodeSelector` for Pods of this node.
444433

445434
For more information on node selectors, consult the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).
435+
436+
## Deprecated Fields
437+
438+
### `spec.<group>.resources.requests.storage: storageUnit`
439+
440+
This setting specifies the amount of storage required for each server of this group.
441+
The default value is `8Gi`.
442+
443+
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`
444+
because servers in these groups do not need persistent storage.
445+
446+
Please use VolumeClaimTemplate from now on. This field is not considered if
447+
VolumeClaimTemplate is set. Note however, that the information in requests
448+
is completely handed over to the pod in this case.
449+
450+
### `spec.<group>.storageClassName: string`
451+
452+
This setting specifies the `storageClass` for the `PersistentVolume`s created
453+
for each server of this group.
454+
455+
This setting is not available for group `coordinators`, `syncmasters` & `syncworkers`
456+
because servers in these groups do not need persistent storage.
457+
458+
Please use VolumeClaimTemplate from now on. This field is not considered if
459+
VolumeClaimTemplate is set. Note however, that the information in requests
460+
is completely handed over to the pod in this case.

pkg/apis/deployment/v1alpha/server_group_spec.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type ServerGroupSpec struct {
6060
Probes *ServerGroupProbesSpec `json:"probes,omitempty"`
6161
// PriorityClassName specifies a priority class name
6262
PriorityClassName string `json:"priorityClassName,omitempty"`
63+
// VolumeClaimTemplate specifies a template for volume claims
64+
VolumeClaimTemplate *v1.PersistentVolumeClaim `json:"volumeClaimTemplate,omitempty"`
6365
// Sidecars specifies a list of additional containers to be started
6466
Sidecars []v1.Container `json:"sidecars,omitempty"`
6567
}
@@ -101,6 +103,16 @@ func (s ServerGroupProbesSpec) IsReadinessProbeDisabled() bool {
101103
return util.BoolOrDefault(s.ReadinessProbeDisabled)
102104
}
103105

106+
// HasVolumeClaimTemplate returns whether there is a volumeClaimTemplate or not
107+
func (s ServerGroupSpec) HasVolumeClaimTemplate() bool {
108+
return s.VolumeClaimTemplate != nil
109+
}
110+
111+
// GetVolumeClaimTemplate returns a pointer to a volume claim template or nil if none is specified
112+
func (s ServerGroupSpec) GetVolumeClaimTemplate() *v1.PersistentVolumeClaim {
113+
return s.VolumeClaimTemplate
114+
}
115+
104116
// GetCount returns the value of count.
105117
func (s ServerGroupSpec) GetCount() int {
106118
return util.IntOrDefault(s.Count)
@@ -243,13 +255,25 @@ func (s *ServerGroupSpec) SetDefaults(group ServerGroup, used bool, mode Deploym
243255
s.MinCount = nil
244256
s.MaxCount = nil
245257
}
246-
if _, found := s.Resources.Requests[v1.ResourceStorage]; !found {
247-
switch group {
248-
case ServerGroupSingle, ServerGroupAgents, ServerGroupDBServers:
249-
if s.Resources.Requests == nil {
250-
s.Resources.Requests = make(map[v1.ResourceName]resource.Quantity)
258+
if !s.HasVolumeClaimTemplate() {
259+
if _, found := s.Resources.Requests[v1.ResourceStorage]; !found {
260+
switch group {
261+
case ServerGroupSingle, ServerGroupAgents, ServerGroupDBServers:
262+
volumeMode := v1.PersistentVolumeFilesystem
263+
s.VolumeClaimTemplate = &v1.PersistentVolumeClaim{
264+
Spec: v1.PersistentVolumeClaimSpec{
265+
AccessModes: []v1.PersistentVolumeAccessMode{
266+
v1.ReadWriteOnce,
267+
},
268+
VolumeMode: &volumeMode,
269+
Resources: v1.ResourceRequirements{
270+
Requests: v1.ResourceList{
271+
v1.ResourceStorage: resource.MustParse("8Gi"),
272+
},
273+
},
274+
},
275+
}
251276
}
252-
s.Resources.Requests[v1.ResourceStorage] = resource.MustParse("8Gi")
253277
}
254278
}
255279
}
@@ -294,6 +318,9 @@ func (s *ServerGroupSpec) SetDefaultsFrom(source ServerGroupSpec) {
294318
}
295319
setDefaultsFromResourceList(&s.Resources.Limits, source.Resources.Limits)
296320
setDefaultsFromResourceList(&s.Resources.Requests, source.Resources.Requests)
321+
if s.VolumeClaimTemplate == nil {
322+
s.VolumeClaimTemplate = source.VolumeClaimTemplate.DeepCopy()
323+
}
297324
}
298325

299326
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
@@ -306,5 +333,9 @@ func (s ServerGroupSpec) ResetImmutableFields(group ServerGroup, fieldPrefix str
306333
resetFields = append(resetFields, fieldPrefix+".count")
307334
}
308335
}
336+
if s.HasVolumeClaimTemplate() != target.HasVolumeClaimTemplate() {
337+
target.VolumeClaimTemplate = s.GetVolumeClaimTemplate()
338+
resetFields = append(resetFields, fieldPrefix+".volumeClaimTemplate")
339+
}
309340
return resetFields
310341
}

pkg/apis/deployment/v1alpha/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/deployment/context_impl.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ func (d *Deployment) UpdateStatus(status api.DeploymentStatus, lastVersion int32
118118
return nil
119119
}
120120

121+
// UpdateMember updates the deployment status wrt the given member.
122+
func (d *Deployment) UpdateMember(member api.MemberStatus) error {
123+
status, lastVersion := d.GetStatus()
124+
_, group, found := status.Members.ElementByID(member.ID)
125+
if !found {
126+
return maskAny(fmt.Errorf("Member %s not found", member.ID))
127+
}
128+
if err := status.Members.Update(member, group); err != nil {
129+
return maskAny(err)
130+
}
131+
if err := d.UpdateStatus(status, lastVersion); err != nil {
132+
log.Debug().Err(err).Msg("Updating CR status failed")
133+
return maskAny(err)
134+
}
135+
return nil
136+
}
137+
121138
// GetDatabaseClient returns a cached client for the entire database (cluster coordinators or single server),
122139
// creating one if needed.
123140
func (d *Deployment) GetDatabaseClient(ctx context.Context) (driver.Client, error) {

pkg/deployment/images.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
"time"
3131

3232
"github.com/rs/zerolog"
33-
"k8s.io/api/core/v1"
33+
v1 "k8s.io/api/core/v1"
3434
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3535
"k8s.io/client-go/kubernetes"
3636

@@ -198,7 +198,7 @@ func (ib *imagesBuilder) fetchArangoDBImageIDAndVersion(ctx context.Context, ima
198198
}
199199
}
200200
if err := k8sutil.CreateArangodPod(ib.KubeCli, true, ib.APIObject, role, id, podName, "", image, "", "", ib.Spec.GetImagePullPolicy(), "", false, terminationGracePeriod, args, env, nil, nil, nil,
201-
tolerations, serviceAccountName, "", "", "", nil, "", v1.ResourceRequirements{}, nil, nil); err != nil {
201+
tolerations, serviceAccountName, "", "", "", nil, "", v1.ResourceRequirements{}, nil, nil, nil); err != nil {
202202
log.Debug().Err(err).Msg("Failed to create image ID pod")
203203
return true, maskAny(err)
204204
}

pkg/deployment/reconcile/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ type Context interface {
4646
// UpdateStatus replaces the status of the deployment with the given status and
4747
// updates the resources in k8s.
4848
UpdateStatus(status api.DeploymentStatus, lastVersion int32, force ...bool) error
49+
// UpdateMember updates the deployment status wrt the given member.
50+
UpdateMember(member api.MemberStatus) error
4951
// GetDatabaseClient returns a cached client for the entire database (cluster coordinators or single server),
5052
// creating one if needed.
5153
GetDatabaseClient(ctx context.Context) (driver.Client, error)

pkg/deployment/reconcile/plan_builder.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,14 @@ func podNeedsRotation(log zerolog.Logger, p v1.Pod, apiObject metav1.Object, spe
383383
}
384384

385385
// Check resource requirements
386-
if resourcesRequireRotation(
387-
k8sutil.FilterStorageResourceRequirement(spec.GetServerGroupSpec(group).Resources),
388-
k8sutil.GetArangoDBContainerFromPod(&p).Resources) {
386+
var resources v1.ResourceRequirements
387+
if groupSpec.HasVolumeClaimTemplate() {
388+
resources = groupSpec.Resources // If there is a volume claim template compare all resources
389+
} else {
390+
resources = k8sutil.ExtractPodResourceRequirement(groupSpec.Resources)
391+
}
392+
393+
if resourcesRequireRotation(resources, k8sutil.GetArangoDBContainerFromPod(&p).Resources) {
389394
return true, "Resource Requirements changed"
390395
}
391396

pkg/deployment/reconcile/reconciler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ func (r *Reconciler) CheckDeployment() error {
6363
r.log.Error().Err(err).Msg("Failed to delete pod")
6464
}
6565
m.Phase = api.MemberPhaseNone
66-
if err := status.Members.Update(m, api.ServerGroupCoordinators); err != nil {
66+
67+
if err := r.context.UpdateMember(m); err != nil {
6768
r.log.Error().Err(err).Msg("Failed to update member")
6869
}
6970
}

pkg/deployment/resources/pod_creator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import (
4141
"github.com/arangodb/kube-arangodb/pkg/util/constants"
4242
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
4343
"github.com/pkg/errors"
44-
"k8s.io/api/core/v1"
44+
v1 "k8s.io/api/core/v1"
4545
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4646
)
4747

@@ -660,7 +660,7 @@ func (r *Resources) createPodForMember(spec api.DeploymentSpec, memberID string,
660660
finalizers := r.createPodFinalizers(group)
661661
if err := k8sutil.CreateArangodPod(kubecli, spec.IsDevelopment(), apiObject, role, m.ID, m.PodName, m.PersistentVolumeClaimName, imageInfo.ImageID, lifecycleImage, alpineImage, spec.GetImagePullPolicy(),
662662
engine, requireUUID, terminationGracePeriod, args, env, finalizers, livenessProbe, readinessProbe, tolerations, serviceAccountName, tlsKeyfileSecretName, rocksdbEncryptionSecretName,
663-
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.PriorityClassName, groupSpec.Resources, exporter, groupSpec.GetSidecars()); err != nil {
663+
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.PriorityClassName, groupSpec.Resources, exporter, groupSpec.GetSidecars(), groupSpec.VolumeClaimTemplate); err != nil {
664664
return maskAny(err)
665665
}
666666
log.Debug().Str("pod-name", m.PodName).Msg("Created pod")

pkg/deployment/resources/pvc_inspector.go

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,41 @@ func (r *Resources) InspectPVCs(ctx context.Context) (util.Interval, error) {
8383

8484
// Resize inspector
8585
groupSpec := spec.GetServerGroupSpec(group)
86-
if requestedSize, ok := groupSpec.Resources.Requests[apiv1.ResourceStorage]; ok {
87-
if volumeSize, ok := p.Spec.Resources.Requests[apiv1.ResourceStorage]; ok {
88-
cmp := volumeSize.Cmp(requestedSize)
89-
if cmp < 0 {
90-
// Size of the volume is smaller than the requested size
91-
// Update the pvc with the request size
92-
p.Spec.Resources.Requests[apiv1.ResourceStorage] = requestedSize
93-
94-
log.Debug().Str("pvc-capacity", volumeSize.String()).Str("requested", requestedSize.String()).Msg("PVC capacity differs - updating")
95-
kube := r.context.GetKubeCli()
96-
if _, err := kube.CoreV1().PersistentVolumeClaims(r.context.GetNamespace()).Update(&p); err != nil {
97-
log.Error().Err(err).Msg("Failed to update pvc")
98-
}
86+
87+
if groupSpec.HasVolumeClaimTemplate() {
88+
res := groupSpec.GetVolumeClaimTemplate().Spec.Resources.Requests
89+
// For pvc only resources.requests is mutable
90+
if compareResourceList(p.Spec.Resources.Requests, res) {
91+
p.Spec.Resources.Requests = res
92+
log.Debug().Msg("volumeClaimTemplate requested resources changed - updating")
93+
kube := r.context.GetKubeCli()
94+
if _, err := kube.CoreV1().PersistentVolumeClaims(r.context.GetNamespace()).Update(&p); err != nil {
95+
log.Error().Err(err).Msg("Failed to update pvc")
96+
} else {
9997
r.context.CreateEvent(k8sutil.NewPVCResizedEvent(r.context.GetAPIObject(), p.Name))
100-
} else if cmp > 0 {
101-
log.Error().Str("server-group", group.AsRole()).Str("pvc-storage-size", volumeSize.String()).Str("requested-size", requestedSize.String()).
102-
Msg("Volume size should not shrink")
103-
r.context.CreateEvent(k8sutil.NewCannotShrinkVolumeEvent(r.context.GetAPIObject(), p.Name))
98+
}
99+
}
100+
} else {
101+
if requestedSize, ok := groupSpec.Resources.Requests[apiv1.ResourceStorage]; ok {
102+
if volumeSize, ok := p.Spec.Resources.Requests[apiv1.ResourceStorage]; ok {
103+
cmp := volumeSize.Cmp(requestedSize)
104+
if cmp < 0 {
105+
// Size of the volume is smaller than the requested size
106+
// Update the pvc with the request size
107+
p.Spec.Resources.Requests[apiv1.ResourceStorage] = requestedSize
108+
109+
log.Debug().Str("pvc-capacity", volumeSize.String()).Str("requested", requestedSize.String()).Msg("PVC capacity differs - updating")
110+
kube := r.context.GetKubeCli()
111+
if _, err := kube.CoreV1().PersistentVolumeClaims(r.context.GetNamespace()).Update(&p); err != nil {
112+
log.Error().Err(err).Msg("Failed to update pvc")
113+
} else {
114+
r.context.CreateEvent(k8sutil.NewPVCResizedEvent(r.context.GetAPIObject(), p.Name))
115+
}
116+
} else if cmp > 0 {
117+
log.Error().Str("server-group", group.AsRole()).Str("pvc-storage-size", volumeSize.String()).Str("requested-size", requestedSize.String()).
118+
Msg("Volume size should not shrink")
119+
r.context.CreateEvent(k8sutil.NewCannotShrinkVolumeEvent(r.context.GetAPIObject(), p.Name))
120+
}
104121
}
105122
}
106123
}
@@ -118,3 +135,21 @@ func (r *Resources) InspectPVCs(ctx context.Context) (util.Interval, error) {
118135

119136
return nextInterval, nil
120137
}
138+
139+
func compareResourceList(wanted, given apiv1.ResourceList) bool {
140+
for k, v := range wanted {
141+
if gv, ok := given[k]; !ok {
142+
return true
143+
} else if v.Cmp(gv) != 0 {
144+
return true
145+
}
146+
}
147+
148+
for k := range given {
149+
if _, ok := wanted[k]; !ok {
150+
return true
151+
}
152+
}
153+
154+
return false
155+
}

0 commit comments

Comments
 (0)