Merge branch 'master' into docs-backup-restore
This commit is contained in:
commit
bb474ec542
|
|
@ -395,6 +395,8 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
wal_s3_bucket:
|
wal_s3_bucket:
|
||||||
type: string
|
type: string
|
||||||
|
wal_az_storage_account:
|
||||||
|
type: string
|
||||||
logical_backup:
|
logical_backup:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
||||||
|
|
@ -561,6 +561,24 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
iops:
|
iops:
|
||||||
type: integer
|
type: integer
|
||||||
|
selector:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
matchLabels:
|
||||||
|
type: object
|
||||||
size:
|
size:
|
||||||
type: string
|
type: string
|
||||||
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,9 @@ configAwsOrGcp:
|
||||||
# GCS bucket to use for shipping WAL segments with WAL-E
|
# GCS bucket to use for shipping WAL segments with WAL-E
|
||||||
# wal_gs_bucket: ""
|
# wal_gs_bucket: ""
|
||||||
|
|
||||||
|
# Azure Storage Account to use for shipping WAL segments with WAL-G
|
||||||
|
# wal_az_storage_account: ""
|
||||||
|
|
||||||
# configure K8s cron job managed by the operator
|
# configure K8s cron job managed by the operator
|
||||||
configLogicalBackup:
|
configLogicalBackup:
|
||||||
# image for pods of the logical backup job (example runs pg_dumpall)
|
# image for pods of the logical backup job (example runs pg_dumpall)
|
||||||
|
|
|
||||||
|
|
@ -836,6 +836,63 @@ pod_environment_configmap: "postgres-operator-system/pod-env-overrides"
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Azure setup
|
||||||
|
|
||||||
|
To configure the operator on Azure these prerequisites are needed:
|
||||||
|
|
||||||
|
* A storage account in the same region as the Kubernetes cluster.
|
||||||
|
|
||||||
|
The configuration parameters that we will be using are:
|
||||||
|
|
||||||
|
* `pod_environment_secret`
|
||||||
|
* `wal_az_storage_account`
|
||||||
|
|
||||||
|
1. Generate the K8s secret resource that will contain your storage account's
|
||||||
|
access key. You will need a copy of this secret in every namespace you want to
|
||||||
|
create postgresql clusters.
|
||||||
|
|
||||||
|
The latest version of WAL-G (v1.0) supports the use of a SASS token, but you'll
|
||||||
|
have to make due with using the primary or secondary access token until the
|
||||||
|
version of WAL-G is updated in the postgres-operator.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: psql-backup-creds
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
AZURE_STORAGE_ACCESS_KEY: <primary or secondary access key>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Setup pod environment configmap that instructs the operator to use WAL-G,
|
||||||
|
instead of WAL-E, for backup and restore.
|
||||||
|
```yml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: pod-env-overrides
|
||||||
|
namespace: postgres-operator-system
|
||||||
|
data:
|
||||||
|
# Any env variable used by spilo can be added
|
||||||
|
USE_WALG_BACKUP: "true"
|
||||||
|
USE_WALG_RESTORE: "true"
|
||||||
|
CLONE_USE_WALG_RESTORE: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Setup your operator configuration values. With the `psql-backup-creds`
|
||||||
|
and `pod-env-overrides` resources applied to your cluster, ensure that the operator's configuration
|
||||||
|
is set up like the following:
|
||||||
|
```yml
|
||||||
|
...
|
||||||
|
aws_or_gcp:
|
||||||
|
pod_environment_secret: "pgsql-backup-creds"
|
||||||
|
pod_environment_configmap: "postgres-operator-system/pod-env-overrides"
|
||||||
|
wal_az_storage_account: "postgresbackupsbucket28302F2" # name of storage account to save the WAL-G logs
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
### Restoring physical backups
|
### Restoring physical backups
|
||||||
|
|
||||||
If cluster members have to be (re)initialized restoring physical backups
|
If cluster members have to be (re)initialized restoring physical backups
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,11 @@ properties of the persistent storage that stores Postgres data.
|
||||||
When running the operator on AWS the latest generation of EBS volumes (`gp3`)
|
When running the operator on AWS the latest generation of EBS volumes (`gp3`)
|
||||||
allows for configuring the throughput in MB/s. Maximum is 1000. Optional.
|
allows for configuring the throughput in MB/s. Maximum is 1000. Optional.
|
||||||
|
|
||||||
|
* **selector**
|
||||||
|
A label query over PVs to consider for binding. See the [Kubernetes
|
||||||
|
documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
|
||||||
|
for details on using `matchLabels` and `matchExpressions`. Optional
|
||||||
|
|
||||||
## Sidecar definitions
|
## Sidecar definitions
|
||||||
|
|
||||||
Those parameters are defined under the `sidecars` key. They consist of a list
|
Those parameters are defined under the `sidecars` key. They consist of a list
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,12 @@ yet officially supported.
|
||||||
[service accounts](https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform).
|
[service accounts](https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform).
|
||||||
The default is empty
|
The default is empty
|
||||||
|
|
||||||
|
* **wal_az_storage_account**
|
||||||
|
Azure Storage Account to use for shipping WAL segments with WAL-G. The
|
||||||
|
storage account must exist and be accessible by Postgres pods. Note, only the
|
||||||
|
name of the storage account is required.
|
||||||
|
The default is empty.
|
||||||
|
|
||||||
* **log_s3_bucket**
|
* **log_s3_bucket**
|
||||||
S3 bucket to use for shipping Postgres daily logs. Works only with S3 on AWS.
|
S3 bucket to use for shipping Postgres daily logs. Works only with S3 on AWS.
|
||||||
The bucket has to be present and accessible by Postgres pods. The default is
|
The bucket has to be present and accessible by Postgres pods. The default is
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,12 @@ spec:
|
||||||
# storageClass: my-sc
|
# storageClass: my-sc
|
||||||
# iops: 1000 # for EBS gp3
|
# iops: 1000 # for EBS gp3
|
||||||
# throughput: 250 # in MB/s for EBS gp3
|
# throughput: 250 # in MB/s for EBS gp3
|
||||||
|
# selector:
|
||||||
|
# matchExpressions:
|
||||||
|
# - { key: flavour, operator: In, values: [ "banana", "chocolate" ] }
|
||||||
|
# matchLabels:
|
||||||
|
# environment: dev
|
||||||
|
# service: postgres
|
||||||
additionalVolumes:
|
additionalVolumes:
|
||||||
- name: empty
|
- name: empty
|
||||||
mountPath: /opt/empty
|
mountPath: /opt/empty
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ data:
|
||||||
# team_api_role_configuration: "log_statement:all"
|
# team_api_role_configuration: "log_statement:all"
|
||||||
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
|
# teams_api_url: http://fake-teams-api.default.svc.cluster.local
|
||||||
# toleration: ""
|
# toleration: ""
|
||||||
|
# wal_az_storage_account: ""
|
||||||
# wal_gs_bucket: ""
|
# wal_gs_bucket: ""
|
||||||
# wal_s3_bucket: ""
|
# wal_s3_bucket: ""
|
||||||
watched_namespace: "*" # listen to all namespaces
|
watched_namespace: "*" # listen to all namespaces
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,8 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
log_s3_bucket:
|
log_s3_bucket:
|
||||||
type: string
|
type: string
|
||||||
|
wal_az_storage_account:
|
||||||
|
type: string
|
||||||
wal_gs_bucket:
|
wal_gs_bucket:
|
||||||
type: string
|
type: string
|
||||||
wal_s3_bucket:
|
wal_s3_bucket:
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@ configuration:
|
||||||
# gcp_credentials: ""
|
# gcp_credentials: ""
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
|
# wal_az_storage_account: ""
|
||||||
# wal_gs_bucket: ""
|
# wal_gs_bucket: ""
|
||||||
# wal_s3_bucket: ""
|
# wal_s3_bucket: ""
|
||||||
logical_backup:
|
logical_backup:
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,24 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
iops:
|
iops:
|
||||||
type: integer
|
type: integer
|
||||||
|
selector:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
matchLabels:
|
||||||
|
type: object
|
||||||
size:
|
size:
|
||||||
type: string
|
type: string
|
||||||
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
|
||||||
|
|
|
||||||
|
|
@ -841,6 +841,54 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
|
||||||
"iops": {
|
"iops": {
|
||||||
Type: "integer",
|
Type: "integer",
|
||||||
},
|
},
|
||||||
|
"selector": {
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextv1.JSONSchemaProps{
|
||||||
|
"matchExpressions": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &apiextv1.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextv1.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Required: []string{"key", "operator", "values"},
|
||||||
|
Properties: map[string]apiextv1.JSONSchemaProps{
|
||||||
|
"key": {
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
Type: "string",
|
||||||
|
Enum: []apiextv1.JSON{
|
||||||
|
{
|
||||||
|
Raw: []byte(`"In"`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Raw: []byte(`"NotIn"`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Raw: []byte(`"Exists"`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Raw: []byte(`"DoesNotExist"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"values": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &apiextv1.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextv1.JSONSchemaProps{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"matchLabels": {
|
||||||
|
Type: "object",
|
||||||
|
XPreserveUnknownFields: util.True(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"size": {
|
"size": {
|
||||||
Type: "string",
|
Type: "string",
|
||||||
Description: "Value must not be zero",
|
Description: "Value must not be zero",
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ type AWSGCPConfiguration struct {
|
||||||
AWSRegion string `json:"aws_region,omitempty"`
|
AWSRegion string `json:"aws_region,omitempty"`
|
||||||
WALGSBucket string `json:"wal_gs_bucket,omitempty"`
|
WALGSBucket string `json:"wal_gs_bucket,omitempty"`
|
||||||
GCPCredentials string `json:"gcp_credentials,omitempty"`
|
GCPCredentials string `json:"gcp_credentials,omitempty"`
|
||||||
|
WALAZStorageAccount string `json:"wal_az_storage_account,omitempty"`
|
||||||
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
|
LogS3Bucket string `json:"log_s3_bucket,omitempty"`
|
||||||
KubeIAMRole string `json:"kube_iam_role,omitempty"`
|
KubeIAMRole string `json:"kube_iam_role,omitempty"`
|
||||||
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ type MaintenanceWindow struct {
|
||||||
|
|
||||||
// Volume describes a single volume in the manifest.
|
// Volume describes a single volume in the manifest.
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
|
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||||
Size string `json:"size"`
|
Size string `json:"size"`
|
||||||
StorageClass string `json:"storageClass,omitempty"`
|
StorageClass string `json:"storageClass,omitempty"`
|
||||||
SubPath string `json:"subPath,omitempty"`
|
SubPath string `json:"subPath,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ package v1
|
||||||
import (
|
import (
|
||||||
config "github.com/zalando/postgres-operator/pkg/util/config"
|
config "github.com/zalando/postgres-operator/pkg/util/config"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -314,22 +315,6 @@ func (in *MaintenanceWindow) DeepCopy() *MaintenanceWindow {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *MajorVersionUpgradeConfiguration) DeepCopyInto(out *MajorVersionUpgradeConfiguration) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MajorVersionUpgradeConfiguration.
|
|
||||||
func (in *MajorVersionUpgradeConfiguration) DeepCopy() *MajorVersionUpgradeConfiguration {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(MajorVersionUpgradeConfiguration)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OperatorConfiguration) DeepCopyInto(out *OperatorConfiguration) {
|
func (in *OperatorConfiguration) DeepCopyInto(out *OperatorConfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
@ -385,7 +370,6 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.PostgresUsersConfiguration = in.PostgresUsersConfiguration
|
out.PostgresUsersConfiguration = in.PostgresUsersConfiguration
|
||||||
out.MajorVersionUpgrade = in.MajorVersionUpgrade
|
|
||||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||||
out.PostgresPodResources = in.PostgresPodResources
|
out.PostgresPodResources = in.PostgresPodResources
|
||||||
out.Timeouts = in.Timeouts
|
out.Timeouts = in.Timeouts
|
||||||
|
|
@ -1197,6 +1181,11 @@ func (in UserFlags) DeepCopy() UserFlags {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Volume) DeepCopyInto(out *Volume) {
|
func (in *Volume) DeepCopyInto(out *Volume) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Selector != nil {
|
||||||
|
in, out := &in.Selector, &out.Selector
|
||||||
|
*out = new(metav1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.Iops != nil {
|
if in.Iops != nil {
|
||||||
in, out := &in.Iops, &out.Iops
|
in, out := &in.Iops, &out.Iops
|
||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,8 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tolerationsSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
|
||||||
|
|
||||||
podTemplate := &v1.PodTemplateSpec{
|
podTemplate := &v1.PodTemplateSpec{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Labels: c.connectionPoolerLabels(role, true).MatchLabels,
|
Labels: c.connectionPoolerLabels(role, true).MatchLabels,
|
||||||
|
|
@ -294,12 +296,18 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
Spec: v1.PodSpec{
|
Spec: v1.PodSpec{
|
||||||
TerminationGracePeriodSeconds: &gracePeriod,
|
TerminationGracePeriodSeconds: &gracePeriod,
|
||||||
Containers: []v1.Container{poolerContainer},
|
Containers: []v1.Container{poolerContainer},
|
||||||
// TODO: add tolerations to scheduler pooler on the same node
|
Tolerations: tolerationsSpec,
|
||||||
// as database
|
|
||||||
//Tolerations: *tolerationsSpec,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeAffinity := nodeAffinity(c.OpConfig.NodeReadinessLabel, spec.NodeAffinity)
|
||||||
|
if c.OpConfig.EnablePodAntiAffinity {
|
||||||
|
labelsSet := labels.Set(c.connectionPoolerLabels(role, false).MatchLabels)
|
||||||
|
podTemplate.Spec.Affinity = generatePodAffinity(labelsSet, c.OpConfig.PodAntiAffinityTopologyKey, nodeAffinity)
|
||||||
|
} else if nodeAffinity != nil {
|
||||||
|
podTemplate.Spec.Affinity = nodeAffinity
|
||||||
|
}
|
||||||
|
|
||||||
return podTemplate, nil
|
return podTemplate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -798,6 +798,12 @@ func (c *Cluster) generateSpiloPodEnvVars(uid types.UID, spiloConfiguration stri
|
||||||
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
|
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.OpConfig.WALAZStorageAccount != "" {
|
||||||
|
envVars = append(envVars, v1.EnvVar{Name: "AZURE_STORAGE_ACCOUNT", Value: c.OpConfig.WALAZStorageAccount})
|
||||||
|
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_SUFFIX", Value: getBucketScopeSuffix(string(uid))})
|
||||||
|
envVars = append(envVars, v1.EnvVar{Name: "WAL_BUCKET_SCOPE_PREFIX", Value: ""})
|
||||||
|
}
|
||||||
|
|
||||||
if c.OpConfig.GCPCredentials != "" {
|
if c.OpConfig.GCPCredentials != "" {
|
||||||
envVars = append(envVars, v1.EnvVar{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: c.OpConfig.GCPCredentials})
|
envVars = append(envVars, v1.EnvVar{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: c.OpConfig.GCPCredentials})
|
||||||
}
|
}
|
||||||
|
|
@ -1170,9 +1176,6 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the spilo container
|
// generate the spilo container
|
||||||
c.logger.Debugf("Generating Spilo container, environment variables")
|
|
||||||
c.logger.Debugf("%v", spiloEnvVars)
|
|
||||||
|
|
||||||
spiloContainer := generateContainer(constants.PostgresContainerName,
|
spiloContainer := generateContainer(constants.PostgresContainerName,
|
||||||
&effectiveDockerImage,
|
&effectiveDockerImage,
|
||||||
resourceRequirements,
|
resourceRequirements,
|
||||||
|
|
@ -1275,7 +1278,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||||
}
|
}
|
||||||
|
|
||||||
if volumeClaimTemplate, err = generatePersistentVolumeClaimTemplate(spec.Volume.Size,
|
if volumeClaimTemplate, err = generatePersistentVolumeClaimTemplate(spec.Volume.Size,
|
||||||
spec.Volume.StorageClass); err != nil {
|
spec.Volume.StorageClass, spec.Volume.Selector); err != nil {
|
||||||
return nil, fmt.Errorf("could not generate volume claim template: %v", err)
|
return nil, fmt.Errorf("could not generate volume claim template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1523,7 +1526,8 @@ func (c *Cluster) addAdditionalVolumes(podSpec *v1.PodSpec,
|
||||||
podSpec.Volumes = volumes
|
podSpec.Volumes = volumes
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePersistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) (*v1.PersistentVolumeClaim, error) {
|
func generatePersistentVolumeClaimTemplate(volumeSize, volumeStorageClass string,
|
||||||
|
volumeSelector *metav1.LabelSelector) (*v1.PersistentVolumeClaim, error) {
|
||||||
|
|
||||||
var storageClassName *string
|
var storageClassName *string
|
||||||
|
|
||||||
|
|
@ -1556,6 +1560,7 @@ func generatePersistentVolumeClaimTemplate(volumeSize, volumeStorageClass string
|
||||||
},
|
},
|
||||||
StorageClassName: storageClassName,
|
StorageClassName: storageClassName,
|
||||||
VolumeMode: &volumeMode,
|
VolumeMode: &volumeMode,
|
||||||
|
Selector: volumeSelector,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1806,6 +1811,14 @@ func (c *Cluster) generateCloneEnvironment(description *acidv1.CloneDescription)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
result = append(result, envs...)
|
result = append(result, envs...)
|
||||||
|
} else if c.OpConfig.WALAZStorageAccount != "" {
|
||||||
|
envs := []v1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: "CLONE_AZURE_STORAGE_ACCOUNT",
|
||||||
|
Value: c.OpConfig.WALAZStorageAccount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result = append(result, envs...)
|
||||||
} else {
|
} else {
|
||||||
c.logger.Error("Cannot figure out S3 or GS bucket. Both are empty.")
|
c.logger.Error("Cannot figure out S3 or GS bucket. Both are empty.")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1509,3 +1509,106 @@ func TestGenerateCapabilities(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVolumeSelector(t *testing.T) {
|
||||||
|
testName := "TestVolumeSelector"
|
||||||
|
makeSpec := func(volume acidv1.Volume) acidv1.PostgresSpec {
|
||||||
|
return acidv1.PostgresSpec{
|
||||||
|
TeamID: "myapp",
|
||||||
|
NumberOfInstances: 0,
|
||||||
|
Resources: acidv1.Resources{
|
||||||
|
ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "10"},
|
||||||
|
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "10"},
|
||||||
|
},
|
||||||
|
Volume: volume,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
subTest string
|
||||||
|
volume acidv1.Volume
|
||||||
|
wantSelector *metav1.LabelSelector
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
subTest: "PVC template has no selector",
|
||||||
|
volume: acidv1.Volume{
|
||||||
|
Size: "1G",
|
||||||
|
},
|
||||||
|
wantSelector: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subTest: "PVC template has simple label selector",
|
||||||
|
volume: acidv1.Volume{
|
||||||
|
Size: "1G",
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"environment": "unittest"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantSelector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"environment": "unittest"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subTest: "PVC template has full selector",
|
||||||
|
volume: acidv1.Volume{
|
||||||
|
Size: "1G",
|
||||||
|
Selector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"environment": "unittest"},
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "flavour",
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
Values: []string{"banana", "chocolate"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantSelector: &metav1.LabelSelector{
|
||||||
|
MatchLabels: map[string]string{"environment": "unittest"},
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "flavour",
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
Values: []string{"banana", "chocolate"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := New(
|
||||||
|
Config{
|
||||||
|
OpConfig: config.Config{
|
||||||
|
PodManagementPolicy: "ordered_ready",
|
||||||
|
ProtectedRoles: []string{"admin"},
|
||||||
|
Auth: config.Auth{
|
||||||
|
SuperUsername: superUserName,
|
||||||
|
ReplicationUsername: replicationUserName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
pgSpec := makeSpec(tt.volume)
|
||||||
|
sts, err := cluster.generateStatefulSet(&pgSpec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s %s: no statefulset created %v", testName, tt.subTest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volIdx := len(sts.Spec.VolumeClaimTemplates)
|
||||||
|
for i, ct := range sts.Spec.VolumeClaimTemplates {
|
||||||
|
if ct.ObjectMeta.Name == constants.DataVolumeName {
|
||||||
|
volIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if volIdx == len(sts.Spec.VolumeClaimTemplates) {
|
||||||
|
t.Errorf("%s %s: no datavolume found in sts", testName, tt.subTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
selector := sts.Spec.VolumeClaimTemplates[volIdx].Spec.Selector
|
||||||
|
if !reflect.DeepEqual(selector, tt.wantSelector) {
|
||||||
|
t.Errorf("%s %s: expected: %#v but got: %#v", testName, tt.subTest, tt.wantSelector, selector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
|
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
|
||||||
result.WALGSBucket = fromCRD.AWSGCP.WALGSBucket
|
result.WALGSBucket = fromCRD.AWSGCP.WALGSBucket
|
||||||
result.GCPCredentials = fromCRD.AWSGCP.GCPCredentials
|
result.GCPCredentials = fromCRD.AWSGCP.GCPCredentials
|
||||||
|
result.WALAZStorageAccount = fromCRD.AWSGCP.WALAZStorageAccount
|
||||||
result.AdditionalSecretMount = fromCRD.AWSGCP.AdditionalSecretMount
|
result.AdditionalSecretMount = fromCRD.AWSGCP.AdditionalSecretMount
|
||||||
result.AdditionalSecretMountPath = util.Coalesce(fromCRD.AWSGCP.AdditionalSecretMountPath, "/meta/credentials")
|
result.AdditionalSecretMountPath = util.Coalesce(fromCRD.AWSGCP.AdditionalSecretMountPath, "/meta/credentials")
|
||||||
result.EnableEBSGp3Migration = fromCRD.AWSGCP.EnableEBSGp3Migration
|
result.EnableEBSGp3Migration = fromCRD.AWSGCP.EnableEBSGp3Migration
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ type Config struct {
|
||||||
KubeIAMRole string `name:"kube_iam_role"`
|
KubeIAMRole string `name:"kube_iam_role"`
|
||||||
WALGSBucket string `name:"wal_gs_bucket"`
|
WALGSBucket string `name:"wal_gs_bucket"`
|
||||||
GCPCredentials string `name:"gcp_credentials"`
|
GCPCredentials string `name:"gcp_credentials"`
|
||||||
|
WALAZStorageAccount string `name:"wal_az_storage_account"`
|
||||||
AdditionalSecretMount string `name:"additional_secret_mount"`
|
AdditionalSecretMount string `name:"additional_secret_mount"`
|
||||||
AdditionalSecretMountPath string `name:"additional_secret_mount_path" default:"/meta/credentials"`
|
AdditionalSecretMountPath string `name:"additional_secret_mount_path" default:"/meta/credentials"`
|
||||||
EnableEBSGp3Migration bool `name:"enable_ebs_gp3_migration" default:"false"`
|
EnableEBSGp3Migration bool `name:"enable_ebs_gp3_migration" default:"false"`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue