Skip to content

Commit 45c89b3

Browse files
authored
[WIP] Add set_memory_request_to_limit option (zalando#406)
* Add set_memory_request_to_limit option
1 parent f25351c commit 45c89b3

16 files changed

+145
-18
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ cd postgres-operator
9090
./run_operator_locally.sh
9191
```
9292

93+
Note we provide the `/manifests` directory as an example only; you should consider adjusting the manifests to your particular setting.
94+
9395
## Running and testing the operator
9496

9597
The best way to test the operator is to run it locally in [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/). See developer docs(`docs/developer.yaml`) for details.

delivery.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pipeline:
2222
go version
2323
- desc: 'Install Docker'
2424
cmd: |
25-
curl -sSL https://get.docker.com/ | sh
25+
curl -fLOsS https://delivery.cloud.zalando.com/utils/ensure-docker && sh ensure-docker && rm ensure-docker
2626
- desc: 'Symlink sources into the GOPATH'
2727
cmd: |
2828
mkdir -p $OPERATOR_TOP_DIR

docs/administrator.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ manifests:
4141

4242
```bash
4343
$ kubectl create namespace test
44-
$ kubectl config set-context --namespace=test
44+
$ kubectl config set-context $(kubectl config current-context) --namespace=test
4545
```
4646

4747
All subsequent `kubectl` commands will work with the `test` namespace. The
48-
operator will run in this namespace and look up needed resources - such as its
49-
config map - there.
48+
operator will run in this namespace and look up needed resources - such as its
49+
config map - there. Please note that the namespace for service accounts and cluster role bindings in [operator RBAC rules](manifests/operator-service-account-rbac.yaml) needs to be adjusted to the non-default value.
5050

5151
## Specify the namespace to watch
5252

docs/reference/operator_parameters.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ CRD-based configuration.
221221
memory limits for the postgres containers, unless overridden by cluster-specific
222222
settings. The default is `1Gi`.
223223

224+
* **set_memory_request_to_limit**
225+
Set `memory_request` to `memory_limit` for all Postgres clusters (the default value is also increased). This prevents certain cases of memory overcommitment at the cost of overprovisioning memory and potential scheduling problems for containers with high memory limits due to the lack of memory on Kubernetes cluster nodes. This affects all containers (Postgres, Scalyr sidecar, and other sidecars). The default is `false`.
226+
224227
## Operator timeouts
225228

226229
This set of parameters define various timeouts related to some operator

manifests/complete-postgres-manifest.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ metadata:
66
spec:
77
teamId: "ACID"
88
volume:
9-
size: 5Gi
9+
size: 1Gi
1010
numberOfInstances: 2
1111
users: #Application/Robot users
1212
zalando:
@@ -31,7 +31,7 @@ spec:
3131
memory: 100Mi
3232
limits:
3333
cpu: 300m
34-
memory: 3000Mi
34+
memory: 300Mi
3535
patroni:
3636
initdb:
3737
encoding: "UTF8"

manifests/configmap.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ data:
1010

1111
debug_logging: "true"
1212
workers: "4"
13-
docker_image: registry.opensource.zalan.do/acid/spilo-cdp-10:1.4-p29
13+
docker_image: registry.opensource.zalan.do/acid/spilo-cdp-10:1.5-p35
1414
pod_service_account_name: "zalando-postgres-operator"
1515
secret_name_template: '{username}.{cluster}.credentials'
1616
super_username: postgres
1717
enable_teams_api: "false"
18+
# set_memory_request_to_limit: "true"
1819
# postgres_superuser_teams: "postgres_superusers"
1920
# enable_team_superuser: "false"
2021
# team_admin_role: "admin"

pkg/apis/acid.zalan.do/v1/operator_configuration_type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ type OperatorConfigurationData struct {
131131
PostgresUsersConfiguration PostgresUsersConfiguration `json:"users"`
132132
Kubernetes KubernetesMetaConfiguration `json:"kubernetes"`
133133
PostgresPodResources PostgresPodResourcesDefaults `json:"postgres_pod_resources"`
134+
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
134135
Timeouts OperatorTimeouts `json:"timeouts"`
135136
LoadBalancer LoadBalancerConfiguration `json:"load_balancer"`
136137
AWSGCP AWSGCPConfiguration `json:"aws_or_gcp"`

pkg/apis/acid.zalan.do/v1/postgresql_type.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ type ResourceDescription struct {
9090

9191
// Resources describes requests and limits for the cluster resouces.
9292
type Resources struct {
93-
ResourceRequest ResourceDescription `json:"requests,omitempty"`
94-
ResourceLimits ResourceDescription `json:"limits,omitempty"`
93+
ResourceRequests ResourceDescription `json:"requests,omitempty"`
94+
ResourceLimits ResourceDescription `json:"limits,omitempty"`
9595
}
9696

9797
// Patroni contains Patroni-specific configuration

pkg/apis/acid.zalan.do/v1/util_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ var unmarshalCluster = []struct {
240240
Slots: map[string]map[string]string{"permanent_logical_1": {"type": "logical", "database": "foo", "plugin": "pgoutput"}},
241241
},
242242
Resources: Resources{
243-
ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"},
244-
ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"},
243+
ResourceRequests: ResourceDescription{CPU: "10m", Memory: "50Mi"},
244+
ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"},
245245
},
246246

247247
TeamID: "ACID",

pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cluster/k8sres.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,18 @@ func (c *Cluster) makeDefaultResources() acidv1.Resources {
9292
defaultRequests := acidv1.ResourceDescription{CPU: config.DefaultCPURequest, Memory: config.DefaultMemoryRequest}
9393
defaultLimits := acidv1.ResourceDescription{CPU: config.DefaultCPULimit, Memory: config.DefaultMemoryLimit}
9494

95-
return acidv1.Resources{ResourceRequest: defaultRequests, ResourceLimits: defaultLimits}
95+
return acidv1.Resources{ResourceRequests: defaultRequests, ResourceLimits: defaultLimits}
9696
}
9797

9898
func generateResourceRequirements(resources acidv1.Resources, defaultResources acidv1.Resources) (*v1.ResourceRequirements, error) {
9999
var err error
100100

101-
specRequests := resources.ResourceRequest
101+
specRequests := resources.ResourceRequests
102102
specLimits := resources.ResourceLimits
103103

104104
result := v1.ResourceRequirements{}
105105

106-
result.Requests, err = fillResourceList(specRequests, defaultResources.ResourceRequest)
106+
result.Requests, err = fillResourceList(specRequests, defaultResources.ResourceRequests)
107107
if err != nil {
108108
return nil, fmt.Errorf("could not fill resource requests: %v", err)
109109
}
@@ -377,8 +377,8 @@ func generateSidecarContainers(sidecars []acidv1.Sidecar,
377377

378378
resources, err := generateResourceRequirements(
379379
makeResources(
380-
sidecar.Resources.ResourceRequest.CPU,
381-
sidecar.Resources.ResourceRequest.Memory,
380+
sidecar.Resources.ResourceRequests.CPU,
381+
sidecar.Resources.ResourceRequests.Memory,
382382
sidecar.Resources.ResourceLimits.CPU,
383383
sidecar.Resources.ResourceLimits.Memory,
384384
),
@@ -625,7 +625,7 @@ func getBucketScopeSuffix(uid string) string {
625625

626626
func makeResources(cpuRequest, memoryRequest, cpuLimit, memoryLimit string) acidv1.Resources {
627627
return acidv1.Resources{
628-
ResourceRequest: acidv1.ResourceDescription{
628+
ResourceRequests: acidv1.ResourceDescription{
629629
CPU: cpuRequest,
630630
Memory: memoryRequest,
631631
},
@@ -644,6 +644,60 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*v1beta1.State
644644
podTemplate *v1.PodTemplateSpec
645645
volumeClaimTemplate *v1.PersistentVolumeClaim
646646
)
647+
648+
if c.OpConfig.SetMemoryRequestToLimit {
649+
650+
// controller adjusts the default memory request at operator startup
651+
652+
request := spec.Resources.ResourceRequests.Memory
653+
if request == "" {
654+
request = c.OpConfig.DefaultMemoryRequest
655+
}
656+
657+
limit := spec.Resources.ResourceLimits.Memory
658+
if limit == "" {
659+
limit = c.OpConfig.DefaultMemoryLimit
660+
}
661+
662+
isSmaller, err := util.RequestIsSmallerThanLimit(request, limit)
663+
if err != nil {
664+
return nil, err
665+
}
666+
if isSmaller {
667+
c.logger.Warningf("The memory request of %v for the Postgres container is increased to match the memory limit of %v.", request, limit)
668+
spec.Resources.ResourceRequests.Memory = limit
669+
670+
}
671+
672+
// controller adjusts the Scalyr sidecar request at operator startup
673+
// as this sidecar is managed separately
674+
675+
// adjust sidecar containers defined for that particular cluster
676+
for _, sidecar := range spec.Sidecars {
677+
678+
// TODO #413
679+
sidecarRequest := sidecar.Resources.ResourceRequests.Memory
680+
if request == "" {
681+
request = c.OpConfig.DefaultMemoryRequest
682+
}
683+
684+
sidecarLimit := sidecar.Resources.ResourceLimits.Memory
685+
if limit == "" {
686+
limit = c.OpConfig.DefaultMemoryLimit
687+
}
688+
689+
isSmaller, err := util.RequestIsSmallerThanLimit(sidecarRequest, sidecarLimit)
690+
if err != nil {
691+
return nil, err
692+
}
693+
if isSmaller {
694+
c.logger.Warningf("The memory request of %v for the %v sidecar container is increased to match the memory limit of %v.", sidecar.Resources.ResourceRequests.Memory, sidecar.Name, sidecar.Resources.ResourceLimits.Memory)
695+
sidecar.Resources.ResourceRequests.Memory = sidecar.Resources.ResourceLimits.Memory
696+
}
697+
}
698+
699+
}
700+
647701
defaultResources := c.makeDefaultResources()
648702

649703
resourceRequirements, err := generateResourceRequirements(spec.Resources, defaultResources)

pkg/controller/controller.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,29 @@ func (c *Controller) initOperatorConfig() {
110110
c.opConfig = config.NewFromMap(configMapData)
111111
c.warnOnDeprecatedOperatorParameters()
112112

113+
if c.opConfig.SetMemoryRequestToLimit {
114+
115+
isSmaller, err := util.RequestIsSmallerThanLimit(c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit)
116+
if err != nil {
117+
panic(err)
118+
}
119+
if isSmaller {
120+
c.logger.Warningf("The default memory request of %v for Postgres containers is increased to match the default memory limit of %v.", c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit)
121+
c.opConfig.DefaultMemoryRequest = c.opConfig.DefaultMemoryLimit
122+
}
123+
124+
isSmaller, err = util.RequestIsSmallerThanLimit(c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit)
125+
if err != nil {
126+
panic(err)
127+
}
128+
if isSmaller {
129+
c.logger.Warningf("The memory request of %v for the Scalyr sidecar container is increased to match the memory limit of %v.", c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit)
130+
c.opConfig.ScalyrMemoryRequest = c.opConfig.ScalyrMemoryLimit
131+
}
132+
133+
// generateStatefulSet adjusts values for individual Postgres clusters
134+
}
135+
113136
}
114137

115138
func (c *Controller) modifyConfigFromEnvironment() {

pkg/controller/operator_config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
5555
result.DefaultMemoryRequest = fromCRD.PostgresPodResources.DefaultMemoryRequest
5656
result.DefaultCPULimit = fromCRD.PostgresPodResources.DefaultCPULimit
5757
result.DefaultMemoryLimit = fromCRD.PostgresPodResources.DefaultMemoryLimit
58+
result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit
5859

5960
result.ResourceCheckInterval = time.Duration(fromCRD.Timeouts.ResourceCheckInterval)
6061
result.ResourceCheckTimeout = time.Duration(fromCRD.Timeouts.ResourceCheckTimeout)

pkg/util/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ type Config struct {
104104
PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"`
105105
ProtectedRoles []string `name:"protected_role_names" default:"admin"`
106106
PostgresSuperuserTeams []string `name:"postgres_superuser_teams" default:""`
107+
SetMemoryRequestToLimit bool `name:"set_memory_request_to_limit" defaults:"false"`
107108
}
108109

109110
// MustMarshal marshals the config or panics

pkg/util/util.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package util
33
import (
44
"crypto/md5" // #nosec we need it to for PostgreSQL md5 passwords
55
"encoding/hex"
6+
"fmt"
67
"math/rand"
78
"regexp"
89
"strings"
910
"time"
1011

1112
"github.com/motomux/pretty"
13+
resource "k8s.io/apimachinery/pkg/api/resource"
1214
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1315

1416
"github.com/zalando-incubator/postgres-operator/pkg/spec"
@@ -127,3 +129,19 @@ func Coalesce(val, defaultVal string) string {
127129
}
128130
return val
129131
}
132+
133+
// RequestIsSmallerThanLimit
134+
func RequestIsSmallerThanLimit(requestStr, limitStr string) (bool, error) {
135+
136+
request, err := resource.ParseQuantity(requestStr)
137+
if err != nil {
138+
return false, fmt.Errorf("could not parse memory request %v : %v", requestStr, err)
139+
}
140+
141+
limit, err2 := resource.ParseQuantity(limitStr)
142+
if err2 != nil {
143+
return false, fmt.Errorf("could not parse memory limit %v : %v", limitStr, err2)
144+
}
145+
146+
return request.Cmp(limit) == -1, nil
147+
}

pkg/util/util_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ var substringMatch = []struct {
6969
{regexp.MustCompile(`aaaa (\d+) bbbb`), "aaaa 123 bbbb", nil},
7070
}
7171

72+
var requestIsSmallerThanLimitTests = []struct {
73+
request string
74+
limit string
75+
out bool
76+
}{
77+
{"1G", "2G", true},
78+
{"1G", "1Gi", true}, // G is 1000^3 bytes, Gi is 1024^3 bytes
79+
{"1024Mi", "1G", false},
80+
{"1e9", "1G", false}, // 1e9 bytes == 1G
81+
}
82+
7283
func TestRandomPassword(t *testing.T) {
7384
const pwdLength = 10
7485
pwd := RandomPassword(pwdLength)
@@ -143,3 +154,15 @@ func TestMapContains(t *testing.T) {
143154
}
144155
}
145156
}
157+
158+
func TestRequestIsSmallerThanLimit(t *testing.T) {
159+
for _, tt := range requestIsSmallerThanLimitTests {
160+
res, err := RequestIsSmallerThanLimit(tt.request, tt.limit)
161+
if err != nil {
162+
t.Errorf("RequestIsSmallerThanLimit returned unexpected error: %#v", err)
163+
}
164+
if res != tt.out {
165+
t.Errorf("RequestIsSmallerThanLimit expected: %#v, got: %#v", tt.out, res)
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)