Merge branch 'master' into feat/configurable-pooler-securitycontext
This commit is contained in:
commit
3e93fae514
12
Makefile
12
Makefile
|
|
@ -29,16 +29,19 @@ PKG := `go list ./... | grep -v /vendor/`
|
||||||
|
|
||||||
ifeq ($(DEBUG),1)
|
ifeq ($(DEBUG),1)
|
||||||
DOCKERFILE = DebugDockerfile
|
DOCKERFILE = DebugDockerfile
|
||||||
DEBUG_POSTFIX := -debug-$(shell date hhmmss)
|
DEBUG_POSTFIX := -debug-$(shell date +"%H%M%S")
|
||||||
BUILD_FLAGS += -gcflags "-N -l"
|
BUILD_FLAGS += -gcflags "-N -l"
|
||||||
else
|
else
|
||||||
DOCKERFILE = Dockerfile
|
DOCKERFILE = Dockerfile
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
ifeq ($(FRESH),1)
|
ifeq ($(FRESH),1)
|
||||||
DEBUG_FRESH=$(shell date +"%H-%M-%S")
|
DEBUG_FRESH=$(shell date +"%H-%M-%S")
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
SED := $(shell command -v gsed 2>/dev/null || command -v sed)
|
||||||
|
|
||||||
ifdef CDP_PULL_REQUEST_NUMBER
|
ifdef CDP_PULL_REQUEST_NUMBER
|
||||||
CDP_TAG := -${CDP_BUILD_VERSION}
|
CDP_TAG := -${CDP_BUILD_VERSION}
|
||||||
endif
|
endif
|
||||||
|
|
@ -69,14 +72,17 @@ $(GENERATED_CRDS): $(GENERATED)
|
||||||
go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests
|
go tool controller-gen crd:crdVersions=v1,allowDangerousTypes=true paths=./pkg/apis/acid.zalan.do/... output:crd:dir=manifests
|
||||||
@mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml
|
@mv manifests/acid.zalan.do_postgresqls.yaml manifests/postgresql.crd.yaml
|
||||||
@# hack to use lowercase kind and listKind
|
@# hack to use lowercase kind and listKind
|
||||||
@sed -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml
|
@$(SED) -i -e 's/kind: Postgresql/kind: postgresql/' manifests/postgresql.crd.yaml
|
||||||
@sed -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
|
@$(SED) -i -e 's/listKind: PostgresqlList/listKind: postgresqlList/' manifests/postgresql.crd.yaml
|
||||||
@hack/adjust_postgresql_crd.sh
|
@hack/adjust_postgresql_crd.sh
|
||||||
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
|
@mv manifests/acid.zalan.do_operatorconfigurations.yaml manifests/operatorconfiguration.crd.yaml
|
||||||
@hack/adjust_operatorconfiguration_crd.sh
|
@hack/adjust_operatorconfiguration_crd.sh
|
||||||
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
|
@mv manifests/acid.zalan.do_postgresteams.yaml manifests/postgresteam.crd.yaml
|
||||||
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
@cp manifests/postgresql.crd.yaml pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml
|
||||||
|
@cp manifests/postgresql.crd.yaml charts/postgres-operator/crds/postgresqls.yaml
|
||||||
@cp manifests/operatorconfiguration.crd.yaml pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml
|
@cp manifests/operatorconfiguration.crd.yaml pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml
|
||||||
|
@cp manifests/operatorconfiguration.crd.yaml charts/postgres-operator/crds/operatorconfigurations.yaml
|
||||||
|
@cp manifests/postgresteam.crd.yaml charts/postgres-operator/crds/postgresteams.yaml
|
||||||
|
|
||||||
local: ${SOURCES} $(GENERATED_CRDS)
|
local: ${SOURCES} $(GENERATED_CRDS)
|
||||||
CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY} $(LOCAL_BUILD_FLAGS) -ldflags "$(LDFLAGS)" $(SOURCES)
|
CGO_ENABLED=${CGO_ENABLED} go build -o build/${BINARY} $(LOCAL_BUILD_FLAGS) -ldflags "$(LDFLAGS)" $(SOURCES)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
---
|
||||||
apiVersion: apiextensions.k8s.io/v1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: operatorconfigurations.acid.zalan.do
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.17.3
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: postgres-operator
|
app.kubernetes.io/name: postgres-operator
|
||||||
|
name: operatorconfigurations.acid.zalan.do
|
||||||
spec:
|
spec:
|
||||||
group: acid.zalan.do
|
group: acid.zalan.do
|
||||||
names:
|
names:
|
||||||
|
|
@ -70,6 +73,8 @@ spec:
|
||||||
type: integer
|
type: integer
|
||||||
gcp_credentials:
|
gcp_credentials:
|
||||||
type: string
|
type: string
|
||||||
|
irsa_role_arn:
|
||||||
|
type: string
|
||||||
kube_iam_role:
|
kube_iam_role:
|
||||||
type: string
|
type: string
|
||||||
log_s3_bucket:
|
log_s3_bucket:
|
||||||
|
|
@ -450,8 +455,7 @@ spec:
|
||||||
default: 20m
|
default: 20m
|
||||||
description: timeout for successful migration of master pods from
|
description: timeout for successful migration of master pods from
|
||||||
unschedulable node
|
unschedulable node
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
node_readiness_label:
|
node_readiness_label:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -524,8 +528,7 @@ spec:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: Postgres pods are terminated forcefully after this
|
description: Postgres pods are terminated forcefully after this
|
||||||
timeout
|
timeout
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
secret_name_template:
|
secret_name_template:
|
||||||
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
||||||
description: |-
|
description: |-
|
||||||
|
|
@ -785,13 +788,11 @@ spec:
|
||||||
repair_period:
|
repair_period:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: period between consecutive repair requests
|
description: period between consecutive repair requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resync_period:
|
resync_period:
|
||||||
default: 30m
|
default: 30m
|
||||||
description: period between consecutive sync requests
|
description: period between consecutive sync requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
scalyr:
|
scalyr:
|
||||||
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -857,7 +858,9 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
protected_role_names:
|
protected_role_names:
|
||||||
default: '["admin", "cron_admin"]'
|
default:
|
||||||
|
- admin
|
||||||
|
- cron_admin
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
|
@ -885,48 +888,40 @@ spec:
|
||||||
default: 1s
|
default: 1s
|
||||||
description: interval between consecutive attempts of operator
|
description: interval between consecutive attempts of operator
|
||||||
calling the Patroni API
|
calling the Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
patroni_api_check_timeout:
|
patroni_api_check_timeout:
|
||||||
default: 5s
|
default: 5s
|
||||||
description: timeout when waiting for successful response from
|
description: timeout when waiting for successful response from
|
||||||
Patroni API
|
Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_deletion_wait_timeout:
|
pod_deletion_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the Postgres pods to be
|
description: timeout when waiting for the Postgres pods to be
|
||||||
deleted
|
deleted
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_label_wait_timeout:
|
pod_label_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for pod role and cluster labels
|
description: timeout when waiting for pod role and cluster labels
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_interval:
|
ready_wait_interval:
|
||||||
default: 4s
|
default: 4s
|
||||||
description: interval between consecutive attempts waiting for
|
description: interval between consecutive attempts waiting for
|
||||||
postgresql CRD to be created
|
postgresql CRD to be created
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_timeout:
|
ready_wait_timeout:
|
||||||
default: 30s
|
default: 30s
|
||||||
description: timeout for the complete postgres CRD creation
|
description: timeout for the complete postgres CRD creation
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_interval:
|
resource_check_interval:
|
||||||
default: 3s
|
default: 3s
|
||||||
description: interval to wait between consecutive attempts to
|
description: interval to wait between consecutive attempts to
|
||||||
check for some K8s resources
|
check for some K8s resources
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_timeout:
|
resource_check_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the presence of a certain
|
description: timeout when waiting for the presence of a certain
|
||||||
K8s resource
|
K8s resource
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
type: object
|
type: object
|
||||||
users:
|
users:
|
||||||
description: PostgresUsersConfiguration defines the system users of
|
description: PostgresUsersConfiguration defines the system users of
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,70 +1,84 @@
|
||||||
|
---
|
||||||
apiVersion: apiextensions.k8s.io/v1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: postgresteams.acid.zalan.do
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.17.3
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: postgres-operator
|
app.kubernetes.io/name: postgres-operator
|
||||||
|
name: postgresteams.acid.zalan.do
|
||||||
spec:
|
spec:
|
||||||
group: acid.zalan.do
|
group: acid.zalan.do
|
||||||
names:
|
names:
|
||||||
|
categories:
|
||||||
|
- all
|
||||||
kind: PostgresTeam
|
kind: PostgresTeam
|
||||||
listKind: PostgresTeamList
|
listKind: PostgresTeamList
|
||||||
plural: postgresteams
|
plural: postgresteams
|
||||||
singular: postgresteam
|
|
||||||
shortNames:
|
shortNames:
|
||||||
- pgteam
|
- pgteam
|
||||||
categories:
|
singular: postgresteam
|
||||||
- all
|
|
||||||
scope: Namespaced
|
scope: Namespaced
|
||||||
versions:
|
versions:
|
||||||
- name: v1
|
- name: v1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
description: PostgresTeam defines Custom Resource Definition Object for team
|
||||||
|
management.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: |-
|
||||||
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: |-
|
||||||
|
Kind is a string value representing the REST resource this object represents.
|
||||||
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
spec:
|
||||||
|
description: PostgresTeamSpec defines the specification for the PostgresTeam
|
||||||
|
TPR.
|
||||||
|
properties:
|
||||||
|
additionalMembers:
|
||||||
|
additionalProperties:
|
||||||
|
description: List of users who will also be added to the Postgres
|
||||||
|
cluster.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
description: Map for teamId and associated additional users
|
||||||
|
type: object
|
||||||
|
additionalSuperuserTeams:
|
||||||
|
additionalProperties:
|
||||||
|
description: List of teams to become Postgres superusers
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
description: Map for teamId and associated additional superuser teams
|
||||||
|
type: object
|
||||||
|
additionalTeams:
|
||||||
|
additionalProperties:
|
||||||
|
description: List of teams whose members will also be added to the
|
||||||
|
Postgres cluster.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
description: Map for teamId and associated additional teams
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- metadata
|
||||||
|
- spec
|
||||||
|
type: object
|
||||||
served: true
|
served: true
|
||||||
storage: true
|
storage: true
|
||||||
subresources:
|
subresources:
|
||||||
status: {}
|
status: {}
|
||||||
schema:
|
|
||||||
openAPIV3Schema:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- kind
|
|
||||||
- apiVersion
|
|
||||||
- spec
|
|
||||||
properties:
|
|
||||||
kind:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- PostgresTeam
|
|
||||||
apiVersion:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- acid.zalan.do/v1
|
|
||||||
spec:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
additionalSuperuserTeams:
|
|
||||||
type: object
|
|
||||||
description: "Map for teamId and associated additional superuser teams"
|
|
||||||
additionalProperties:
|
|
||||||
type: array
|
|
||||||
nullable: true
|
|
||||||
description: "List of teams to become Postgres superusers"
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
additionalTeams:
|
|
||||||
type: object
|
|
||||||
description: "Map for teamId and associated additional teams"
|
|
||||||
additionalProperties:
|
|
||||||
type: array
|
|
||||||
nullable: true
|
|
||||||
description: "List of teams whose members will also be added to the Postgres cluster"
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
additionalMembers:
|
|
||||||
type: object
|
|
||||||
description: "Map for teamId and associated additional users"
|
|
||||||
additionalProperties:
|
|
||||||
type: array
|
|
||||||
nullable: true
|
|
||||||
description: "List of users who will also be added to the Postgres cluster"
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,7 @@ rules:
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
- create
|
- create
|
||||||
|
- update
|
||||||
# to create role bindings to the postgres-pod service account
|
# to create role bindings to the postgres-pod service account
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- rbac.authorization.k8s.io
|
- rbac.authorization.k8s.io
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ configGeneral:
|
||||||
# ignore_resources_limits_annotation_key: ""
|
# ignore_resources_limits_annotation_key: ""
|
||||||
|
|
||||||
# Select if setup uses endpoints (default), or configmaps to manage leader (DCS=k8s)
|
# Select if setup uses endpoints (default), or configmaps to manage leader (DCS=k8s)
|
||||||
# kubernetes_use_configmaps: false
|
kubernetes_use_configmaps: true
|
||||||
|
|
||||||
# maintenance windows applied to all Postgres clusters unless overridden in the manifest
|
# maintenance windows applied to all Postgres clusters unless overridden in the manifest
|
||||||
# maintenance_windows:
|
# maintenance_windows:
|
||||||
|
|
@ -360,6 +360,8 @@ configAwsOrGcp:
|
||||||
|
|
||||||
# AWS IAM role to supply in the iam.amazonaws.com/role annotation of Postgres pods
|
# AWS IAM role to supply in the iam.amazonaws.com/role annotation of Postgres pods
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
|
# Full ARN for IRSA (IAM Roles for Service Accounts) on EKS
|
||||||
|
# irsa_role_arn: ""
|
||||||
|
|
||||||
# S3 bucket to use for shipping postgres daily logs
|
# S3 bucket to use for shipping postgres daily logs
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
|
|
@ -536,7 +538,6 @@ podPriorityClassName:
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 500m
|
|
||||||
memory: 500Mi
|
memory: 500Mi
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
|
|
|
||||||
|
|
@ -1094,6 +1094,32 @@ configuration:
|
||||||
wal_s3_bucket: your-backup-path
|
wal_s3_bucket: your-backup-path
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, if your cluster uses EKS with OIDC, you can use
|
||||||
|
[IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
|
||||||
|
(IAM Roles for Service Accounts) instead of kube2iam. Set `irsa_role_arn` to
|
||||||
|
the full ARN of the IAM role:
|
||||||
|
|
||||||
|
**OperatorConfiguration**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "acid.zalan.do/v1"
|
||||||
|
kind: OperatorConfiguration
|
||||||
|
metadata:
|
||||||
|
name: postgresql-operator-configuration
|
||||||
|
configuration:
|
||||||
|
aws_or_gcp:
|
||||||
|
aws_region: eu-central-1
|
||||||
|
irsa_role_arn: arn:aws:iam::123456789012:role/postgres-pod-role
|
||||||
|
wal_s3_bucket: your-backup-path
|
||||||
|
```
|
||||||
|
|
||||||
|
When `irsa_role_arn` is set the operator annotates the pod service account with
|
||||||
|
`eks.amazonaws.com/role-arn` on every reconcile. The EKS OIDC webhook then
|
||||||
|
injects an AWS web identity token into each pod, which takes precedence over
|
||||||
|
the EC2 metadata credentials used by kube2iam. Both `kube_iam_role` and
|
||||||
|
`irsa_role_arn` can coexist during a migration — existing pods retain the
|
||||||
|
kube2iam annotation until they are rotated, at which point only IRSA is used.
|
||||||
|
|
||||||
The referenced IAM role should contain the following privileges to make sure
|
The referenced IAM role should contain the following privileges to make sure
|
||||||
Postgres can send compressed WAL files to the given S3 bucket:
|
Postgres can send compressed WAL files to the given S3 bucket:
|
||||||
|
|
||||||
|
|
@ -1204,6 +1230,7 @@ aws_or_gcp:
|
||||||
# additional_secret_mount_path: ""
|
# additional_secret_mount_path: ""
|
||||||
# aws_region: eu-central-1
|
# aws_region: eu-central-1
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
|
# irsa_role_arn: ""
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
# wal_s3_bucket: ""
|
# wal_s3_bucket: ""
|
||||||
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
||||||
|
|
@ -1253,6 +1280,7 @@ aws_or_gcp:
|
||||||
additional_secret_mount_path: "/var/secrets/google" # or where ever you want to mount the file
|
additional_secret_mount_path: "/var/secrets/google" # or where ever you want to mount the file
|
||||||
# aws_region: eu-central-1
|
# aws_region: eu-central-1
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
|
# irsa_role_arn: ""
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
# wal_s3_bucket: ""
|
# wal_s3_bucket: ""
|
||||||
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
wal_gs_bucket: "postgres-backups-bucket-28302F2" # name of bucket on where to save the WAL-E logs
|
||||||
|
|
|
||||||
|
|
@ -27,20 +27,11 @@ git clone https://github.com/zalando/postgres-operator.git
|
||||||
|
|
||||||
## Building the operator
|
## Building the operator
|
||||||
|
|
||||||
We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling
|
We use [Go Modules](https://github.com/golang/go/wiki/Modules) for handling dependencies.
|
||||||
dependencies. When using Go below v1.13 you need to explicitly enable Go modules
|
Run `go mod vendor && go mod tidy` to install them.
|
||||||
by setting the `GO111MODULE` environment variable to `on`. The make targets do
|
|
||||||
this for you, so simply run
|
|
||||||
|
|
||||||
```bash
|
Build the operator with the `make docker` command. You may define the TAG variable to assign an explicit tag to your Docker image and the IMAGE to set the image name. By default, the tag is computed with
|
||||||
make
|
`git describe --tags --always --dirty` and the image is `ghcr.io/zalando/postgres-operator`. On macos search and replace `sed -i` commands with `sed -i ''` for the make commands to work.
|
||||||
```
|
|
||||||
|
|
||||||
Build the operator with the `make docker` command. You may define the TAG
|
|
||||||
variable to assign an explicit tag to your Docker image and the IMAGE to set
|
|
||||||
the image name. By default, the tag is computed with
|
|
||||||
`git describe --tags --always --dirty` and the image is
|
|
||||||
`ghcr.io/zalando/postgres-operator`
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export TAG=$(git describe --tags --always --dirty)
|
export TAG=$(git describe --tags --always --dirty)
|
||||||
|
|
@ -296,8 +287,7 @@ Please run flake8 [before submitting a PR](http://flake8.pycqa.org/en/latest/use
|
||||||
In the case you want to add functionality to the operator that shall be
|
In the case you want to add functionality to the operator that shall be
|
||||||
controlled via the operator configuration there are a few places that need to
|
controlled via the operator configuration there are a few places that need to
|
||||||
be updated. As explained [here](reference/operator_parameters.md), it's possible
|
be updated. As explained [here](reference/operator_parameters.md), it's possible
|
||||||
to configure the operator either with a ConfigMap or CRD, but currently we aim
|
to configure the operator either with a ConfigMap or CRD.
|
||||||
to synchronize parameters everywhere.
|
|
||||||
|
|
||||||
When choosing a parameter name for a new option in a Postgres cluster manifest,
|
When choosing a parameter name for a new option in a Postgres cluster manifest,
|
||||||
keep in mind the naming conventions there. We use `camelCase` for manifest
|
keep in mind the naming conventions there. We use `camelCase` for manifest
|
||||||
|
|
@ -320,32 +310,28 @@ manifest files:
|
||||||
|
|
||||||
Postgres manifest parameters are defined in the [api package](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/postgresql_type.go).
|
Postgres manifest parameters are defined in the [api package](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/postgresql_type.go).
|
||||||
The operator behavior has to be implemented at least in [k8sres.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres.go).
|
The operator behavior has to be implemented at least in [k8sres.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres.go).
|
||||||
Validation of CRD parameters is controlled in [crds.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/crds.go).
|
|
||||||
Please, reflect your changes in tests, for example in:
|
Please, reflect your changes in tests, for example in:
|
||||||
|
|
||||||
* [config_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/util/config/config_test.go)
|
* [config_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/util/config/config_test.go)
|
||||||
* [k8sres_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres_test.go)
|
* [k8sres_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/cluster/k8sres_test.go)
|
||||||
* [util_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/util_test.go)
|
* [util_test.go](https://github.com/zalando/postgres-operator/blob/master/pkg/apis/acid.zalan.do/v1/util_test.go)
|
||||||
|
|
||||||
### Updating manifest files
|
### Generating the CRDs
|
||||||
|
|
||||||
For the CRD-based configuration, please update the following files:
|
The CRDs can be automatically generated from the go structs. Use the correct kubebuilder annotations for defining the validation, constraints or default values etc.. Run `make` to update the CRDs which are stored in three locations:
|
||||||
|
- In the Go api package
|
||||||
* the default [OperatorConfiguration](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml)
|
- The example manifests folder
|
||||||
* the CRD's [validation](https://github.com/zalando/postgres-operator/blob/master/manifests/operatorconfiguration.crd.yaml)
|
- The helm chart folder
|
||||||
* the CRD's validation in the [Helm chart](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/crds/operatorconfigurations.yaml)
|
|
||||||
|
|
||||||
Add new options also to the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml) file.
|
|
||||||
It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap.
|
|
||||||
Last but no least, update the [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest example as well.
|
|
||||||
|
|
||||||
### Updating documentation
|
### Updating documentation
|
||||||
|
|
||||||
Finally, add a section for each new configuration option and/or cluster manifest
|
Config changes need to be reflected in the Helm chart's [values file](https://github.com/zalando/postgres-operator/blob/master/charts/postgres-operator/values.yaml), too. It follows the OperatorConfiguration CRD layout. Nested values will be flattened for the ConfigMap.
|
||||||
|
|
||||||
|
Add a section for each new configuration option and/or cluster manifest
|
||||||
parameter in the reference documents:
|
parameter in the reference documents:
|
||||||
|
|
||||||
* [config reference](reference/operator_parameters.md)
|
* [config reference](reference/operator_parameters.md)
|
||||||
* [manifest reference](reference/cluster_manifest.md)
|
* [manifest reference](reference/cluster_manifest.md)
|
||||||
|
|
||||||
It also helps users to explain new features with examples in the
|
It can also help other K8s admins to explain new features with examples in the
|
||||||
[administrator docs](administrator.md).
|
[administrator docs](administrator.md) and also update the [OperatorConfiguration CRD](https://github.com/zalando/postgres-operator/blob/master/manifests/postgresql-operator-default-configuration.yaml) and [ConfigMap](https://github.com/zalando/postgres-operator/blob/master/manifests/configmap.yaml) manifest examples.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<h1>Migrate from v1 to v2</h1>
|
||||||
|
|
||||||
|
Version 2.0 changes some default settings and removes deprecated fields. Please read the following sections before upgrading the Postgres Operator deployment.
|
||||||
|
|
||||||
|
## K8s Endpoints are deprecated
|
||||||
|
|
||||||
|
If your current operator v1.x deployment is relying on K8s endpoints (the default setup) for Patroni to manage the HA state you have to start planning to switch to configmaps, because endpoints are deprecated from K8s 1.33 onwards. The default of the corresponding parameter `kubernetes_use_configmaps` is changing to `true` with v2.0 of the operator. This means you have to explicity set it to `false` in your configuration before you start the upgrade.
|
||||||
|
|
||||||
|
We explicitly warn you to go straight to configmap-based HA management with database clusters that use replicas, because there's is a danger to run into split-brain scenarios during the rolling update of pods when there exists a leader endpoint and leader config map at the same time. To play it safe, here is what you should do - before or after the Postgres Operator upgrade:
|
||||||
|
|
||||||
|
1. Scale-in all your database clusters to only one primary instance. This can be done by changing the global config options `max_instances` and `min_instances` to `1`. If you have allowed users to ignore globally defined instance limits by configuring an `ignore_instance_limits_annotation_key`, remove it for now.
|
||||||
|
|
||||||
|
2. Wait for all clusters to be healthy and change the `kubernetes_use_configmaps` setting to `true`. This will trigger the replacement of the primary pod of all clusters and cause downtime for as long as the pods are rescheduled and start up.
|
||||||
|
|
||||||
|
3. Check again that all clusters are healthy with configmaps created. There should be three for each cluster called like cluster name with suffixes `-config`, `-failover` and `-leader`. Now, revert the changes from step 1 and scale-out the to number of instances set in the manifests.
|
||||||
|
|
||||||
|
4. The orphaned endpoints, which use the same names like the new configmaps, have to be deleted by you or your K8s garbage collection.
|
||||||
|
|
||||||
|
|
||||||
|
## Dropped manifest fields
|
||||||
|
|
||||||
|
We removed some deprecated fields from the Postgresql CRD. Please, make sure that you do not specify them in any of your cluster manifests. If you do, switch to the listed alternative:
|
||||||
|
|
||||||
|
| Removed field in v2 | Alternative |
|
||||||
|
| --- | --- |
|
||||||
|
| init_containers | initContainers |
|
||||||
|
| pod_priority_class_name | podPriorityClassName |
|
||||||
|
| replicaLoadBalancer | enableReplicaLoadBalancer|
|
||||||
|
| useLoadBalancer | enableMasterLoadBalancer |
|
||||||
|
|
@ -42,14 +42,14 @@ and change it.
|
||||||
|
|
||||||
To test the CRD-based configuration locally, use the following
|
To test the CRD-based configuration locally, use the following
|
||||||
|
|
||||||
```bash
|
```
|
||||||
kubectl create -f manifests/operatorconfiguration.crd.yaml # registers the CRD
|
kubectl create -f manifests/operatorconfiguration.crd.yaml # registers the CRD
|
||||||
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
|
kubectl create -f manifests/postgresql-operator-default-configuration.yaml
|
||||||
|
|
||||||
kubectl create -f manifests/operator-service-account-rbac.yaml
|
kubectl create -f manifests/operator-service-account-rbac.yaml
|
||||||
kubectl create -f manifests/postgres-operator.yaml # set the env var as mentioned above
|
kubectl create -f manifests/postgres-operator.yaml # set the env var as mentioned above
|
||||||
|
|
||||||
kubectl get operatorconfigurations postgresql-operator-default-configuration -o yaml
|
kubectl get operatorconfigurations postgresql-operator-default-configuration -o yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
The CRD-based configuration is more powerful than the one based on ConfigMaps
|
The CRD-based configuration is more powerful than the one based on ConfigMaps
|
||||||
|
|
@ -100,15 +100,13 @@ Those are top-level keys, containing both leaf keys and groups.
|
||||||
Kubernetes-native DCS).
|
Kubernetes-native DCS).
|
||||||
|
|
||||||
* **kubernetes_use_configmaps**
|
* **kubernetes_use_configmaps**
|
||||||
Select if setup uses endpoints (default), or configmaps to manage leader when
|
Select if setup uses endpoints or configmaps (default) to manage leader when
|
||||||
DCS is kubernetes (not etcd or similar). In OpenShift it is not possible to
|
DCS is kubernetes (not etcd or similar). In OpenShift it is not possible to
|
||||||
use endpoints option, and configmaps is required. Starting with K8s 1.33,
|
use endpoints option, and configmaps is required. Starting with K8s 1.33,
|
||||||
endpoints are marked as deprecated. It's recommended to switch to config maps
|
endpoints are marked as deprecated. It's recommended to switch to config maps
|
||||||
instead. But, to do so make sure you scale the Postgres cluster down to just
|
instead. But, to do so make sure you scale the Postgres cluster down to just
|
||||||
one primary pod (e.g. using `max_instances` option). Otherwise, you risk
|
one primary pod (e.g. using `max_instances` option). Otherwise, you risk
|
||||||
running into a split-brain scenario.
|
running into a split-brain scenario. Default is `true`.
|
||||||
By default, `kubernetes_use_configmaps: false`, meaning endpoints will be used.
|
|
||||||
Starting from v1.16.0 the default will be changed to `true`.
|
|
||||||
|
|
||||||
* **docker_image**
|
* **docker_image**
|
||||||
Spilo Docker image for Postgres instances. For production, don't rely on the
|
Spilo Docker image for Postgres instances. For production, don't rely on the
|
||||||
|
|
@ -800,6 +798,15 @@ yet officially supported.
|
||||||
[kube2iam](https://github.com/jtblin/kube2iam) project on AWS. The default is
|
[kube2iam](https://github.com/jtblin/kube2iam) project on AWS. The default is
|
||||||
empty.
|
empty.
|
||||||
|
|
||||||
|
* **irsa_role_arn**
|
||||||
|
Full AWS IAM role ARN to supply in the `eks.amazonaws.com/role-arn` annotation
|
||||||
|
of the Postgres pod service account, enabling
|
||||||
|
[IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
|
||||||
|
(IAM Roles for Service Accounts) on EKS. When set, the operator annotates the
|
||||||
|
pod service account on every sync so that the EKS OIDC webhook can inject AWS
|
||||||
|
credentials directly into pods. Must be a full ARN, e.g.
|
||||||
|
`arn:aws:iam::123456789012:role/my-postgres-role`. The default is empty.
|
||||||
|
|
||||||
* **aws_region**
|
* **aws_region**
|
||||||
AWS region used to store EBS volumes. The default is `eu-central-1`. Note,
|
AWS region used to store EBS volumes. The default is `eu-central-1`. Note,
|
||||||
this option is not meant for specifying the AWS region for backups and
|
this option is not meant for specifying the AWS region for backups and
|
||||||
|
|
|
||||||
|
|
@ -1430,7 +1430,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_resources)
|
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_resources)
|
||||||
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"},
|
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"},
|
||||||
"Operator does not get in sync")
|
"Operator does not get in sync", retries=120)
|
||||||
|
|
||||||
# wait for switched over
|
# wait for switched over
|
||||||
k8s.wait_for_pod_failover(replica_nodes, 'spilo-role=master,' + cluster_label)
|
k8s.wait_for_pod_failover(replica_nodes, 'spilo-role=master,' + cluster_label)
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@
|
||||||
|
|
||||||
file="${1:-"manifests/postgresql.crd.yaml"}"
|
file="${1:-"manifests/postgresql.crd.yaml"}"
|
||||||
|
|
||||||
sed -i '/^[[:space:]]*standby:$/{
|
SED=$(command -v gsed 2>/dev/null || command -v sed)
|
||||||
|
|
||||||
|
$SED -i '/^[[:space:]]*standby:$/{
|
||||||
# Capture the indentation
|
# Capture the indentation
|
||||||
s/^\([[:space:]]*\)standby:$/\1standby:\n\1 anyOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host\n\1 not:\n\1 required:\n\1 - s3_wal_path\n\1 - gs_wal_path/
|
s/^\([[:space:]]*\)standby:$/\1standby:\n\1 anyOf:\n\1 - required:\n\1 - s3_wal_path\n\1 - required:\n\1 - gs_wal_path\n\1 - required:\n\1 - standby_host\n\1 not:\n\1 required:\n\1 - s3_wal_path\n\1 - gs_wal_path/
|
||||||
}' "$file"
|
}' "$file"
|
||||||
|
|
||||||
sed -i '/^[[:space:]]*maintenanceWindows:$/{
|
$SED -i '/^[[:space:]]*maintenanceWindows:$/{
|
||||||
# Capture the indentation
|
# Capture the indentation
|
||||||
s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/
|
s/^\([[:space:]]*\)maintenanceWindows:$/\1maintenanceWindows:\n\1 items:\n\1 pattern: '\''^\\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))-((2[0-3]|[01]?\\d):([0-5]?\\d)|(2[0-3]|[01]?\\d):([0-5]?\\d))\\ *$'\''\n\1 type: string/
|
||||||
}' "$file"
|
}' "$file"
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ data:
|
||||||
# inherited_annotations: owned-by
|
# inherited_annotations: owned-by
|
||||||
# inherited_labels: application,environment
|
# inherited_labels: application,environment
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
|
# irsa_role_arn: ""
|
||||||
kubernetes_use_configmaps: "false"
|
kubernetes_use_configmaps: "false"
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
# logical_backup_azure_storage_account_name: ""
|
# logical_backup_azure_storage_account_name: ""
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.17.3
|
controller-gen.kubebuilder.io/version: v0.17.3
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgres-operator
|
||||||
name: operatorconfigurations.acid.zalan.do
|
name: operatorconfigurations.acid.zalan.do
|
||||||
spec:
|
spec:
|
||||||
group: acid.zalan.do
|
group: acid.zalan.do
|
||||||
|
|
@ -71,6 +73,8 @@ spec:
|
||||||
type: integer
|
type: integer
|
||||||
gcp_credentials:
|
gcp_credentials:
|
||||||
type: string
|
type: string
|
||||||
|
irsa_role_arn:
|
||||||
|
type: string
|
||||||
kube_iam_role:
|
kube_iam_role:
|
||||||
type: string
|
type: string
|
||||||
log_s3_bucket:
|
log_s3_bucket:
|
||||||
|
|
@ -445,8 +449,7 @@ spec:
|
||||||
default: 20m
|
default: 20m
|
||||||
description: timeout for successful migration of master pods from
|
description: timeout for successful migration of master pods from
|
||||||
unschedulable node
|
unschedulable node
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
node_readiness_label:
|
node_readiness_label:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -519,8 +522,7 @@ spec:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: Postgres pods are terminated forcefully after this
|
description: Postgres pods are terminated forcefully after this
|
||||||
timeout
|
timeout
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
secret_name_template:
|
secret_name_template:
|
||||||
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
||||||
description: |-
|
description: |-
|
||||||
|
|
@ -780,13 +782,11 @@ spec:
|
||||||
repair_period:
|
repair_period:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: period between consecutive repair requests
|
description: period between consecutive repair requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resync_period:
|
resync_period:
|
||||||
default: 30m
|
default: 30m
|
||||||
description: period between consecutive sync requests
|
description: period between consecutive sync requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
scalyr:
|
scalyr:
|
||||||
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -852,7 +852,9 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
protected_role_names:
|
protected_role_names:
|
||||||
default: '["admin", "cron_admin"]'
|
default:
|
||||||
|
- admin
|
||||||
|
- cron_admin
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
|
@ -880,48 +882,40 @@ spec:
|
||||||
default: 1s
|
default: 1s
|
||||||
description: interval between consecutive attempts of operator
|
description: interval between consecutive attempts of operator
|
||||||
calling the Patroni API
|
calling the Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
patroni_api_check_timeout:
|
patroni_api_check_timeout:
|
||||||
default: 5s
|
default: 5s
|
||||||
description: timeout when waiting for successful response from
|
description: timeout when waiting for successful response from
|
||||||
Patroni API
|
Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_deletion_wait_timeout:
|
pod_deletion_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the Postgres pods to be
|
description: timeout when waiting for the Postgres pods to be
|
||||||
deleted
|
deleted
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_label_wait_timeout:
|
pod_label_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for pod role and cluster labels
|
description: timeout when waiting for pod role and cluster labels
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_interval:
|
ready_wait_interval:
|
||||||
default: 4s
|
default: 4s
|
||||||
description: interval between consecutive attempts waiting for
|
description: interval between consecutive attempts waiting for
|
||||||
postgresql CRD to be created
|
postgresql CRD to be created
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_timeout:
|
ready_wait_timeout:
|
||||||
default: 30s
|
default: 30s
|
||||||
description: timeout for the complete postgres CRD creation
|
description: timeout for the complete postgres CRD creation
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_interval:
|
resource_check_interval:
|
||||||
default: 3s
|
default: 3s
|
||||||
description: interval to wait between consecutive attempts to
|
description: interval to wait between consecutive attempts to
|
||||||
check for some K8s resources
|
check for some K8s resources
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_timeout:
|
resource_check_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the presence of a certain
|
description: timeout when waiting for the presence of a certain
|
||||||
K8s resource
|
K8s resource
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
type: object
|
type: object
|
||||||
users:
|
users:
|
||||||
description: PostgresUsersConfiguration defines the system users of
|
description: PostgresUsersConfiguration defines the system users of
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ configuration:
|
||||||
etcd_host: ""
|
etcd_host: ""
|
||||||
# ignore_instance_limits_annotation_key: ""
|
# ignore_instance_limits_annotation_key: ""
|
||||||
# ignore_resources_limits_annotation_key: ""
|
# ignore_resources_limits_annotation_key: ""
|
||||||
# kubernetes_use_configmaps: false
|
kubernetes_use_configmaps: true
|
||||||
# maintenance_windows:
|
# maintenance_windows:
|
||||||
# - "Sat:22:00-23:59"
|
# - "Sat:22:00-23:59"
|
||||||
# - "Sun:00:00-01:00"
|
# - "Sun:00:00-01:00"
|
||||||
|
|
@ -171,6 +171,7 @@ configuration:
|
||||||
# enable_ebs_gp3_migration_max_size: 1000
|
# enable_ebs_gp3_migration_max_size: 1000
|
||||||
# gcp_credentials: ""
|
# gcp_credentials: ""
|
||||||
# kube_iam_role: ""
|
# kube_iam_role: ""
|
||||||
|
# irsa_role_arn: ""
|
||||||
# log_s3_bucket: ""
|
# log_s3_bucket: ""
|
||||||
# wal_az_storage_account: ""
|
# wal_az_storage_account: ""
|
||||||
# wal_gs_bucket: ""
|
# wal_gs_bucket: ""
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,6 +4,8 @@ kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.17.3
|
controller-gen.kubebuilder.io/version: v0.17.3
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgres-operator
|
||||||
name: postgresteams.acid.zalan.do
|
name: postgresteams.acid.zalan.do
|
||||||
spec:
|
spec:
|
||||||
group: acid.zalan.do
|
group: acid.zalan.do
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type postgresqlCopy Postgresql
|
type postgresqlCopy Postgresql
|
||||||
|
|
@ -120,29 +119,3 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON convert to Duration from byte slice of json
|
|
||||||
func (d *Duration) UnmarshalJSON(b []byte) error {
|
|
||||||
var (
|
|
||||||
v interface{}
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if err = json.Unmarshal(b, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch val := v.(type) {
|
|
||||||
case string:
|
|
||||||
t, err := time.ParseDuration(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*d = Duration(t)
|
|
||||||
return nil
|
|
||||||
case float64:
|
|
||||||
t := time.Duration(val)
|
|
||||||
*d = Duration(t)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("could not recognize type %T as a valid type to unmarshal to Duration", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ package v1
|
||||||
import (
|
import (
|
||||||
"github.com/zalando/postgres-operator/pkg/util/config"
|
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zalando/postgres-operator/pkg/spec"
|
"github.com/zalando/postgres-operator/pkg/spec"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -24,6 +22,7 @@ import (
|
||||||
// +kubebuilder:printcolumn:name="Min-Instances",type=integer,JSONPath=`.configuration.min_instances`,description="Minimum number of instances per Postgres cluster"
|
// +kubebuilder:printcolumn:name="Min-Instances",type=integer,JSONPath=`.configuration.min_instances`,description="Minimum number of instances per Postgres cluster"
|
||||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the OperatorConfiguration resource"
|
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the OperatorConfiguration resource"
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||||
type OperatorConfiguration struct {
|
type OperatorConfiguration struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata"`
|
metav1.ObjectMeta `json:"metadata"`
|
||||||
|
|
@ -77,7 +76,7 @@ type KubernetesMetaConfiguration struct {
|
||||||
PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"`
|
PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"`
|
||||||
// +kubebuilder:default="5m"
|
// +kubebuilder:default="5m"
|
||||||
// Postgres pods are terminated forcefully after this timeout
|
// Postgres pods are terminated forcefully after this timeout
|
||||||
PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"`
|
PodTerminateGracePeriod *metav1.Duration `json:"pod_terminate_grace_period,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
LivenessProbe *v1.Probe `json:"liveness_probe"`
|
LivenessProbe *v1.Probe `json:"liveness_probe"`
|
||||||
SpiloPrivileged bool `json:"spilo_privileged,omitempty"`
|
SpiloPrivileged bool `json:"spilo_privileged,omitempty"`
|
||||||
|
|
@ -142,9 +141,9 @@ type KubernetesMetaConfiguration struct {
|
||||||
PodPriorityClassName string `json:"pod_priority_class_name,omitempty"`
|
PodPriorityClassName string `json:"pod_priority_class_name,omitempty"`
|
||||||
// +kubebuilder:default="20m"
|
// +kubebuilder:default="20m"
|
||||||
// timeout for successful migration of master pods from unschedulable node
|
// timeout for successful migration of master pods from unschedulable node
|
||||||
MasterPodMoveTimeout Duration `json:"master_pod_move_timeout,omitempty"`
|
MasterPodMoveTimeout *metav1.Duration `json:"master_pod_move_timeout,omitempty"`
|
||||||
EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"`
|
EnablePodAntiAffinity bool `json:"enable_pod_antiaffinity,omitempty"`
|
||||||
PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"`
|
PodAntiAffinityPreferredDuringScheduling bool `json:"pod_antiaffinity_preferred_during_scheduling,omitempty"`
|
||||||
// +kubebuilder:default="kubernetes.io/hostname"
|
// +kubebuilder:default="kubernetes.io/hostname"
|
||||||
PodAntiAffinityTopologyKey string `json:"pod_antiaffinity_topology_key,omitempty"`
|
PodAntiAffinityTopologyKey string `json:"pod_antiaffinity_topology_key,omitempty"`
|
||||||
// +kubebuilder:validation:Enum=ordered_ready;parallel
|
// +kubebuilder:validation:Enum=ordered_ready;parallel
|
||||||
|
|
@ -185,28 +184,28 @@ type PostgresPodResourcesDefaults struct {
|
||||||
type OperatorTimeouts struct {
|
type OperatorTimeouts struct {
|
||||||
// +kubebuilder:default="3s"
|
// +kubebuilder:default="3s"
|
||||||
// interval to wait between consecutive attempts to check for some K8s resources
|
// interval to wait between consecutive attempts to check for some K8s resources
|
||||||
ResourceCheckInterval Duration `json:"resource_check_interval,omitempty"`
|
ResourceCheckInterval *metav1.Duration `json:"resource_check_interval,omitempty"`
|
||||||
// +kubebuilder:default="10m"
|
// +kubebuilder:default="10m"
|
||||||
// timeout when waiting for the presence of a certain K8s resource
|
// timeout when waiting for the presence of a certain K8s resource
|
||||||
ResourceCheckTimeout Duration `json:"resource_check_timeout,omitempty"`
|
ResourceCheckTimeout *metav1.Duration `json:"resource_check_timeout,omitempty"`
|
||||||
// +kubebuilder:default="10m"
|
// +kubebuilder:default="10m"
|
||||||
// timeout when waiting for pod role and cluster labels
|
// timeout when waiting for pod role and cluster labels
|
||||||
PodLabelWaitTimeout Duration `json:"pod_label_wait_timeout,omitempty"`
|
PodLabelWaitTimeout *metav1.Duration `json:"pod_label_wait_timeout,omitempty"`
|
||||||
// +kubebuilder:default="10m"
|
// +kubebuilder:default="10m"
|
||||||
// timeout when waiting for the Postgres pods to be deleted
|
// timeout when waiting for the Postgres pods to be deleted
|
||||||
PodDeletionWaitTimeout Duration `json:"pod_deletion_wait_timeout,omitempty"`
|
PodDeletionWaitTimeout *metav1.Duration `json:"pod_deletion_wait_timeout,omitempty"`
|
||||||
// +kubebuilder:default="4s"
|
// +kubebuilder:default="4s"
|
||||||
// interval between consecutive attempts waiting for postgresql CRD to be created
|
// interval between consecutive attempts waiting for postgresql CRD to be created
|
||||||
ReadyWaitInterval Duration `json:"ready_wait_interval,omitempty"`
|
ReadyWaitInterval *metav1.Duration `json:"ready_wait_interval,omitempty"`
|
||||||
// +kubebuilder:default="30s"
|
// +kubebuilder:default="30s"
|
||||||
// timeout for the complete postgres CRD creation
|
// timeout for the complete postgres CRD creation
|
||||||
ReadyWaitTimeout Duration `json:"ready_wait_timeout,omitempty"`
|
ReadyWaitTimeout *metav1.Duration `json:"ready_wait_timeout,omitempty"`
|
||||||
// +kubebuilder:default="1s"
|
// +kubebuilder:default="1s"
|
||||||
// interval between consecutive attempts of operator calling the Patroni API
|
// interval between consecutive attempts of operator calling the Patroni API
|
||||||
PatroniAPICheckInterval Duration `json:"patroni_api_check_interval,omitempty"`
|
PatroniAPICheckInterval *metav1.Duration `json:"patroni_api_check_interval,omitempty"`
|
||||||
// +kubebuilder:default="5s"
|
// +kubebuilder:default="5s"
|
||||||
// timeout when waiting for successful response from Patroni API
|
// timeout when waiting for successful response from Patroni API
|
||||||
PatroniAPICheckTimeout Duration `json:"patroni_api_check_timeout,omitempty"`
|
PatroniAPICheckTimeout *metav1.Duration `json:"patroni_api_check_timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadBalancerConfiguration defines the LB configuration
|
// LoadBalancerConfiguration defines the LB configuration
|
||||||
|
|
@ -253,6 +252,7 @@ type AWSGCPConfiguration struct {
|
||||||
WALAZStorageAccount string `json:"wal_az_storage_account,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"`
|
||||||
|
IRSARoleARN string `json:"irsa_role_arn,omitempty"`
|
||||||
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
AdditionalSecretMount string `json:"additional_secret_mount,omitempty"`
|
||||||
AdditionalSecretMountPath string `json:"additional_secret_mount_path,omitempty"`
|
AdditionalSecretMountPath string `json:"additional_secret_mount_path,omitempty"`
|
||||||
EnableEBSGp3Migration bool `json:"enable_ebs_gp3_migration,omitempty"`
|
EnableEBSGp3Migration bool `json:"enable_ebs_gp3_migration,omitempty"`
|
||||||
|
|
@ -283,7 +283,7 @@ type TeamsAPIConfiguration struct {
|
||||||
PamRoleName string `json:"pam_role_name,omitempty"`
|
PamRoleName string `json:"pam_role_name,omitempty"`
|
||||||
// +kubebuilder:default="https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
|
// +kubebuilder:default="https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"
|
||||||
PamConfiguration string `json:"pam_configuration,omitempty"`
|
PamConfiguration string `json:"pam_configuration,omitempty"`
|
||||||
// +kubebuilder:default="[\"admin\", \"cron_admin\"]"
|
// +kubebuilder:default={"admin", "cron_admin"}
|
||||||
ProtectedRoles []string `json:"protected_role_names,omitempty"`
|
ProtectedRoles []string `json:"protected_role_names,omitempty"`
|
||||||
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
PostgresSuperuserTeams []string `json:"postgres_superuser_teams,omitempty"`
|
||||||
// +kubebuilder:default=true
|
// +kubebuilder:default=true
|
||||||
|
|
@ -419,7 +419,7 @@ type OperatorConfigurationData struct {
|
||||||
// +kubebuilder:default=""
|
// +kubebuilder:default=""
|
||||||
EtcdHost string `json:"etcd_host,omitempty"`
|
EtcdHost string `json:"etcd_host,omitempty"`
|
||||||
// +kubebuilder:default=true
|
// +kubebuilder:default=true
|
||||||
KubernetesUseConfigMaps bool `json:"kubernetes_use_configmaps,omitempty"`
|
KubernetesUseConfigMaps *bool `json:"kubernetes_use_configmaps,omitempty"`
|
||||||
// +kubebuilder:default="ghcr.io/zalando/spilo-18:4.1-p1"
|
// +kubebuilder:default="ghcr.io/zalando/spilo-18:4.1-p1"
|
||||||
DockerImage string `json:"docker_image,omitempty"`
|
DockerImage string `json:"docker_image,omitempty"`
|
||||||
// +kubebuilder:validation:Minimum=1
|
// +kubebuilder:validation:Minimum=1
|
||||||
|
|
@ -427,16 +427,16 @@ type OperatorConfigurationData struct {
|
||||||
Workers uint32 `json:"workers,omitempty"`
|
Workers uint32 `json:"workers,omitempty"`
|
||||||
// +kubebuilder:default="30m"
|
// +kubebuilder:default="30m"
|
||||||
// period between consecutive sync requests
|
// period between consecutive sync requests
|
||||||
ResyncPeriod Duration `json:"resync_period,omitempty"`
|
ResyncPeriod *metav1.Duration `json:"resync_period,omitempty"`
|
||||||
// +kubebuilder:default="5m"
|
// +kubebuilder:default="5m"
|
||||||
// period between consecutive repair requests
|
// period between consecutive repair requests
|
||||||
RepairPeriod Duration `json:"repair_period,omitempty"`
|
RepairPeriod *metav1.Duration `json:"repair_period,omitempty"`
|
||||||
// +kubebuilder:default=true
|
// +kubebuilder:default=true
|
||||||
EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"`
|
EnableMaintenanceWindows *bool `json:"enable_maintenance_windows,omitempty"`
|
||||||
// +kubebuilder:validation:Schemaless
|
|
||||||
// +kubebuilder:validation:Type=array
|
// +kubebuilder:validation:Type=array
|
||||||
MaintenanceWindows []MaintenanceWindow `json:"maintenance_windows,omitempty"`
|
// +kubebuilder:validation:items:Pattern=`^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$`
|
||||||
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
|
MaintenanceWindows []string `json:"maintenance_windows,omitempty"`
|
||||||
|
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
|
||||||
// +kubebuilder:default=true
|
// +kubebuilder:default=true
|
||||||
ShmVolume *bool `json:"enable_shm_volume,omitempty"`
|
ShmVolume *bool `json:"enable_shm_volume,omitempty"`
|
||||||
SidecarImages map[string]string `json:"sidecar_docker_images,omitempty"` // deprecated in favour of SidecarContainers
|
SidecarImages map[string]string `json:"sidecar_docker_images,omitempty"` // deprecated in favour of SidecarContainers
|
||||||
|
|
@ -485,6 +485,3 @@ type OperatorConfigurationData struct {
|
||||||
IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"`
|
IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"`
|
||||||
IgnoreResourcesLimitsAnnotationKey string `json:"ignore_resources_limits_annotation_key,omitempty"`
|
IgnoreResourcesLimitsAnnotationKey string `json:"ignore_resources_limits_annotation_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration shortens this frequently used name
|
|
||||||
type Duration time.Duration
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
controller-gen.kubebuilder.io/version: v0.17.3
|
controller-gen.kubebuilder.io/version: v0.17.3
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: postgres-operator
|
||||||
name: operatorconfigurations.acid.zalan.do
|
name: operatorconfigurations.acid.zalan.do
|
||||||
spec:
|
spec:
|
||||||
group: acid.zalan.do
|
group: acid.zalan.do
|
||||||
|
|
@ -71,6 +73,8 @@ spec:
|
||||||
type: integer
|
type: integer
|
||||||
gcp_credentials:
|
gcp_credentials:
|
||||||
type: string
|
type: string
|
||||||
|
irsa_role_arn:
|
||||||
|
type: string
|
||||||
kube_iam_role:
|
kube_iam_role:
|
||||||
type: string
|
type: string
|
||||||
log_s3_bucket:
|
log_s3_bucket:
|
||||||
|
|
@ -445,8 +449,7 @@ spec:
|
||||||
default: 20m
|
default: 20m
|
||||||
description: timeout for successful migration of master pods from
|
description: timeout for successful migration of master pods from
|
||||||
unschedulable node
|
unschedulable node
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
node_readiness_label:
|
node_readiness_label:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -519,8 +522,7 @@ spec:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: Postgres pods are terminated forcefully after this
|
description: Postgres pods are terminated forcefully after this
|
||||||
timeout
|
timeout
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
secret_name_template:
|
secret_name_template:
|
||||||
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
default: '{username}.{cluster}.credentials.{tprkind}.{tprgroup}'
|
||||||
description: |-
|
description: |-
|
||||||
|
|
@ -780,13 +782,11 @@ spec:
|
||||||
repair_period:
|
repair_period:
|
||||||
default: 5m
|
default: 5m
|
||||||
description: period between consecutive repair requests
|
description: period between consecutive repair requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resync_period:
|
resync_period:
|
||||||
default: 30m
|
default: 30m
|
||||||
description: period between consecutive sync requests
|
description: period between consecutive sync requests
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
scalyr:
|
scalyr:
|
||||||
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
description: ScalyrConfiguration defines the configuration for ScalyrAPI
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -852,7 +852,9 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
protected_role_names:
|
protected_role_names:
|
||||||
default: '["admin", "cron_admin"]'
|
default:
|
||||||
|
- admin
|
||||||
|
- cron_admin
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
|
@ -880,48 +882,40 @@ spec:
|
||||||
default: 1s
|
default: 1s
|
||||||
description: interval between consecutive attempts of operator
|
description: interval between consecutive attempts of operator
|
||||||
calling the Patroni API
|
calling the Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
patroni_api_check_timeout:
|
patroni_api_check_timeout:
|
||||||
default: 5s
|
default: 5s
|
||||||
description: timeout when waiting for successful response from
|
description: timeout when waiting for successful response from
|
||||||
Patroni API
|
Patroni API
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_deletion_wait_timeout:
|
pod_deletion_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the Postgres pods to be
|
description: timeout when waiting for the Postgres pods to be
|
||||||
deleted
|
deleted
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
pod_label_wait_timeout:
|
pod_label_wait_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for pod role and cluster labels
|
description: timeout when waiting for pod role and cluster labels
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_interval:
|
ready_wait_interval:
|
||||||
default: 4s
|
default: 4s
|
||||||
description: interval between consecutive attempts waiting for
|
description: interval between consecutive attempts waiting for
|
||||||
postgresql CRD to be created
|
postgresql CRD to be created
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
ready_wait_timeout:
|
ready_wait_timeout:
|
||||||
default: 30s
|
default: 30s
|
||||||
description: timeout for the complete postgres CRD creation
|
description: timeout for the complete postgres CRD creation
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_interval:
|
resource_check_interval:
|
||||||
default: 3s
|
default: 3s
|
||||||
description: interval to wait between consecutive attempts to
|
description: interval to wait between consecutive attempts to
|
||||||
check for some K8s resources
|
check for some K8s resources
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
resource_check_timeout:
|
resource_check_timeout:
|
||||||
default: 10m
|
default: 10m
|
||||||
description: timeout when waiting for the presence of a certain
|
description: timeout when waiting for the presence of a certain
|
||||||
K8s resource
|
K8s resource
|
||||||
format: int64
|
type: string
|
||||||
type: integer
|
|
||||||
type: object
|
type: object
|
||||||
users:
|
users:
|
||||||
description: PostgresUsersConfiguration defines the system users of
|
description: PostgresUsersConfiguration defines the system users of
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
// +kubebuilder:resource:shortName=pgteam,categories=all
|
// +kubebuilder:resource:shortName=pgteam,categories=all
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||||
type PostgresTeam struct {
|
type PostgresTeam struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata"`
|
metav1.ObjectMeta `json:"metadata"`
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,6 +24,7 @@ import (
|
||||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the PostgreSQL cluster"
|
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age of the PostgreSQL cluster"
|
||||||
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.PostgresClusterStatus`,description="Current sync status of postgresql resource"
|
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.PostgresClusterStatus`,description="Current sync status of postgresql resource"
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:metadata:labels=app.kubernetes.io/name=postgres-operator
|
||||||
type Postgresql struct {
|
type Postgresql struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata"`
|
metav1.ObjectMeta `json:"metadata"`
|
||||||
|
|
@ -75,12 +76,6 @@ type PostgresSpec struct {
|
||||||
EnableReplicaPoolerNodePort *bool `json:"enableReplicaPoolerNodePort,omitempty"`
|
EnableReplicaPoolerNodePort *bool `json:"enableReplicaPoolerNodePort,omitempty"`
|
||||||
ReplicaPoolerNodePort *int32 `json:"replicaPoolerNodePort,omitempty"`
|
ReplicaPoolerNodePort *int32 `json:"replicaPoolerNodePort,omitempty"`
|
||||||
|
|
||||||
// deprecated load balancer settings maintained for backward compatibility
|
|
||||||
// see "Load balancers" operator docs
|
|
||||||
UseLoadBalancer *bool `json:"useLoadBalancer,omitempty"`
|
|
||||||
// deprecated
|
|
||||||
ReplicaLoadBalancer *bool `json:"replicaLoadBalancer,omitempty"`
|
|
||||||
|
|
||||||
// load balancers' source ranges are the same for master and replica services
|
// load balancers' source ranges are the same for master and replica services
|
||||||
// +nullable
|
// +nullable
|
||||||
// +kubebuilder:validation:items:Pattern=`^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$`
|
// +kubebuilder:validation:items:Pattern=`^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$`
|
||||||
|
|
@ -129,11 +124,6 @@ type PostgresSpec struct {
|
||||||
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
|
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
|
||||||
Streams []Stream `json:"streams,omitempty"`
|
Streams []Stream `json:"streams,omitempty"`
|
||||||
Env []v1.EnvVar `json:"env,omitempty"`
|
Env []v1.EnvVar `json:"env,omitempty"`
|
||||||
|
|
||||||
// deprecated
|
|
||||||
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
|
||||||
// deprecated
|
|
||||||
PodPriorityClassNameOld string `json:"pod_priority_class_name,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zalando/postgres-operator/pkg/util"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -178,7 +177,8 @@ var unmarshalCluster = []struct {
|
||||||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
||||||
},
|
},
|
||||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":"Invalid"}`),
|
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":"Invalid"}`),
|
||||||
err: nil},
|
err: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
about: "example with /status subresource",
|
about: "example with /status subresource",
|
||||||
in: []byte(`{
|
in: []byte(`{
|
||||||
|
|
@ -199,156 +199,8 @@ var unmarshalCluster = []struct {
|
||||||
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
"metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), &tmp).Error(),
|
||||||
},
|
},
|
||||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":null},"status":{"PostgresClusterStatus":"Invalid"}}`),
|
||||||
err: nil},
|
err: nil,
|
||||||
{
|
},
|
||||||
about: "example with detailed input manifest and deprecated pod_priority_class_name -> podPriorityClassName",
|
|
||||||
in: []byte(`{
|
|
||||||
"kind": "Postgresql",
|
|
||||||
"apiVersion": "acid.zalan.do/v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "acid-testcluster1"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"teamId": "acid",
|
|
||||||
"pod_priority_class_name": "spilo-pod-priority",
|
|
||||||
"volume": {
|
|
||||||
"size": "5Gi",
|
|
||||||
"storageClass": "SSD",
|
|
||||||
"subPath": "subdir"
|
|
||||||
},
|
|
||||||
"numberOfInstances": 2,
|
|
||||||
"users": {
|
|
||||||
"zalando": [
|
|
||||||
"superuser",
|
|
||||||
"createdb"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"allowedSourceRanges": [
|
|
||||||
"127.0.0.1/32"
|
|
||||||
],
|
|
||||||
"postgresql": {
|
|
||||||
"version": "18",
|
|
||||||
"parameters": {
|
|
||||||
"shared_buffers": "32MB",
|
|
||||||
"max_connections": "10",
|
|
||||||
"log_statement": "all"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
"requests": {
|
|
||||||
"cpu": "10m",
|
|
||||||
"memory": "50Mi"
|
|
||||||
},
|
|
||||||
"limits": {
|
|
||||||
"cpu": "300m",
|
|
||||||
"memory": "3000Mi"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"clone" : {
|
|
||||||
"cluster": "acid-batman"
|
|
||||||
},
|
|
||||||
"enableShmVolume": false,
|
|
||||||
"patroni": {
|
|
||||||
"initdb": {
|
|
||||||
"encoding": "UTF8",
|
|
||||||
"locale": "en_US.UTF-8",
|
|
||||||
"data-checksums": "true"
|
|
||||||
},
|
|
||||||
"pg_hba": [
|
|
||||||
"hostssl all all 0.0.0.0/0 md5",
|
|
||||||
"host all all 0.0.0.0/0 md5"
|
|
||||||
],
|
|
||||||
"ttl": 30,
|
|
||||||
"loop_wait": 10,
|
|
||||||
"retry_timeout": 10,
|
|
||||||
"maximum_lag_on_failover": 33554432,
|
|
||||||
"slots" : {
|
|
||||||
"permanent_logical_1" : {
|
|
||||||
"type" : "logical",
|
|
||||||
"database" : "foo",
|
|
||||||
"plugin" : "pgoutput"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"maintenanceWindows": [
|
|
||||||
"Mon:01:00-06:00",
|
|
||||||
"Sat:00:00-04:00",
|
|
||||||
"05:00-05:15"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`),
|
|
||||||
out: Postgresql{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Postgresql",
|
|
||||||
APIVersion: "acid.zalan.do/v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "acid-testcluster1",
|
|
||||||
},
|
|
||||||
Spec: PostgresSpec{
|
|
||||||
PostgresqlParam: PostgresqlParam{
|
|
||||||
PgVersion: "18",
|
|
||||||
Parameters: map[string]string{
|
|
||||||
"shared_buffers": "32MB",
|
|
||||||
"max_connections": "10",
|
|
||||||
"log_statement": "all",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PodPriorityClassNameOld: "spilo-pod-priority",
|
|
||||||
Volume: Volume{
|
|
||||||
Size: "5Gi",
|
|
||||||
StorageClass: "SSD",
|
|
||||||
SubPath: "subdir",
|
|
||||||
},
|
|
||||||
ShmVolume: util.False(),
|
|
||||||
Patroni: Patroni{
|
|
||||||
InitDB: map[string]string{
|
|
||||||
"encoding": "UTF8",
|
|
||||||
"locale": "en_US.UTF-8",
|
|
||||||
"data-checksums": "true",
|
|
||||||
},
|
|
||||||
PgHba: []string{"hostssl all all 0.0.0.0/0 md5", "host all all 0.0.0.0/0 md5"},
|
|
||||||
TTL: 30,
|
|
||||||
LoopWait: 10,
|
|
||||||
RetryTimeout: 10,
|
|
||||||
MaximumLagOnFailover: 33554432,
|
|
||||||
Slots: map[string]map[string]string{"permanent_logical_1": {"type": "logical", "database": "foo", "plugin": "pgoutput"}},
|
|
||||||
},
|
|
||||||
Resources: &Resources{
|
|
||||||
ResourceRequests: ResourceDescription{CPU: stringToPointer("10m"), Memory: stringToPointer("50Mi")},
|
|
||||||
ResourceLimits: ResourceDescription{CPU: stringToPointer("300m"), Memory: stringToPointer("3000Mi")},
|
|
||||||
},
|
|
||||||
|
|
||||||
TeamID: "acid",
|
|
||||||
AllowedSourceRanges: []string{"127.0.0.1/32"},
|
|
||||||
NumberOfInstances: 2,
|
|
||||||
Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}},
|
|
||||||
MaintenanceWindows: []MaintenanceWindow{{
|
|
||||||
Everyday: false,
|
|
||||||
Weekday: time.Monday,
|
|
||||||
StartTime: mustParseTime("01:00"),
|
|
||||||
EndTime: mustParseTime("06:00"),
|
|
||||||
}, {
|
|
||||||
Everyday: false,
|
|
||||||
Weekday: time.Saturday,
|
|
||||||
StartTime: mustParseTime("00:00"),
|
|
||||||
EndTime: mustParseTime("04:00"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Everyday: true,
|
|
||||||
Weekday: time.Sunday,
|
|
||||||
StartTime: mustParseTime("05:00"),
|
|
||||||
EndTime: mustParseTime("05:15"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Clone: &CloneDescription{
|
|
||||||
ClusterName: "acid-batman",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Error: "",
|
|
||||||
},
|
|
||||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"18","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"pod_priority_class_name":"spilo-pod-priority","volume":{"size":"5Gi","storageClass":"SSD", "subPath": "subdir"},"enableShmVolume":false,"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"acid","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
|
||||||
err: nil},
|
|
||||||
{
|
{
|
||||||
about: "example with clone",
|
about: "example with clone",
|
||||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
|
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "clone": {"cluster": "team-batman"}}}`),
|
||||||
|
|
@ -369,7 +221,8 @@ var unmarshalCluster = []struct {
|
||||||
Error: "",
|
Error: "",
|
||||||
},
|
},
|
||||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}},"status":{"PostgresClusterStatus":""}}`),
|
||||||
err: nil},
|
err: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
about: "standby example",
|
about: "standby example",
|
||||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "standby": {"s3_wal_path": "s3://custom/path/to/bucket/"}}}`),
|
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": "acid", "standby": {"s3_wal_path": "s3://custom/path/to/bucket/"}}}`),
|
||||||
|
|
@ -390,7 +243,8 @@ var unmarshalCluster = []struct {
|
||||||
Error: "",
|
Error: "",
|
||||||
},
|
},
|
||||||
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"standby":{"s3_wal_path":"s3://custom/path/to/bucket/"}},"status":{"PostgresClusterStatus":""}}`),
|
marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0,"slots":null},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"standby":{"s3_wal_path":"s3://custom/path/to/bucket/"}},"status":{"PostgresClusterStatus":""}}`),
|
||||||
err: nil},
|
err: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
about: "expect error on malformatted JSON",
|
about: "expect error on malformatted JSON",
|
||||||
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
|
in: []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
||||||
*out = new(bool)
|
*out = new(bool)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.PodTerminateGracePeriod != nil {
|
||||||
|
in, out := &in.PodTerminateGracePeriod, &out.PodTerminateGracePeriod
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.LivenessProbe != nil {
|
if in.LivenessProbe != nil {
|
||||||
in, out := &in.LivenessProbe, &out.LivenessProbe
|
in, out := &in.LivenessProbe, &out.LivenessProbe
|
||||||
*out = new(corev1.Probe)
|
*out = new(corev1.Probe)
|
||||||
|
|
@ -290,6 +295,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.PodEnvironmentConfigMap = in.PodEnvironmentConfigMap
|
out.PodEnvironmentConfigMap = in.PodEnvironmentConfigMap
|
||||||
|
if in.MasterPodMoveTimeout != nil {
|
||||||
|
in, out := &in.MasterPodMoveTimeout, &out.MasterPodMoveTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.PersistentVolumeClaimRetentionPolicy != nil {
|
if in.PersistentVolumeClaimRetentionPolicy != nil {
|
||||||
in, out := &in.PersistentVolumeClaimRetentionPolicy, &out.PersistentVolumeClaimRetentionPolicy
|
in, out := &in.PersistentVolumeClaimRetentionPolicy, &out.PersistentVolumeClaimRetentionPolicy
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
|
@ -443,6 +453,21 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.KubernetesUseConfigMaps != nil {
|
||||||
|
in, out := &in.KubernetesUseConfigMaps, &out.KubernetesUseConfigMaps
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ResyncPeriod != nil {
|
||||||
|
in, out := &in.ResyncPeriod, &out.ResyncPeriod
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.RepairPeriod != nil {
|
||||||
|
in, out := &in.RepairPeriod, &out.RepairPeriod
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.EnableMaintenanceWindows != nil {
|
if in.EnableMaintenanceWindows != nil {
|
||||||
in, out := &in.EnableMaintenanceWindows, &out.EnableMaintenanceWindows
|
in, out := &in.EnableMaintenanceWindows, &out.EnableMaintenanceWindows
|
||||||
*out = new(bool)
|
*out = new(bool)
|
||||||
|
|
@ -450,10 +475,8 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
||||||
}
|
}
|
||||||
if in.MaintenanceWindows != nil {
|
if in.MaintenanceWindows != nil {
|
||||||
in, out := &in.MaintenanceWindows, &out.MaintenanceWindows
|
in, out := &in.MaintenanceWindows, &out.MaintenanceWindows
|
||||||
*out = make([]MaintenanceWindow, len(*in))
|
*out = make([]string, len(*in))
|
||||||
for i := range *in {
|
copy(*out, *in)
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if in.ShmVolume != nil {
|
if in.ShmVolume != nil {
|
||||||
in, out := &in.ShmVolume, &out.ShmVolume
|
in, out := &in.ShmVolume, &out.ShmVolume
|
||||||
|
|
@ -478,7 +501,7 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
||||||
in.MajorVersionUpgrade.DeepCopyInto(&out.MajorVersionUpgrade)
|
in.MajorVersionUpgrade.DeepCopyInto(&out.MajorVersionUpgrade)
|
||||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||||
out.PostgresPodResources = in.PostgresPodResources
|
out.PostgresPodResources = in.PostgresPodResources
|
||||||
out.Timeouts = in.Timeouts
|
in.Timeouts.DeepCopyInto(&out.Timeouts)
|
||||||
in.LoadBalancer.DeepCopyInto(&out.LoadBalancer)
|
in.LoadBalancer.DeepCopyInto(&out.LoadBalancer)
|
||||||
out.AWSGCP = in.AWSGCP
|
out.AWSGCP = in.AWSGCP
|
||||||
in.OperatorDebug.DeepCopyInto(&out.OperatorDebug)
|
in.OperatorDebug.DeepCopyInto(&out.OperatorDebug)
|
||||||
|
|
@ -594,6 +617,46 @@ func (in *OperatorLogicalBackupConfiguration) DeepCopy() *OperatorLogicalBackupC
|
||||||
// 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 *OperatorTimeouts) DeepCopyInto(out *OperatorTimeouts) {
|
func (in *OperatorTimeouts) DeepCopyInto(out *OperatorTimeouts) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.ResourceCheckInterval != nil {
|
||||||
|
in, out := &in.ResourceCheckInterval, &out.ResourceCheckInterval
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ResourceCheckTimeout != nil {
|
||||||
|
in, out := &in.ResourceCheckTimeout, &out.ResourceCheckTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodLabelWaitTimeout != nil {
|
||||||
|
in, out := &in.PodLabelWaitTimeout, &out.PodLabelWaitTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodDeletionWaitTimeout != nil {
|
||||||
|
in, out := &in.PodDeletionWaitTimeout, &out.PodDeletionWaitTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ReadyWaitInterval != nil {
|
||||||
|
in, out := &in.ReadyWaitInterval, &out.ReadyWaitInterval
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ReadyWaitTimeout != nil {
|
||||||
|
in, out := &in.ReadyWaitTimeout, &out.ReadyWaitTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PatroniAPICheckInterval != nil {
|
||||||
|
in, out := &in.PatroniAPICheckInterval, &out.PatroniAPICheckInterval
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PatroniAPICheckTimeout != nil {
|
||||||
|
in, out := &in.PatroniAPICheckTimeout, &out.PatroniAPICheckTimeout
|
||||||
|
*out = new(metav1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -795,16 +858,6 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
||||||
*out = new(int32)
|
*out = new(int32)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
if in.UseLoadBalancer != nil {
|
|
||||||
in, out := &in.UseLoadBalancer, &out.UseLoadBalancer
|
|
||||||
*out = new(bool)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
if in.ReplicaLoadBalancer != nil {
|
|
||||||
in, out := &in.ReplicaLoadBalancer, &out.ReplicaLoadBalancer
|
|
||||||
*out = new(bool)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
if in.AllowedSourceRanges != nil {
|
if in.AllowedSourceRanges != nil {
|
||||||
in, out := &in.AllowedSourceRanges, &out.AllowedSourceRanges
|
in, out := &in.AllowedSourceRanges, &out.AllowedSourceRanges
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
|
|
@ -973,13 +1026,6 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in.InitContainersOld != nil {
|
|
||||||
in, out := &in.InitContainersOld, &out.InitContainersOld
|
|
||||||
*out = make([]corev1.Container, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1084,7 +1084,7 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
||||||
|
|
||||||
// Patroni service and endpoints / config maps
|
// Patroni service and endpoints / config maps
|
||||||
if err := c.syncPatroniResources(); err != nil {
|
if err := c.syncPatroniResources(); err != nil {
|
||||||
c.logger.Errorf("could not sync services: %v", err)
|
c.logger.Errorf("could not sync Patroni resources: %v", err)
|
||||||
updateFailed = true
|
updateFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1140,6 +1140,12 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
|
||||||
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume size sync.")
|
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume size sync.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pod service account (IRSA annotation sync)
|
||||||
|
if err := c.syncPodServiceAccount(); err != nil {
|
||||||
|
c.logger.Errorf("could not sync pod service account: %v", err)
|
||||||
|
updateFailed = true
|
||||||
|
}
|
||||||
|
|
||||||
// Statefulset
|
// Statefulset
|
||||||
func() {
|
func() {
|
||||||
if err := c.syncStatefulSet(); err != nil {
|
if err := c.syncStatefulSet(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ func TestCreate(t *testing.T) {
|
||||||
|
|
||||||
client := k8sutil.KubernetesClient{
|
client := k8sutil.KubernetesClient{
|
||||||
DeploymentsGetter: clientSet.AppsV1(),
|
DeploymentsGetter: clientSet.AppsV1(),
|
||||||
|
ConfigMapsGetter: clientSet.CoreV1(),
|
||||||
CronJobsGetter: clientSet.BatchV1(),
|
CronJobsGetter: clientSet.BatchV1(),
|
||||||
EndpointsGetter: clientSet.CoreV1(),
|
EndpointsGetter: clientSet.CoreV1(),
|
||||||
PersistentVolumeClaimsGetter: clientSet.CoreV1(),
|
PersistentVolumeClaimsGetter: clientSet.CoreV1(),
|
||||||
|
|
@ -138,7 +139,8 @@ func TestCreate(t *testing.T) {
|
||||||
var cluster = New(
|
var cluster = New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
PodManagementPolicy: "ordered_ready",
|
PodManagementPolicy: "ordered_ready",
|
||||||
|
PodTerminateGracePeriod: &metav1.Duration{Duration: 600 * time.Second},
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
ClusterLabels: map[string]string{"application": "spilo"},
|
ClusterLabels: map[string]string{"application": "spilo"},
|
||||||
ClusterNameLabel: "cluster-name",
|
ClusterNameLabel: "cluster-name",
|
||||||
|
|
@ -147,8 +149,8 @@ func TestCreate(t *testing.T) {
|
||||||
DefaultMemoryRequest: "300Mi",
|
DefaultMemoryRequest: "300Mi",
|
||||||
DefaultMemoryLimit: "300Mi",
|
DefaultMemoryLimit: "300Mi",
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(3),
|
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||||
ResourceCheckTimeout: time.Duration(10),
|
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||||
},
|
},
|
||||||
EnableFinalizers: util.True(),
|
EnableFinalizers: util.True(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
|
||||||
if connectionPoolerSpec == nil {
|
if connectionPoolerSpec == nil {
|
||||||
connectionPoolerSpec = &acidv1.ConnectionPooler{}
|
connectionPoolerSpec = &acidv1.ConnectionPooler{}
|
||||||
}
|
}
|
||||||
gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds())
|
gracePeriod := int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds())
|
||||||
resources, err := c.generateResourceRequirements(
|
resources, err := c.generateResourceRequirements(
|
||||||
connectionPoolerSpec.Resources,
|
connectionPoolerSpec.Resources,
|
||||||
makeDefaultConnectionPoolerResources(&c.OpConfig),
|
makeDefaultConnectionPoolerResources(&c.OpConfig),
|
||||||
|
|
|
||||||
|
|
@ -1165,7 +1165,7 @@ func (c *Cluster) getPodEnvironmentSecretVariables() ([]v1.EnvVar, error) {
|
||||||
|
|
||||||
secret := &v1.Secret{}
|
secret := &v1.Secret{}
|
||||||
var notFoundErr error
|
var notFoundErr error
|
||||||
err := retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
err := retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
secret, err = c.KubeClient.Secrets(c.Namespace).Get(
|
secret, err = c.KubeClient.Secrets(c.Namespace).Get(
|
||||||
|
|
@ -1340,28 +1340,6 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// backward compatible check for InitContainers
|
|
||||||
if spec.InitContainersOld != nil {
|
|
||||||
msg := "manifest parameter init_containers is deprecated."
|
|
||||||
if spec.InitContainers == nil {
|
|
||||||
c.logger.Warningf("%s Consider using initContainers instead.", msg)
|
|
||||||
spec.InitContainers = spec.InitContainersOld
|
|
||||||
} else {
|
|
||||||
c.logger.Warningf("%s Only value from initContainers is used", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// backward compatible check for PodPriorityClassName
|
|
||||||
if spec.PodPriorityClassNameOld != "" {
|
|
||||||
msg := "manifest parameter pod_priority_class_name is deprecated."
|
|
||||||
if spec.PodPriorityClassName == "" {
|
|
||||||
c.logger.Warningf("%s Consider using podPriorityClassName instead.", msg)
|
|
||||||
spec.PodPriorityClassName = spec.PodPriorityClassNameOld
|
|
||||||
} else {
|
|
||||||
c.logger.Warningf("%s Only value from podPriorityClassName is used", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spiloConfiguration, err := generateSpiloJSONConfiguration(&spec.PostgresqlParam, &spec.Patroni, &c.OpConfig, c.logger)
|
spiloConfiguration, err := generateSpiloJSONConfiguration(&spec.PostgresqlParam, &spec.Patroni, &c.OpConfig, c.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not generate Spilo JSON configuration: %v", err)
|
return nil, fmt.Errorf("could not generate Spilo JSON configuration: %v", err)
|
||||||
|
|
@ -1498,7 +1476,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
|
||||||
&tolerationSpec,
|
&tolerationSpec,
|
||||||
c.nodeAffinity(c.OpConfig.NodeReadinessLabel, spec.NodeAffinity),
|
c.nodeAffinity(c.OpConfig.NodeReadinessLabel, spec.NodeAffinity),
|
||||||
spec.SchedulerName,
|
spec.SchedulerName,
|
||||||
int64(c.OpConfig.PodTerminateGracePeriod.Seconds()),
|
int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds()),
|
||||||
c.OpConfig.PodServiceAccountName,
|
c.OpConfig.PodServiceAccountName,
|
||||||
c.OpConfig.KubeIAMRole,
|
c.OpConfig.KubeIAMRole,
|
||||||
effectivePodPriorityClassName,
|
effectivePodPriorityClassName,
|
||||||
|
|
@ -2300,6 +2278,12 @@ func (c *Cluster) generatePrimaryPodDisruptionBudget() *policyv1.PodDisruptionBu
|
||||||
labels[c.OpConfig.PodRoleLabel] = string(Master)
|
labels[c.OpConfig.PodRoleLabel] = string(Master)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When master selector is disabled and synchronous_mode_strict is on, require
|
||||||
|
// master + synchronous_node_count (default 1) healthy pods for write quorum.
|
||||||
|
if pdbMasterLabelSelector != nil && !*pdbMasterLabelSelector && minAvailable.IntVal > 0 && c.Spec.SynchronousModeStrict {
|
||||||
|
minAvailable = intstr.FromInt32(int32(c.Spec.SynchronousNodeCount + 1))
|
||||||
|
}
|
||||||
|
|
||||||
return &policyv1.PodDisruptionBudget{
|
return &policyv1.PodDisruptionBudget{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: c.PrimaryPodDisruptionBudgetName(),
|
Name: c.PrimaryPodDisruptionBudgetName(),
|
||||||
|
|
@ -2431,7 +2415,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) {
|
||||||
&tolerationsSpec,
|
&tolerationsSpec,
|
||||||
c.nodeAffinity(c.OpConfig.NodeReadinessLabel, nil),
|
c.nodeAffinity(c.OpConfig.NodeReadinessLabel, nil),
|
||||||
nil,
|
nil,
|
||||||
int64(c.OpConfig.PodTerminateGracePeriod.Seconds()),
|
int64(util.CoalesceDuration(c.OpConfig.PodTerminateGracePeriod, "5m").Seconds()),
|
||||||
c.OpConfig.PodServiceAccountName,
|
c.OpConfig.PodServiceAccountName,
|
||||||
c.OpConfig.KubeIAMRole,
|
c.OpConfig.KubeIAMRole,
|
||||||
"",
|
"",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
|
@ -361,8 +360,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) {
|
||||||
opConfig: config.Config{
|
opConfig: config.Config{
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
PodEnvironmentSecret: testPodEnvironmentObjectNotExists,
|
PodEnvironmentSecret: testPodEnvironmentObjectNotExists,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: still failing after %d retries: secret.core %q not found", maxRetries, testPodEnvironmentObjectNotExists),
|
err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: still failing after %d retries: secret.core %q not found", maxRetries, testPodEnvironmentObjectNotExists),
|
||||||
|
|
@ -372,8 +371,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) {
|
||||||
opConfig: config.Config{
|
opConfig: config.Config{
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretNameAPIError,
|
PodEnvironmentSecret: testPodEnvironmentSecretNameAPIError,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: Secret PodEnvironmentSecret API error"),
|
err: fmt.Errorf("could not read Secret PodEnvironmentSecretName: Secret PodEnvironmentSecret API error"),
|
||||||
|
|
@ -383,8 +382,8 @@ func TestPodEnvironmentSecretVariables(t *testing.T) {
|
||||||
opConfig: config.Config{
|
opConfig: config.Config{
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
envVars: []v1.EnvVar{
|
envVars: []v1.EnvVar{
|
||||||
|
|
@ -583,60 +582,60 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedValuesS3Bucket := []ExpectedValue{
|
expectedValuesS3Bucket := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "WAL_S3_BUCKET",
|
envVarConstant: "WAL_S3_BUCKET",
|
||||||
envVarValue: "global-s3-bucket",
|
envVarValue: "global-s3-bucket",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
|
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
|
||||||
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 17,
|
envIndex: 18,
|
||||||
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
|
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
|
||||||
envVarValue: "",
|
envVarValue: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedValuesGCPCreds := []ExpectedValue{
|
expectedValuesGCPCreds := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "WAL_GS_BUCKET",
|
envVarConstant: "WAL_GS_BUCKET",
|
||||||
envVarValue: "global-gs-bucket",
|
envVarValue: "global-gs-bucket",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
|
envVarConstant: "WAL_BUCKET_SCOPE_SUFFIX",
|
||||||
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 17,
|
envIndex: 18,
|
||||||
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
|
envVarConstant: "WAL_BUCKET_SCOPE_PREFIX",
|
||||||
envVarValue: "",
|
envVarValue: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 18,
|
envIndex: 19,
|
||||||
envVarConstant: "GOOGLE_APPLICATION_CREDENTIALS",
|
envVarConstant: "GOOGLE_APPLICATION_CREDENTIALS",
|
||||||
envVarValue: "some-path-to-credentials",
|
envVarValue: "some-path-to-credentials",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedS3BucketConfigMap := []ExpectedValue{
|
expectedS3BucketConfigMap := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 17,
|
envIndex: 18,
|
||||||
envVarConstant: "wal_s3_bucket",
|
envVarConstant: "wal_s3_bucket",
|
||||||
envVarValue: "global-s3-bucket-configmap",
|
envVarValue: "global-s3-bucket-configmap",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCustomS3BucketSpec := []ExpectedValue{
|
expectedCustomS3BucketSpec := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "WAL_S3_BUCKET",
|
envVarConstant: "WAL_S3_BUCKET",
|
||||||
envVarValue: "custom-s3-bucket",
|
envVarValue: "custom-s3-bucket",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCustomVariableSecret := []ExpectedValue{
|
expectedCustomVariableSecret := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "custom_variable",
|
envVarConstant: "custom_variable",
|
||||||
envVarValueRef: &v1.EnvVarSource{
|
envVarValueRef: &v1.EnvVarSource{
|
||||||
SecretKeyRef: &v1.SecretKeySelector{
|
SecretKeyRef: &v1.SecretKeySelector{
|
||||||
|
|
@ -650,72 +649,72 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedCustomVariableConfigMap := []ExpectedValue{
|
expectedCustomVariableConfigMap := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "custom_variable",
|
envVarConstant: "custom_variable",
|
||||||
envVarValue: "configmap-test",
|
envVarValue: "configmap-test",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCustomVariableSpec := []ExpectedValue{
|
expectedCustomVariableSpec := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "CUSTOM_VARIABLE",
|
envVarConstant: "CUSTOM_VARIABLE",
|
||||||
envVarValue: "spec-env-test",
|
envVarValue: "spec-env-test",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCloneEnvSpec := []ExpectedValue{
|
expectedCloneEnvSpec := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "CLONE_WALE_S3_PREFIX",
|
envVarConstant: "CLONE_WALE_S3_PREFIX",
|
||||||
envVarValue: "s3://another-bucket",
|
envVarValue: "s3://another-bucket",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 19,
|
envIndex: 20,
|
||||||
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_PREFIX",
|
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_PREFIX",
|
||||||
envVarValue: "",
|
envVarValue: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 20,
|
envIndex: 21,
|
||||||
envVarConstant: "CLONE_AWS_ENDPOINT",
|
envVarConstant: "CLONE_AWS_ENDPOINT",
|
||||||
envVarValue: "s3.eu-central-1.amazonaws.com",
|
envVarValue: "s3.eu-central-1.amazonaws.com",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCloneEnvSpecEnv := []ExpectedValue{
|
expectedCloneEnvSpecEnv := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_PREFIX",
|
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_PREFIX",
|
||||||
envVarValue: "test-cluster",
|
envVarValue: "test-cluster",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 17,
|
envIndex: 18,
|
||||||
envVarConstant: "CLONE_WALE_S3_PREFIX",
|
envVarConstant: "CLONE_WALE_S3_PREFIX",
|
||||||
envVarValue: "s3://another-bucket",
|
envVarValue: "s3://another-bucket",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 21,
|
envIndex: 22,
|
||||||
envVarConstant: "CLONE_AWS_ENDPOINT",
|
envVarConstant: "CLONE_AWS_ENDPOINT",
|
||||||
envVarValue: "s3.eu-central-1.amazonaws.com",
|
envVarValue: "s3.eu-central-1.amazonaws.com",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCloneEnvConfigMap := []ExpectedValue{
|
expectedCloneEnvConfigMap := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 16,
|
envIndex: 17,
|
||||||
envVarConstant: "CLONE_WAL_S3_BUCKET",
|
envVarConstant: "CLONE_WAL_S3_BUCKET",
|
||||||
envVarValue: "global-s3-bucket",
|
envVarValue: "global-s3-bucket",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 17,
|
envIndex: 18,
|
||||||
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_SUFFIX",
|
envVarConstant: "CLONE_WAL_BUCKET_SCOPE_SUFFIX",
|
||||||
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
envVarValue: fmt.Sprintf("/%s", dummyUUID),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 21,
|
envIndex: 22,
|
||||||
envVarConstant: "clone_aws_endpoint",
|
envVarConstant: "clone_aws_endpoint",
|
||||||
envVarValue: "s3.eu-west-1.amazonaws.com",
|
envVarValue: "s3.eu-west-1.amazonaws.com",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedCloneEnvSecret := []ExpectedValue{
|
expectedCloneEnvSecret := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 21,
|
envIndex: 22,
|
||||||
envVarConstant: "clone_aws_access_key_id",
|
envVarConstant: "clone_aws_access_key_id",
|
||||||
envVarValueRef: &v1.EnvVarSource{
|
envVarValueRef: &v1.EnvVarSource{
|
||||||
SecretKeyRef: &v1.SecretKeySelector{
|
SecretKeyRef: &v1.SecretKeySelector{
|
||||||
|
|
@ -729,12 +728,12 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedStandbyEnvSecret := []ExpectedValue{
|
expectedStandbyEnvSecret := []ExpectedValue{
|
||||||
{
|
{
|
||||||
envIndex: 15,
|
envIndex: 16,
|
||||||
envVarConstant: "STANDBY_WALE_GS_PREFIX",
|
envVarConstant: "STANDBY_WALE_GS_PREFIX",
|
||||||
envVarValue: "gs://some/path/",
|
envVarValue: "gs://some/path/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
envIndex: 20,
|
envIndex: 21,
|
||||||
envVarConstant: "standby_google_application_credentials",
|
envVarConstant: "standby_google_application_credentials",
|
||||||
envVarValueRef: &v1.EnvVarSource{
|
envVarValueRef: &v1.EnvVarSource{
|
||||||
SecretKeyRef: &v1.SecretKeySelector{
|
SecretKeyRef: &v1.SecretKeySelector{
|
||||||
|
|
@ -848,8 +847,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
Name: testPodEnvironmentConfigMapName,
|
Name: testPodEnvironmentConfigMapName,
|
||||||
},
|
},
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cloneDescription: &acidv1.CloneDescription{},
|
cloneDescription: &acidv1.CloneDescription{},
|
||||||
|
|
@ -877,8 +876,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
Name: testPodEnvironmentConfigMapName,
|
Name: testPodEnvironmentConfigMapName,
|
||||||
},
|
},
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cloneDescription: &acidv1.CloneDescription{},
|
cloneDescription: &acidv1.CloneDescription{},
|
||||||
|
|
@ -968,8 +967,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
opConfig: config.Config{
|
opConfig: config.Config{
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
WALES3Bucket: "global-s3-bucket",
|
WALES3Bucket: "global-s3-bucket",
|
||||||
},
|
},
|
||||||
|
|
@ -986,8 +985,8 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
|
||||||
opConfig: config.Config{
|
opConfig: config.Config{
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
PodEnvironmentSecret: testPodEnvironmentSecretName,
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
},
|
},
|
||||||
WALES3Bucket: "global-s3-bucket",
|
WALES3Bucket: "global-s3-bucket",
|
||||||
},
|
},
|
||||||
|
|
@ -2692,13 +2691,13 @@ func TestGeneratePodDisruptionBudget(t *testing.T) {
|
||||||
k8sutil.KubernetesClient{},
|
k8sutil.KubernetesClient{},
|
||||||
acidv1.Postgresql{
|
acidv1.Postgresql{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "myapp-database", Namespace: "myapp"},
|
ObjectMeta: metav1.ObjectMeta{Name: "myapp-database", Namespace: "myapp"},
|
||||||
Spec: acidv1.PostgresSpec{TeamID: "myapp", NumberOfInstances: 3}},
|
Spec: acidv1.PostgresSpec{TeamID: "myapp", NumberOfInstances: 3, Patroni: acidv1.Patroni{SynchronousModeStrict: true, SynchronousNodeCount: 1}}},
|
||||||
logger,
|
logger,
|
||||||
eventRecorder),
|
eventRecorder),
|
||||||
check: []func(cluster *Cluster, podDisruptionBudget *policyv1.PodDisruptionBudget) error{
|
check: []func(cluster *Cluster, podDisruptionBudget *policyv1.PodDisruptionBudget) error{
|
||||||
testPodDisruptionBudgetOwnerReference,
|
testPodDisruptionBudgetOwnerReference,
|
||||||
hasName("postgres-myapp-database-pdb"),
|
hasName("postgres-myapp-database-pdb"),
|
||||||
hasMinAvailable(1),
|
hasMinAvailable(2),
|
||||||
testLabelsAndSelectors(true),
|
testLabelsAndSelectors(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -2977,6 +2976,7 @@ func getServices(serviceType v1.ServiceType, sourceRanges []string, extTrafficPo
|
||||||
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy),
|
ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy),
|
||||||
LoadBalancerSourceRanges: sourceRanges,
|
LoadBalancerSourceRanges: sourceRanges,
|
||||||
Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}},
|
Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}},
|
||||||
|
Selector: map[string]string{"spilo-role": "master", "application": "spilo", "cluster-name": clusterName},
|
||||||
Type: serviceType,
|
Type: serviceType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ func (c *Cluster) getPatroniConfig(pod *v1.Pod) (acidv1.Patroni, map[string]stri
|
||||||
pgParameters map[string]string
|
pgParameters map[string]string
|
||||||
)
|
)
|
||||||
podName := util.NameFromMeta(pod.ObjectMeta)
|
podName := util.NameFromMeta(pod.ObjectMeta)
|
||||||
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout,
|
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
patroniConfig, pgParameters, err = c.patroni.GetConfig(pod)
|
patroniConfig, pgParameters, err = c.patroni.GetConfig(pod)
|
||||||
|
|
@ -355,7 +355,7 @@ func (c *Cluster) getPatroniConfig(pod *v1.Pod) (acidv1.Patroni, map[string]stri
|
||||||
|
|
||||||
func (c *Cluster) getPatroniMemberData(pod *v1.Pod) (patroni.MemberData, error) {
|
func (c *Cluster) getPatroniMemberData(pod *v1.Pod) (patroni.MemberData, error) {
|
||||||
var memberData patroni.MemberData
|
var memberData patroni.MemberData
|
||||||
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout,
|
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
memberData, err = c.patroni.GetMemberData(pod)
|
memberData, err = c.patroni.GetMemberData(pod)
|
||||||
|
|
@ -506,7 +506,7 @@ func (c *Cluster) getSwitchoverCandidate(master *v1.Pod) (spec.NamespacedName, e
|
||||||
candidates := make([]patroni.ClusterMember, 0)
|
candidates := make([]patroni.ClusterMember, 0)
|
||||||
syncCandidates := make([]patroni.ClusterMember, 0)
|
syncCandidates := make([]patroni.ClusterMember, 0)
|
||||||
|
|
||||||
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval, c.OpConfig.PatroniAPICheckTimeout,
|
err := retryutil.Retry(c.OpConfig.PatroniAPICheckInterval.Duration, c.OpConfig.PatroniAPICheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
var err error
|
var err error
|
||||||
members, err = c.patroni.GetClusterMembers(master)
|
members, err = c.patroni.GetClusterMembers(master)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/patroni"
|
"github.com/zalando/postgres-operator/pkg/util/patroni"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetSwitchoverCandidate(t *testing.T) {
|
func TestGetSwitchoverCandidate(t *testing.T) {
|
||||||
|
|
@ -28,8 +29,8 @@ func TestGetSwitchoverCandidate(t *testing.T) {
|
||||||
var cluster = New(
|
var cluster = New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
PatroniAPICheckInterval: time.Duration(1),
|
PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second},
|
||||||
PatroniAPICheckTimeout: time.Duration(5),
|
PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second},
|
||||||
},
|
},
|
||||||
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
|
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ func (c *Cluster) replaceStatefulSet(newStatefulSet *appsv1.StatefulSet) error {
|
||||||
// wait until the statefulset is truly deleted
|
// wait until the statefulset is truly deleted
|
||||||
c.logger.Debug("waiting for the statefulset to be deleted")
|
c.logger.Debug("waiting for the statefulset to be deleted")
|
||||||
|
|
||||||
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
_, err2 := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Get(context.TODO(), oldStatefulset.Name, metav1.GetOptions{})
|
_, err2 := c.KubeClient.StatefulSets(oldStatefulset.Namespace).Get(context.TODO(), oldStatefulset.Name, metav1.GetOptions{})
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
|
|
@ -550,7 +550,7 @@ func (c *Cluster) deletePrimaryPodDisruptionBudget() error {
|
||||||
c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.PrimaryPodDisruptionBudget.ObjectMeta))
|
c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.PrimaryPodDisruptionBudget.ObjectMeta))
|
||||||
c.PrimaryPodDisruptionBudget = nil
|
c.PrimaryPodDisruptionBudget = nil
|
||||||
|
|
||||||
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
_, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{})
|
_, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{})
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
|
|
@ -588,7 +588,7 @@ func (c *Cluster) deleteCriticalOpPodDisruptionBudget() error {
|
||||||
c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.CriticalOpPodDisruptionBudget.ObjectMeta))
|
c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.CriticalOpPodDisruptionBudget.ObjectMeta))
|
||||||
c.CriticalOpPodDisruptionBudget = nil
|
c.CriticalOpPodDisruptionBudget = nil
|
||||||
|
|
||||||
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
_, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{})
|
_, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(context.TODO(), pdbName.Name, metav1.GetOptions{})
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,10 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if !c.patroniKubernetesUseConfigMaps() {
|
||||||
|
c.logger.Warning("K8s endpoints are deprecated. Please, enable kubernetes_use_configmaps. Requires scale-in to a single primary, see v1 -> v2 migration docs!")
|
||||||
|
}
|
||||||
|
|
||||||
if err = c.syncFinalizer(); err != nil {
|
if err = c.syncFinalizer(); err != nil {
|
||||||
c.logger.Debugf("could not sync finalizers: %v", err)
|
c.logger.Debugf("could not sync finalizers: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +107,10 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = c.syncPodServiceAccount(); err != nil {
|
||||||
|
c.logger.Errorf("could not sync pod service account: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err = c.syncStatefulSet(); err != nil {
|
if err = c.syncStatefulSet(); err != nil {
|
||||||
if !k8sutil.ResourceAlreadyExists(err) {
|
if !k8sutil.ResourceAlreadyExists(err) {
|
||||||
err = fmt.Errorf("could not sync statefulsets: %v", err)
|
err = fmt.Errorf("could not sync statefulsets: %v", err)
|
||||||
|
|
@ -626,6 +634,10 @@ func (c *Cluster) syncStatefulSet() error {
|
||||||
if !cmp.rollingUpdate {
|
if !cmp.rollingUpdate {
|
||||||
updatedPodAnnotations := map[string]*string{}
|
updatedPodAnnotations := map[string]*string{}
|
||||||
for _, anno := range cmp.deletedPodAnnotations {
|
for _, anno := range cmp.deletedPodAnnotations {
|
||||||
|
// during IRSA migration let kube2iam annotation drain naturally via pod rotation
|
||||||
|
if c.OpConfig.IRSARoleARN != "" && anno == constants.KubeIAmAnnotation {
|
||||||
|
continue
|
||||||
|
}
|
||||||
updatedPodAnnotations[anno] = nil
|
updatedPodAnnotations[anno] = nil
|
||||||
}
|
}
|
||||||
for anno, val := range desiredSts.Spec.Template.Annotations {
|
for anno, val := range desiredSts.Spec.Template.Annotations {
|
||||||
|
|
@ -1799,6 +1811,7 @@ func (c *Cluster) syncLogicalBackupJob() error {
|
||||||
// no existing logical backup job, create new one
|
// no existing logical backup job, create new one
|
||||||
c.logger.Info("could not find the cluster's logical backup job")
|
c.logger.Info("could not find the cluster's logical backup job")
|
||||||
|
|
||||||
|
|
||||||
if err = c.createLogicalBackupJob(); err == nil {
|
if err = c.createLogicalBackupJob(); err == nil {
|
||||||
c.logger.Infof("created missing logical backup job %s", jobName)
|
c.logger.Infof("created missing logical backup job %s", jobName)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1813,3 +1826,62 @@ func (c *Cluster) syncLogicalBackupJob() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cluster) syncPodServiceAccount() error {
|
||||||
|
sa, err := c.KubeClient.ServiceAccounts(c.Namespace).Get(context.TODO(), c.OpConfig.PodServiceAccountName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get pod service account %q: %v", c.OpConfig.PodServiceAccountName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
|
||||||
|
if c.OpConfig.IRSARoleARN != "" {
|
||||||
|
if val, ok := sa.Annotations[constants.IRSAAnnotation]; !ok || val != c.OpConfig.IRSARoleARN {
|
||||||
|
if sa.Annotations == nil {
|
||||||
|
sa.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
sa.Annotations[constants.IRSAAnnotation] = c.OpConfig.IRSARoleARN
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := sa.Annotations[constants.IRSAAnnotation]; ok {
|
||||||
|
delete(sa.Annotations, constants.IRSAAnnotation)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
if _, err = c.KubeClient.ServiceAccounts(c.Namespace).Update(context.TODO(), sa, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("could not update pod service account %q: %v", sa.Name, err)
|
||||||
|
}
|
||||||
|
c.logger.Infof("synced annotations on pod service account %q", sa.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OpConfig.IRSARoleARN != "" {
|
||||||
|
c.logIRSAMigrationProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cluster) logIRSAMigrationProgress() {
|
||||||
|
pods, err := c.listPods()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warnf("IRSA migration: could not list pods: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(pods)
|
||||||
|
remaining := 0
|
||||||
|
for _, pod := range pods {
|
||||||
|
if _, ok := pod.Annotations[constants.KubeIAmAnnotation]; ok {
|
||||||
|
remaining++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remaining > 0 {
|
||||||
|
c.logger.Infof("IRSA migration in progress: %d/%d pods still carry kube2iam annotation, will be removed on next rotation", remaining, total)
|
||||||
|
} else {
|
||||||
|
c.logger.Infof("IRSA migration complete: all %d pods have rotated, kube2iam annotation fully drained", total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ func TestSyncStatefulSetsAnnotations(t *testing.T) {
|
||||||
DefaultMemoryLimit: "300Mi",
|
DefaultMemoryLimit: "300Mi",
|
||||||
InheritedAnnotations: []string{inheritedAnnotation},
|
InheritedAnnotations: []string{inheritedAnnotation},
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(3),
|
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||||
ResourceCheckTimeout: time.Duration(10),
|
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, client, pg, logger, eventRecorder)
|
}, client, pg, logger, eventRecorder)
|
||||||
|
|
@ -187,8 +187,8 @@ func TestPodAnnotationsSync(t *testing.T) {
|
||||||
var cluster = New(
|
var cluster = New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
PatroniAPICheckInterval: time.Duration(1),
|
PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second},
|
||||||
PatroniAPICheckTimeout: time.Duration(5),
|
PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second},
|
||||||
PodManagementPolicy: "ordered_ready",
|
PodManagementPolicy: "ordered_ready",
|
||||||
CustomPodAnnotations: customPodAnnotations,
|
CustomPodAnnotations: customPodAnnotations,
|
||||||
ConnectionPooler: config.ConnectionPooler{
|
ConnectionPooler: config.ConnectionPooler{
|
||||||
|
|
@ -207,8 +207,8 @@ func TestPodAnnotationsSync(t *testing.T) {
|
||||||
DefaultMemoryLimit: "300Mi",
|
DefaultMemoryLimit: "300Mi",
|
||||||
MaxInstances: -1,
|
MaxInstances: -1,
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(3),
|
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||||
ResourceCheckTimeout: time.Duration(10),
|
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, client, pg, logger, eventRecorder)
|
}, client, pg, logger, eventRecorder)
|
||||||
|
|
@ -381,8 +381,8 @@ func TestCheckAndSetGlobalPostgreSQLConfiguration(t *testing.T) {
|
||||||
DefaultMemoryRequest: "300Mi",
|
DefaultMemoryRequest: "300Mi",
|
||||||
DefaultMemoryLimit: "300Mi",
|
DefaultMemoryLimit: "300Mi",
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(3),
|
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||||
ResourceCheckTimeout: time.Duration(10),
|
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, client, pg, logger, eventRecorder)
|
}, client, pg, logger, eventRecorder)
|
||||||
|
|
@ -694,8 +694,8 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) {
|
||||||
var cluster = New(
|
var cluster = New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
PatroniAPICheckInterval: time.Duration(1),
|
PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second},
|
||||||
PatroniAPICheckTimeout: time.Duration(5),
|
PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second},
|
||||||
PodManagementPolicy: "ordered_ready",
|
PodManagementPolicy: "ordered_ready",
|
||||||
Resources: config.Resources{
|
Resources: config.Resources{
|
||||||
ClusterLabels: map[string]string{"application": applicationLabel},
|
ClusterLabels: map[string]string{"application": applicationLabel},
|
||||||
|
|
@ -707,8 +707,8 @@ func TestSyncStandbyClusterConfiguration(t *testing.T) {
|
||||||
MinInstances: int32(-1),
|
MinInstances: int32(-1),
|
||||||
MaxInstances: int32(-1),
|
MaxInstances: int32(-1),
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(3),
|
ResourceCheckInterval: &metav1.Duration{Duration: 3 * time.Second},
|
||||||
ResourceCheckTimeout: time.Duration(10),
|
ResourceCheckTimeout: &metav1.Duration{Duration: 10 * time.Minute},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, client, pg, logger, eventRecorder)
|
}, client, pg, logger, eventRecorder)
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ func (c *Cluster) annotationsSet(annotations map[string]string) map[string]strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopCh chan struct{}, role *PostgresRole) (*v1.Pod, error) {
|
func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopCh chan struct{}, role *PostgresRole) (*v1.Pod, error) {
|
||||||
timeout := time.After(c.OpConfig.PodLabelWaitTimeout)
|
timeout := time.After(c.OpConfig.PodLabelWaitTimeout.Duration)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case podEvent := <-podEvents:
|
case podEvent := <-podEvents:
|
||||||
|
|
@ -364,7 +364,7 @@ func (c *Cluster) waitForPodLabel(podEvents chan PodEvent, stopCh chan struct{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) waitForPodDeletion(podEvents chan PodEvent) error {
|
func (c *Cluster) waitForPodDeletion(podEvents chan PodEvent) error {
|
||||||
timeout := time.After(c.OpConfig.PodDeletionWaitTimeout)
|
timeout := time.After(c.OpConfig.PodDeletionWaitTimeout.Duration)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case podEvent := <-podEvents:
|
case podEvent := <-podEvents:
|
||||||
|
|
@ -378,7 +378,7 @@ func (c *Cluster) waitForPodDeletion(podEvents chan PodEvent) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cluster) waitStatefulsetReady() error {
|
func (c *Cluster) waitStatefulsetReady() error {
|
||||||
return retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
return retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
listOptions := metav1.ListOptions{
|
listOptions := metav1.ListOptions{
|
||||||
LabelSelector: c.labelsSet(false).String(),
|
LabelSelector: c.labelsSet(false).String(),
|
||||||
|
|
@ -428,7 +428,7 @@ func (c *Cluster) _waitPodLabelsReady(anyReplica bool) error {
|
||||||
c.logger.Debug("Waiting for any replica pod to become ready")
|
c.logger.Debug("Waiting for any replica pod to become ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
|
err := retryutil.Retry(c.OpConfig.ResourceCheckInterval.Duration, c.OpConfig.ResourceCheckTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
masterCount := 0
|
masterCount := 0
|
||||||
if !anyReplica {
|
if !anyReplica {
|
||||||
|
|
@ -627,9 +627,12 @@ func (c *Cluster) patroniKubernetesUseConfigMaps() bool {
|
||||||
if !c.patroniUsesKubernetes() {
|
if !c.patroniUsesKubernetes() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if c.OpConfig.KubernetesUseConfigMaps == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// otherwise, follow the operator configuration
|
// otherwise, follow the operator configuration
|
||||||
return c.OpConfig.KubernetesUseConfigMaps
|
return *c.OpConfig.KubernetesUseConfigMaps
|
||||||
}
|
}
|
||||||
|
|
||||||
// Earlier arguments take priority
|
// Earlier arguments take priority
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ func newFakeK8sAnnotationsClient() (k8sutil.KubernetesClient, *k8sFake.Clientset
|
||||||
EndpointsGetter: clientSet.CoreV1(),
|
EndpointsGetter: clientSet.CoreV1(),
|
||||||
ConfigMapsGetter: clientSet.CoreV1(),
|
ConfigMapsGetter: clientSet.CoreV1(),
|
||||||
PodsGetter: clientSet.CoreV1(),
|
PodsGetter: clientSet.CoreV1(),
|
||||||
|
ServiceAccountsGetter: clientSet.CoreV1(),
|
||||||
DeploymentsGetter: clientSet.AppsV1(),
|
DeploymentsGetter: clientSet.AppsV1(),
|
||||||
CronJobsGetter: clientSet.BatchV1(),
|
CronJobsGetter: clientSet.BatchV1(),
|
||||||
}, clientSet
|
}, clientSet
|
||||||
|
|
@ -297,9 +298,9 @@ func newInheritedAnnotationsCluster(client k8sutil.KubernetesClient) (*Cluster,
|
||||||
cluster := New(
|
cluster := New(
|
||||||
Config{
|
Config{
|
||||||
OpConfig: config.Config{
|
OpConfig: config.Config{
|
||||||
PatroniAPICheckInterval: time.Duration(1),
|
PatroniAPICheckInterval: &metav1.Duration{Duration: 1 * time.Second},
|
||||||
PatroniAPICheckTimeout: time.Duration(5),
|
PatroniAPICheckTimeout: &metav1.Duration{Duration: 5 * time.Second},
|
||||||
KubernetesUseConfigMaps: true,
|
KubernetesUseConfigMaps: util.True(),
|
||||||
ConnectionPooler: config.ConnectionPooler{
|
ConnectionPooler: config.ConnectionPooler{
|
||||||
ConnectionPoolerDefaultCPURequest: "100m",
|
ConnectionPoolerDefaultCPURequest: "100m",
|
||||||
ConnectionPoolerDefaultCPULimit: "100m",
|
ConnectionPoolerDefaultCPULimit: "100m",
|
||||||
|
|
@ -318,8 +319,8 @@ func newInheritedAnnotationsCluster(client k8sutil.KubernetesClient) (*Cluster,
|
||||||
DefaultMemoryLimit: "300Mi",
|
DefaultMemoryLimit: "300Mi",
|
||||||
InheritedAnnotations: []string{"owned-by"},
|
InheritedAnnotations: []string{"owned-by"},
|
||||||
PodRoleLabel: "spilo-role",
|
PodRoleLabel: "spilo-role",
|
||||||
ResourceCheckInterval: time.Duration(testResourceCheckInterval),
|
ResourceCheckInterval: &metav1.Duration{Duration: testResourceCheckInterval},
|
||||||
ResourceCheckTimeout: time.Duration(testResourceCheckTimeout),
|
ResourceCheckTimeout: &metav1.Duration{Duration: testResourceCheckTimeout},
|
||||||
MinInstances: -1,
|
MinInstances: -1,
|
||||||
MaxInstances: -1,
|
MaxInstances: -1,
|
||||||
},
|
},
|
||||||
|
|
@ -388,7 +389,7 @@ func createPatroniResources(cluster *Cluster) error {
|
||||||
Labels: cluster.labelsSet(false),
|
Labels: cluster.labelsSet(false),
|
||||||
}
|
}
|
||||||
|
|
||||||
if cluster.OpConfig.KubernetesUseConfigMaps {
|
if cluster.OpConfig.KubernetesUseConfigMaps != nil && *cluster.OpConfig.KubernetesUseConfigMaps {
|
||||||
configMap := v1.ConfigMap{
|
configMap := v1.ConfigMap{
|
||||||
ObjectMeta: metadata,
|
ObjectMeta: metadata,
|
||||||
}
|
}
|
||||||
|
|
@ -598,7 +599,7 @@ func TestInheritedAnnotations(t *testing.T) {
|
||||||
// 3. Change from ConfigMaps to Endpoints
|
// 3. Change from ConfigMaps to Endpoints
|
||||||
err = cluster.deletePatroniResources()
|
err = cluster.deletePatroniResources()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
cluster.OpConfig.KubernetesUseConfigMaps = false
|
cluster.OpConfig.KubernetesUseConfigMaps = util.False()
|
||||||
err = createPatroniResources(cluster)
|
err = createPatroniResources(cluster)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = cluster.Sync(newSpec.DeepCopy())
|
err = cluster.Sync(newSpec.DeepCopy())
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,12 @@ func (c *Controller) initPodServiceAccount() {
|
||||||
c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
|
c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
|
||||||
}
|
}
|
||||||
c.PodServiceAccount.Namespace = ""
|
c.PodServiceAccount.Namespace = ""
|
||||||
|
if c.opConfig.IRSARoleARN != "" {
|
||||||
|
if c.PodServiceAccount.Annotations == nil {
|
||||||
|
c.PodServiceAccount.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
c.PodServiceAccount.Annotations[constants.IRSAAnnotation] = c.opConfig.IRSARoleARN
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
|
// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ func (c *Controller) nodeDelete(obj interface{}) {
|
||||||
|
|
||||||
func (c *Controller) moveMasterPodsOffNode(node *v1.Node) {
|
func (c *Controller) moveMasterPodsOffNode(node *v1.Node) {
|
||||||
// retry to move master until configured timeout is reached
|
// retry to move master until configured timeout is reached
|
||||||
err := retryutil.Retry(1*time.Minute, c.opConfig.MasterPodMoveTimeout,
|
err := retryutil.Retry(1*time.Minute, c.opConfig.MasterPodMoveTimeout.Duration,
|
||||||
func() (bool, error) {
|
func() (bool, error) {
|
||||||
err := c.attemptToMoveMasterPodsOffNode(node)
|
err := c.attemptToMoveMasterPodsOffNode(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||||
"github.com/zalando/postgres-operator/pkg/util"
|
"github.com/zalando/postgres-operator/pkg/util"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/config"
|
"github.com/zalando/postgres-operator/pkg/util/config"
|
||||||
|
|
@ -37,30 +35,21 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.EnableSpiloWalPathCompat = fromCRD.EnableSpiloWalPathCompat
|
result.EnableSpiloWalPathCompat = fromCRD.EnableSpiloWalPathCompat
|
||||||
result.EnableTeamIdClusternamePrefix = fromCRD.EnableTeamIdClusternamePrefix
|
result.EnableTeamIdClusternamePrefix = fromCRD.EnableTeamIdClusternamePrefix
|
||||||
result.EtcdHost = fromCRD.EtcdHost
|
result.EtcdHost = fromCRD.EtcdHost
|
||||||
result.KubernetesUseConfigMaps = fromCRD.KubernetesUseConfigMaps
|
result.KubernetesUseConfigMaps = util.CoalesceBool(fromCRD.KubernetesUseConfigMaps, util.True())
|
||||||
result.DockerImage = util.Coalesce(fromCRD.DockerImage, "ghcr.io/zalando/spilo-18:4.1-p1")
|
result.DockerImage = util.Coalesce(fromCRD.DockerImage, "ghcr.io/zalando/spilo-18:4.1-p1")
|
||||||
result.Workers = util.CoalesceUInt32(fromCRD.Workers, 8)
|
result.Workers = util.CoalesceUInt32(fromCRD.Workers, 8)
|
||||||
result.MinInstances = fromCRD.MinInstances
|
result.MinInstances = fromCRD.MinInstances
|
||||||
result.MaxInstances = fromCRD.MaxInstances
|
result.MaxInstances = fromCRD.MaxInstances
|
||||||
result.IgnoreInstanceLimitsAnnotationKey = fromCRD.IgnoreInstanceLimitsAnnotationKey
|
result.IgnoreInstanceLimitsAnnotationKey = fromCRD.IgnoreInstanceLimitsAnnotationKey
|
||||||
result.IgnoreResourcesLimitsAnnotationKey = fromCRD.IgnoreResourcesLimitsAnnotationKey
|
result.IgnoreResourcesLimitsAnnotationKey = fromCRD.IgnoreResourcesLimitsAnnotationKey
|
||||||
result.ResyncPeriod = util.CoalesceDuration(time.Duration(fromCRD.ResyncPeriod), "30m")
|
result.ResyncPeriod = util.CoalesceDuration(fromCRD.ResyncPeriod, "30m")
|
||||||
result.RepairPeriod = util.CoalesceDuration(time.Duration(fromCRD.RepairPeriod), "5m")
|
result.RepairPeriod = util.CoalesceDuration(fromCRD.RepairPeriod, "5m")
|
||||||
result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit
|
result.SetMemoryRequestToLimit = fromCRD.SetMemoryRequestToLimit
|
||||||
result.ShmVolume = util.CoalesceBool(fromCRD.ShmVolume, util.True())
|
result.ShmVolume = util.CoalesceBool(fromCRD.ShmVolume, util.True())
|
||||||
result.SidecarImages = fromCRD.SidecarImages
|
result.SidecarImages = fromCRD.SidecarImages
|
||||||
result.SidecarContainers = fromCRD.SidecarContainers
|
result.SidecarContainers = fromCRD.SidecarContainers
|
||||||
result.EnableMaintenanceWindows = util.CoalesceBool(fromCRD.EnableMaintenanceWindows, util.True())
|
result.EnableMaintenanceWindows = util.CoalesceBool(fromCRD.EnableMaintenanceWindows, util.True())
|
||||||
if len(fromCRD.MaintenanceWindows) > 0 {
|
result.MaintenanceWindows = fromCRD.MaintenanceWindows
|
||||||
result.MaintenanceWindows = make([]string, 0, len(fromCRD.MaintenanceWindows))
|
|
||||||
for _, window := range fromCRD.MaintenanceWindows {
|
|
||||||
w, err := window.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("could not marshal configured maintenance window: %v", err))
|
|
||||||
}
|
|
||||||
result.MaintenanceWindows = append(result.MaintenanceWindows, string(w))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// user config
|
// user config
|
||||||
result.SuperUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.SuperUsername, "postgres")
|
result.SuperUsername = util.Coalesce(fromCRD.PostgresUsersConfiguration.SuperUsername, "postgres")
|
||||||
|
|
@ -84,7 +73,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
||||||
result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap
|
result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap
|
||||||
result.PodEnvironmentSecret = fromCRD.Kubernetes.PodEnvironmentSecret
|
result.PodEnvironmentSecret = fromCRD.Kubernetes.PodEnvironmentSecret
|
||||||
result.PodTerminateGracePeriod = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod), "5m")
|
result.PodTerminateGracePeriod = util.CoalesceDuration(fromCRD.Kubernetes.PodTerminateGracePeriod, "5m")
|
||||||
result.LivenessProbe = fromCRD.Kubernetes.LivenessProbe
|
result.LivenessProbe = fromCRD.Kubernetes.LivenessProbe
|
||||||
result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged
|
result.SpiloPrivileged = fromCRD.Kubernetes.SpiloPrivileged
|
||||||
result.SpiloAllowPrivilegeEscalation = util.CoalesceBool(fromCRD.Kubernetes.SpiloAllowPrivilegeEscalation, util.True())
|
result.SpiloAllowPrivilegeEscalation = util.CoalesceBool(fromCRD.Kubernetes.SpiloAllowPrivilegeEscalation, util.True())
|
||||||
|
|
@ -138,7 +127,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.EnableSecretsDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnableSecretsDeletion, util.True())
|
result.EnableSecretsDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnableSecretsDeletion, util.True())
|
||||||
result.EnablePersistentVolumeClaimDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnablePersistentVolumeClaimDeletion, util.True())
|
result.EnablePersistentVolumeClaimDeletion = util.CoalesceBool(fromCRD.Kubernetes.EnablePersistentVolumeClaimDeletion, util.True())
|
||||||
result.EnableReadinessProbe = fromCRD.Kubernetes.EnableReadinessProbe
|
result.EnableReadinessProbe = fromCRD.Kubernetes.EnableReadinessProbe
|
||||||
result.MasterPodMoveTimeout = util.CoalesceDuration(time.Duration(fromCRD.Kubernetes.MasterPodMoveTimeout), "10m")
|
result.MasterPodMoveTimeout = util.CoalesceDuration(fromCRD.Kubernetes.MasterPodMoveTimeout, "10m")
|
||||||
result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity
|
result.EnablePodAntiAffinity = fromCRD.Kubernetes.EnablePodAntiAffinity
|
||||||
result.PodAntiAffinityTopologyKey = util.Coalesce(fromCRD.Kubernetes.PodAntiAffinityTopologyKey, "kubernetes.io/hostname")
|
result.PodAntiAffinityTopologyKey = util.Coalesce(fromCRD.Kubernetes.PodAntiAffinityTopologyKey, "kubernetes.io/hostname")
|
||||||
result.PodAntiAffinityPreferredDuringScheduling = fromCRD.Kubernetes.PodAntiAffinityPreferredDuringScheduling
|
result.PodAntiAffinityPreferredDuringScheduling = fromCRD.Kubernetes.PodAntiAffinityPreferredDuringScheduling
|
||||||
|
|
@ -155,14 +144,14 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.MaxMemoryRequest = fromCRD.PostgresPodResources.MaxMemoryRequest
|
result.MaxMemoryRequest = fromCRD.PostgresPodResources.MaxMemoryRequest
|
||||||
|
|
||||||
// timeout config
|
// timeout config
|
||||||
result.ResourceCheckInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ResourceCheckInterval), "3s")
|
result.ResourceCheckInterval = util.CoalesceDuration(fromCRD.Timeouts.ResourceCheckInterval, "3s")
|
||||||
result.ResourceCheckTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ResourceCheckTimeout), "10m")
|
result.ResourceCheckTimeout = util.CoalesceDuration(fromCRD.Timeouts.ResourceCheckTimeout, "10m")
|
||||||
result.PodLabelWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PodLabelWaitTimeout), "10m")
|
result.PodLabelWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.PodLabelWaitTimeout, "10m")
|
||||||
result.PodDeletionWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PodDeletionWaitTimeout), "10m")
|
result.PodDeletionWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.PodDeletionWaitTimeout, "10m")
|
||||||
result.ReadyWaitInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ReadyWaitInterval), "4s")
|
result.ReadyWaitInterval = util.CoalesceDuration(fromCRD.Timeouts.ReadyWaitInterval, "4s")
|
||||||
result.ReadyWaitTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ReadyWaitTimeout), "30s")
|
result.ReadyWaitTimeout = util.CoalesceDuration(fromCRD.Timeouts.ReadyWaitTimeout, "30s")
|
||||||
result.PatroniAPICheckInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PatroniAPICheckInterval), "1s")
|
result.PatroniAPICheckInterval = util.CoalesceDuration(fromCRD.Timeouts.PatroniAPICheckInterval, "1s")
|
||||||
result.PatroniAPICheckTimeout = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.PatroniAPICheckTimeout), "5s")
|
result.PatroniAPICheckTimeout = util.CoalesceDuration(fromCRD.Timeouts.PatroniAPICheckTimeout, "5s")
|
||||||
|
|
||||||
// load balancer config
|
// load balancer config
|
||||||
result.DbHostedZone = util.Coalesce(fromCRD.LoadBalancer.DbHostedZone, "db.example.com")
|
result.DbHostedZone = util.Coalesce(fromCRD.LoadBalancer.DbHostedZone, "db.example.com")
|
||||||
|
|
@ -186,6 +175,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||||
result.AWSRegion = fromCRD.AWSGCP.AWSRegion
|
result.AWSRegion = fromCRD.AWSGCP.AWSRegion
|
||||||
result.LogS3Bucket = fromCRD.AWSGCP.LogS3Bucket
|
result.LogS3Bucket = fromCRD.AWSGCP.LogS3Bucket
|
||||||
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
|
result.KubeIAMRole = fromCRD.AWSGCP.KubeIAMRole
|
||||||
|
result.IRSARoleARN = fromCRD.AWSGCP.IRSARoleARN
|
||||||
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.WALAZStorageAccount = fromCRD.AWSGCP.WALAZStorageAccount
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
func (c *Controller) clusterResync(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
func (c *Controller) clusterResync(stopCh <-chan struct{}, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(c.opConfig.ResyncPeriod)
|
ticker := time.NewTicker(c.opConfig.ResyncPeriod.Duration)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -213,22 +213,10 @@ func (c *Controller) processEvent(event ClusterEvent, isInInitialList bool) {
|
||||||
}
|
}
|
||||||
lg.Debugf("observed cluster status %s, running sync scan to repair the cluster", lastOperationStatus)
|
lg.Debugf("observed cluster status %s, running sync scan to repair the cluster", lastOperationStatus)
|
||||||
event.EventType = EventSync
|
event.EventType = EventSync
|
||||||
}
|
} else if event.EventType != EventDelete {
|
||||||
|
|
||||||
if event.EventType == EventAdd || event.EventType == EventUpdate || event.EventType == EventSync {
|
|
||||||
// handle deprecated parameters by possibly assigning their values to the new ones.
|
|
||||||
if event.OldSpec != nil {
|
|
||||||
c.mergeDeprecatedPostgreSQLSpecParameters(&event.OldSpec.Spec)
|
|
||||||
}
|
|
||||||
if event.NewSpec != nil {
|
|
||||||
c.warnOnDeprecatedPostgreSQLSpecParameters(&event.NewSpec.Spec)
|
|
||||||
c.mergeDeprecatedPostgreSQLSpecParameters(&event.NewSpec.Spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = c.submitRBACCredentials(event); err != nil {
|
if err = c.submitRBACCredentials(event); err != nil {
|
||||||
c.logger.Warnf("pods and/or Patroni may misfunction due to the lack of permissions: %v", err)
|
c.logger.Warnf("pods and/or Patroni may misfunction due to the lack of permissions: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch event.EventType {
|
switch event.EventType {
|
||||||
|
|
@ -397,45 +385,6 @@ func (c *Controller) processClusterEventsQueue(idx int, stopCh <-chan struct{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) warnOnDeprecatedPostgreSQLSpecParameters(spec *acidv1.PostgresSpec) {
|
|
||||||
deprecate := func(deprecated, replacement string) {
|
|
||||||
c.logger.Warningf("parameter %q is deprecated. Consider setting %q instead", deprecated, replacement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.UseLoadBalancer != nil {
|
|
||||||
deprecate("useLoadBalancer", "enableMasterLoadBalancer")
|
|
||||||
}
|
|
||||||
if spec.ReplicaLoadBalancer != nil {
|
|
||||||
deprecate("replicaLoadBalancer", "enableReplicaLoadBalancer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spec.UseLoadBalancer != nil || spec.ReplicaLoadBalancer != nil) &&
|
|
||||||
(spec.EnableReplicaLoadBalancer != nil || spec.EnableMasterLoadBalancer != nil) {
|
|
||||||
c.logger.Warnf("both old and new load balancer parameters are present in the manifest, ignoring old ones")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeDeprecatedPostgreSQLSpecParameters modifies the spec passed to the cluster by setting current parameter
|
|
||||||
// values from the obsolete ones. Note: while the spec that is modified is a copy made in queueClusterEvent, it is
|
|
||||||
// still a shallow copy, so be extra careful not to modify values pointer fields point to, but copy them instead.
|
|
||||||
func (c *Controller) mergeDeprecatedPostgreSQLSpecParameters(spec *acidv1.PostgresSpec) *acidv1.PostgresSpec {
|
|
||||||
if (spec.UseLoadBalancer != nil || spec.ReplicaLoadBalancer != nil) &&
|
|
||||||
(spec.EnableReplicaLoadBalancer == nil && spec.EnableMasterLoadBalancer == nil) {
|
|
||||||
if spec.UseLoadBalancer != nil {
|
|
||||||
spec.EnableMasterLoadBalancer = new(bool)
|
|
||||||
*spec.EnableMasterLoadBalancer = *spec.UseLoadBalancer
|
|
||||||
}
|
|
||||||
if spec.ReplicaLoadBalancer != nil {
|
|
||||||
spec.EnableReplicaLoadBalancer = new(bool)
|
|
||||||
*spec.EnableReplicaLoadBalancer = *spec.ReplicaLoadBalancer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spec.ReplicaLoadBalancer = nil
|
|
||||||
spec.UseLoadBalancer = nil
|
|
||||||
|
|
||||||
return spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) queueClusterEvent(informerOldSpec, informerNewSpec *acidv1.Postgresql, eventType EventType) {
|
func (c *Controller) queueClusterEvent(informerOldSpec, informerNewSpec *acidv1.Postgresql, eventType EventType) {
|
||||||
var (
|
var (
|
||||||
uid types.UID
|
uid types.UID
|
||||||
|
|
|
||||||
|
|
@ -64,35 +64,6 @@ func TestControllerOwnershipOnPostgresql(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeDeprecatedPostgreSQLSpecParameters(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
in *acidv1.PostgresSpec
|
|
||||||
out *acidv1.PostgresSpec
|
|
||||||
error string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"Check that old parameters propagate values to the new ones",
|
|
||||||
&acidv1.PostgresSpec{UseLoadBalancer: &True, ReplicaLoadBalancer: &True},
|
|
||||||
&acidv1.PostgresSpec{UseLoadBalancer: nil, ReplicaLoadBalancer: nil,
|
|
||||||
EnableMasterLoadBalancer: &True, EnableReplicaLoadBalancer: &True},
|
|
||||||
"New parameters should be set from the values of old ones",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Check that new parameters are not set when both old and new ones are present",
|
|
||||||
&acidv1.PostgresSpec{UseLoadBalancer: &True, EnableMasterLoadBalancer: &False},
|
|
||||||
&acidv1.PostgresSpec{UseLoadBalancer: nil, EnableMasterLoadBalancer: &False},
|
|
||||||
"New parameters should remain unchanged when both old and new are present",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := postgresqlTestController.mergeDeprecatedPostgreSQLSpecParameters(tt.in)
|
|
||||||
if !reflect.DeepEqual(result, tt.out) {
|
|
||||||
t.Errorf("%s: %v", tt.name, tt.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMeetsClusterDeleteAnnotations(t *testing.T) {
|
func TestMeetsClusterDeleteAnnotations(t *testing.T) {
|
||||||
// set delete annotations in configuration
|
// set delete annotations in configuration
|
||||||
postgresqlTestController.opConfig.DeleteAnnotationDateKey = "delete-date"
|
postgresqlTestController.opConfig.DeleteAnnotationDateKey = "delete-date"
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,33 @@ package config
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/zalando/postgres-operator/pkg/spec"
|
"github.com/zalando/postgres-operator/pkg/spec"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/constants"
|
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CRD describes CustomResourceDefinition specific configuration parameters
|
// CRD describes CustomResourceDefinition specific configuration parameters
|
||||||
type CRD struct {
|
type CRD struct {
|
||||||
ReadyWaitInterval time.Duration `name:"ready_wait_interval" default:"4s"`
|
ReadyWaitInterval *metav1.Duration `name:"ready_wait_interval" default:"4s"`
|
||||||
ReadyWaitTimeout time.Duration `name:"ready_wait_timeout" default:"30s"`
|
ReadyWaitTimeout *metav1.Duration `name:"ready_wait_timeout" default:"30s"`
|
||||||
ResyncPeriod time.Duration `name:"resync_period" default:"30m"`
|
ResyncPeriod *metav1.Duration `name:"resync_period" default:"30m"`
|
||||||
RepairPeriod time.Duration `name:"repair_period" default:"5m"`
|
RepairPeriod *metav1.Duration `name:"repair_period" default:"5m"`
|
||||||
EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"`
|
EnableCRDRegistration *bool `name:"enable_crd_registration" default:"true"`
|
||||||
CRDCategories []string `name:"crd_categories" default:"all"`
|
CRDCategories []string `name:"crd_categories" default:"all"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resources describes kubernetes resource specific configuration parameters
|
// Resources describes kubernetes resource specific configuration parameters
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
EnableOwnerReferences *bool `name:"enable_owner_references" default:"false"`
|
EnableOwnerReferences *bool `name:"enable_owner_references" default:"false"`
|
||||||
ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"`
|
ResourceCheckInterval *metav1.Duration `name:"resource_check_interval" default:"3s"`
|
||||||
ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"`
|
ResourceCheckTimeout *metav1.Duration `name:"resource_check_timeout" default:"10m"`
|
||||||
PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"`
|
PodLabelWaitTimeout *metav1.Duration `name:"pod_label_wait_timeout" default:"10m"`
|
||||||
PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"`
|
PodDeletionWaitTimeout *metav1.Duration `name:"pod_deletion_wait_timeout" default:"10m"`
|
||||||
PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"`
|
PodTerminateGracePeriod *metav1.Duration `name:"pod_terminate_grace_period" default:"5m"`
|
||||||
LivenessProbe *v1.Probe `name:"-"`
|
LivenessProbe *v1.Probe `name:"-"`
|
||||||
SpiloRunAsUser *int64 `name:"spilo_runasuser"`
|
SpiloRunAsUser *int64 `name:"spilo_runasuser"`
|
||||||
SpiloRunAsGroup *int64 `name:"spilo_runasgroup"`
|
SpiloRunAsGroup *int64 `name:"spilo_runasgroup"`
|
||||||
|
|
@ -184,7 +184,7 @@ type Config struct {
|
||||||
ConnectionPooler
|
ConnectionPooler
|
||||||
|
|
||||||
WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
|
WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
|
||||||
KubernetesUseConfigMaps bool `name:"kubernetes_use_configmaps" default:"false"`
|
KubernetesUseConfigMaps *bool `name:"kubernetes_use_configmaps" default:"true"`
|
||||||
EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
|
EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
|
||||||
EnableMaintenanceWindows *bool `name:"enable_maintenance_windows" default:"true"`
|
EnableMaintenanceWindows *bool `name:"enable_maintenance_windows" default:"true"`
|
||||||
MaintenanceWindows []string `name:"maintenance_windows"`
|
MaintenanceWindows []string `name:"maintenance_windows"`
|
||||||
|
|
@ -195,12 +195,13 @@ type Config struct {
|
||||||
// value of this string must be valid JSON or YAML; see initPodServiceAccount
|
// value of this string must be valid JSON or YAML; see initPodServiceAccount
|
||||||
PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""`
|
PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""`
|
||||||
PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""`
|
PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""`
|
||||||
MasterPodMoveTimeout time.Duration `name:"master_pod_move_timeout" default:"20m"`
|
MasterPodMoveTimeout *metav1.Duration `name:"master_pod_move_timeout" default:"20m"`
|
||||||
DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"`
|
DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"`
|
||||||
AWSRegion string `name:"aws_region" default:"eu-central-1"`
|
AWSRegion string `name:"aws_region" default:"eu-central-1"`
|
||||||
WALES3Bucket string `name:"wal_s3_bucket"`
|
WALES3Bucket string `name:"wal_s3_bucket"`
|
||||||
LogS3Bucket string `name:"log_s3_bucket"`
|
LogS3Bucket string `name:"log_s3_bucket"`
|
||||||
KubeIAMRole string `name:"kube_iam_role"`
|
KubeIAMRole string `name:"kube_iam_role"`
|
||||||
|
IRSARoleARN string `name:"irsa_role_arn"`
|
||||||
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"`
|
WALAZStorageAccount string `name:"wal_az_storage_account"`
|
||||||
|
|
@ -249,7 +250,7 @@ type Config struct {
|
||||||
RingLogLines int `name:"ring_log_lines" default:"100"`
|
RingLogLines int `name:"ring_log_lines" default:"100"`
|
||||||
ClusterHistoryEntries int `name:"cluster_history_entries" default:"1000"`
|
ClusterHistoryEntries int `name:"cluster_history_entries" default:"1000"`
|
||||||
TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"`
|
TeamAPIRoleConfiguration map[string]string `name:"team_api_role_configuration" default:"log_statement:all"`
|
||||||
PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"`
|
PodTerminateGracePeriod *metav1.Duration `name:"pod_terminate_grace_period" default:"5m"`
|
||||||
PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"`
|
PodManagementPolicy string `name:"pod_management_policy" default:"ordered_ready"`
|
||||||
EnableReadinessProbe bool `name:"enable_readiness_probe" default:"false"`
|
EnableReadinessProbe bool `name:"enable_readiness_probe" default:"false"`
|
||||||
ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"`
|
ProtectedRoles []string `name:"protected_role_names" default:"admin,cron_admin"`
|
||||||
|
|
@ -265,8 +266,8 @@ type Config struct {
|
||||||
MajorVersionUpgradeTeamAllowList []string `name:"major_version_upgrade_team_allow_list" default:""`
|
MajorVersionUpgradeTeamAllowList []string `name:"major_version_upgrade_team_allow_list" default:""`
|
||||||
MinimalMajorVersion string `name:"minimal_major_version" default:"14"`
|
MinimalMajorVersion string `name:"minimal_major_version" default:"14"`
|
||||||
TargetMajorVersion string `name:"target_major_version" default:"18"`
|
TargetMajorVersion string `name:"target_major_version" default:"18"`
|
||||||
PatroniAPICheckInterval time.Duration `name:"patroni_api_check_interval" default:"1s"`
|
PatroniAPICheckInterval *metav1.Duration `name:"patroni_api_check_interval" default:"1s"`
|
||||||
PatroniAPICheckTimeout time.Duration `name:"patroni_api_check_timeout" default:"5s"`
|
PatroniAPICheckTimeout *metav1.Duration `name:"patroni_api_check_timeout" default:"5s"`
|
||||||
EnablePatroniFailsafeMode *bool `name:"enable_patroni_failsafe_mode" default:"false"`
|
EnablePatroniFailsafeMode *bool `name:"enable_patroni_failsafe_mode" default:"false"`
|
||||||
EnableSecretsDeletion *bool `name:"enable_secrets_deletion" default:"true"`
|
EnableSecretsDeletion *bool `name:"enable_secrets_deletion" default:"true"`
|
||||||
EnablePersistentVolumeClaimDeletion *bool `name:"enable_persistent_volume_claim_deletion" default:"true"`
|
EnablePersistentVolumeClaimDeletion *bool `name:"enable_persistent_volume_claim_deletion" default:"true"`
|
||||||
|
|
|
||||||
|
|
@ -230,16 +230,16 @@ var newFromMapTests = []struct {
|
||||||
{
|
{
|
||||||
description: "duration parsing",
|
description: "duration parsing",
|
||||||
input: map[string]string{
|
input: map[string]string{
|
||||||
"ready_wait_interval": "10s",
|
"patroni_api_check_interval": "1s",
|
||||||
"ready_wait_timeout": "1m",
|
"patroni_api_check_timeout": "5s",
|
||||||
},
|
},
|
||||||
expectPanic: false,
|
expectPanic: false,
|
||||||
validateFunc: func(t *testing.T, cfg *Config) {
|
validateFunc: func(t *testing.T, cfg *Config) {
|
||||||
if cfg.ReadyWaitInterval.Seconds() != 10 {
|
if cfg.PatroniAPICheckInterval.Seconds() != 1 {
|
||||||
t.Errorf("expected ReadyWaitInterval=10s, got %v", cfg.ReadyWaitInterval)
|
t.Errorf("expected check interval of 1s, got %.0fs", cfg.PatroniAPICheckInterval.Seconds())
|
||||||
}
|
}
|
||||||
if cfg.ReadyWaitTimeout.Minutes() != 1 {
|
if cfg.PatroniAPICheckTimeout.Seconds() != 5 {
|
||||||
t.Errorf("expected ReadyWaitTimeout=1m, got %v", cfg.ReadyWaitTimeout)
|
t.Errorf("expected check timeout of 5s, got %.0fs", cfg.PatroniAPICheckTimeout.Seconds())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type decoder interface {
|
type decoder interface {
|
||||||
|
|
@ -101,13 +103,7 @@ func processField(value string, field reflect.Value) error {
|
||||||
val int64
|
val int64
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
|
val, err = strconv.ParseInt(value, 0, typ.Bits())
|
||||||
var d time.Duration
|
|
||||||
d, err = time.ParseDuration(value)
|
|
||||||
val = int64(d)
|
|
||||||
} else {
|
|
||||||
val, err = strconv.ParseInt(value, 0, typ.Bits())
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -165,6 +161,15 @@ func processField(value string, field reflect.Value) error {
|
||||||
mp.SetMapIndex(k, v)
|
mp.SetMapIndex(k, v)
|
||||||
}
|
}
|
||||||
field.Set(mp)
|
field.Set(mp)
|
||||||
|
case reflect.Struct:
|
||||||
|
if typ.Name() == "Duration" {
|
||||||
|
var d time.Duration
|
||||||
|
d, err := time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(metav1.Duration{Duration: d}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package constants
|
||||||
const (
|
const (
|
||||||
ZalandoDNSNameAnnotation = "external-dns.alpha.kubernetes.io/hostname"
|
ZalandoDNSNameAnnotation = "external-dns.alpha.kubernetes.io/hostname"
|
||||||
KubeIAmAnnotation = "iam.amazonaws.com/role"
|
KubeIAmAnnotation = "iam.amazonaws.com/role"
|
||||||
|
IRSAAnnotation = "eks.amazonaws.com/role-arn"
|
||||||
VolumeStorateProvisionerAnnotation = "pv.kubernetes.io/provisioned-by"
|
VolumeStorateProvisionerAnnotation = "pv.kubernetes.io/provisioned-by"
|
||||||
PostgresqlControllerAnnotationKey = "acid.zalan.do/controller"
|
PostgresqlControllerAnnotationKey = "acid.zalan.do/controller"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -311,14 +311,14 @@ func CoalesceBool(val, defaultVal *bool) *bool {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoalesceDuration works like coalesce but for time.Duration
|
// CoalesceDuration works like coalesce but for metav1.Duration
|
||||||
func CoalesceDuration(val time.Duration, defaultVal string) time.Duration {
|
func CoalesceDuration(val *metav1.Duration, defaultVal string) *metav1.Duration {
|
||||||
if val == 0 {
|
if val == nil || val.Duration == 0 {
|
||||||
duration, err := time.ParseDuration(defaultVal)
|
duration, err := time.ParseDuration(defaultVal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return duration
|
return &metav1.Duration{Duration: duration}
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue