merge with master
This commit is contained in:
		
						commit
						00698f8394
					
				
							
								
								
									
										30
									
								
								docs/user.md
								
								
								
								
							
							
						
						
									
										30
									
								
								docs/user.md
								
								
								
								
							|  | @ -30,7 +30,7 @@ spec: | ||||||
|   databases: |   databases: | ||||||
|     foo: zalando |     foo: zalando | ||||||
|   postgresql: |   postgresql: | ||||||
|     version: "10" |     version: "11" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Once you cloned the Postgres Operator [repository](https://github.com/zalando/postgres-operator) | Once you cloned the Postgres Operator [repository](https://github.com/zalando/postgres-operator) | ||||||
|  | @ -42,6 +42,8 @@ kubectl create -f manifests/minimal-postgres-manifest.yaml | ||||||
| 
 | 
 | ||||||
| Make sure, the `spec` section of the manifest contains at least a `teamId`, the | Make sure, the `spec` section of the manifest contains at least a `teamId`, the | ||||||
| `numberOfInstances` and the `postgresql` object with the `version` specified. | `numberOfInstances` and the `postgresql` object with the `version` specified. | ||||||
|  | The minimum volume size to run the `postgresql` resource on Elastic Block | ||||||
|  | Storage (EBS) is `1Gi`. | ||||||
| 
 | 
 | ||||||
| Note, that the name of the cluster must start with the `teamId` and `-`. At | Note, that the name of the cluster must start with the `teamId` and `-`. At | ||||||
| Zalando we use team IDs (nicknames) to lower the chance of duplicate cluster | Zalando we use team IDs (nicknames) to lower the chance of duplicate cluster | ||||||
|  | @ -214,6 +216,28 @@ to choose superusers, group roles, [PAM configuration](https://github.com/CyberD | ||||||
| etc. An OAuth2 token can be passed to the Teams API via a secret. The name for | etc. An OAuth2 token can be passed to the Teams API via a secret. The name for | ||||||
| this secret is configurable with the `oauth_token_secret_name` parameter. | this secret is configurable with the `oauth_token_secret_name` parameter. | ||||||
| 
 | 
 | ||||||
|  | ## Resource definition | ||||||
|  | 
 | ||||||
|  | The compute resources to be used for the Postgres containers in the pods can be | ||||||
|  | specified in the postgresql cluster manifest. | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | spec: | ||||||
|  |   resources: | ||||||
|  |     requests: | ||||||
|  |       cpu: 10m | ||||||
|  |       memory: 100Mi | ||||||
|  |     limits: | ||||||
|  |       cpu: 300m | ||||||
|  |       memory: 300Mi | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The minimum limit to properly run the `postgresql` resource is `256m` for `cpu` | ||||||
|  | and `256Mi` for `memory`. If a lower value is set in the manifest the operator | ||||||
|  | will cancel ADD or UPDATE events on this resource with an error. If no | ||||||
|  | resources are defined in the manifest the operator will obtain the configured | ||||||
|  | [default requests](reference/operator_parameters.md#kubernetes-resource-requests). | ||||||
|  | 
 | ||||||
| ## Use taints and tolerations for dedicated PostgreSQL nodes | ## Use taints and tolerations for dedicated PostgreSQL nodes | ||||||
| 
 | 
 | ||||||
| To ensure Postgres pods are running on nodes without any other application pods, | To ensure Postgres pods are running on nodes without any other application pods, | ||||||
|  | @ -318,7 +342,7 @@ Things to note: | ||||||
| - There is no way to transform a non-standby cluster to a standby cluster | - There is no way to transform a non-standby cluster to a standby cluster | ||||||
|   through the operator. Adding the standby section to the manifest of a running |   through the operator. Adding the standby section to the manifest of a running | ||||||
|   Postgres cluster will have no effect. However, it can be done through Patroni |   Postgres cluster will have no effect. However, it can be done through Patroni | ||||||
|   by adding the [standby_cluster] (https://github.com/zalando/patroni/blob/bd2c54581abb42a7d3a3da551edf0b8732eefd27/docs/replica_bootstrap.rst#standby-cluster) |   by adding the [standby_cluster](https://github.com/zalando/patroni/blob/bd2c54581abb42a7d3a3da551edf0b8732eefd27/docs/replica_bootstrap.rst#standby-cluster) | ||||||
|   section using `patronictl edit-config`. Note that the transformed standby |   section using `patronictl edit-config`. Note that the transformed standby | ||||||
|   cluster will not be doing any streaming. It will be in standby mode and allow |   cluster will not be doing any streaming. It will be in standby mode and allow | ||||||
|   read-only transactions only. |   read-only transactions only. | ||||||
|  | @ -385,7 +409,7 @@ specified but globally disabled in the configuration. The | ||||||
| 
 | 
 | ||||||
| ## Increase volume size | ## Increase volume size | ||||||
| 
 | 
 | ||||||
| PostgreSQL operator supports statefulset volume resize if you're using the | Postgres operator supports statefulset volume resize if you're using the | ||||||
| operator on top of AWS. For that you need to change the size field of the | operator on top of AWS. For that you need to change the size field of the | ||||||
| volume description in the cluster manifest and apply the change: | volume description in the cluster manifest and apply the change: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ spec: | ||||||
|     size: 1Gi |     size: 1Gi | ||||||
|   numberOfInstances: 1 |   numberOfInstances: 1 | ||||||
|   postgresql: |   postgresql: | ||||||
|     version: "10" |     version: "11" | ||||||
| # Make this a standby cluster and provide the s3 bucket path of source cluster for continuous streaming. | # Make this a standby cluster and provide the s3 bucket path of source cluster for continuous streaming. | ||||||
|   standby: |   standby: | ||||||
|     s3_wal_path: "s3://path/to/bucket/containing/wal/of/source/cluster/" |     s3_wal_path: "s3://path/to/bucket/containing/wal/of/source/cluster/" | ||||||
|  |  | ||||||
|  | @ -227,6 +227,10 @@ func (c *Cluster) Create() error { | ||||||
| 
 | 
 | ||||||
| 	c.setStatus(acidv1.ClusterStatusCreating) | 	c.setStatus(acidv1.ClusterStatusCreating) | ||||||
| 
 | 
 | ||||||
|  | 	if err = c.validateResources(&c.Spec); err != nil { | ||||||
|  | 		return fmt.Errorf("insufficient resource limits specified: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	for _, role := range []PostgresRole{Master, Replica} { | 	for _, role := range []PostgresRole{Master, Replica} { | ||||||
| 
 | 
 | ||||||
| 		if c.Endpoints[role] != nil { | 		if c.Endpoints[role] != nil { | ||||||
|  | @ -491,6 +495,44 @@ func compareResourcesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.Resourc | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Cluster) validateResources(spec *acidv1.PostgresSpec) error { | ||||||
|  | 
 | ||||||
|  | 	// setting limits too low can cause unnecessary evictions / OOM kills
 | ||||||
|  | 	const ( | ||||||
|  | 		cpuMinLimit    = "256m" | ||||||
|  | 		memoryMinLimit = "256Mi" | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var ( | ||||||
|  | 		isSmaller bool | ||||||
|  | 		err       error | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	cpuLimit := spec.Resources.ResourceLimits.CPU | ||||||
|  | 	if cpuLimit != "" { | ||||||
|  | 		isSmaller, err = util.IsSmallerQuantity(cpuLimit, cpuMinLimit) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error validating CPU limit: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if isSmaller { | ||||||
|  | 			return fmt.Errorf("defined CPU limit %s is below required minimum %s to properly run postgresql resource", cpuLimit, cpuMinLimit) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	memoryLimit := spec.Resources.ResourceLimits.Memory | ||||||
|  | 	if memoryLimit != "" { | ||||||
|  | 		isSmaller, err = util.IsSmallerQuantity(memoryLimit, memoryMinLimit) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error validating memory limit: %v", err) | ||||||
|  | 		} | ||||||
|  | 		if isSmaller { | ||||||
|  | 			return fmt.Errorf("defined memory limit %s is below required minimum %s to properly run postgresql resource", memoryLimit, memoryMinLimit) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
 | // Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
 | ||||||
| // (i.e. service) is treated as an error
 | // (i.e. service) is treated as an error
 | ||||||
| // logical backup cron jobs are an exception: a user-initiated Update can enable a logical backup job
 | // logical backup cron jobs are an exception: a user-initiated Update can enable a logical backup job
 | ||||||
|  | @ -501,6 +543,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
| 	defer c.mu.Unlock() | 	defer c.mu.Unlock() | ||||||
| 
 | 
 | ||||||
|  | 	oldStatus := c.Status | ||||||
| 	c.setStatus(acidv1.ClusterStatusUpdating) | 	c.setStatus(acidv1.ClusterStatusUpdating) | ||||||
| 	c.setSpec(newSpec) | 	c.setSpec(newSpec) | ||||||
| 
 | 
 | ||||||
|  | @ -512,6 +555,22 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error { | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
|  | 	if err := c.validateResources(&newSpec.Spec); err != nil { | ||||||
|  | 		err = fmt.Errorf("insufficient resource limits specified: %v", err) | ||||||
|  | 
 | ||||||
|  | 		// cancel update only when (already too low) pod resources were edited
 | ||||||
|  | 		// if cluster was successfully running before the update, continue but log a warning
 | ||||||
|  | 		isCPULimitSmaller, err2 := util.IsSmallerQuantity(newSpec.Spec.Resources.ResourceLimits.CPU, oldSpec.Spec.Resources.ResourceLimits.CPU) | ||||||
|  | 		isMemoryLimitSmaller, err3 := util.IsSmallerQuantity(newSpec.Spec.Resources.ResourceLimits.Memory, oldSpec.Spec.Resources.ResourceLimits.Memory) | ||||||
|  | 
 | ||||||
|  | 		if oldStatus.Running() && !isCPULimitSmaller && !isMemoryLimitSmaller && err2 == nil && err3 == nil { | ||||||
|  | 			c.logger.Warning(err) | ||||||
|  | 		} else { | ||||||
|  | 			updateFailed = true | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if oldSpec.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison
 | 	if oldSpec.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison
 | ||||||
| 		c.logger.Warningf("postgresql version change(%q -> %q) has no effect", oldSpec.Spec.PgVersion, newSpec.Spec.PgVersion) | 		c.logger.Warningf("postgresql version change(%q -> %q) has no effect", oldSpec.Spec.PgVersion, newSpec.Spec.PgVersion) | ||||||
| 		//we need that hack to generate statefulset with the old version
 | 		//we need that hack to generate statefulset with the old version
 | ||||||
|  |  | ||||||
|  | @ -741,7 +741,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef | ||||||
| 			limit = c.OpConfig.DefaultMemoryLimit | 			limit = c.OpConfig.DefaultMemoryLimit | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		isSmaller, err := util.RequestIsSmallerThanLimit(request, limit) | 		isSmaller, err := util.IsSmallerQuantity(request, limit) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -768,7 +768,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef | ||||||
| 				limit = c.OpConfig.DefaultMemoryLimit | 				limit = c.OpConfig.DefaultMemoryLimit | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			isSmaller, err := util.RequestIsSmallerThanLimit(sidecarRequest, sidecarLimit) | 			isSmaller, err := util.IsSmallerQuantity(sidecarRequest, sidecarLimit) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
| 	defer c.mu.Unlock() | 	defer c.mu.Unlock() | ||||||
| 
 | 
 | ||||||
|  | 	oldStatus := c.Status | ||||||
| 	c.setSpec(newSpec) | 	c.setSpec(newSpec) | ||||||
| 
 | 
 | ||||||
| 	defer func() { | 	defer func() { | ||||||
|  | @ -34,6 +35,16 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error { | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
|  | 	if err = c.validateResources(&c.Spec); err != nil { | ||||||
|  | 		err = fmt.Errorf("insufficient resource limits specified: %v", err) | ||||||
|  | 		if oldStatus.Running() { | ||||||
|  | 			c.logger.Warning(err) | ||||||
|  | 			err = nil | ||||||
|  | 		} else { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err = c.initUsers(); err != nil { | 	if err = c.initUsers(); err != nil { | ||||||
| 		err = fmt.Errorf("could not init users: %v", err) | 		err = fmt.Errorf("could not init users: %v", err) | ||||||
| 		return err | 		return err | ||||||
|  |  | ||||||
|  | @ -111,7 +111,7 @@ func (c *Controller) initOperatorConfig() { | ||||||
| 
 | 
 | ||||||
| 	if c.opConfig.SetMemoryRequestToLimit { | 	if c.opConfig.SetMemoryRequestToLimit { | ||||||
| 
 | 
 | ||||||
| 		isSmaller, err := util.RequestIsSmallerThanLimit(c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit) | 		isSmaller, err := util.IsSmallerQuantity(c.opConfig.DefaultMemoryRequest, c.opConfig.DefaultMemoryLimit) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			panic(err) | 			panic(err) | ||||||
| 		} | 		} | ||||||
|  | @ -120,7 +120,7 @@ func (c *Controller) initOperatorConfig() { | ||||||
| 			c.opConfig.DefaultMemoryRequest = c.opConfig.DefaultMemoryLimit | 			c.opConfig.DefaultMemoryRequest = c.opConfig.DefaultMemoryLimit | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		isSmaller, err = util.RequestIsSmallerThanLimit(c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit) | 		isSmaller, err = util.IsSmallerQuantity(c.opConfig.ScalyrMemoryRequest, c.opConfig.ScalyrMemoryLimit) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			panic(err) | 			panic(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -141,17 +141,17 @@ func Coalesce(val, defaultVal string) string { | ||||||
| 	return val | 	return val | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RequestIsSmallerThanLimit : ...
 | // IsSmallerQuantity : checks if first resource is of a smaller quantity than the second
 | ||||||
| func RequestIsSmallerThanLimit(requestStr, limitStr string) (bool, error) { | func IsSmallerQuantity(requestStr, limitStr string) (bool, error) { | ||||||
| 
 | 
 | ||||||
| 	request, err := resource.ParseQuantity(requestStr) | 	request, err := resource.ParseQuantity(requestStr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, fmt.Errorf("could not parse memory request %v : %v", requestStr, err) | 		return false, fmt.Errorf("could not parse request %v : %v", requestStr, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	limit, err2 := resource.ParseQuantity(limitStr) | 	limit, err2 := resource.ParseQuantity(limitStr) | ||||||
| 	if err2 != nil { | 	if err2 != nil { | ||||||
| 		return false, fmt.Errorf("could not parse memory limit %v : %v", limitStr, err2) | 		return false, fmt.Errorf("could not parse limit %v : %v", limitStr, err2) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return request.Cmp(limit) == -1, nil | 	return request.Cmp(limit) == -1, nil | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ var substringMatch = []struct { | ||||||
| 	{regexp.MustCompile(`aaaa (\d+) bbbb`), "aaaa 123 bbbb", nil}, | 	{regexp.MustCompile(`aaaa (\d+) bbbb`), "aaaa 123 bbbb", nil}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var requestIsSmallerThanLimitTests = []struct { | var requestIsSmallerQuantityTests = []struct { | ||||||
| 	request string | 	request string | ||||||
| 	limit   string | 	limit   string | ||||||
| 	out     bool | 	out     bool | ||||||
|  | @ -155,14 +155,14 @@ func TestMapContains(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestRequestIsSmallerThanLimit(t *testing.T) { | func TestIsSmallerQuantity(t *testing.T) { | ||||||
| 	for _, tt := range requestIsSmallerThanLimitTests { | 	for _, tt := range requestIsSmallerQuantityTests { | ||||||
| 		res, err := RequestIsSmallerThanLimit(tt.request, tt.limit) | 		res, err := IsSmallerQuantity(tt.request, tt.limit) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Errorf("RequestIsSmallerThanLimit returned unexpected error: %#v", err) | 			t.Errorf("IsSmallerQuantity returned unexpected error: %#v", err) | ||||||
| 		} | 		} | ||||||
| 		if res != tt.out { | 		if res != tt.out { | ||||||
| 			t.Errorf("RequestIsSmallerThanLimit expected: %#v, got: %#v", tt.out, res) | 			t.Errorf("IsSmallerQuantity expected: %#v, got: %#v", tt.out, res) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue